diff --git a/.netconfig b/.netconfig
index d5f4b4f..2a317ba 100644
--- a/.netconfig
+++ b/.netconfig
@@ -19,9 +19,6 @@
[file "src/kzu.snk"]
url = https://github.com/devlooped/oss/blob/main/src/kzu.snk
skip
-[file "SponsorLink.sln"]
- url = https://github.com/devlooped/oss/blob/main/SponsorLink.sln
- skip
[file ".github/workflows/changelog.yml"]
url = https://github.com/devlooped/oss/blob/main/.github/workflows/changelog.yml
sha = 5fb172362c767bef7c36478f1a6bdc264723f8f9
@@ -155,307 +152,13 @@
etag = 013a47739e348f06891f37c45164478cca149854e6cd5c5158e6f073f852b61a
weak
-[file "src/SponsorLink"]
- url = https://github.com/devlooped/sponsorLink/tree/main/samples/dotnet/
-[file "src/SponsorLink/Analyzer/Analyzer.csproj"]
- url = https://github.com/devlooped/SponsorLink/blob/main/samples/dotnet/Analyzer/Analyzer.csproj
- sha = 8f0e6216360f3f8700b4845f3ec2310aabd996f3
-
- etag = 671a82f0f6770a990f9364ecf321eeea75bd6092f98c009039af02df172152df
- weak
-[file "src/SponsorLink/Analyzer/GraceApiAnalyzer.cs"]
- url = https://github.com/devlooped/SponsorLink/blob/main/samples/dotnet/Analyzer/GraceApiAnalyzer.cs
- sha = 4638da914b0527c156227f3705ca60a85c1871e4
-
- etag = 6603b004f41e023d03b86f175d9fc4e0a462d1b2519406e46b4831e36c378e6f
- weak
-[file "src/SponsorLink/Analyzer/Properties/launchSettings.json"]
- url = https://github.com/devlooped/SponsorLink/blob/main/samples/dotnet/Analyzer/Properties/launchSettings.json
- sha = f47528874a6d9192b5546f84b455f5ccc474a707
-
- etag = 6c59ab4d008e3221e316c9e3b6e0da155b892680d48cdc400a39d53cb9a12aac
- weak
-[file "src/SponsorLink/Analyzer/StatusReportingAnalyzer.cs"]
- url = https://github.com/devlooped/SponsorLink/blob/main/samples/dotnet/Analyzer/StatusReportingAnalyzer.cs
- sha = eceeb2c5596285c95db4d1a031cc36238a7cd22d
-
- etag = db37e051eeea1a0e368ccc8bfdf59c373486a583c57ad8301d6be9ab21da4e0d
- weak
-[file "src/SponsorLink/Analyzer/StatusReportingGenerator.cs"]
- url = https://github.com/devlooped/SponsorLink/blob/main/samples/dotnet/Analyzer/StatusReportingGenerator.cs
- sha = 08d80dd734525b1e6f46adbffd2aab77d73afb71
-
- etag = 09f466f0a23877a980ec01a7b15330c6c36c44960028188d826a8ef48f8756aa
- weak
-[file "src/SponsorLink/Analyzer/buildTransitive/SponsorableLib.targets"]
- url = https://github.com/devlooped/SponsorLink/blob/main/samples/dotnet/Analyzer/buildTransitive/SponsorableLib.targets
- sha = eceeb2c5596285c95db4d1a031cc36238a7cd22d
-
- etag = 727bd941b7a8be190c7f17a41c791ef2248be5e25a36460a0457bc080a7d4503
- weak
-[file "src/SponsorLink/Directory.Build.props"]
- url = https://github.com/devlooped/SponsorLink/blob/main/samples/dotnet/Directory.Build.props
- sha = 7b5109b5b5a53a2cc16759b776c4a092aec5ca57
-
- etag = 5d4e433c71291ea953d328aa26b2d93cdf4708271f0eb024138ba2e0db93ab15
- weak
-[file "src/SponsorLink/Directory.Build.targets"]
- url = https://github.com/devlooped/SponsorLink/blob/main/samples/dotnet/Directory.Build.targets
- sha = f47528874a6d9192b5546f84b455f5ccc474a707
-
- etag = 9938f29c3573bf8bdb9686e1d9884dee177256b1d5dd7ee41472dd64bfbdd92d
- weak
-[file "src/SponsorLink/Library/Library.csproj"]
- url = https://github.com/devlooped/SponsorLink/blob/main/samples/dotnet/Library/Library.csproj
- sha = 0f551e3be564625ee4d078649c55363bf35954ba
-
- etag = 1ba2df85e2aae342f575b9ea08c38b2117f43c131b24d38082d1d4394716f3d0
- weak
-[file "src/SponsorLink/Library/MyClass.cs"]
- url = https://github.com/devlooped/SponsorLink/blob/main/samples/dotnet/Library/MyClass.cs
- sha = f47528874a6d9192b5546f84b455f5ccc474a707
-
- etag = b5b3ccd6cd14bb90dd9702b9d7e52cc22c11e601c039617738d688f9fd45d49b
- weak
-[file "src/SponsorLink/Library/Resources.resx"]
- url = https://github.com/devlooped/SponsorLink/blob/main/samples/dotnet/Library/Resources.resx
- etag = aff6051733d22982e761f2b414173aafeab40e0a76a142e2b33025dced213eb2
- weak
- sha = f47528874a6d9192b5546f84b455f5ccc474a707
-
-[file "src/SponsorLink/Library/readme.md"]
- url = https://github.com/devlooped/SponsorLink/blob/main/samples/dotnet/Library/readme.md
- sha = f47528874a6d9192b5546f84b455f5ccc474a707
-
- etag = 5002ac8c5bbeee60c13937a32c1b6c1a5dbf0065617c8f2550e6eca6fded256d
- weak
-[file "src/SponsorLink/SponsorLink.Analyzer.Tests.targets"]
- url = https://github.com/devlooped/SponsorLink/blob/main/samples/dotnet/SponsorLink.Analyzer.Tests.targets
- sha = 8a4082211918b604ad95ef0f3da3cd414747c46a
-
-
- etag = ac4e82c24d5a812eb7a1ad20d2d076b7aeedddd90c8196eaea0c227693a2ede6
- weak
-[file "src/SponsorLink/SponsorLink.Analyzer.targets"]
- url = https://github.com/devlooped/SponsorLink/blob/main/samples/dotnet/SponsorLink.Analyzer.targets
- sha = 8a4082211918b604ad95ef0f3da3cd414747c46a
-
-
-
- etag = b75dd01945453c3ccd9eb96f65959ff1607a2cf11226fac5014b01b7cb6314d7
- weak
-[file "src/SponsorLink/SponsorLink/AnalyzerOptionsExtensions.cs"]
- url = https://github.com/devlooped/SponsorLink/blob/main/samples/dotnet/SponsorLink/AnalyzerOptionsExtensions.cs
- sha = 38a11504cc9cbd994fb7380fd580102e7514b3b5
-
- etag = 9d0e3495b4db00915f79f7e0549b20f2ffff38865741a69810251550686102cc
- weak
-[file "src/SponsorLink/SponsorLink/AppDomainDictionary.cs"]
- url = https://github.com/devlooped/SponsorLink/blob/main/samples/dotnet/SponsorLink/AppDomainDictionary.cs
- sha = f47528874a6d9192b5546f84b455f5ccc474a707
-
- etag = 4a70f86e73f951bca95618c221d821e38a31ef9092af4ac61447eab845671a28
- weak
-[file "src/SponsorLink/SponsorLink/DiagnosticsManager.cs"]
- url = https://github.com/devlooped/SponsorLink/blob/main/samples/dotnet/SponsorLink/DiagnosticsManager.cs
- sha = 29921560c73bb91c2a21a21800daf0b250773598
-
- etag = a5d79dbc0ed9fac4fb1879fb3790b9ebab18e47c14c454554ce9f53f21487bb5
- weak
-[file "src/SponsorLink/SponsorLink/Resources.es-AR.resx"]
- url = https://github.com/devlooped/SponsorLink/blob/main/samples/dotnet/SponsorLink/Resources.es-AR.resx
- sha = 586398c3e650495f36601ecc8983a14ed745e058
-
- etag = 1d6ca61601815a20581fc13f9efdad151ee0e5cf952318723265d5c183d3e1cc
- weak
-[file "src/SponsorLink/SponsorLink/Resources.es.resx"]
- url = https://github.com/devlooped/SponsorLink/blob/main/samples/dotnet/SponsorLink/Resources.es.resx
- etag = 89a7bb797aeacca43e043196a00eea91f282df4caf9bbe937749026a03f707ad
- weak
- sha = 21d8dac3077c75cd07d7cc7f9e10f2620afce834
-
-[file "src/SponsorLink/SponsorLink/Resources.resx"]
- url = https://github.com/devlooped/SponsorLink/blob/main/samples/dotnet/SponsorLink/Resources.resx
- sha = 21d8dac3077c75cd07d7cc7f9e10f2620afce834
-
- etag = 8902652b8907de2fbccf73f3738d0fce503fc667a084171d6b88bf3373e559e7
- weak
-[file "src/SponsorLink/SponsorLink/SponsorLink.cs"]
- url = https://github.com/devlooped/SponsorLink/blob/main/samples/dotnet/SponsorLink/SponsorLink.cs
- sha = a755e4be0f7cb73cfde208857e28f7cfeba2dcc3
-
- etag = 402e2beb11cf64c07be3d0fc3e89115fd09fc24133c08a8951bf0e784909c510
- weak
-[file "src/SponsorLink/SponsorLink/SponsorLink.csproj"]
- url = https://github.com/devlooped/SponsorLink/blob/main/samples/dotnet/SponsorLink/SponsorLink.csproj
- sha = e8ec200934a3b3788c2e31d7022c717f5fd152fa
-
- etag = 1a58baf82b1813f68610272aa6161a18a70d5c619154734039a0d48fce6d735a
- weak
-[file "src/SponsorLink/SponsorLink/SponsorLinkAnalyzer.cs"]
- url = https://github.com/devlooped/SponsorLink/blob/main/samples/dotnet/SponsorLink/SponsorLinkAnalyzer.cs
- sha = 46e9abe02e5a6abadda66ef050ddc5b9859aa2b8
-
- etag = 062a02b6eb45e5e49cc73c77c25d66bf2695fc365e13ce7dc39f813a030fc370
- weak
-[file "src/SponsorLink/SponsorLink/SponsorManifest.cs"]
- url = https://github.com/devlooped/SponsorLink/blob/main/samples/dotnet/SponsorLink/SponsorManifest.cs
- etag = 55ef89e8441156541c1c74a50675b7f56633b56493031f0ffa877460839e3536
- weak
- sha = a755e4be0f7cb73cfde208857e28f7cfeba2dcc3
-
-[file "src/SponsorLink/SponsorLink/SponsorStatus.cs"]
- url = https://github.com/devlooped/SponsorLink/blob/main/samples/dotnet/SponsorLink/SponsorStatus.cs
- sha = 29921560c73bb91c2a21a21800daf0b250773598
-
- etag = 419a823edb42d9175ae96d66a8b0191d8fc91921268c2a5340cf8d34519d4535
- weak
-[file "src/SponsorLink/SponsorLink/SponsorableLib.targets"]
- url = https://github.com/devlooped/SponsorLink/blob/main/samples/dotnet/SponsorLink/SponsorableLib.targets
- sha = f47528874a6d9192b5546f84b455f5ccc474a707
-
- etag = 2f923a97081481a6a264d63c8ff70ce5ba65c3dbaf7ea078cbe1388fb0868e1c
- weak
-[file "src/SponsorLink/SponsorLink/Tracing.cs"]
- url = https://github.com/devlooped/SponsorLink/blob/main/samples/dotnet/SponsorLink/Tracing.cs
- sha = f47528874a6d9192b5546f84b455f5ccc474a707
-
- etag = 29d6c0362f4c47eedfebea5018d563adb04a8f7b30da87495c5c8a4561e2c4ed
- weak
-[file "src/SponsorLink/SponsorLink/buildTransitive/Devlooped.Sponsors.targets"]
- url = https://github.com/devlooped/SponsorLink/blob/main/samples/dotnet/SponsorLink/buildTransitive/Devlooped.Sponsors.targets
- sha = 697e210b68c7d6f0ececca7673d13f4309df6cd7
-
- etag = e2cb4d1bbf4096f4b3fcfa0b20abccb33520442b656f19e01e5da928fd927da8
- weak
-[file "src/SponsorLink/SponsorLink/sponsorable.md"]
- url = https://github.com/devlooped/SponsorLink/blob/main/samples/dotnet/SponsorLink/sponsorable.md
- sha = f47528874a6d9192b5546f84b455f5ccc474a707
-
- etag = 9c275d50705a2e661f0f86f1ae5e555c0033a05e86e12f936283a5b5ef47ae77
- weak
-[file "src/SponsorLink/SponsorLinkAnalyzer.sln"]
- url = https://github.com/devlooped/SponsorLink/blob/main/samples/dotnet/SponsorLinkAnalyzer.sln
- sha = f47528874a6d9192b5546f84b455f5ccc474a707
-
- etag = fc2928c9b303d81ff23891ee791a859b794d9f2d4b9f4e81b9ed15e5b74db487
- weak
-[file "src/SponsorLink/Tests/.netconfig"]
- url = https://github.com/devlooped/SponsorLink/blob/main/samples/dotnet/Tests/.netconfig
- sha = f47528874a6d9192b5546f84b455f5ccc474a707
-
- etag = 0323e19eb4582113dd409853ba83e9845069bf35733ed84a0bdc9fb6990502a9
- weak
-[file "src/SponsorLink/Tests/AnalyzerTests.cs"]
- url = https://github.com/devlooped/SponsorLink/blob/main/samples/dotnet/Tests/AnalyzerTests.cs
- sha = 697e210b68c7d6f0ececca7673d13f4309df6cd7
-
- etag = 44ef3022d2ebe1251896542b697baa9dcef9b9805b68845ccc9d0ff0181ba9d1
- weak
-[file "src/SponsorLink/Tests/Attributes.cs"]
- url = https://github.com/devlooped/SponsorLink/blob/main/samples/dotnet/Tests/Attributes.cs
- sha = f47528874a6d9192b5546f84b455f5ccc474a707
-
- etag = 1d7c17a2c9424db73746112c338a39e0000134ac878b398e2aa88f7ea5c0c488
- weak
-[file "src/SponsorLink/Tests/Extensions.cs"]
- url = https://github.com/devlooped/SponsorLink/blob/main/samples/dotnet/Tests/Extensions.cs
- etag = 9e51b7e6540fae140490a5283b1e67ce071bd18a267bc2ae0b35c7248261aed1
- weak
- sha = f47528874a6d9192b5546f84b455f5ccc474a707
-
-[file "src/SponsorLink/Tests/JsonOptions.cs"]
- url = https://github.com/devlooped/SponsorLink/blob/main/samples/dotnet/Tests/JsonOptions.cs
- sha = f47528874a6d9192b5546f84b455f5ccc474a707
-
- etag = 17799725ad9b24eb5998365962c30b9a487bddadca37c616e35b76b8c9eb161a
- weak
-[file "src/SponsorLink/Tests/Resources.resx"]
- url = https://github.com/devlooped/SponsorLink/blob/main/samples/dotnet/Tests/Resources.resx
- sha = f47528874a6d9192b5546f84b455f5ccc474a707
-
- etag = 13d1bb8b0de32a8c9b5dbdc806a036ed89d423cd7c0be187b8c56055c9bf7783
- weak
-[file "src/SponsorLink/Tests/Sample.cs"]
- url = https://github.com/devlooped/SponsorLink/blob/main/samples/dotnet/Tests/Sample.cs
- sha = ca82a9d6298a933192c5dfd2c5881ebadb85d0fe
-
- etag = 1875555adb7eab21acf1e730b6baeb8c095d9f6f9f07303a87ad9c16e0f6490d
- weak
-[file "src/SponsorLink/Tests/SponsorManifestTests.cs"]
- url = https://github.com/devlooped/SponsorLink/blob/main/samples/dotnet/Tests/SponsorManifestTests.cs
- sha = a755e4be0f7cb73cfde208857e28f7cfeba2dcc3
-
- etag = 82ae1c417265f2e136544980b4f687a1cc2c1bfb24df93d354c259053550f4a3
- weak
-[file "src/SponsorLink/Tests/SponsorableManifest.cs"]
- url = https://github.com/devlooped/SponsorLink/blob/main/samples/dotnet/Tests/SponsorableManifest.cs
- sha = f47528874a6d9192b5546f84b455f5ccc474a707
-
- etag = eb2292c6d7bf53a56acbb73d7c89ccc78fd8bec2e2198d70e36da93c01d36374
- weak
-[file "src/SponsorLink/Tests/Tests.csproj"]
- url = https://github.com/devlooped/SponsorLink/blob/main/samples/dotnet/Tests/Tests.csproj
- sha = e8ec200934a3b3788c2e31d7022c717f5fd152fa
-
- etag = eb34fc9fe25b0169f069ff692379a19c59673727d8abb6f45816012661329df5
- weak
-[file "src/SponsorLink/Tests/keys/kzu.key"]
- url = https://github.com/devlooped/SponsorLink/blob/main/samples/dotnet/Tests/keys/kzu.key
- sha = f47528874a6d9192b5546f84b455f5ccc474a707
-
- etag = bd8f5b16d248829e9cf4d8695677b2b7c09607d2b50b1cda05dbaa48c2a3fe04
- weak
-[file "src/SponsorLink/Tests/keys/kzu.key.jwk"]
- url = https://github.com/devlooped/SponsorLink/blob/main/samples/dotnet/Tests/keys/kzu.key.jwk
- sha = f47528874a6d9192b5546f84b455f5ccc474a707
-
- etag = dca60d636ab866adf211662a5aa597e4d1f477a280f6ee82cd7f7b390535a458
- weak
-[file "src/SponsorLink/Tests/keys/kzu.key.txt"]
- url = https://github.com/devlooped/SponsorLink/blob/main/samples/dotnet/Tests/keys/kzu.key.txt
- sha = f47528874a6d9192b5546f84b455f5ccc474a707
-
- etag = 7553487806f6dbd219b4dbda5d6fb097b8047a1d1856255a339e049c7496da43
- weak
-[file "src/SponsorLink/Tests/keys/kzu.pub"]
- url = https://github.com/devlooped/SponsorLink/blob/main/samples/dotnet/Tests/keys/kzu.pub
- sha = f47528874a6d9192b5546f84b455f5ccc474a707
-
- etag = 75c544bb911372c909a58d6d07e89abe776ef618861f6d580915b0e79c6bb2fe
- weak
-[file "src/SponsorLink/Tests/keys/kzu.pub.jwk"]
- url = https://github.com/devlooped/SponsorLink/blob/main/samples/dotnet/Tests/keys/kzu.pub.jwk
- sha = f47528874a6d9192b5546f84b455f5ccc474a707
-
- etag = 9a2829bf01fe53089c0f4ff46f5bca60955338bbfc7a2354482cde05dc750806
- weak
-[file "src/SponsorLink/Tests/keys/kzu.pub.txt"]
- url = https://github.com/devlooped/SponsorLink/blob/main/samples/dotnet/Tests/keys/kzu.pub.txt
- sha = f47528874a6d9192b5546f84b455f5ccc474a707
-
- etag = 6308869899eb7efeee34dc4daa71ee04a06f21cc09199beb74a78af8e213f576
- weak
-[file "src/SponsorLink/Tests/keys/sponsorlink.jwt"]
- url = https://github.com/devlooped/SponsorLink/blob/main/samples/dotnet/Tests/keys/sponsorlink.jwt
- sha = f47528874a6d9192b5546f84b455f5ccc474a707
-
- etag = af05cc803434a0e22b67521be8bb66676c5c0ca0795afb4430bd26751ce307e1
- weak
-[file "src/SponsorLink/jwk.ps1"]
- url = https://github.com/devlooped/SponsorLink/blob/main/samples/dotnet/jwk.ps1
- etag = f399e05ecb56adaf41d2545171f299a319142b17dd09fc38e452ca8c5d13bd0d
- weak
- sha = f47528874a6d9192b5546f84b455f5ccc474a707
-
-[file "src/SponsorLink/readme.md"]
- url = https://github.com/devlooped/SponsorLink/blob/main/samples/dotnet/readme.md
- sha = 697e210b68c7d6f0ececca7673d13f4309df6cd7
-
- etag = 3f3bb07d204d2539d90a28145653c4b48c1f373d7186b39d2593338cebcd3299
- weak
[file ".github/workflows/dotnet-env.yml"]
url = https://github.com/devlooped/oss/blob/main/.github/workflows/dotnet-env.yml
sha = 77e83f238196d2723640abef0c7b6f43994f9747
etag = fcb9759a96966df40dcd24906fd328ddec05953b7e747a6bb8d0d1e4c3865274
weak
+[file "osmfeula.txt"]
+ url = https://github.com/devlooped/.github/blob/main/osmfeula.txt
+ sha = 666a2a7c315f72199c418f11482a950fc69a8901
+ etag = 91ea15c07bfd784036c6ca931f5b2df7e9767b8367146d96c79caef09d63899f
+ weak
diff --git a/osmfeula.txt b/osmfeula.txt
new file mode 100644
index 0000000..e0cb088
--- /dev/null
+++ b/osmfeula.txt
@@ -0,0 +1,63 @@
+End User License Agreement
+
+This Open Source Maintenance Fee Agreement ("Agreement") is a legal agreement
+between you ("User") and Devlooped ("Project") for the use of
+$product$ ("Software"), an open source software project licensed under
+the MIT License ("OSI License"), an OSI-approved open source license.
+Project offers a Binary Release of the Software to Users in exchange for a
+maintenance fee ("Fee"). "Binary Release" refers to pre-compiled executable
+versions of the Software provided by Project. By accessing or using the
+Binary Release, User agrees to be bound by the terms of this Agreement.
+
+1. Applicability
+
+Project agrees to provide User with the Binary Release in exchange for the
+Fees outlined in Section 2, subject to the terms of this Agreement. The Fee
+applies only to Users that generate revenue by the Software.
+Non-revenue-generating use of the Software is exempt from this Fee. In
+addition, Users who pay separate support and/or maintenance fees to the
+maintainers of the Software are exempt from the Fee outlined in this
+Agreement. This distinction ensures that duplicate fees are not imposed,
+promoting fairness and consistency while respecting alternative support
+arrangements.
+
+2. Monthly Fee and Payment Terms
+
+Revenue-generating Users required to pay the Fee shall follow the payment
+terms set forth by the Project. Failure to comply with these terms may result
+in suspending access to the Binary Release. However, this does not restrict
+the User from obtaining or redistributing binaries from other sources or
+self-compiling them.
+
+3. Nature of the Fee
+
+The Fee is not a license fee. The Software's source code is licensed to User
+under the OSI License and remains freely distributable under the terms of the
+OSI License and any applicable open-source licenses.
+
+4. Conflicts with OSI License
+
+To the extent any term of this Agreement conflicts with User's rights
+under the OSI License regarding the Software, the OSI License shall govern.
+This Agreement applies only to the Binary Release and does not limit User's
+ability to access, modify, or distribute the Software's source code or
+self-compiled binaries. User may independently compile binaries from the
+Software's source code without this Agreement, subject to OSI License terms.
+User may redistribute the Binary Release received under this Agreement,
+provided such redistribution complies with the OSI License (e.g., including
+copyright and permission notices). This Agreement imposes no additional
+restrictions on such rights.
+
+5. Disclaimer of Warranty and Limitation of Liability
+
+THE SOFTWARE AND BINARY RELEASE ARE PROVIDED BY THE PROJECT "AS IS" AND ANY
+EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE PROJECT OR ITS CONTRIBUTORS BE LIABLE FOR
+ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THE SOFTWARE AND BINARY RELEASE, EVEN IF ADVISED OF THE POSSIBILITY OF
+SUCH DAMAGE.
diff --git a/readme.md b/readme.md
index b589a88..4d33354 100644
--- a/readme.md
+++ b/readme.md
@@ -2,10 +2,11 @@
================
[](https://www.nuget.org/packages/Merq)
-[](https://www.nuget.org/packages/Merq)
-[](https://github.com/devlooped/Merq/blob/main/license.txt)
+[](https://www.nuget.org/packages/Merq)
+[](osmfeula.txt)
+[](license.txt)
-
+
> **Mercury:** messenger of the Roman gods
> *Mercury* > *Merq-ry* > **Merq**
@@ -33,7 +34,9 @@ Clearly, in the case of VSCode, everything is in-process, but the benefits of
a clean and predictable API are pretty obvious.
*Merq* provides the same capabilities for .NET apps.
-
+
+
+
## Events
Events can be any type, there is no restriction or interfaces you must implement.
diff --git a/src/Directory.props b/src/Directory.props
index 684612c..7c5ab27 100644
--- a/src/Directory.props
+++ b/src/Directory.props
@@ -4,6 +4,9 @@
Merq
true
NU1507;$(NoWarn)
+
+ OSMFEULA.txt
+ true
diff --git a/src/Merq.Abstractions/Merq.Abstractions.csproj b/src/Merq.Abstractions/Merq.Abstractions.csproj
index 743cb86..75d650f 100644
--- a/src/Merq.Abstractions/Merq.Abstractions.csproj
+++ b/src/Merq.Abstractions/Merq.Abstractions.csproj
@@ -26,6 +26,7 @@
+
diff --git a/src/Merq.Abstractions/readme.md b/src/Merq.Abstractions/readme.md
index 8ecc3fc..0b67f94 100644
--- a/src/Merq.Abstractions/readme.md
+++ b/src/Merq.Abstractions/readme.md
@@ -1,5 +1,8 @@
-
-
+[](osmfeula.txt)
+[](license.txt)
+[](https://github.com/devlooped/Merq)
-
+
+
+
\ No newline at end of file
diff --git a/src/Merq.AutoMapper/Merq.AutoMapper.csproj b/src/Merq.AutoMapper/Merq.AutoMapper.csproj
index 8dff6e2..670cf06 100644
--- a/src/Merq.AutoMapper/Merq.AutoMapper.csproj
+++ b/src/Merq.AutoMapper/Merq.AutoMapper.csproj
@@ -26,4 +26,8 @@
+
+
+
+
diff --git a/src/Merq.AutoMapper/readme.md b/src/Merq.AutoMapper/readme.md
index c955095..7248d76 100644
--- a/src/Merq.AutoMapper/readme.md
+++ b/src/Merq.AutoMapper/readme.md
@@ -1,9 +1,12 @@
-
+[](osmfeula.txt)
+[](license.txt)
+[](https://github.com/devlooped/Merq)
-For usage and authoring of commands and events, see [Merq](https://nuget.org/packages/Merq) readme.
+
-
+For usage and authoring of commands and events, see [Merq](https://nuget.org/packages/Merq) readme.
-
+
+
diff --git a/src/Merq.CodeAnalysis.Tests/Merq.CodeAnalysis.Tests.csproj b/src/Merq.CodeAnalysis.Tests/Merq.CodeAnalysis.Tests.csproj
index e8579f5..44737a1 100644
--- a/src/Merq.CodeAnalysis.Tests/Merq.CodeAnalysis.Tests.csproj
+++ b/src/Merq.CodeAnalysis.Tests/Merq.CodeAnalysis.Tests.csproj
@@ -34,7 +34,4 @@
-
-
-
diff --git a/src/Merq.CodeAnalysis/Merq.CodeAnalysis.csproj b/src/Merq.CodeAnalysis/Merq.CodeAnalysis.csproj
index 871b358..eee0387 100644
--- a/src/Merq.CodeAnalysis/Merq.CodeAnalysis.csproj
+++ b/src/Merq.CodeAnalysis/Merq.CodeAnalysis.csproj
@@ -11,13 +11,6 @@
true
-
- $(MSBuildThisFileDirectory)..\SponsorLink\SponsorLink.Analyzer.targets
- Merq;Merq.Abstractions;Merq.VisualStudio
- MERQ
- 30
-
-
@@ -27,8 +20,4 @@
-
-
-
-
diff --git a/src/Merq.Tests/Merq.Tests.csproj b/src/Merq.Tests/Merq.Tests.csproj
index 49c0d91..a8d3663 100644
--- a/src/Merq.Tests/Merq.Tests.csproj
+++ b/src/Merq.Tests/Merq.Tests.csproj
@@ -47,7 +47,4 @@
-
-
-
diff --git a/src/Merq.VisualStudio/Merq.VisualStudio.csproj b/src/Merq.VisualStudio/Merq.VisualStudio.csproj
index 14dfcd2..5db37ec 100644
--- a/src/Merq.VisualStudio/Merq.VisualStudio.csproj
+++ b/src/Merq.VisualStudio/Merq.VisualStudio.csproj
@@ -22,6 +22,7 @@
+
diff --git a/src/Merq.VisualStudio/readme.md b/src/Merq.VisualStudio/readme.md
index c682db1..fe19e0c 100644
--- a/src/Merq.VisualStudio/readme.md
+++ b/src/Merq.VisualStudio/readme.md
@@ -1,4 +1,10 @@
-This package provides a MEF-ready customization of the `Merq`
+[](osmfeula.txt)
+[](license.txt)
+[](https://github.com/devlooped/AI)
+
+
+
+This package provides a MEF-ready customization of the `Merq`
default implementation, which makes it trivial to consume from
an application that uses [Microsoft.VisualStudio.Composition](https://nuget.org/packages/Microsoft.VisualStudio.Composition)
to load MEF-based components.
@@ -165,4 +171,7 @@ it won't be instantiated at all unless someone called `Observe`, which minimi
the startup and ongoing cost of having this extensibility mechanism built-in.
If you are [hosting VS MEF](https://github.com/microsoft/vs-mef/blob/main/doc/hosting.md)
-in your app, the same concepts apply, so it should be a familiar experience.
\ No newline at end of file
+in your app, the same concepts apply, so it should be a familiar experience.
+
+
+
\ No newline at end of file
diff --git a/src/Merq/Merq.csproj b/src/Merq/Merq.csproj
index 6de049d..c959534 100644
--- a/src/Merq/Merq.csproj
+++ b/src/Merq/Merq.csproj
@@ -27,6 +27,7 @@
+
\ No newline at end of file
diff --git a/src/Merq/readme.md b/src/Merq/readme.md
index 069dfcb..655b10e 100644
--- a/src/Merq/readme.md
+++ b/src/Merq/readme.md
@@ -1,6 +1,10 @@
-
-
+[](osmfeula.txt)
+[](license.txt)
+[](https://github.com/devlooped/Merq)
-
+
+
+
+
diff --git a/src/Samples/ConsoleApp/ConsoleApp.csproj b/src/Samples/ConsoleApp/ConsoleApp.csproj
index 026d9d3..b31c98d 100644
--- a/src/Samples/ConsoleApp/ConsoleApp.csproj
+++ b/src/Samples/ConsoleApp/ConsoleApp.csproj
@@ -42,8 +42,4 @@
-
-
-
-
diff --git a/src/Samples/Library1/Library1.csproj b/src/Samples/Library1/Library1.csproj
index f3b02ca..52c41f7 100644
--- a/src/Samples/Library1/Library1.csproj
+++ b/src/Samples/Library1/Library1.csproj
@@ -18,8 +18,4 @@
-
-
-
-
diff --git a/src/Samples/Library2/Library2.csproj b/src/Samples/Library2/Library2.csproj
index f3b02ca..52c41f7 100644
--- a/src/Samples/Library2/Library2.csproj
+++ b/src/Samples/Library2/Library2.csproj
@@ -18,8 +18,4 @@
-
-
-
-
diff --git a/src/Samples/Sample/Merq.Sample.csproj b/src/Samples/Sample/Merq.Sample.csproj
index b0518d7..7ef011b 100644
--- a/src/Samples/Sample/Merq.Sample.csproj
+++ b/src/Samples/Sample/Merq.Sample.csproj
@@ -17,8 +17,4 @@
-
-
-
-
diff --git a/src/SponsorLink/Analyzer/Analyzer.csproj b/src/SponsorLink/Analyzer/Analyzer.csproj
deleted file mode 100644
index d6063e3..0000000
--- a/src/SponsorLink/Analyzer/Analyzer.csproj
+++ /dev/null
@@ -1,42 +0,0 @@
-
-
-
- SponsorableLib.Analyzers
- netstandard2.0
- true
- analyzers/dotnet/roslyn4.0
- true
- false
- true
- $(MSBuildThisFileDirectory)..\SponsorLink.Analyzer.targets
- disable
- SponsorableLib
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- $([System.IO.File]::ReadAllText('$(MSBuildThisFileDirectory)..\Tests\keys\kzu.pub.jwk'))
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/src/SponsorLink/Analyzer/GraceApiAnalyzer.cs b/src/SponsorLink/Analyzer/GraceApiAnalyzer.cs
deleted file mode 100644
index 73b1ab9..0000000
--- a/src/SponsorLink/Analyzer/GraceApiAnalyzer.cs
+++ /dev/null
@@ -1,61 +0,0 @@
-using System.Collections.Immutable;
-using System.Linq;
-using Devlooped.Sponsors;
-using Microsoft.CodeAnalysis;
-using Microsoft.CodeAnalysis.CSharp;
-using Microsoft.CodeAnalysis.Diagnostics;
-using static Devlooped.Sponsors.SponsorLink;
-
-namespace Analyzer;
-
-///
-/// Links the sponsor status for the current compilation.
-///
-[DiagnosticAnalyzer(LanguageNames.CSharp, LanguageNames.VisualBasic)]
-public class GraceApiAnalyzer : DiagnosticAnalyzer
-{
- public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create(
- new DiagnosticDescriptor(
- "SL010", "Grace API usage", "Reports info for APIs that are in grace period", "Sponsors",
- DiagnosticSeverity.Info, true, helpLinkUri: Funding.HelpUrl),
- new DiagnosticDescriptor(
- "SL011", "Report Sponsoring Status", "Fake to get it to call us", "Sponsors",
- DiagnosticSeverity.Warning, true)
- );
-
-#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);
- // Report info grace and expiring diagnostics.
- context.RegisterSyntaxNodeAction(AnalyzeNode, SyntaxKind.InvocationExpression);
- context.RegisterSyntaxNodeAction(AnalyzeNode, SyntaxKind.SimpleMemberAccessExpression);
- }
-
- void AnalyzeNode(SyntaxNodeAnalysisContext context)
- {
- var status = Diagnostics.GetOrSetStatus(() => context.Options);
- if (status != SponsorStatus.Grace)
- return;
-
- ReportGraceSymbol(context, context.Node.GetLocation(), context.SemanticModel.GetSymbolInfo(context.Node).Symbol);
- }
-
- void ReportGraceSymbol(SyntaxNodeAnalysisContext context, Location location, ISymbol? symbol)
- {
- if (symbol != null &&
- symbol.GetAttributes().Any(attr =>
- attr.AttributeClass?.ToDisplayString() == "System.ComponentModel.CategoryAttribute" &&
- attr.ConstructorArguments.Any(arg => arg.Value as string == "Sponsored")))
- {
- context.ReportDiagnostic(Diagnostic.Create(
- SupportedDiagnostics[0],
- location));
- }
- }
-}
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 1f02282..0000000
--- a/src/SponsorLink/Analyzer/StatusReportingAnalyzer.cs
+++ /dev/null
@@ -1,57 +0,0 @@
-using System;
-using System.Collections.Immutable;
-using System.IO;
-using System.Linq;
-using Devlooped.Sponsors;
-using Humanizer;
-using Microsoft.CodeAnalysis;
-using Microsoft.CodeAnalysis.Diagnostics;
-using Microsoft.CodeAnalysis.Text;
-using static Devlooped.Sponsors.SponsorLink;
-
-namespace Analyzer;
-
-[DiagnosticAnalyzer(LanguageNames.CSharp, LanguageNames.VisualBasic)]
-public class StatusReportingAnalyzer : DiagnosticAnalyzer
-{
- public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create(
- new DiagnosticDescriptor(
- "SL001", "Report Sponsoring Status", "Reports sponsoring status determined by SponsorLink", "Sponsors",
- DiagnosticSeverity.Info, true),
- new DiagnosticDescriptor(
- "SL002", "Report Sponsoring Status", "Fake to get it to call us", "Sponsors",
- DiagnosticSeverity.Warning, true)
- );
-
- public override void Initialize(AnalysisContext context)
- {
- context.EnableConcurrentExecution();
- context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None);
-
- context.RegisterCompilationAction(c =>
- {
- var installed = c.Options.AdditionalFiles.Where(x =>
- {
- var options = c.Options.AnalyzerConfigOptionsProvider.GetOptions(x);
- // In release builds, we'll have a single such item, since we IL-merge the analyzer.
- return options.TryGetValue("build_metadata.Analyzer.ItemType", out var itemType) &&
- options.TryGetValue("build_metadata.Analyzer.NuGetPackageId", out var packageId) &&
- itemType == "Analyzer" &&
- packageId == "SponsorableLib";
- }).Select(x => File.GetLastWriteTime(x.Path)).OrderByDescending(x => x).FirstOrDefault();
-
- var status = Diagnostics.GetOrSetStatus(() => c.Options);
-
- var location = Location.None;
- if (c.Options.AnalyzerConfigOptionsProvider.GlobalOptions.TryGetValue("build_property.MSBuildProjectFullPath", out var value))
- location = Location.Create(value, new TextSpan(), new LinePositionSpan());
-
- c.ReportDiagnostic(Diagnostic.Create(SupportedDiagnostics[0], location, status.ToString()));
-
- if (installed != default)
- Tracing.Trace($"Status: {status}, Installed: {(DateTime.Now - installed).Humanize()} ago");
- else
- Tracing.Trace($"Status: {status}, unknown install time");
- });
- }
-}
\ No newline at end of file
diff --git a/src/SponsorLink/Analyzer/StatusReportingGenerator.cs b/src/SponsorLink/Analyzer/StatusReportingGenerator.cs
deleted file mode 100644
index 8ba7031..0000000
--- a/src/SponsorLink/Analyzer/StatusReportingGenerator.cs
+++ /dev/null
@@ -1,26 +0,0 @@
-using Devlooped.Sponsors;
-using Microsoft.CodeAnalysis;
-using static Devlooped.Sponsors.SponsorLink;
-
-namespace Analyzer;
-
-[Generator]
-public class StatusReportingGenerator : IIncrementalGenerator
-{
- public void Initialize(IncrementalGeneratorInitializationContext context)
- {
- context.RegisterSourceOutput(
- // this is required to ensure status is registered properly independently
- // of analyzer runs.
- context.GetStatusOptions(),
- (spc, source) =>
- {
- var status = Diagnostics.GetOrSetStatus(source);
- spc.AddSource("StatusReporting.cs",
- $"""
- // Status: {status}
- // DesignTimeBuild: {source.GlobalOptions.IsDesignTimeBuild()}
- """);
- });
- }
-}
diff --git a/src/SponsorLink/Analyzer/buildTransitive/SponsorableLib.targets b/src/SponsorLink/Analyzer/buildTransitive/SponsorableLib.targets
deleted file mode 100644
index bb1b113..0000000
--- a/src/SponsorLink/Analyzer/buildTransitive/SponsorableLib.targets
+++ /dev/null
@@ -1,8 +0,0 @@
-
-
-
-
-
-
-
-
\ 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 191107d..0000000
--- a/src/SponsorLink/Directory.Build.props
+++ /dev/null
@@ -1,27 +0,0 @@
-
-
-
- false
- latest
- true
- annotations
- true
-
- false
- $([System.IO.Path]::GetFullPath('$(MSBuildThisFileDirectory)bin'))
-
- https://pkg.kzu.app/index.json;https://api.nuget.org/v3/index.json
- $(PackageOutputPath);$(RestoreSources)
-
-
- $([System.DateTime]::Parse("2024-03-15"))
- $([System.DateTime]::UtcNow.Subtract($(Epoc)).TotalDays)
- $([System.Math]::Truncate($(TotalDays)))
- $([System.Math]::Floor($([MSBuild]::Divide($([System.DateTime]::UtcNow.TimeOfDay.TotalSeconds), 10))))
- 42.$(Days).$(Seconds)
-
- SponsorableLib
-
-
-
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 @@
-
-
-
-
-
-
-
\ 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 39424e3..0000000
--- a/src/SponsorLink/Library/Library.csproj
+++ /dev/null
@@ -1,25 +0,0 @@
-
-
-
- SponsorableLib
- netstandard2.0
- true
- SponsorableLib
- Sample library incorporating SponsorLink checks
- true
- true
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
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 8e5141e..0000000
Binary files a/src/SponsorLink/Library/Resources.resx and /dev/null differ
diff --git a/src/SponsorLink/Library/readme.md b/src/SponsorLink/Library/readme.md
deleted file mode 100644
index ba4ce37..0000000
--- a/src/SponsorLink/Library/readme.md
+++ /dev/null
@@ -1,5 +0,0 @@
-# Sponsorable Library
-
-Example of a library that is available for sponsorship and leverages
-[SponsorLink](https://github.com/devlooped/SponsorLink) to remind users
-in an IDE (VS/Rider).
diff --git a/src/SponsorLink/SponsorLink.Analyzer.Tests.targets b/src/SponsorLink/SponsorLink.Analyzer.Tests.targets
deleted file mode 100644
index 4687e1e..0000000
--- a/src/SponsorLink/SponsorLink.Analyzer.Tests.targets
+++ /dev/null
@@ -1,49 +0,0 @@
-
-
-
-
- true
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- $([MSBuild]::ValueOrDefault('%(FullPath)', '').Replace('net6.0', 'netstandard2.0').Replace('net8.0', 'netstandard2.0').Replace('netcoreapp3.1', 'netstandard2.0'))
-
-
-
-
-
-
-
diff --git a/src/SponsorLink/SponsorLink.Analyzer.targets b/src/SponsorLink/SponsorLink.Analyzer.targets
deleted file mode 100644
index 6a78464..0000000
--- a/src/SponsorLink/SponsorLink.Analyzer.targets
+++ /dev/null
@@ -1,238 +0,0 @@
-
-
-
-
-
-
- true
-
- true
- false
-
- true
-
- CoreResGen;$(CoreCompileDependsOn)
-
-
- $(Product)
- $(PackageId)
-
- $([System.Text.RegularExpressions.Regex]::Replace("$(FundingProduct)", "[^A-Z]", ""))
-
- 15
-
- https://github.com/devlooped#sponsorlink
-
-
- false
-
-
-
-
-
-
-
-
-
-
-
- SponsorLink\%(RecursiveDir)%(Filename)%(Extension)
-
-
- SponsorLink\%(RecursiveDir)%(Filename)%(Extension)
-
-
- SponsorLink\%(RecursiveDir)%(Filename)%(Extension)
-
-
- SponsorLink\%(PackagePath)
-
-
-
-
-
- false
-
-
- false
-
-
- false
-
-
- false
-
-
-
-
- true
- false
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- $(FundingProduct)
-
-
-
-
-
-
- <_FundingAnalyzerPackageId Include="@(FundingAnalyzerPackageId -> '"%(Identity)"')" />
-
-
- <_FundingPackageIds>@(_FundingAnalyzerPackageId, ',')
-
-
-
- using System.Collections.Generic%3B
-
-namespace Devlooped.Sponsors%3B
-
-partial class SponsorLink
-{
- public partial class Funding
- {
- public static HashSet<string> PackageIds { get%3B } = [$(_FundingPackageIds)]%3B
- public const string Product = "$(FundingProduct)"%3B
- public const string Prefix = "$(FundingPrefix)"%3B
- public const string HelpUrl = "$(FundingHelpUrl)"%3B
- public const int Grace = $(FundingGrace)%3B
- }
-}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- $([System.IO.Path]::GetFullPath($([System.IO.Path]::Combine('$(MSBuildProjectDirectory)','$(AssemblyOriginatorKeyFile)'))))
- /keyfile:"$(AbsoluteAssemblyOriginatorKeyFile)" /delaysign
- $(ILRepackArgs) /internalize
- $(ILRepackArgs) /union
-
- $(ILRepackArgs) @(LibDir -> '/lib:"%(Identity)."', ' ')
- $(ILRepackArgs) /out:"@(IntermediateAssembly -> '%(FullPath)')"
- $(ILRepackArgs) "@(IntermediateAssembly -> '%(FullPath)')"
- $(ILRepackArgs) @(MergedAssemblies -> '"%(FullPath)"', ' ')
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- $([System.IO.File]::ReadAllText('$(MSBuildProjectDirectory)\$(BaseIntermediateOutputPath)devlooped.jwk'))
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- true
-
-
-
diff --git a/src/SponsorLink/SponsorLink/AnalyzerOptionsExtensions.cs b/src/SponsorLink/SponsorLink/AnalyzerOptionsExtensions.cs
deleted file mode 100644
index d8c29c6..0000000
--- a/src/SponsorLink/SponsorLink/AnalyzerOptionsExtensions.cs
+++ /dev/null
@@ -1,18 +0,0 @@
-using Microsoft.CodeAnalysis.Diagnostics;
-
-static class AnalyzerOptionsExtensions
-{
- ///
- /// Gets whether the current build is a design-time build.
- ///
- public static bool IsDesignTimeBuild(this AnalyzerConfigOptionsProvider options) =>
- options.GlobalOptions.TryGetValue("build_property.DesignTimeBuild", out var value) &&
- bool.TryParse(value, out var isDesignTime) && isDesignTime;
-
- ///
- /// Gets whether the current build is a design-time build.
- ///
- public static bool IsDesignTimeBuild(this AnalyzerConfigOptions options) =>
- options.TryGetValue("build_property.DesignTimeBuild", out var value) &&
- bool.TryParse(value, out var isDesignTime) && isDesignTime;
-}
\ 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 @@
-//
-#nullable enable
-using System;
-
-namespace Devlooped.Sponsors;
-
-///
-/// A helper class to store and retrieve values from the current
-/// as typed named values.
-///
-///
-/// This allows tools that run within the same app domain to share state, such as
-/// MSBuild tasks or Roslyn analyzers.
-///
-static class AppDomainDictionary
-{
- ///
- /// Gets the value associated with the specified name, or creates a new one if it doesn't exist.
- ///
- public static TValue Get(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 b2d56f8..0000000
--- a/src/SponsorLink/SponsorLink/DiagnosticsManager.cs
+++ /dev/null
@@ -1,302 +0,0 @@
-//
-#nullable enable
-using System;
-using System.Collections.Concurrent;
-using System.Collections.Generic;
-using System.Collections.Immutable;
-using System.Globalization;
-using System.IO;
-using System.Linq;
-using Humanizer;
-using Microsoft.CodeAnalysis;
-using Microsoft.CodeAnalysis.Diagnostics;
-using static Devlooped.Sponsors.SponsorLink;
-
-namespace Devlooped.Sponsors;
-
-///
-/// Manages diagnostics for the SponsorLink analyzer so that there are no duplicates
-/// when multiple projects share the same product name (i.e. ThisAssembly).
-///
-class DiagnosticsManager
-{
- static readonly Guid appDomainDiagnosticsKey = new(0x8d0e2670, 0xe6c4, 0x45c8, 0x81, 0xba, 0x5a, 0x36, 0x81, 0xd3, 0x65, 0x3e);
-
- public static Dictionary KnownDescriptors { get; } = new()
- {
- // Requires:
- //
- //
- { SponsorStatus.Unknown, CreateUnknown([.. Sponsorables.Keys], Funding.Product, Funding.Prefix) },
- { SponsorStatus.Grace, CreateGrace([.. Sponsorables.Keys], Funding.Product, Funding.Prefix) },
- { SponsorStatus.User, CreateSponsor([.. Sponsorables.Keys], Funding.Prefix) },
- { SponsorStatus.Contributor, CreateContributor([.. Sponsorables.Keys], Funding.Prefix, hidden: true) },
- // NOTE: similar to contributor, we don't show OSS author membership in the IDE.
- { SponsorStatus.OpenSource, CreateOpenSource([.. Sponsorables.Keys], Funding.Prefix) },
- // NOTE: organization is a special case of sponsor, but we report it as hidden since the user isn't directly involved.
- { SponsorStatus.Organization, CreateSponsor([.. Sponsorables.Keys], Funding.Prefix, hidden: true) },
- // NOTE: similar to organization, we don't show team membership in the IDE.
- { SponsorStatus.Team, CreateContributor([.. Sponsorables.Keys], Funding.Prefix, hidden: true) },
- { SponsorStatus.Expiring, CreateExpiring([.. Sponsorables.Keys], Funding.Prefix) },
- { SponsorStatus.Expired, CreateExpired([.. Sponsorables.Keys], Funding.Prefix) },
- };
-
- ///
- /// Acceses the diagnostics dictionary for the current .
- ///
- ConcurrentDictionary Diagnostics
- => AppDomainDictionary.Get>(appDomainDiagnosticsKey.ToString());
-
- ///
- /// Gets the status of the given product based on a previously stored diagnostic.
- /// To ensure the value is always set before returning, use .
- /// This method is safe to use (and would get a non-null value) in analyzers that run after CompilationStartAction(see
- /// https://github.com/dotnet/roslyn/blob/main/docs/analyzers/Analyzer%20Actions%20Semantics.md under Ordering of actions).
- ///
- /// Optional that was reported, if any.
- ///
- /// The SponsorLinkAnalyzer.GetOrSetStatus 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.
- ///
- public SponsorStatus? GetStatus()
- => Diagnostics.TryGetValue(Funding.Product, out var diagnostic) ? GetStatus(diagnostic) : null;
-
- ///
- /// Gets the status of the , or sets it from
- /// the given set of if not already set.
- ///
- public SponsorStatus GetOrSetStatus(ImmutableArray manifests, AnalyzerConfigOptionsProvider options)
- => GetOrSetStatus(() => manifests, () => options.GlobalOptions);
-
- ///
- /// Gets the status of the , or sets it from
- /// the given set of if not already set.
- ///
- public SponsorStatus GetOrSetStatus(StatusOptions options)
- => GetOrSetStatus(() => options.AdditionalFiles, () => options.GlobalOptions);
-
- ///
- /// Gets the status of the , or sets it from
- /// the given analyzer if not already set.
- ///
- public SponsorStatus GetOrSetStatus(Func options)
- => GetOrSetStatus(() => options().GetSponsorAdditionalFiles(), () => options()?.AnalyzerConfigOptionsProvider.GlobalOptions);
-
- ///
- /// Attemps to get the diagnostic for the given product.
- ///
- /// The product diagnostic that might have been pushed previously.
- /// The removed diagnostic, or if none was previously pushed.
- public Diagnostic? TryGet(string product = Funding.Product)
- {
- // Don't pop grace diagnostics, as we report them more than once.
- if (GetStatus() == SponsorStatus.Grace && Diagnostics.TryGetValue(product, out var grace))
- return grace;
-
- if (Diagnostics.TryRemove(product, out var diagnostic) &&
- GetStatus(diagnostic) != SponsorStatus.Grace)
- {
- return diagnostic;
- }
-
- return null;
- }
-
- ///
- /// Pushes a diagnostic for the given product.
- ///
- SponsorStatus Push(Diagnostic diagnostic, SponsorStatus status, string product = Funding.Product)
- {
- // We only expect to get one warning per sponsorable+product
- // combination, and first one to set wins.
- Diagnostics.TryAdd(product, diagnostic);
- return status;
- }
-
- SponsorStatus GetOrSetStatus(Func> getAdditionalFiles, Func getGlobalOptions)
- {
- if (GetStatus() is { } status)
- return status;
-
- if (!SponsorLink.TryRead(out var claims, getAdditionalFiles().Where(x => x.Path.EndsWith(".jwt")).Select(text =>
- (text.GetText()?.ToString() ?? "", Sponsorables[Path.GetFileNameWithoutExtension(text.Path)]))) ||
- claims.GetExpiration() is not DateTime exp)
- {
- var noGrace = getGlobalOptions() is { } globalOptions &&
- globalOptions.TryGetValue("build_property.SponsorLinkNoInstallGrace", out var value) &&
- bool.TryParse(value, out var skipCheck) && skipCheck;
-
- if (noGrace != true)
- {
- // Consider grace period if we can find the install time.
- var installed = getAdditionalFiles()
- .Where(x => x.Path.EndsWith(".dll"))
- .Select(x => File.GetLastWriteTime(x.Path))
- .OrderByDescending(x => x)
- .FirstOrDefault();
-
- if (installed != default && ((DateTime.Now - installed).TotalDays <= Funding.Grace))
- {
- // get days until grace expiration
- var days = Math.Abs((int)(installed.Date.AddDays(Funding.Grace) - DateTime.Now.Date).TotalDays);
- // report unknown, either unparsed manifest or one with no expiration (which we never emit).
- return Push(Diagnostic.Create(KnownDescriptors[SponsorStatus.Grace], null,
- effectiveSeverity: DiagnosticSeverity.Info,
- additionalLocations: null,
- properties: ImmutableDictionary.Create()
- .Add(nameof(SponsorStatus), nameof(SponsorStatus.Grace))
- .Add(nameof(SponsorStatus.Grace), days.ToString()),
- days, Funding.Product, Sponsorables.Keys.Humanize(Resources.Or)),
- SponsorStatus.Grace);
- }
- }
-
- // report unknown, either unparsed manifest or one with no expiration (which we never emit).
- return Push(Diagnostic.Create(KnownDescriptors[SponsorStatus.Unknown], null,
- properties: ImmutableDictionary.Create().Add(nameof(SponsorStatus), nameof(SponsorStatus.Unknown)),
- Funding.Product, Sponsorables.Keys.Select(x => "@" + x).Humanize(Resources.Or)),
- SponsorStatus.Unknown);
- }
- else if (exp < DateTime.Now)
- {
- var days = Math.Abs((int)(exp.AddDays(Funding.Grace) - DateTime.Now).TotalDays);
- // report expired or expiring soon if still within the configured days of grace period
- if (exp.AddDays(Funding.Grace) < DateTime.Now)
- {
- // report expiring soon
- return Push(Diagnostic.Create(KnownDescriptors[SponsorStatus.Expiring], null,
- properties: ImmutableDictionary.Create()
- .Add(nameof(SponsorStatus), nameof(SponsorStatus.Expiring))
- .Add(nameof(SponsorStatus.Expiring), days.ToString())),
- SponsorStatus.Expiring);
- }
- else
- {
- // report expired
- return Push(Diagnostic.Create(KnownDescriptors[SponsorStatus.Expired], null,
- properties: ImmutableDictionary.Create()
- .Add(nameof(SponsorStatus), nameof(SponsorStatus.Expired))
- // add how many days ago expiration happened
- .Add(nameof(SponsorStatus.Expired), days.ToString())),
- SponsorStatus.Expired);
- }
- }
- else
- {
- status =
- claims.IsInRole("team") ?
- SponsorStatus.Team :
- claims.IsInRole("user") ?
- SponsorStatus.User :
- claims.IsInRole("contrib") ?
- SponsorStatus.Contributor :
- claims.IsInRole("org") ?
- SponsorStatus.Organization :
- claims.IsInRole("oss") ?
- SponsorStatus.OpenSource :
- SponsorStatus.Unknown;
-
- if (KnownDescriptors.TryGetValue(status, out var descriptor))
- return Push(Diagnostic.Create(descriptor, null,
- properties: ImmutableDictionary.Create().Add(nameof(SponsorStatus), status.ToString()),
- Funding.Product), status);
-
- return status;
- }
- }
-
- SponsorStatus? GetStatus(Diagnostic? diagnostic) => diagnostic?.Properties.TryGetValue(nameof(SponsorStatus), out var value) == true
- ? value switch
- {
- nameof(SponsorStatus.Grace) => SponsorStatus.Grace,
- nameof(SponsorStatus.Unknown) => SponsorStatus.Unknown,
- nameof(SponsorStatus.User) => SponsorStatus.User,
- nameof(SponsorStatus.Expiring) => SponsorStatus.Expiring,
- nameof(SponsorStatus.Expired) => SponsorStatus.Expired,
- _ => null,
- }
- : null;
-
- internal static DiagnosticDescriptor CreateUnknown(string[] sponsorable, string product, string prefix) => new(
- $"{prefix}100",
- Resources.Unknown_Title,
- Resources.Unknown_Message,
- "SponsorLink",
- DiagnosticSeverity.Warning,
- isEnabledByDefault: true,
- description: string.Format(CultureInfo.CurrentCulture, Resources.Unknown_Description,
- string.Join(", ", sponsorable.Select(x => $"https://github.com/sponsors/{x}")),
- string.Join(" ", sponsorable.Select(x => "@" + x))),
- helpLinkUri: Funding.HelpUrl,
- WellKnownDiagnosticTags.NotConfigurable, "CompilationEnd");
-
- internal static DiagnosticDescriptor CreateGrace(string[] sponsorable, string product, string prefix) => new(
- $"{prefix}101",
- Resources.Grace_Title,
- Resources.Grace_Message,
- "SponsorLink",
- DiagnosticSeverity.Warning,
- isEnabledByDefault: true,
- description: string.Format(CultureInfo.CurrentCulture, Resources.Grace_Description,
- string.Join(", ", sponsorable.Select(x => $"https://github.com/sponsors/{x}")),
- string.Join(" ", sponsorable.Select(x => "@" + x))),
- helpLinkUri: Funding.HelpUrl,
- WellKnownDiagnosticTags.NotConfigurable);
-
- internal static DiagnosticDescriptor CreateExpiring(string[] sponsorable, string prefix) => new(
- $"{prefix}102",
- Resources.Expiring_Title,
- Resources.Expiring_Message,
- "SponsorLink",
- DiagnosticSeverity.Warning,
- isEnabledByDefault: true,
- description: string.Format(CultureInfo.CurrentCulture, Resources.Expiring_Description, string.Join(" ", sponsorable)),
- helpLinkUri: "https://github.com/devlooped#autosync",
- "DoesNotSupportF1Help", WellKnownDiagnosticTags.NotConfigurable, "CompilationEnd");
-
- internal static DiagnosticDescriptor CreateExpired(string[] sponsorable, string prefix) => new(
- $"{prefix}103",
- Resources.Expired_Title,
- Resources.Expired_Message,
- "SponsorLink",
- DiagnosticSeverity.Warning,
- isEnabledByDefault: true,
- description: string.Format(CultureInfo.CurrentCulture, Resources.Expired_Description, string.Join(" ", sponsorable)),
- helpLinkUri: "https://github.com/devlooped#autosync",
- "DoesNotSupportF1Help", WellKnownDiagnosticTags.NotConfigurable, "CompilationEnd");
-
- internal static DiagnosticDescriptor CreateSponsor(string[] sponsorable, string prefix, bool hidden = false) => new(
- $"{prefix}110",
- Resources.Sponsor_Title,
- Resources.Sponsor_Message,
- "SponsorLink",
- hidden ? DiagnosticSeverity.Hidden : DiagnosticSeverity.Info,
- isEnabledByDefault: true,
- description: Resources.Sponsor_Description,
- helpLinkUri: Funding.HelpUrl,
- "DoesNotSupportF1Help", "CompilationEnd");
-
- internal static DiagnosticDescriptor CreateContributor(string[] sponsorable, string prefix, bool hidden = false) => new(
- $"{prefix}111",
- Resources.Contributor_Title,
- Resources.Contributor_Message,
- "SponsorLink",
- hidden ? DiagnosticSeverity.Hidden : DiagnosticSeverity.Info,
- isEnabledByDefault: true,
- description: Resources.Contributor_Description,
- helpLinkUri: Funding.HelpUrl,
- "DoesNotSupportF1Help", "CompilationEnd");
-
- internal static DiagnosticDescriptor CreateOpenSource(string[] sponsorable, string prefix, bool hidden = false) => new(
- $"{prefix}112",
- Resources.OpenSource_Title,
- Resources.OpenSource_Message,
- "SponsorLink",
- hidden ? DiagnosticSeverity.Hidden : DiagnosticSeverity.Info,
- isEnabledByDefault: true,
- description: Resources.OpenSource_Description,
- helpLinkUri: Funding.HelpUrl,
- "DoesNotSupportF1Help", "CompilationEnd");
-}
diff --git a/src/SponsorLink/SponsorLink/Resources.es-AR.resx b/src/SponsorLink/SponsorLink/Resources.es-AR.resx
deleted file mode 100644
index 094761f..0000000
--- a/src/SponsorLink/SponsorLink/Resources.es-AR.resx
+++ /dev/null
@@ -1,214 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- text/microsoft-resx
-
-
- 2.0
-
-
- System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
-
-
- System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
-
-
- Patrocinar los proyectos en que dependés asegura que se mantengan activos, y que recibas el apoyo que necesitás. También es muy económico y está disponible en todo el mundo!
-Por favor considerá apoyar el proyecto patrocinando en {0} y ejecutando posteriormente 'sponsor sync {1}'.
-
-
- Por favor considerá apoyar {0} patrocinando {1} 🙏
-
-
- Estado de patrocinio desconocido
-
-
- Funcionalidades exclusivas para patrocinadores pueden no estar disponibles. Ejecutá 'sponsor sync {0}' y, opcionalmente, habilita la sincronización automática.
-
-
- El estado de patrocino ha expirado y la sincronización automática no está habilitada.
-
-
- El estado de patrocino ha expirado
-
-
- Sos un verdadero héroe. Tu patrocinio ayuda a mantener el proyecto vivo y próspero 🙏.
-
-
- Gracias por apoyar a {0} con tu patrocinio 💟!
-
-
- Sos un patrocinador del proyecto, sos lo máximo 💟!
-
-
- El estado de patrocino ha expirado y estás en un período de gracia. Ejecutá 'sponsor sync {0}' y, opcionalmente, habilitá la sincronización automática.
-
-
- El estado de patrocino necesita actualización periódica y la sincronización automática no está habilitada.
-
-
- El estado de patrocino ha expirado y el período de gracia terminará pronto
-
-
- y
-
-
- o
-
-
- Gracias por ser parte del equipo por tu contribución 🙏.
-
-
- Gracias por ser parte del equipo {0} con tu contribución 💟!
-
-
- Sos un contribuidor al proyecto, sos groso 💟!
-
-
- Patrocinar los proyectos en que dependés asegura que se mantengan activos, y que recibas el apoyo que necesitás. También es muy económico y está disponible en todo el mundo!
-Por favor considerá apoyar el proyecto patrocinando en {0} y ejecutando posteriormente 'sponsor sync {1}'.
-
-
- El período de prueba finaliza en {0} día(s). Disfrutá y por favor considerá apoyar {1} patrocinando {2} 🙏
-
-
- Estado de patrocinio desconocido, período de prueba
-
-
- /// <remarks>
-/// GitHub Sponsors es una excelente manera de apoyar proyectos de código abierto, y está disponible en la mayor parte del mundo.
-/// Sos considerado sponsor si:
-/// - Bancás directamente a Devlooped 💪.
-/// - Contribuiste con código a cualquiera de los proyectos de Devlooped.
-/// - Pertenecés a una organización de GitHub que está patrocinando a Devlooped.
-/// - Contribuiste a nugets activos y populares que son open-source.
-///
-/// En este caso, simplemente ejecutá:
-/// > dotnet tool install -g dotnet-sponsor; sponsor sync devlooped
-///
-/// Y el atributo [Obsolete] vuela de una. Gracias totales! 🙏
-/// </remarks>
-
-
- /// <remarks>
-/// Esta API requiere patrocinio. Tu período de yapa termina en {0} día(s).
-///
-/// GitHub Sponsors es una excelente manera de apoyar proyectos de código abierto, y está disponible en la mayor parte del mundo.
-/// Sos considerado sponsor si:
-/// - Bancás directamente a Devlooped 💪.
-/// - Contribuiste con código a cualquiera de los proyectos de Devlooped.
-/// - Pertenecés a una organización de GitHub que está patrocinando a Devlooped.
-/// - Contribuiste a nugets activos y populares que son open-source.
-///
-/// En este caso, simplemente ejecutá:
-/// > dotnet tool install -g dotnet-sponsor; sponsor sync devlooped
-///
-/// ¡Gracias totales! 🙏
-/// </remarks>
-
-
\ No newline at end of file
diff --git a/src/SponsorLink/SponsorLink/Resources.es.resx b/src/SponsorLink/SponsorLink/Resources.es.resx
deleted file mode 100644
index 17a7de0..0000000
--- a/src/SponsorLink/SponsorLink/Resources.es.resx
+++ /dev/null
@@ -1,227 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- text/microsoft-resx
-
-
- 2.0
-
-
- System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
-
-
- System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
-
-
- Patrocinar los proyectos en que dependes asegura que se mantengan activos, y que recibas el apoyo que necesitas. También es muy económico y está disponible en todo el mundo!
-Por favor considera apoyar el proyecto patrocinando en {0} y ejecutando posteriormente 'sponsor sync {1}'.
-
-
- Por favor considere apoyar {0} patrocinando {1} 🙏
-
-
- Estado de patrocinio desconocido
-
-
- Funcionalidades exclusivas para patrocinadores pueden no estar disponibles. Ejecuta 'sponsor sync {0}' y, opcionalmente, habilita la sincronización automática.
-
-
- El estado de patrocino ha expirado y la sincronización automática no está habilitada.
-
-
- El estado de patrocino ha expirado
-
-
- Eres un verdadero héroe. Tu patrocinio ayuda a mantener el proyecto vivo y próspero 🙏.
-
-
- Gracias por apoyar a {0} con tu patrocinio 💟!
-
-
- Eres un patrocinador del proyecto, eres lo máximo 💟!
-
-
- El estado de patrocino ha expirado y estás en un período de gracia. Ejecuta 'sponsor sync {0}' y, opcionalmente, habilita la sincronización automática.
-
-
- El estado de patrocino necesita actualización periódica y la sincronización automática no está habilitada.
-
-
- El estado de patrocino ha expirado y el período de gracia terminará pronto
-
-
- y
-
-
- o
-
-
- Gracias por ser parte del equipo por tu contribución 🙏.
-
-
- Gracias por ser parte del equipo {0} con tu contribución 💟!
-
-
- Eres un contribuidor al proyecto, eres lo máximo 💟!
-
-
- El uso de {0} sin warnings en el editor requiere un patrocinio activo. Ver mas en {1}.
-
-
- Patrocinar los proyectos en que dependes asegura que se mantengan activos, y que recibas el apoyo que necesitas. También es muy económico y está disponible en todo el mundo!
-Por favor considera apoyar el proyecto patrocinando en {0} y ejecutando posteriormente 'sponsor sync {1}'.
-
-
- El período de prueba finaliza en {0} día(s). Disfrute y por favor considere apoyar {1} patrocinando {2} 🙏
-
-
- Estado de patrocinio desconocido, período de prueba
-
-
- /// <remarks>
-/// GitHub Sponsors es una excelente manera de apoyar proyectos de código abierto, y está disponible en la mayor parte del mundo.
-/// Se te considera un patrocinador si:
-/// - Estás patrocinando directamente a Devlooped.
-/// - Has contribuido con código a cualquiera de los proyectos de Devlooped.
-/// - Perteneces a una organización de GitHub que está patrocinando a Devlooped.
-/// - Has contribuido a nugets activos y populares que son de código abierto.
-///
-/// Si es así, simplemente ejecuta:
-/// > dotnet tool install -g dotnet-sponsor; sponsor sync devlooped
-///
-/// Posteriormente, el atributo [Obsolete] será eliminado.
-/// ¡Gracias! 🙏
-/// </remarks>
-
-
- /// <remarks>
-/// Esta API requiere patrocinio. Su período de gracia termina en {0} día(s).
-///
-/// GitHub Sponsors es una excelente manera de apoyar proyectos de código abierto, y está disponible en la mayor parte del mundo.
-/// Se te considera un patrocinador si:
-/// - Estás patrocinando directamente a Devlooped.
-/// - Has contribuido con código a cualquiera de los proyectos de Devlooped.
-/// - Perteneces a una organización de GitHub que está patrocinando a Devlooped.
-/// - Has contribuido a packetes en nuget.org activos y populares que son de código abierto
-///
-/// Si es así, simplemente ejecuta:
-/// > dotnet tool install -g dotnet-sponsor; sponsor sync devlooped
-///
-/// ¡Gracias! 🙏
-/// </remarks>
-
-
- Gracias por ser parte de la comunidad de código abierto con tus contribuciones 🙏.
-
-
- Gracias por ser autor de código abierto 💟!
-
-
- Sos un autor de código abierto, eres lo máximo 💟!
-
-
\ No newline at end of file
diff --git a/src/SponsorLink/SponsorLink/Resources.resx b/src/SponsorLink/SponsorLink/Resources.resx
deleted file mode 100644
index 93b75bc..0000000
--- a/src/SponsorLink/SponsorLink/Resources.resx
+++ /dev/null
@@ -1,228 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- text/microsoft-resx
-
-
- 2.0
-
-
- System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
-
-
- System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
-
-
- 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 {0} and running 'sponsor sync {1}' afterwards.
- Unknown sponsor description
-
-
- Please consider supporting {0} by sponsoring {1} 🙏
-
-
- Unknown sponsor status
-
-
- Sponsor-only features may be disabled. Please run 'sponsor sync {0}' and optionally enable automatic sync.
-
-
- Sponsor status has expired and automatic sync has not been enabled.
-
-
- Sponsor status expired
-
-
- You are a true hero. Your sponsorship helps keep the project alive and thriving 🙏.
-
-
- Thank you for supporting {0} with your sponsorship 💟!
-
-
- You are a sponsor of the project, you rock 💟!
-
-
- Sponsor status has expired and you are in the grace period. Please run 'sponsor sync {0}' and optionally enable automatic sync.
-
-
- Sponsor status needs periodic updating and automatic sync has not been enabled.
-
-
- Sponsor status expired, grace period ending soon
-
-
- and
-
-
- or
-
-
- Thanks for being part of the team with your contributions 🙏.
-
-
- Thank you for being part of team {0} with your contributions 💟!
-
-
- You are a contributor to the project, you rock 💟!
-
-
- Editor usage of {0} without warnings requires an active sponsorship. Learn more at {1}.
-
-
- Sponsoring projects you depend on ensures they remain active, and that you get the support you need. It's also super affordable and available worldwide!
-Please consider supporting the project by sponsoring at {0} and running 'sponsor sync {1}' afterwards.
-
-
- Grace period ends in {0} days. Enjoy and please consider supporting {1} by sponsoring {2} 🙏
-
-
- Unknown sponsor status, grace period
-
-
- /// <remarks>
-/// GitHub Sponsors is a great way to support open source projects, and it's available throughout most of the world.
-/// You are considered a sponsor if:
-/// - You are directly sponsoring Devlooped
-/// - You contributed code to any of Devlooped's projects.
-/// - You belong to a GitHub organization that is sponsoring Devlooped.
-/// - You contributed to active and popular nuget packages that are OSS.
-///
-/// If so, just run:
-/// > dotnet tool install -g dotnet-sponsor; sponsor sync devlooped
-///
-/// Subsequently, the [Obsolete] attribute will be removed.
-/// Thanks! 🙏
-/// </remarks>
-
-
- /// <remarks>
-/// This is a sponsored API. Your grace period will expire in {0} day(s).
-///
-/// GitHub Sponsors is a great way to support open source projects, and it's available throughout most of the world.
-/// You are considered a sponsor if:
-/// - You are directly sponsoring Devlooped
-/// - You contributed code to any of Devlooped's projects.
-/// - You belong to a GitHub organization that is sponsoring Devlooped.
-/// - You contributed to active and popular nuget packages that are OSS.
-///
-/// If so, just run:
-/// > dotnet tool install -g dotnet-sponsor; sponsor sync devlooped
-///
-/// Thanks! 🙏
-/// </remarks>
-
-
- Thanks for being part of the open source community with your contributions 🙏.
-
-
- Thank you for being an open source author 💟!
-
-
- You are a an open source author, you rock 💟!
-
-
\ No newline at end of file
diff --git a/src/SponsorLink/SponsorLink/SponsorLink.cs b/src/SponsorLink/SponsorLink/SponsorLink.cs
deleted file mode 100644
index 2c1e6db..0000000
--- a/src/SponsorLink/SponsorLink/SponsorLink.cs
+++ /dev/null
@@ -1,168 +0,0 @@
-//
-#nullable enable
-using System;
-using System.Collections.Generic;
-using System.Collections.Immutable;
-using System.Diagnostics;
-using System.Diagnostics.CodeAnalysis;
-using System.IO;
-using System.Linq;
-using System.Reflection;
-using System.Security.Claims;
-using Microsoft.CodeAnalysis;
-using Microsoft.CodeAnalysis.Diagnostics;
-using Microsoft.IdentityModel.JsonWebTokens;
-using Microsoft.IdentityModel.Tokens;
-
-namespace Devlooped.Sponsors;
-
-static partial class SponsorLink
-{
- public record StatusOptions(ImmutableArray AdditionalFiles, AnalyzerConfigOptions GlobalOptions);
-
- ///
- /// Statically cached dictionary of sponsorable accounts and their public key (in JWK format),
- /// retrieved from assembly metadata attributes starting with "Funding.GitHub.".
- ///
- public static Dictionary Sponsorables { get; } = typeof(SponsorLink).Assembly
- .GetCustomAttributes()
- .Where(x => x.Key.StartsWith("Funding.GitHub."))
- .Select(x => new { Key = x.Key[15..], x.Value })
- .ToDictionary(x => x.Key, x => x.Value);
-
- ///
- /// Whether the current process is running in an IDE, either
- /// or .
- ///
- public static bool IsEditor => IsVisualStudio || IsRider;
-
- ///
- /// Whether the current process is running as part of an active Visual Studio instance.
- ///
- public static bool IsVisualStudio =>
- Environment.GetEnvironmentVariable("ServiceHubLogSessionKey") != null ||
- Environment.GetEnvironmentVariable("VSAPPIDNAME") != null;
-
- ///
- /// Whether the current process is running as part of an active Rider instance.
- ///
- public static bool IsRider =>
- Environment.GetEnvironmentVariable("RESHARPER_FUS_SESSION") != null ||
- Environment.GetEnvironmentVariable("IDEA_INITIAL_DIRECTORY") != null;
-
- ///
- /// A unique session ID associated with the current IDE or process running the analyzer.
- ///
- public static string SessionId =>
- IsVisualStudio ? Environment.GetEnvironmentVariable("ServiceHubLogSessionKey") :
- IsRider ? Environment.GetEnvironmentVariable("RESHARPER_FUS_SESSION") :
- Process.GetCurrentProcess().Id.ToString();
-
- ///
- /// Manages the sharing and reporting of diagnostics across the source generator
- /// and the diagnostic analyzer, to avoid doing the online check more than once.
- ///
- public static DiagnosticsManager Diagnostics { get; } = new();
-
- ///
- /// Gets the expiration date from the principal, if any.
- ///
- ///
- /// Whichever "exp" claim is the latest, or if none found.
- ///
- 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;
-
- ///
- /// Gets all necessary additional files to determine status.
- ///
- public static ImmutableArray GetSponsorAdditionalFiles(this AnalyzerOptions? options)
- => options == null ? ImmutableArray.Create() : options.AdditionalFiles
- .Where(x => x.IsSponsorManifest(options.AnalyzerConfigOptionsProvider) || x.IsSponsorableAnalyzer(options.AnalyzerConfigOptionsProvider))
- .ToImmutableArray();
-
- ///
- /// Gets all sponsor manifests from the provided analyzer options.
- ///
- public static IncrementalValueProvider> GetSponsorAdditionalFiles(this IncrementalGeneratorInitializationContext context)
- => context.AdditionalTextsProvider.Combine(context.AnalyzerConfigOptionsProvider)
- .Where(source =>
- {
- var (text, provider) = source;
- return text.IsSponsorManifest(provider) || text.IsSponsorableAnalyzer(provider);
- })
- .Select((source, c) => source.Left)
- .Collect();
-
- ///
- /// Gets the status options for use within an incremental generator, to avoid depending on
- /// analyzer runs. Used in combination with .
- ///
- public static IncrementalValueProvider GetStatusOptions(this IncrementalGeneratorInitializationContext context)
- => context.GetSponsorAdditionalFiles().Combine(context.AnalyzerConfigOptionsProvider)
- .Select((source, _) => new StatusOptions(source.Left, source.Right.GlobalOptions));
-
- ///
- /// Gets the status options for use within a source generator, to avoid depending on
- /// analyzer runs. Used in combination with .
- ///
- public static StatusOptions GetStatusOptions(this GeneratorExecutionContext context)
- => new StatusOptions(
- context.AdditionalFiles.Where(x => x.IsSponsorManifest(context.AnalyzerConfigOptions) || x.IsSponsorableAnalyzer(context.AnalyzerConfigOptions)).ToImmutableArray(),
- context.AnalyzerConfigOptions.GlobalOptions);
-
- static bool IsSponsorManifest(this AdditionalText text, AnalyzerConfigOptionsProvider provider)
- => provider.GetOptions(text).TryGetValue("build_metadata.SponsorManifest.ItemType", out var itemType) &&
- itemType == "SponsorManifest" &&
- Sponsorables.ContainsKey(Path.GetFileNameWithoutExtension(text.Path));
-
- static bool IsSponsorableAnalyzer(this AdditionalText text, AnalyzerConfigOptionsProvider provider)
- => provider.GetOptions(text) is { } options &&
- options.TryGetValue("build_metadata.Analyzer.ItemType", out var itemType) &&
- options.TryGetValue("build_metadata.Analyzer.NuGetPackageId", out var packageId) &&
- itemType == "Analyzer" &&
- Funding.PackageIds.Contains(packageId);
-
- ///
- /// Reads all manifests, validating their signatures.
- ///
- /// The combined principal with all identities (and their claims) from each provided and valid JWT
- /// The tokens to read and their corresponding JWK for signature verification.
- /// if at least one manifest can be successfully read and is valid.
- /// otherwise.
- public static bool TryRead([NotNullWhen(true)] out ClaimsPrincipal? principal, params (string jwt, string jwk)[] values)
- => TryRead(out principal, values.AsEnumerable());
-
- ///
- /// Reads all manifests, validating their signatures.
- ///
- /// The combined principal with all identities (and their claims) from each provided and valid JWT
- /// The tokens to read and their corresponding JWK for signature verification.
- /// if at least one manifest can be successfully read and is valid.
- /// otherwise.
- 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.jwt) || string.IsNullOrEmpty(value.jwk))
- continue;
-
- if (Validate(value.jwt, value.jwk, out var token, out var identity, false) == ManifestStatus.Valid && identity != null)
- {
- if (principal == null)
- principal = new JwtRolesPrincipal(identity);
- else
- principal.AddIdentity(identity);
- }
- }
-
- return principal != null;
- }
-}
diff --git a/src/SponsorLink/SponsorLink/SponsorLink.csproj b/src/SponsorLink/SponsorLink/SponsorLink.csproj
deleted file mode 100644
index 25a474c..0000000
--- a/src/SponsorLink/SponsorLink/SponsorLink.csproj
+++ /dev/null
@@ -1,100 +0,0 @@
-
-
-
- netstandard2.0
- SponsorLink
- disable
- false
- CoreResGen;$(CoreCompileDependsOn)
- SponsorLink
-
-
-
-
- $(Product)
- $(PackageId)
-
- $([System.Text.RegularExpressions.Regex]::Replace("$(FundingProduct)", "[^A-Z]", ""))
-
- 21
-
- https://github.com/devlooped#sponsorlink
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- $(FundingProduct)
-
-
-
-
-
-
- <_FundingAnalyzerPackageId Include="@(FundingAnalyzerPackageId -> '"%(Identity)"')" />
-
-
- <_FundingPackageIds>@(_FundingAnalyzerPackageId, ',')
-
-
-
- using System.Collections.Generic%3B
-
-namespace Devlooped.Sponsors%3B
-
-partial class SponsorLink
-{
- public partial class Funding
- {
- public static HashSet<string> PackageIds { get%3B } = [$(_FundingPackageIds)]%3B
- public const string Product = "$(FundingProduct)"%3B
- public const string Prefix = "$(FundingPrefix)"%3B
- public const string HelpUrl = "$(FundingHelpUrl)"%3B
- public const int Grace = $(FundingGrace)%3B
- }
-}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- $([System.IO.File]::ReadAllText('$(MSBuildProjectDirectory)\$(BaseIntermediateOutputPath)devlooped.jwk'))
-
-
-
-
-
-
-
diff --git a/src/SponsorLink/SponsorLink/SponsorLinkAnalyzer.cs b/src/SponsorLink/SponsorLink/SponsorLinkAnalyzer.cs
deleted file mode 100644
index 9caa9a2..0000000
--- a/src/SponsorLink/SponsorLink/SponsorLinkAnalyzer.cs
+++ /dev/null
@@ -1,71 +0,0 @@
-//
-#nullable enable
-using System.Collections.Immutable;
-using System.Linq;
-using Microsoft.CodeAnalysis;
-using Microsoft.CodeAnalysis.Diagnostics;
-using static Devlooped.Sponsors.SponsorLink;
-
-namespace Devlooped.Sponsors;
-
-///
-/// Links the sponsor status for the current compilation.
-///
-[DiagnosticAnalyzer(LanguageNames.CSharp, LanguageNames.VisualBasic)]
-public class SponsorLinkAnalyzer : DiagnosticAnalyzer
-{
- public override ImmutableArray SupportedDiagnostics { get; } = DiagnosticsManager.KnownDescriptors.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 =>
- {
- // Setting the status early allows other analyzers to potentially check for it.
- var status = Diagnostics.GetOrSetStatus(() => ctx.Options);
-
- // Never report any diagnostic unless we're in an editor.
- if (IsEditor)
- {
- // NOTE: for multiple projects with the same product name, we only report one diagnostic,
- // so it's expected to NOT get a diagnostic back. Also, we don't want to report
- // multiple diagnostics for each project in a solution that uses the same product.
- ctx.RegisterCompilationEndAction(ctx =>
- {
- // We'd never report Info/hero link if users opted out of it.
- if (status.IsSponsor() &&
- ctx.Options.AnalyzerConfigOptionsProvider.GlobalOptions.TryGetValue("build_property.SponsorLinkHero", out var slHero) &&
- bool.TryParse(slHero, out var isHero) && isHero)
- return;
-
- // Only report if the package is directly referenced in the project for
- // any of the funding packages we monitor (i.e. we could have one or more
- // metapackages we also consider "direct references).
- // See SL_CollectDependencies in buildTransitive\Devlooped.Sponsors.targets
- foreach (var prop in Funding.PackageIds.Select(id => id.Replace('.', '_')))
- {
- if (ctx.Options.AnalyzerConfigOptionsProvider.GlobalOptions.TryGetValue("build_property." + prop, out var package) &&
- package?.Length > 0 &&
- Diagnostics.TryGet() is { } diagnostic)
- {
- ctx.ReportDiagnostic(diagnostic);
- break;
- }
- }
- });
- }
- });
-#pragma warning restore RS1013 // Start action has no registered non-end actions
- }
-}
diff --git a/src/SponsorLink/SponsorLink/SponsorManifest.cs b/src/SponsorLink/SponsorLink/SponsorManifest.cs
deleted file mode 100644
index b4aa9d7..0000000
--- a/src/SponsorLink/SponsorLink/SponsorManifest.cs
+++ /dev/null
@@ -1,160 +0,0 @@
-//
-#nullable enable
-using System;
-using System.Collections.Generic;
-using System.Diagnostics.CodeAnalysis;
-using System.IO;
-using System.Security.Claims;
-using Microsoft.IdentityModel.JsonWebTokens;
-using Microsoft.IdentityModel.Tokens;
-
-namespace Devlooped.Sponsors;
-
-///
-/// The resulting status from validation.
-///
-public enum ManifestStatus
-{
- ///
- /// The manifest couldn't be read at all.
- ///
- Unknown,
- ///
- /// The manifest was read and is valid (not expired and properly signed).
- ///
- Valid,
- ///
- /// The manifest was read but has expired.
- ///
- Expired,
- ///
- /// The manifest was read, but its signature is invalid.
- ///
- Invalid,
-}
-
-///
-/// Represents the sponsorship status of a user.
-///
-/// The status.
-/// The principal potentially containing roles validated from the manifest.
-/// The security token from the validated manifest.
-public record SponsorManifest(ManifestStatus Status, ClaimsPrincipal Principal, SecurityToken? SecurityToken)
-{
- ///
- /// Whether the manifest is .
- ///
- public bool IsValid => Status == ManifestStatus.Valid;
-}
-
-static partial class SponsorLink
-{
- ///
- /// Reads the local manifest (if present) for the specified sponsorable account and validates it
- /// against the given JWK key.
- ///
- /// The sponsorable account to read.
- /// The public key to validate the signature on the manifest JWT if found.
- /// Whether to validate the manifest expiration. If ,
- /// an expired manifest will be reported as . The expiration date
- /// can be checked in that case via the .
- /// A manifest that represents the user status.
- public static SponsorManifest GetManifest(string sponsorable, string jwk, bool validateExpiration = true)
- {
- var path = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile),
- ".sponsorlink", "github", sponsorable + ".jwt");
-
- if (!File.Exists(path))
- return new SponsorManifest(ManifestStatus.Unknown, new ClaimsPrincipal(), null);
-
- return ParseManifest(File.ReadAllText(path), jwk, validateExpiration);
- }
-
- internal static SponsorManifest ParseManifest(string jwt, string jwk, bool validateExpiration)
- {
- var status = Validate(jwt, jwk, out var token, out var identity, validateExpiration);
-
- if (status == ManifestStatus.Unknown || identity == null)
- return new SponsorManifest(status, new ClaimsPrincipal(), token);
-
- return new SponsorManifest(status, new JwtRolesPrincipal(identity), token);
- }
-
- ///
- /// Validates the manifest signature and optional expiration.
- ///
- /// The JWT to validate.
- /// The key to validate the manifest signature with.
- /// Except when returning , returns the security token read from the JWT, even if signature check failed.
- /// The associated claims, only when return value is not .
- /// Whether to check for expiration.
- /// The status of the validation.
- public static ManifestStatus Validate(string jwt, string jwk, out SecurityToken? token, out ClaimsIdentity? identity, bool validateExpiration)
- {
- token = default;
- identity = default;
-
- SecurityKey key;
- try
- {
- key = JsonWebKey.Create(jwk);
- }
- catch (ArgumentException)
- {
- return ManifestStatus.Unknown;
- }
-
- var handler = new JsonWebTokenHandler { MapInboundClaims = false };
-
- if (!handler.CanReadToken(jwt))
- return ManifestStatus.Unknown;
-
- var validation = new TokenValidationParameters
- {
- RequireExpirationTime = false,
- ValidateLifetime = false,
- ValidateAudience = false,
- ValidateIssuer = false,
- ValidateIssuerSigningKey = true,
- IssuerSigningKey = key,
- RoleClaimType = "roles",
- NameClaimType = "sub",
- };
-
- var result = handler.ValidateTokenAsync(jwt, validation).Result;
- if (!result.IsValid || result.Exception != null)
- {
- if (result.Exception is SecurityTokenInvalidSignatureException)
- {
- var jwtToken = handler.ReadJsonWebToken(jwt);
- token = jwtToken;
- identity = new ClaimsIdentity(jwtToken.Claims);
- return ManifestStatus.Invalid;
- }
- else
- {
- var jwtToken = handler.ReadJsonWebToken(jwt);
- token = jwtToken;
- identity = new ClaimsIdentity(jwtToken.Claims);
- return ManifestStatus.Invalid;
- }
- }
-
- token = result.SecurityToken;
- identity = new ClaimsIdentity(result.ClaimsIdentity.Claims, "JWT");
-
- if (validateExpiration && token.ValidTo == DateTime.MinValue)
- return ManifestStatus.Invalid;
-
- // The sponsorable manifest does not have an expiration time.
- if (validateExpiration && token.ValidTo < DateTimeOffset.UtcNow)
- return ManifestStatus.Expired;
-
- return ManifestStatus.Valid;
- }
-
- class JwtRolesPrincipal(ClaimsIdentity identity) : ClaimsPrincipal([identity])
- {
- public override bool IsInRole(string role) => HasClaim("roles", role) || base.IsInRole(role);
- }
-}
diff --git a/src/SponsorLink/SponsorLink/SponsorStatus.cs b/src/SponsorLink/SponsorLink/SponsorStatus.cs
deleted file mode 100644
index d0fe800..0000000
--- a/src/SponsorLink/SponsorLink/SponsorStatus.cs
+++ /dev/null
@@ -1,57 +0,0 @@
-//
-namespace Devlooped.Sponsors;
-
-public static class SponsorStatusExtensions
-{
- ///
- /// Whether represents a sponsor (directly or indirectly).
- ///
- public static bool IsSponsor(this SponsorStatus status)
- => status == SponsorStatus.User ||
- status == SponsorStatus.Team ||
- status == SponsorStatus.Contributor ||
- status == SponsorStatus.Organization;
-}
-
-///
-/// The determined sponsoring status.
-///
-public enum SponsorStatus
-{
- ///
- /// Sponsorship status is unknown.
- ///
- Unknown,
- ///
- /// Sponsorship status is unknown, but within the grace period.
- ///
- Grace,
- ///
- /// The sponsors manifest is expired but within the grace period.
- ///
- Expiring,
- ///
- /// The sponsors manifest is expired and outside the grace period.
- ///
- Expired,
- ///
- /// The user is personally sponsoring.
- ///
- User,
- ///
- /// The user is a team member.
- ///
- Team,
- ///
- /// The user is a contributor.
- ///
- Contributor,
- ///
- /// The user is a member of a contributing organization.
- ///
- Organization,
- ///
- /// The user is a OSS author.
- ///
- OpenSource,
-}
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 @@
-
-
-
-
- $([System.IO.Path]::GetFullPath($(MSBuildThisFileDirectory)sponsorable.md))
-
-
-
-
-
-
-
-
-
- $(WarningsNotAsErrors);LIB001;LIB002;LIB003;LIB004;LIB005
-
- $(BaseIntermediateOutputPath)autosync.stamp
-
- $(HOME)
- $(USERPROFILE)
-
- true
- $([System.IO.Path]::GetFullPath('$(UserProfileHome)/.sponsorlink'))
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- %(GitRoot.FullPath)
-
-
-
-
-
-
-
-
\ 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 ad5d9b3..0000000
--- a/src/SponsorLink/SponsorLink/Tracing.cs
+++ /dev/null
@@ -1,49 +0,0 @@
-//
-#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([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 = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), ".sponsorlink");
- Directory.CreateDirectory(dir);
-
- var tries = 0;
- // Best-effort only
- while (tries < 10)
- {
- try
- {
- File.AppendAllText(Path.Combine(dir, "trace.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 eb4c61b..0000000
--- a/src/SponsorLink/SponsorLink/buildTransitive/Devlooped.Sponsors.targets
+++ /dev/null
@@ -1,126 +0,0 @@
-
-
-
-
- $([System.DateTime]::Now.ToString("yyyy-MM-yy"))
-
- $(BaseIntermediateOutputPath)autosync-$(Today).stamp
-
- $(BaseIntermediateOutputPath)autosync.stamp
-
- $(HOME)
- $(USERPROFILE)
-
- $([System.IO.Path]::GetFullPath('$(UserProfileHome)/.sponsorlink'))
-
- $([System.IO.Path]::Combine('$(SponsorLinkHome)', '.netconfig'))
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- SL_CollectDependencies;SL_CollectSponsorableAnalyzer
- $(SLDependsOn);SL_CheckAutoSync;SL_ReadAutoSyncEnabled;SL_SyncSponsors
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- $([MSBuild]::ValueOrDefault('%(_RestoreGraphEntry.Id)', '').Replace('.', '_'))
-
-
-
-
-
-
-
-
-
-
- <_FundingPackageId>%(FundingPackageId.Identity)
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- %(SLConfigAutoSync.Identity)
- true
- false
-
-
-
-
-
-
-
- $([System.IO.File]::ReadAllText($(AutoSyncStampFile)).Trim())
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ 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 092c205..0000000
--- a/src/SponsorLink/Tests/.netconfig
+++ /dev/null
@@ -1,17 +0,0 @@
-[config]
- root = true
-[file "SponsorableManifest.cs"]
- url = https://github.com/devlooped/SponsorLink/blob/main/src/Core/SponsorableManifest.cs
- sha = 5a4cad3a084f53afe34a6b75e4f3a084a0f1bf9e
- etag = 9a07c856d06e0cde629fce3ec014f64f9adfd5ae5805a35acf623eba0ee045c1
- weak
-[file "JsonOptions.cs"]
- url = https://github.com/devlooped/SponsorLink/blob/main/src/Core/JsonOptions.cs
- sha = 80ea1bfe47049ef6c6ed4f424dcf7febb729cbba
- etag = 17799725ad9b24eb5998365962c30b9a487bddadca37c616e35b76b8c9eb161a
- weak
-[file "Extensions.cs"]
- url = https://github.com/devlooped/SponsorLink/blob/main/src/Core/Extensions.cs
- sha = c455f6fa1a4d404181d076d7f3362345c8ed7df2
- etag = 9e51b7e6540fae140490a5283b1e67ce071bd18a267bc2ae0b35c7248261aed1
- weak
\ No newline at end of file
diff --git a/src/SponsorLink/Tests/AnalyzerTests.cs b/src/SponsorLink/Tests/AnalyzerTests.cs
deleted file mode 100644
index 6192541..0000000
--- a/src/SponsorLink/Tests/AnalyzerTests.cs
+++ /dev/null
@@ -1,279 +0,0 @@
-extern alias Analyzer;
-using System;
-using System.Collections;
-using System.Collections.Generic;
-using System.Data;
-using System.Diagnostics.CodeAnalysis;
-using System.Linq;
-using System.Runtime.CompilerServices;
-using System.Security.Claims;
-using System.Text;
-using System.Threading.Tasks;
-using Analyzer::Devlooped.Sponsors;
-using Devlooped.Sponsors;
-using Microsoft.CodeAnalysis;
-using Microsoft.CodeAnalysis.CSharp;
-using Microsoft.CodeAnalysis.Diagnostics;
-using Microsoft.CodeAnalysis.Text;
-using Microsoft.Extensions.Options;
-using Microsoft.IdentityModel.Tokens;
-using Xunit;
-
-namespace Tests;
-
-public class AnalyzerTests : IDisposable
-{
- static readonly SponsorableManifest sponsorable = new(
- new Uri("https://sponsorlink.devlooped.com"),
- [new Uri("https://github.com/sponsors/devlooped"), new Uri("https://github.com/sponsors/kzu")],
- "a82350fb2bae407b3021",
- new JsonWebKey(ThisAssembly.Resources.keys.kzu_key.Text));
-
- public AnalyzerTests()
- {
- // Simulate being a VS IDE for analyzers to actually run.
- if (Environment.GetEnvironmentVariable("VSAPPIDNAME") == null)
- Environment.SetEnvironmentVariable("VSAPPIDNAME", "test");
- }
-
- void IDisposable.Dispose()
- {
- if (Environment.GetEnvironmentVariable("VSAPPIDNAME") == "test")
- Environment.SetEnvironmentVariable("VSAPPIDNAME", null);
- }
-
- [Fact]
- public async Task WhenNoAdditionalFiles_ThenReportsUnknown()
- {
- var compilation = CSharpCompilation.Create("test", [CSharpSyntaxTree.ParseText("//")])
- .WithAnalyzers([new SponsorLinkAnalyzer()],
- new AnalyzerOptions([], new TestAnalyzerConfigOptionsProvider(new())
- {
- // Force reporting without wait period
- { "build_property.SponsorLinkNoInstallGrace", "true" },
- // Simulate directly referenced package
- { "build_property.SponsorableLib", "1.0.0" },
- }));
-
- var diagnostics = await compilation.GetAnalyzerDiagnosticsAsync();
-
- Assert.NotEmpty(diagnostics);
-
- var diagnostic = diagnostics.Single(x => x.Properties.TryGetValue(nameof(SponsorStatus), out var value));
-
- Assert.True(diagnostic.Properties.TryGetValue(nameof(SponsorStatus), out var value));
- var status = Enum.Parse(value);
-
- Assert.Equal(SponsorStatus.Unknown, status);
- }
-
- [Fact]
- public async Task WhenUnknownAndGrace_ThenDoesNotReport()
- {
- // simulate an analyzer file with the right metadata, which is recent and therefore
- // within the grace period
- var dll = Path.Combine(GetTempPath(), "FakeAnalyzer.dll");
- File.WriteAllText(dll, "");
-
- var compilation = CSharpCompilation.Create("test", [CSharpSyntaxTree.ParseText("//")])
- .WithAnalyzers([new SponsorLinkAnalyzer()], new AnalyzerOptions([new AdditionalTextFile(dll)], new TestAnalyzerConfigOptionsProvider(new())
- {
- { "build_metadata.Analyzer.ItemType", "Analyzer" },
- { "build_metadata.Analyzer.NuGetPackageId", "SponsorableLib" }
- }));
-
- var diagnostics = await compilation.GetAnalyzerDiagnosticsAsync();
-
- Assert.Empty(diagnostics);
- }
-
- [Fact]
- public async Task WhenUnknownAndNoGraceOption_ThenReportsUnknown()
- {
- // simulate an analyzer file with the right metadata, which is recent and therefore
- // within the grace period
- var dll = Path.Combine(GetTempPath(), "FakeAnalyzer.dll");
- File.WriteAllText(dll, "");
-
- var compilation = CSharpCompilation.Create("test", [CSharpSyntaxTree.ParseText("//")])
- .WithAnalyzers([new SponsorLinkAnalyzer()], new AnalyzerOptions([new AdditionalTextFile(dll)], new TestAnalyzerConfigOptionsProvider(new())
- {
- { "build_property.SponsorLinkNoInstallGrace", "true" },
- { "build_metadata.Analyzer.ItemType", "Analyzer" },
- { "build_metadata.Analyzer.NuGetPackageId", "SponsorableLib" },
- // Simulate directly referenced package
- { "build_property.SponsorableLib", "1.0.0" },
- }));
-
- var diagnostics = await compilation.GetAnalyzerDiagnosticsAsync();
-
- Assert.NotEmpty(diagnostics);
-
- var diagnostic = diagnostics.Single(x => x.Properties.TryGetValue(nameof(SponsorStatus), out var value));
-
- Assert.True(diagnostic.Properties.TryGetValue(nameof(SponsorStatus), out var value));
- var status = Enum.Parse(value);
-
- Assert.Equal(SponsorStatus.Unknown, status);
- }
-
- [Fact]
- public async Task WhenUnknownAndGraceExpired_ThenReportsUnknown()
- {
- // simulate an analyzer file with the right metadata, which is recent and therefore
- // within the grace period
- var dll = Path.Combine(GetTempPath(), "FakeAnalyzer.dll");
- File.WriteAllText(dll, "");
- File.SetLastWriteTimeUtc(dll, DateTime.UtcNow - TimeSpan.FromDays(30));
-
- var compilation = CSharpCompilation.Create("test", [CSharpSyntaxTree.ParseText("//")])
- .WithAnalyzers([new SponsorLinkAnalyzer()], new AnalyzerOptions([new AdditionalTextFile(dll)], new TestAnalyzerConfigOptionsProvider(new())
- {
- { "build_metadata.Analyzer.ItemType", "Analyzer" },
- { "build_metadata.Analyzer.NuGetPackageId", "SponsorableLib" },
- // Simulate directly referenced package
- { "build_property.SponsorableLib", "1.0.0" },
- }));
-
- var diagnostics = await compilation.GetAnalyzerDiagnosticsAsync();
-
- Assert.NotEmpty(diagnostics);
-
- var diagnostic = diagnostics.Single(x => x.Properties.TryGetValue(nameof(SponsorStatus), out var value));
-
- Assert.True(diagnostic.Properties.TryGetValue(nameof(SponsorStatus), out var value));
- var status = Enum.Parse(value);
-
- Assert.Equal(SponsorStatus.Unknown, status);
- }
-
- [Theory]
- [InlineData("user", SponsorStatus.User)]
- [InlineData("org", SponsorStatus.Organization)]
- [InlineData("contrib", SponsorStatus.Contributor)]
- [InlineData("team", SponsorStatus.Team)]
- // team trumps everything (since team members will typically also be contributors)
- [InlineData("user,contrib,team", SponsorStatus.Team)]
- // user trumps others
- [InlineData("user,org,contrib", SponsorStatus.User)]
- // contrib trumps org
- [InlineData("org,contrib", SponsorStatus.Contributor)]
- // team trumps contrib (since team members will typically also be contributors
- [InlineData("contrib,team", SponsorStatus.Team)]
- [InlineData("contrib,oss", SponsorStatus.Contributor)]
- [InlineData("user,oss", SponsorStatus.User)]
- [InlineData("org,oss", SponsorStatus.Organization)]
- [InlineData("oss", SponsorStatus.OpenSource)]
- public async Task WhenSponsoringRole_ThenEnsureStatus(string roles, SponsorStatus status)
- {
- var sponsor = sponsorable.Sign(roles.Split(',').Select(x => new Claim("roles", x)), expiration: TimeSpan.FromMinutes(5));
- var jwt = Path.Combine(GetTempPath(), "kzu.jwt");
- File.WriteAllText(jwt, sponsor, Encoding.UTF8);
-
- var compilation = CSharpCompilation.Create("test", [CSharpSyntaxTree.ParseText("//")])
- .WithAnalyzers([new SponsorLinkAnalyzer()], new AnalyzerOptions([new AdditionalTextFile(jwt)], new TestAnalyzerConfigOptionsProvider(new())
- {
- { "build_metadata.SponsorManifest.ItemType", "SponsorManifest" },
- // Simulate directly referenced package
- { "build_property.SponsorableLib", "1.0.0" },
- }));
-
- var diagnostics = await compilation.GetAnalyzerDiagnosticsAsync();
-
- Assert.NotEmpty(diagnostics);
-
- var diagnostic = diagnostics.Single(x => x.Properties.TryGetValue(nameof(SponsorStatus), out var value));
-
- Assert.True(diagnostic.Properties.TryGetValue(nameof(SponsorStatus), out var value));
- var actual = Enum.Parse(value);
-
- Assert.Equal(status, actual);
- }
-
- [Fact]
- public async Task WhenMultipleAnalyzers_ThenReportsOnce()
- {
- var sponsor = sponsorable.Sign([new("roles", "user")], expiration: TimeSpan.FromMinutes(5));
- var jwt = Path.Combine(GetTempPath(), "kzu.jwt");
- File.WriteAllText(jwt, sponsor, Encoding.UTF8);
-
- var compilation = CSharpCompilation.Create("test", [CSharpSyntaxTree.ParseText("//")])
- .WithAnalyzers([new SponsorLinkAnalyzer(), new SponsorLinkAnalyzer()],
- new AnalyzerOptions([new AdditionalTextFile(jwt)], new TestAnalyzerConfigOptionsProvider(new())
- {
- // Force reporting without wait period
- { "build_property.SponsorLinkNoInstallGrace", "true" },
- // Simulate directly referenced package
- { "build_property.SponsorableLib", "1.0.0" },
- { "build_property.SponsorLink", "1.0.0" },
- { "build_metadata.SponsorManifest.ItemType", "SponsorManifest" }
- }));
-
- var diagnostics = (await compilation.GetAnalyzerDiagnosticsAsync())
- .Where(x => x.Properties.TryGetValue(nameof(SponsorStatus), out var _));
-
- Assert.NotEmpty(diagnostics);
- Assert.Single(diagnostics, x => x.Properties.TryGetValue(nameof(SponsorStatus), out var value));
- }
-
- [Fact]
- public async Task WhenAnalyzerNotDirectlyReferenced_ThenDoesNotReport()
- {
- var compilation = CSharpCompilation.Create("test", [CSharpSyntaxTree.ParseText("//")])
- .WithAnalyzers([new SponsorLinkAnalyzer()],
- new AnalyzerOptions([], new TestAnalyzerConfigOptionsProvider(new())
- {
- // Force reporting if necessary without wait period
- { "build_property.SponsorLinkNoInstallGrace", "true" },
- // Directly referenced package would result in a compiler visible property like:
- //{ "build_property.SponsorableLib", "1.0.0" },
- }));
-
- var diagnostics = await compilation.GetAnalyzerDiagnosticsAsync();
-
- Assert.Empty(diagnostics);
- }
-
- string GetTempPath([CallerMemberName] string? test = default)
- {
- var path = Path.Combine(Path.GetTempPath(), test ?? nameof(AnalyzerTests));
- Directory.CreateDirectory(path);
- return path;
- }
-
- class AdditionalTextFile(string path) : AdditionalText
- {
- public override string Path => path;
- public override SourceText GetText(CancellationToken cancellationToken) => SourceText.From(File.ReadAllText(Path), Encoding.UTF8);
- }
-
- class TestAnalyzerConfigOptionsProvider(Dictionary options) : AnalyzerConfigOptionsProvider, IDictionary
- {
- AnalyzerConfigOptions analyzerOptions = new TestAnalyzerConfigOptions(options);
- public override AnalyzerConfigOptions GetOptions(SyntaxTree tree) => analyzerOptions;
-
- public override AnalyzerConfigOptions GetOptions(AdditionalText textFile) => analyzerOptions;
- public void Add(string key, string value) => options.Add(key, value);
- public bool ContainsKey(string key) => options.ContainsKey(key);
- public bool Remove(string key) => options.Remove(key);
- public bool TryGetValue(string key, [MaybeNullWhen(false)] out string value) => options.TryGetValue(key, out value);
- public void Add(KeyValuePair item) => ((ICollection>)options).Add(item);
- public void Clear() => ((ICollection>)options).Clear();
- public bool Contains(KeyValuePair item) => ((ICollection>)options).Contains(item);
- public void CopyTo(KeyValuePair[] array, int arrayIndex) => ((ICollection>)options).CopyTo(array, arrayIndex);
- public bool Remove(KeyValuePair item) => ((ICollection>)options).Remove(item);
- public IEnumerator> GetEnumerator() => ((IEnumerable>)options).GetEnumerator();
- IEnumerator IEnumerable.GetEnumerator() => ((IEnumerable)options).GetEnumerator();
- public override AnalyzerConfigOptions GlobalOptions => analyzerOptions;
- public ICollection Keys => options.Keys;
- public ICollection Values => options.Values;
- public int Count => ((ICollection>)options).Count;
- public bool IsReadOnly => ((ICollection>)options).IsReadOnly;
- public string this[string key] { get => options[key]; set => options[key] = value; }
-
- class TestAnalyzerConfigOptions(Dictionary options) : AnalyzerConfigOptions
- {
- public override bool TryGetValue(string key, out string value) => options.TryGetValue(key, out value);
- }
- }
-}
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()
- .Build();
-
- var missing = new HashSet();
-
- 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 ddffa3c..0000000
Binary files a/src/SponsorLink/Tests/Extensions.cs and /dev/null differ
diff --git a/src/SponsorLink/Tests/JsonOptions.cs b/src/SponsorLink/Tests/JsonOptions.cs
deleted file mode 100644
index b2349b0..0000000
--- a/src/SponsorLink/Tests/JsonOptions.cs
+++ /dev/null
@@ -1,70 +0,0 @@
-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 list || list.Count > 0);
- }
- }
- }
- }
- };
-
-
-#if NET6_0_OR_GREATER
- public class DateOnlyJsonConverter : JsonConverter
- {
- 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/Resources.resx b/src/SponsorLink/Tests/Resources.resx
deleted file mode 100644
index 4fdb1b6..0000000
--- a/src/SponsorLink/Tests/Resources.resx
+++ /dev/null
@@ -1,101 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- text/microsoft-resx
-
-
- 1.3
-
-
- System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.3500.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
-
-
- System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.3500.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
-
-
\ No newline at end of file
diff --git a/src/SponsorLink/Tests/Sample.cs b/src/SponsorLink/Tests/Sample.cs
deleted file mode 100644
index 8ef1ae8..0000000
--- a/src/SponsorLink/Tests/Sample.cs
+++ /dev/null
@@ -1,78 +0,0 @@
-extern alias Analyzer;
-using System;
-using System.Globalization;
-using System.Runtime.CompilerServices;
-using System.Security.Cryptography;
-using Analyzer::Devlooped.Sponsors;
-using Microsoft.CodeAnalysis;
-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.User)]
- [InlineData("es-AR", SponsorStatus.Contributor)]
- [InlineData("es", SponsorStatus.Unknown)]
- [InlineData("es", SponsorStatus.Expiring)]
- [InlineData("es", SponsorStatus.Expired)]
- [InlineData("es", SponsorStatus.User)]
- [InlineData("es", SponsorStatus.Contributor)]
- [InlineData("en", SponsorStatus.Unknown)]
- [InlineData("en", SponsorStatus.Expiring)]
- [InlineData("en", SponsorStatus.Expired)]
- [InlineData("en", SponsorStatus.User)]
- [InlineData("en", SponsorStatus.Contributor)]
- [InlineData("", SponsorStatus.Unknown)]
- [InlineData("", SponsorStatus.Expiring)]
- [InlineData("", SponsorStatus.Expired)]
- [InlineData("", SponsorStatus.User)]
- [InlineData("", SponsorStatus.Contributor)]
- public void Test(string culture, SponsorStatus kind)
- {
- Thread.CurrentThread.CurrentCulture = Thread.CurrentThread.CurrentUICulture =
- culture == "" ? CultureInfo.InvariantCulture : CultureInfo.GetCultureInfo(culture);
-
- var diag = 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),
- });
- }
- }
-
- DiagnosticDescriptor GetDescriptor(string[] sponsorable, string product, string prefix, SponsorStatus status) => status switch
- {
- SponsorStatus.Unknown => DiagnosticsManager.CreateUnknown(sponsorable, product, prefix),
- SponsorStatus.Expiring => DiagnosticsManager.CreateExpiring(sponsorable, prefix),
- SponsorStatus.Expired => DiagnosticsManager.CreateExpired(sponsorable, prefix),
- SponsorStatus.User => DiagnosticsManager.CreateSponsor(sponsorable, prefix),
- SponsorStatus.Contributor => DiagnosticsManager.CreateContributor(sponsorable, prefix),
- _ => throw new NotImplementedException(),
- };
-}
\ No newline at end of file
diff --git a/src/SponsorLink/Tests/SponsorManifestTests.cs b/src/SponsorLink/Tests/SponsorManifestTests.cs
deleted file mode 100644
index 7895a81..0000000
--- a/src/SponsorLink/Tests/SponsorManifestTests.cs
+++ /dev/null
@@ -1,129 +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 SponsorManifestTests
-{
- // We need to convert to jwk string since the analyzer project has merged the JWT assembly and types.
- public static string ToJwk(SecurityKey key)
- => JsonSerializer.Serialize(
- JsonWebKeyConverter.ConvertFromSecurityKey(key),
- JsonOptions.JsonWebKey);
-
- [Fact]
- public void ValidateSponsorable()
- {
- var sponsorable = SponsorableManifest.Create(new Uri("https://foo.com"), [new Uri("https://github.com/sponsors/bar")], "ASDF1234");
- var jwt = sponsorable.ToJwt();
- var jwk = ToJwk(sponsorable.SecurityKey);
-
- // NOTE: sponsorable manifest doesn't have expiration date.
- var manifest = SponsorLink.ParseManifest(jwt, jwk, false);
-
- Assert.True(manifest.IsValid);
- Assert.Equal(ManifestStatus.Valid, manifest.Status);
- }
-
- [Fact]
- public void ValidateWrongKey()
- {
- var sponsorable = SponsorableManifest.Create(new Uri("https://foo.com"), [new Uri("https://github.com/sponsors/bar")], "ASDF1234");
- var jwt = sponsorable.ToJwt();
- var jwk = ToJwk(new RsaSecurityKey(RSA.Create()));
-
- var manifest = SponsorLink.ParseManifest(jwt, jwk, false);
-
- Assert.Equal(ManifestStatus.Invalid, manifest.Status);
-
- // We should still be a able to read the data, knowing it may have been tampered with.
- Assert.NotNull(manifest.Principal);
- Assert.NotNull(manifest.SecurityToken);
- }
-
- [Fact]
- public void ValidateExpiredSponsor()
- {
- var sponsorable = SponsorableManifest.Create(new Uri("https://foo.com"), [new Uri("https://github.com/sponsors/bar")], "ASDF1234");
- var jwk = ToJwk(sponsorable.SecurityKey);
- var sponsor = sponsorable.Sign([], expiration: TimeSpan.Zero);
-
- // Will be expired after this.
- Thread.Sleep(1000);
-
- var manifest = SponsorLink.ParseManifest(sponsor, jwk, true);
-
- Assert.Equal(ManifestStatus.Expired, manifest.Status);
-
- // We should still be a able to read the data, even if expired (but not tampered with).
- Assert.NotNull(manifest.Principal);
- Assert.NotNull(manifest.SecurityToken);
- }
-
- [Fact]
- public void ValidateUnknownFormat()
- {
- var sponsorable = SponsorableManifest.Create(new Uri("https://foo.com"), [new Uri("https://github.com/sponsors/bar")], "ASDF1234");
- var jwk = ToJwk(sponsorable.SecurityKey);
-
- var manifest = SponsorLink.ParseManifest("asdfasdf", jwk, false);
-
- Assert.Equal(ManifestStatus.Unknown, manifest.Status);
-
- // Nothing could be read at all.
- Assert.False(manifest.IsValid);
- Assert.NotNull(manifest.Principal);
- Assert.Null(manifest.Principal.Identity);
- Assert.Null(manifest.SecurityToken);
- }
-
- [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 manifest = SponsorLink.ParseManifest(jwt,
- """
- {
- "e": "AQAB",
- "kty": "RSA",
- "n": "5inhv8QymaDBOihNi1eY-6-hcIB5qSONFZxbxxXAyOtxAdjFCPM-94gIZqM9CDrX3pyg1lTJfml_a_FZSU9dB1ii5mSX_mNHBFXn1_l_gi1ErdbkIF5YbW6oxWFxf3G5mwVXwnPfxHTyQdmWQ3YJR-A3EB4kaFwLqA6Ha5lb2ObGpMTQJNakD4oTAGDhqHMGhu6PupGq5ie4qZcQ7N8ANw8xH7nicTkbqEhQABHWOTmLBWq5f5F6RYGF8P7cl0IWl_w4YcIZkGm2vX2fi26F9F60cU1v13GZEVDTXpJ9kzvYeM9sYk6fWaoyY2jhE51qbv0B0u6hScZiLREtm3n7ClJbIGXhkUppFS2JlNaX3rgQ6t-4LK8gUTyLt3zDs2H8OZyCwlCpfmGmdsUMkm1xX6t2r-95U3zywynxoWZfjBCJf41leM9OMKYwNWZ6LQMyo83HWw1PBIrX4ZLClFwqBcSYsXDyT8_ZLd1cdYmPfmtllIXxZhLClwT5qbCWv73V"
- }
- """
- , false);
-
- Assert.Equal(ManifestStatus.Valid, manifest.Status);
- }
-}
diff --git a/src/SponsorLink/Tests/SponsorableManifest.cs b/src/SponsorLink/Tests/SponsorableManifest.cs
deleted file mode 100644
index 907fc10..0000000
--- a/src/SponsorLink/Tests/SponsorableManifest.cs
+++ /dev/null
@@ -1,357 +0,0 @@
-using System.Diagnostics.CodeAnalysis;
-using System.Security.Claims;
-using System.Security.Cryptography;
-using System.Text.Json;
-using System.Text.Json.Serialization;
-using Microsoft.IdentityModel.JsonWebTokens;
-using Microsoft.IdentityModel.Tokens;
-
-namespace Devlooped.Sponsors;
-
-///
-/// The serializable manifest of a sponsorable user, as persisted
-/// in the .github/sponsorlink.jwt file.
-///
-public class SponsorableManifest
-{
- ///
- /// Overall manifest status.
- ///
- public enum Status
- {
- ///
- /// SponsorLink manifest is invalid.
- ///
- Invalid,
- ///
- /// The manifest has an audience that doesn't match the sponsorable account.
- ///
- AccountMismatch,
- ///
- /// SponsorLink manifest not found for the given account, so it's not supported.
- ///
- NotFound,
- ///
- /// Manifest was successfully fetched and validated.
- ///
- OK,
- }
-
- ///
- /// Creates a new manifest with a new RSA key pair.
- ///
- public static SponsorableManifest Create(Uri issuer, Uri[] audience, string clientId)
- {
- var rsa = RSA.Create(3072);
- return new SponsorableManifest(issuer, audience, clientId, new RsaSecurityKey(rsa));
- }
-
- 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";
- var disposeHttp = http == null;
-
- // Manifest should be public, so no need for any special HTTP client.
- try
- {
- var response = await (http ?? new HttpClient()).GetAsync(url);
- if (!response.IsSuccessStatusCode)
- return (Status.NotFound, default);
-
- var jwt = await response.Content.ReadAsStringAsync();
- if (!TryRead(jwt, out var manifest, out _))
- 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);
- }
- finally
- {
- if (disposeHttp)
- http?.Dispose();
- }
- }
-
- ///
- /// Parses a JWT into a .
- ///
- /// The JWT containing the sponsorable information.
- /// The parsed manifest, if not required claims are missing.
- /// The missing required claim, if any.
- /// A validated manifest.
- public static bool TryRead(string jwt, [NotNullWhen(true)] out SponsorableManifest? manifest, out string? missingClaim)
- {
- var handler = new JsonWebTokenHandler
- {
- MapInboundClaims = false,
- SetDefaultTimesOnTokenCreation = false,
- };
- missingClaim = null;
- manifest = default;
-
- if (!handler.CanReadToken(jwt))
- return false;
-
- var token = handler.ReadJsonWebToken(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 == "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);
-
- return true;
- }
-
- int hashcode;
- string clientId;
- string issuer;
-
- public SponsorableManifest(Uri issuer, Uri[] audience, string clientId, SecurityKey publicKey)
- {
- this.clientId = clientId;
- this.issuer = issuer.AbsoluteUri;
- Audience = audience.Select(a => a.AbsoluteUri.TrimEnd('/')).ToArray();
- SecurityKey = publicKey;
- 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.");
-
- // Force hash code to be computed
- ClientId = clientId;
- }
-
- ///
- /// Converts (and optionally signs) the manifest into a JWT. Never exports the private key.
- ///
- /// Optional credentials when signing the resulting manifest. Defaults to the if it has a private key.
- /// The JWT manifest.
- 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 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("client_id", ClientId),
- // standard claim, serialized as a JSON string, not an encoded JSON object
- new("sub_jwk", JsonSerializer.Serialize(jwk, JsonOptions.JsonWebKey), JsonClaimValueTypes.Json),
- ]);
-
- var handler = new JsonWebTokenHandler
- {
- MapInboundClaims = false,
- SetDefaultTimesOnTokenCreation = false,
- };
-
- return handler.CreateToken(new SecurityTokenDescriptor
- {
- IssuedAt = DateTime.UtcNow,
- Subject = new ClaimsIdentity(claims),
- SigningCredentials = signing,
- });
- }
-
- ///
- /// Sign the JWT claims with the provided RSA key.
- ///
- public string Sign(IEnumerable claims, RSA rsa, TimeSpan? expiration = default)
- {
- var key = new RsaSecurityKey(rsa);
- if (key.PrivateKeyStatus != PrivateKeyStatus.Exists)
- throw new NotSupportedException("No private key found or specified to sign the manifest.");
-
- // 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.
- if (!rsa.ThumbprintEquals(SecurityKey))
- throw new ArgumentException($"Cannot sign with a private key that does not match the manifest public key.");
-
- return Sign(claims, key, expiration);
- }
-
- ///
- /// Sign the JWT claims, optionally overriding the used for signing.
- ///
- public string Sign(IEnumerable claims, SecurityKey? key = default, TimeSpan? expiration = default)
- {
- var credentials = new SigningCredentials(key ?? SecurityKey, 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);
-
- // Removed as we set IssuedAt = DateTime.UtcNow
- var tokenClaims = claims.Where(x => x.Type != JwtRegisteredClaimNames.Iat && x.Type != JwtRegisteredClaimNames.Exp).ToList();
-
- 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));
- }
-
- return new JsonWebTokenHandler
- {
- MapInboundClaims = false,
- SetDefaultTimesOnTokenCreation = false,
- }.CreateToken(new SecurityTokenDescriptor
- {
- Subject = new ClaimsIdentity(tokenClaims),
- IssuedAt = DateTime.UtcNow,
- Expires = expirationDate,
- SigningCredentials = credentials,
- });
- }
-
- public ClaimsIdentity Validate(string jwt, out SecurityToken? token)
- {
- var validation = 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(),
- // We don't validate the issuer in debug builds, to allow testing with localhost-run backend.
-#if DEBUG
- ValidateIssuer = false,
-#else
- ValidIssuer = Issuer,
-#endif
- IssuerSigningKey = SecurityKey,
- };
-
- var result = new JsonWebTokenHandler
- {
- MapInboundClaims = false,
- SetDefaultTimesOnTokenCreation = false,
- }.ValidateTokenAsync(jwt, validation).Result;
-
- token = result.SecurityToken;
- return result.ClaimsIdentity;
- }
-
- ///
- /// Gets the GitHub sponsorable account.
- ///
- public string Sponsorable { get; }
-
- ///
- /// The web endpoint that issues signed JWT to authenticated users.
- ///
- ///
- /// See https://www.rfc-editor.org/rfc/rfc7519.html#section-4.1.1
- ///
- public string Issuer
- {
- get => issuer;
- internal set
- {
- issuer = value;
- var thumb = JsonWebKeyConverter.ConvertFromSecurityKey(SecurityKey).ComputeJwkThumbprint();
- hashcode = new HashCode().Add(Issuer, ClientId, Convert.ToBase64String(thumb)).AddRange(Audience).ToHashCode();
- }
- }
-
- ///
- /// The audience for the JWT, which includes the sponsorable account and potentially other sponsoring platforms.
- ///
- ///
- /// See https://www.rfc-editor.org/rfc/rfc7519.html#section-4.1.3
- ///
- public string[] Audience { get; }
-
- ///
- /// The OAuth client ID (i.e. GitHub OAuth App ID) that is used to
- /// authenticate the user.
- ///
- ///
- /// See https://www.rfc-editor.org/rfc/rfc8693.html#name-client_id-client-identifier
- ///
- public string ClientId
- {
- get => clientId;
- internal set
- {
- clientId = value;
- var thumb = SecurityKey.ComputeJwkThumbprint();
- hashcode = new HashCode().Add(Issuer, ClientId, Convert.ToBase64String(thumb)).AddRange(Audience).ToHashCode();
- }
- }
-
- ///
- /// Public key in a format that can be used to verify JWT signatures.
- ///
- public SecurityKey SecurityKey { get; }
-
- ///
- public override int GetHashCode() => hashcode;
-
- ///
- 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 df8bc07..0000000
--- a/src/SponsorLink/Tests/Tests.csproj
+++ /dev/null
@@ -1,75 +0,0 @@
-
-
-
- net8.0
- true
- CS8981;$(NoWarn)
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- %(GitRoot.FullPath)
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- true
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/src/SponsorLink/Tests/keys/kzu.key b/src/SponsorLink/Tests/keys/kzu.key
deleted file mode 100644
index cddc6c6..0000000
Binary files a/src/SponsorLink/Tests/keys/kzu.key and /dev/null differ
diff --git a/src/SponsorLink/Tests/keys/kzu.key.jwk b/src/SponsorLink/Tests/keys/kzu.key.jwk
deleted file mode 100644
index 3589e3d..0000000
--- a/src/SponsorLink/Tests/keys/kzu.key.jwk
+++ /dev/null
@@ -1,11 +0,0 @@
-{
- "d": "OmDrKyd0ap7Az2V09C8E6aASK0nnXHGUdTIymmU1tGoxaWpkZ4gLGaYDp4L5fKc-AaqqD3PjvfJvEWfXLqJtEWfUl4gahWrgUkmuzPyAVFFioIzeGIvTGELsR6lTRke0IB2kvfvS7hRgX8Py8ohCAGHiidmoec2SyKkEg0aPdxrIKV8hx5ybC_D_4zRKn0GuVwATIeVZzPpTcyJX_sn4NHDOqut0Xg02iHhMKpF850BSoC97xGMlcSjLocFSTwI63msz6jWQ-6LRVXsfRr2mqakAvsPpqEQ3Ytk9Ud9xW0ctuAWyo6UXev5w2XEL8cSXm33-57fi3ekC_jGCqW0KfAU4Cr2UbuTC0cv8Vv0F4Xm5FizolmuSBFOvf55-eqsjmpwf9hftYAiIlFF-49-P0DpeJejSeoL06BE3e3_IVu3g3HNnSWVUOLJ5Uk5FQ-ieHhf-r2Tq5qZ8_-losHekQbCxCMY2isc-r6V6BMnVL_9kWPxpXwhjKrYxNFZEXUJ1",
- "dp": "HjCs_QF1Hn1SGS2OqZzYhGhNk4PTw9Hs97E7g3pb4liY0uECYOYp1RNoMyzvNBZVwlxhpeTTS299yPoXeYmseXfLtcjfIVi6mSWS_u27Hd0zfSdaPDOXyyK-mZfIV7Q76RTost0QY3LA0ciJbj3gJqpl38dhuNQ8h9Yqt-TFyb3CUaM3A_JUNKOTce8qnkLrasytPEuSroOBT8bgCWJIjw_mXWMGcoqRFWHw9Nyp9mIyvtPjUQ9ig3bGSP_-3LZf",
- "dq": "IP6EsAZ_6psFdlQrvnugYFs91fEP5QfBzNHmbfmsPRVX4QM5B3L6klQyJsLqvPfF1Xu17ZffLFkNBKuiphIcLPo0yZTJG9Y7S8gLuPAmrH-ndfxG-bQ8Yt0ZB1pA77ILIS8bUTKrMqAWS-VcaxcSCIyhSusLEWYYDi3PEzB375OUw4aXIk3ob8bePG7UqFSL6qmDPgkGLTxkY9m5dEiOshHygtVY-H_jjOIawliEPgmgAr2M-zlXiphovDyAT0PV",
- "e": "AQAB",
- "kty": "RSA",
- "n": "yP71VgOgHDtGbxdyN31mIFFITmGYEk2cwepKbyqKTbTYXF1OXaMoP5n3mfwqwzUQmEAsrclAigPcK4GIy5WWlc5YujIxKauJjsKe0FBxMnFp9o1UcBUHfgDJjaAKieQxb44717b1MwCcflEGnCGTXkntdr45y9Gi1D9-oBw5zIVZekgMP55XxmKvkJd1k-bYWSv-QFG2JJwRIGwr29Jr62juCsLB7Tg83ZGKCa22Y_7lQcezxRRD5OrGWhf3gTYArbrEzbYy653zbHfbOCJeVBe_bXDkR74yG3mmq_Ne0qhNk6wXuX-NrKEvdPxRSRBF7C465fcVY9PM6eTqEPQwKDiarHpU1NTwUetzb-YKry-h678RJWMhC7I9lzzWVobbC0YVKG7XpeVqBB4u7Q6cGo5Xkf19VldkIxQMu9sFeuHGDSoiCLqmRmwNn9GsMV77oZWr-OPrxEdZzL9BcI4fMJMz7YdiIu-qbIp_vqatbalfNasumf8RgtPOkR2vgc59",
- "p": "6JTf8Qb0iHRL6MIIs7MlkEKBNpnAu_Nie4HTqhxy2wfE4cBr6QZ98iJniXffDIjq_GxVpw9K-Bv2gTcNrlzOiBaLf3X2Itfice_Qd-luhNbnXVfiA5sg6dZ2wbBuue5ann5iJ_TIbxO4CLUiqQp0PCReUPzTQhzesHxM2-dBC9AYDl7P6p1FF53Hh_Knx9UywhoPvNtoCJy35-5rj0ghgPYz289dbOBccZnvabRueOr_wpHGMKaznqiDMrcFSZ07",
- "q": "3TvrN8R9imw6E6JkVQ4PtveE0vkvkSWHUpn9KwKFIJJiwL_HSS4z_8IYR1_0Q1OgK5-z-QcXhq9P7jTjz02I2uwWhP3RZQf99RZACfMaeIs8O2V-I89WdlJYOerzAelW4nYw7zyeVoT5c5osicGWfSmWslLRjA1yx7x1KA_MCU_KIEBlpe1RgEUYPET3OtvPKFIVQYoJfQC5PFlmrC-kgHZMSpdHjWgWi5gPn0fIBCKFsXcPrt2n_lKKGc4lFOen",
- "qi": "m-tgdFqO1Ax3C00oe7kdkYLHMD56wkGARdqPCqS5IGhFVKCOA8U6O_s5bSL4r0TzPE0KrJ4A5QJEwjbH4bXssPaaAlv1ZdWjn8YMQCYFolg_pgUWYYI5vNxG1gIsLGXPTfE8a6SObkJ2Q9VC5ZZp14r4lPvJhwFICIGSRBKcvS-gO_gqB3LKuG9TQBi-CE4DHDLJwsCbEBR8Ber45oTqvG7hphpOhBHsFZ8_6f3Reg_sK1BCz9HFCx8hhi8rBfUp"
-}
\ No newline at end of file
diff --git a/src/SponsorLink/Tests/keys/kzu.key.txt b/src/SponsorLink/Tests/keys/kzu.key.txt
deleted file mode 100644
index 5fe8758..0000000
--- a/src/SponsorLink/Tests/keys/kzu.key.txt
+++ /dev/null
@@ -1 +0,0 @@
-MIIG4wIBAAKCAYEAyP71VgOgHDtGbxdyN31mIFFITmGYEk2cwepKbyqKTbTYXF1OXaMoP5n3mfwqwzUQmEAsrclAigPcK4GIy5WWlc5YujIxKauJjsKe0FBxMnFp9o1UcBUHfgDJjaAKieQxb44717b1MwCcflEGnCGTXkntdr45y9Gi1D9+oBw5zIVZekgMP55XxmKvkJd1k+bYWSv+QFG2JJwRIGwr29Jr62juCsLB7Tg83ZGKCa22Y/7lQcezxRRD5OrGWhf3gTYArbrEzbYy653zbHfbOCJeVBe/bXDkR74yG3mmq/Ne0qhNk6wXuX+NrKEvdPxRSRBF7C465fcVY9PM6eTqEPQwKDiarHpU1NTwUetzb+YKry+h678RJWMhC7I9lzzWVobbC0YVKG7XpeVqBB4u7Q6cGo5Xkf19VldkIxQMu9sFeuHGDSoiCLqmRmwNn9GsMV77oZWr+OPrxEdZzL9BcI4fMJMz7YdiIu+qbIp/vqatbalfNasumf8RgtPOkR2vgc59AgMBAAECggGAOmDrKyd0ap7Az2V09C8E6aASK0nnXHGUdTIymmU1tGoxaWpkZ4gLGaYDp4L5fKc+AaqqD3PjvfJvEWfXLqJtEWfUl4gahWrgUkmuzPyAVFFioIzeGIvTGELsR6lTRke0IB2kvfvS7hRgX8Py8ohCAGHiidmoec2SyKkEg0aPdxrIKV8hx5ybC/D/4zRKn0GuVwATIeVZzPpTcyJX/sn4NHDOqut0Xg02iHhMKpF850BSoC97xGMlcSjLocFSTwI63msz6jWQ+6LRVXsfRr2mqakAvsPpqEQ3Ytk9Ud9xW0ctuAWyo6UXev5w2XEL8cSXm33+57fi3ekC/jGCqW0KfAU4Cr2UbuTC0cv8Vv0F4Xm5FizolmuSBFOvf55+eqsjmpwf9hftYAiIlFF+49+P0DpeJejSeoL06BE3e3/IVu3g3HNnSWVUOLJ5Uk5FQ+ieHhf+r2Tq5qZ8/+losHekQbCxCMY2isc+r6V6BMnVL/9kWPxpXwhjKrYxNFZEXUJ1AoHBAOiU3/EG9Ih0S+jCCLOzJZBCgTaZwLvzYnuB06occtsHxOHAa+kGffIiZ4l33wyI6vxsVacPSvgb9oE3Da5czogWi3919iLX4nHv0HfpboTW511X4gObIOnWdsGwbrnuWp5+Yif0yG8TuAi1IqkKdDwkXlD800Ic3rB8TNvnQQvQGA5ez+qdRRedx4fyp8fVMsIaD7zbaAict+fua49IIYD2M9vPXWzgXHGZ72m0bnjq/8KRxjCms56ogzK3BUmdOwKBwQDdO+s3xH2KbDoTomRVDg+294TS+S+RJYdSmf0rAoUgkmLAv8dJLjP/whhHX/RDU6Arn7P5BxeGr0/uNOPPTYja7BaE/dFlB/31FkAJ8xp4izw7ZX4jz1Z2Ulg56vMB6VbidjDvPJ5WhPlzmiyJwZZ9KZayUtGMDXLHvHUoD8wJT8ogQGWl7VGARRg8RPc6288oUhVBigl9ALk8WWasL6SAdkxKl0eNaBaLmA+fR8gEIoWxdw+u3af+UooZziUU56cCgcAeMKz9AXUefVIZLY6pnNiEaE2Tg9PD0ez3sTuDelviWJjS4QJg5inVE2gzLO80FlXCXGGl5NNLb33I+hd5iax5d8u1yN8hWLqZJZL+7bsd3TN9J1o8M5fLIr6Zl8hXtDvpFOiy3RBjcsDRyIluPeAmqmXfx2G41DyH1iq35MXJvcJRozcD8lQ0o5Nx7yqeQutqzK08S5Kug4FPxuAJYkiPD+ZdYwZyipEVYfD03Kn2YjK+0+NRD2KDdsZI//7ctl8CgcAg/oSwBn/qmwV2VCu+e6BgWz3V8Q/lB8HM0eZt+aw9FVfhAzkHcvqSVDImwuq898XVe7Xtl98sWQ0Eq6KmEhws+jTJlMkb1jtLyAu48Casf6d1/Eb5tDxi3RkHWkDvsgshLxtRMqsyoBZL5VxrFxIIjKFK6wsRZhgOLc8TMHfvk5TDhpciTehvxt48btSoVIvqqYM+CQYtPGRj2bl0SI6yEfKC1Vj4f+OM4hrCWIQ+CaACvYz7OVeKmGi8PIBPQ9UCgcEAm+tgdFqO1Ax3C00oe7kdkYLHMD56wkGARdqPCqS5IGhFVKCOA8U6O/s5bSL4r0TzPE0KrJ4A5QJEwjbH4bXssPaaAlv1ZdWjn8YMQCYFolg/pgUWYYI5vNxG1gIsLGXPTfE8a6SObkJ2Q9VC5ZZp14r4lPvJhwFICIGSRBKcvS+gO/gqB3LKuG9TQBi+CE4DHDLJwsCbEBR8Ber45oTqvG7hphpOhBHsFZ8/6f3Reg/sK1BCz9HFCx8hhi8rBfUp
\ No newline at end of file
diff --git a/src/SponsorLink/Tests/keys/kzu.pub b/src/SponsorLink/Tests/keys/kzu.pub
deleted file mode 100644
index 5594797..0000000
Binary files a/src/SponsorLink/Tests/keys/kzu.pub and /dev/null differ
diff --git a/src/SponsorLink/Tests/keys/kzu.pub.jwk b/src/SponsorLink/Tests/keys/kzu.pub.jwk
deleted file mode 100644
index b4bfb31..0000000
--- a/src/SponsorLink/Tests/keys/kzu.pub.jwk
+++ /dev/null
@@ -1,5 +0,0 @@
-{
- "e": "AQAB",
- "kty": "RSA",
- "n": "yP71VgOgHDtGbxdyN31mIFFITmGYEk2cwepKbyqKTbTYXF1OXaMoP5n3mfwqwzUQmEAsrclAigPcK4GIy5WWlc5YujIxKauJjsKe0FBxMnFp9o1UcBUHfgDJjaAKieQxb44717b1MwCcflEGnCGTXkntdr45y9Gi1D9-oBw5zIVZekgMP55XxmKvkJd1k-bYWSv-QFG2JJwRIGwr29Jr62juCsLB7Tg83ZGKCa22Y_7lQcezxRRD5OrGWhf3gTYArbrEzbYy653zbHfbOCJeVBe_bXDkR74yG3mmq_Ne0qhNk6wXuX-NrKEvdPxRSRBF7C465fcVY9PM6eTqEPQwKDiarHpU1NTwUetzb-YKry-h678RJWMhC7I9lzzWVobbC0YVKG7XpeVqBB4u7Q6cGo5Xkf19VldkIxQMu9sFeuHGDSoiCLqmRmwNn9GsMV77oZWr-OPrxEdZzL9BcI4fMJMz7YdiIu-qbIp_vqatbalfNasumf8RgtPOkR2vgc59"
-}
\ No newline at end of file
diff --git a/src/SponsorLink/Tests/keys/kzu.pub.txt b/src/SponsorLink/Tests/keys/kzu.pub.txt
deleted file mode 100644
index 729ecd5..0000000
--- a/src/SponsorLink/Tests/keys/kzu.pub.txt
+++ /dev/null
@@ -1 +0,0 @@
-MIIBigKCAYEAyP71VgOgHDtGbxdyN31mIFFITmGYEk2cwepKbyqKTbTYXF1OXaMoP5n3mfwqwzUQmEAsrclAigPcK4GIy5WWlc5YujIxKauJjsKe0FBxMnFp9o1UcBUHfgDJjaAKieQxb44717b1MwCcflEGnCGTXkntdr45y9Gi1D9+oBw5zIVZekgMP55XxmKvkJd1k+bYWSv+QFG2JJwRIGwr29Jr62juCsLB7Tg83ZGKCa22Y/7lQcezxRRD5OrGWhf3gTYArbrEzbYy653zbHfbOCJeVBe/bXDkR74yG3mmq/Ne0qhNk6wXuX+NrKEvdPxRSRBF7C465fcVY9PM6eTqEPQwKDiarHpU1NTwUetzb+YKry+h678RJWMhC7I9lzzWVobbC0YVKG7XpeVqBB4u7Q6cGo5Xkf19VldkIxQMu9sFeuHGDSoiCLqmRmwNn9GsMV77oZWr+OPrxEdZzL9BcI4fMJMz7YdiIu+qbIp/vqatbalfNasumf8RgtPOkR2vgc59AgMBAAE=
\ No newline at end of file
diff --git a/src/SponsorLink/Tests/keys/sponsorlink.jwt b/src/SponsorLink/Tests/keys/sponsorlink.jwt
deleted file mode 100644
index b53fe62..0000000
--- a/src/SponsorLink/Tests/keys/sponsorlink.jwt
+++ /dev/null
@@ -1 +0,0 @@
-eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE3MTk4NjgyMzAsImlzcyI6Imh0dHBzOi8vc3BvbnNvcmxpbmsuZGV2bG9vcGVkLmNvbS8iLCJhdWQiOlsiaHR0cHM6Ly9naXRodWIuY29tL3Nwb25zb3JzL2t6dSIsImh0dHBzOi8vZ2l0aHViLmNvbS9zcG9uc29ycy9kZXZsb29wZWQiXSwiY2xpZW50X2lkIjoiYTgyMzUwZmIyYmFlNDA3YjMwMjEiLCJzdWJfandrIjp7ImUiOiJBUUFCIiwia3R5IjoiUlNBIiwibiI6InlQNzFWZ09nSER0R2J4ZHlOMzFtSUZGSVRtR1lFazJjd2VwS2J5cUtUYlRZWEYxT1hhTW9QNW4zbWZ3cXd6VVFtRUFzcmNsQWlnUGNLNEdJeTVXV2xjNVl1akl4S2F1SmpzS2UwRkJ4TW5GcDlvMVVjQlVIZmdESmphQUtpZVF4YjQ0NzE3YjFNd0NjZmxFR25DR1RYa250ZHI0NXk5R2kxRDktb0J3NXpJVlpla2dNUDU1WHhtS3ZrSmQxay1iWVdTdi1RRkcySkp3UklHd3IyOUpyNjJqdUNzTEI3VGc4M1pHS0NhMjJZXzdsUWNlenhSUkQ1T3JHV2hmM2dUWUFyYnJFemJZeTY1M3piSGZiT0NKZVZCZV9iWERrUjc0eUczbW1xX05lMHFoTms2d1h1WC1OcktFdmRQeFJTUkJGN0M0NjVmY1ZZOVBNNmVUcUVQUXdLRGlhckhwVTFOVHdVZXR6Yi1ZS3J5LWg2NzhSSldNaEM3STlsenpXVm9iYkMwWVZLRzdYcGVWcUJCNHU3UTZjR281WGtmMTlWbGRrSXhRTXU5c0ZldUhHRFNvaUNMcW1SbXdObjlHc01WNzdvWldyLU9QcnhFZFp6TDlCY0k0Zk1KTXo3WWRpSXUtcWJJcF92cWF0YmFsZk5hc3VtZjhSZ3RQT2tSMnZnYzU5In19.er4apYbEjHVKlQ_aMXoRhHYeR8N-3uIrCk3HX8UuZO7mb0CaS94-422EI3z5O9vRvckcGkNVoiSIX0ykZqUMHTZxBae-QZc1u_rhdBOChoaxWqpUiPXLZ5-yi7mcRwqg2DOUb2eHTNfRjwJ-0tjL1R1TqZw9d8Bgku1zw2ZTuJl_WsBRHKHTD_s5KyCP5yhSOUumrsf3nXYrc20fJ7ql0FsL0MP66utJk7TFYHGhQV3cfcXYqFEpv-k6tqB9k3Syc0UnepmQT0Y3dtcBzQzCOzfKQ8bdaAXVHjfp4VvXBluHmh9lP6TeZmpvlmQDFvyk0kp1diTbo9pqmX_llNDWNxBdvaSZGa7RZMG_dE2WJGtQNu0C_sbEZDPZsKncxdtm-j-6Y7GRqx7uxe4Py8tAZ7SxjiPgD64jf9KF2OT6f6drVtzohVzYCs6-vhcXzC2sQvd_gQ-SoFNTa1MEcMgGbL-fFWUC7-7bQV1DlSg2YFwrxEIwbM-gHpLZHyyJLvYD
\ No newline at end of file
diff --git a/src/SponsorLink/jwk.ps1 b/src/SponsorLink/jwk.ps1
deleted file mode 100644
index c66f56f..0000000
--- a/src/SponsorLink/jwk.ps1
+++ /dev/null
@@ -1 +0,0 @@
-curl https://raw.githubusercontent.com/devlooped/.github/main/sponsorlink.jwt --silent | jq -R 'split(".") | .[1] | @base64d | fromjson' | jq '.sub_jwk'
\ No newline at end of file
diff --git a/src/SponsorLink/readme.md b/src/SponsorLink/readme.md
deleted file mode 100644
index a502452..0000000
--- a/src/SponsorLink/readme.md
+++ /dev/null
@@ -1,53 +0,0 @@
-# SponsorLink .NET Analyzer Sample
-
-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 can include all the necessary files by using the [dotnet-file](https://github.com/devlooped/dotnet-file)
-tool and sync all files to a folder, such as:
-
-```shell
-dotnet file add https://github.com/devlooped/SponsorLink/tree/main/samples/dotnet/ src/SponsorLink/
-```
-
-Including the analyzer and targets in a project involves two steps.
-
-1. Create an analyzer project and add the following property:
-
-```xml
-
- ...
- $(MSBuildThisFileDirectory)..\SponsorLink\SponsorLink.Analyzer.targets
-
-```
-
-2. Add a `buildTransitive\[PackageId].targets` file with the following import:
-
-```xml
-
-
-
-```
-
-3. Set the package id(s) that will be checked for funding in the analyzer, such as:
-
-```xml
-
- SponsorableLib;SponsorableLib.Core
-
-```
-
- The default analyzer will report a diagnostic for sponsorship status only
- if the project being compiled as a direct package reference to one of the
- specified package ids.
-
- This property defaults to `$(PackageId)` if present. Otherwise, it defaults
- to `$(FundingProduct)`, which in turn defaults to `$(Product)` if not provided.
-
-As long as NuGetizer is used, the right packaging will be done automatically.
\ No newline at end of file