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 @@ ================ [![Version](https://img.shields.io/nuget/vpre/Merq.svg?color=royalblue)](https://www.nuget.org/packages/Merq) -[![Downloads](https://img.shields.io/nuget/dt/Merq.svg?color=green)](https://www.nuget.org/packages/Merq) -[![License](https://img.shields.io/github/license/devlooped/Merq.svg?color=blue)](https://github.com/devlooped/Merq/blob/main/license.txt) +[![Downloads](https://img.shields.io/nuget/dt/Merq.svg?color=darkmagenta)](https://www.nuget.org/packages/Merq) +[![EULA](https://img.shields.io/badge/EULA-OSMF-blue?labelColor=black&color=C9FF30)](osmfeula.txt) +[![OSS](https://img.shields.io/github/license/devlooped/oss.svg?color=blue)](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 @@ - - +[![EULA](https://img.shields.io/badge/EULA-OSMF-blue?labelColor=black&color=C9FF30)](osmfeula.txt) +[![OSS](https://img.shields.io/github/license/devlooped/oss.svg?color=blue)](license.txt) +[![GitHub](https://img.shields.io/badge/-source-181717.svg?logo=GitHub)](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 @@ - +[![EULA](https://img.shields.io/badge/EULA-OSMF-blue?labelColor=black&color=C9FF30)](osmfeula.txt) +[![OSS](https://img.shields.io/github/license/devlooped/oss.svg?color=blue)](license.txt) +[![GitHub](https://img.shields.io/badge/-source-181717.svg?logo=GitHub)](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` +[![EULA](https://img.shields.io/badge/EULA-OSMF-blue?labelColor=black&color=C9FF30)](osmfeula.txt) +[![OSS](https://img.shields.io/github/license/devlooped/oss.svg?color=blue)](license.txt) +[![GitHub](https://img.shields.io/badge/-source-181717.svg?logo=GitHub)](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 @@ - - +[![EULA](https://img.shields.io/badge/EULA-OSMF-blue?labelColor=black&color=C9FF30)](osmfeula.txt) +[![OSS](https://img.shields.io/github/license/devlooped/oss.svg?color=blue)](license.txt) +[![GitHub](https://img.shields.io/badge/-source-181717.svg?logo=GitHub)](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