<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Peter H. Boling</title>
    <description>The latest articles on DEV Community by Peter H. Boling (@galtzo).</description>
    <link>https://dev.to/galtzo</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F561326%2F9da2905c-8840-4f44-8d63-6a9e03065371.jpeg</url>
      <title>DEV Community: Peter H. Boling</title>
      <link>https://dev.to/galtzo</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/galtzo"/>
    <language>en</language>
    <item>
      <title>💎REL: oauth2 v2.0.18</title>
      <dc:creator>Peter H. Boling</dc:creator>
      <pubDate>Wed, 01 Apr 2026 05:59:42 +0000</pubDate>
      <link>https://dev.to/galtzo/rel-oauth2-v2018-43pf</link>
      <guid>https://dev.to/galtzo/rel-oauth2-v2018-43pf</guid>
      <description>&lt;p&gt;oauth2 v2.0.18 was released... &lt;a href="https://github.com/ruby-oauth/oauth2/releases/tag/v2.0.18" rel="noopener noreferrer"&gt;almost five months ago&lt;/a&gt;. And I never got around to posting about it. Being unemployed is a LOT of work...&lt;/p&gt;

&lt;p&gt;As a participant in &lt;a href="https://github.blog/open-source/maintainers/securing-the-ai-software-supply-chain-security-results-across-67-open-source-projects/" rel="noopener noreferrer"&gt;Session 3 of GitHub Secure Open Source Fund&lt;/a&gt; I was able to learn about implementing:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;an incident response plan&lt;/li&gt;
&lt;li&gt;threat model analysis&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These are critical tools to have in place so you have a guide when things go awry. ;) Those are the main changes in v2.0.18.&lt;/p&gt;

&lt;p&gt;Another release is coming soon...&lt;/p&gt;

&lt;h1&gt;
  
  
  Ruby #GitHub #Security
&lt;/h1&gt;

&lt;p&gt;Cover Photo (Cropped) by &lt;a href="https://unsplash.com/@drskdr?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText" rel="noopener noreferrer"&gt;Dasha Yukhymyuk&lt;/a&gt; on &lt;a href="https://unsplash.com/photos/red-and-black-led-light-rPG1Fg8UDUw?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText" rel="noopener noreferrer"&gt;Unsplash&lt;/a&gt;&lt;/p&gt;

</description>
      <category>ruby</category>
      <category>oauth</category>
      <category>security</category>
      <category>github</category>
    </item>
    <item>
      <title>🐠 ANN: appraisal2 v3.0.6 - support frozen appraisal lockfiles</title>
      <dc:creator>Peter H. Boling</dc:creator>
      <pubDate>Wed, 18 Feb 2026 06:20:33 +0000</pubDate>
      <link>https://dev.to/galtzo/ann-appraisal2-v306-support-frozen-appraisal-lockfiles-20ml</link>
      <guid>https://dev.to/galtzo/ann-appraisal2-v306-support-frozen-appraisal-lockfiles-20ml</guid>
      <description>&lt;p&gt;An issue was reported by Richard Kramer, and made me aware of a use case that I had never personally used, or even considered - which is committing the lockfiles for appraisals. I have always added &lt;code&gt;gemfiles/*.gemfile.lock&lt;/code&gt; to my .gitignore.&lt;/p&gt;

&lt;p&gt;In fact, when I had use cases where I wanted to run a CI workflow against a frozen lockfile I would avoid using appraisal2.  But no more. We now have first class support for frozen lockfiles, and the implied active bundler version switching that can happen at runtime.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/appraisal-rb/appraisal2/issues/21" rel="noopener noreferrer"&gt;Issue #21&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/appraisal-rb/appraisal2/pull/23" rel="noopener noreferrer"&gt;Pull #23&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;No breaking changes. No code changes needed for implementations. Just update, and go. Report back if anything breaks!&lt;/p&gt;

&lt;p&gt;I need your support. Please sponsor me here on &lt;a href="https://opencollective.com/appraisal-rb" rel="noopener noreferrer"&gt;OpenCollective&lt;/a&gt;, or on:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/sponsors/pboling" rel="noopener noreferrer"&gt;@pboling on GitHub Sponsors&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://liberapay.com/pboling/donate" rel="noopener noreferrer"&gt;@pboling on Liberapay&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Thanks,&lt;br&gt;
@pboling&lt;/p&gt;

</description>
      <category>news</category>
      <category>ruby</category>
      <category>testing</category>
      <category>tooling</category>
    </item>
    <item>
      <title>Hostile Takeover of RubyGems: My Thoughts</title>
      <dc:creator>Peter H. Boling</dc:creator>
      <pubDate>Fri, 06 Feb 2026 01:00:32 +0000</pubDate>
      <link>https://dev.to/galtzo/hostile-takeover-of-rubygems-my-thoughts-5hlo</link>
      <guid>https://dev.to/galtzo/hostile-takeover-of-rubygems-my-thoughts-5hlo</guid>
      <description>&lt;p&gt;I'll keep this post evergreen, as the situation evolves. Also, when you are done reading - &lt;a href="https://galtzo.com" rel="noopener noreferrer"&gt;hire me&lt;/a&gt;.&lt;/p&gt;

&lt;h1&gt;
  
  
  👣🔍️ First some background reading 🕵️
&lt;/h1&gt;

&lt;ul&gt;
&lt;li&gt;RubyGems (the &lt;a href="https://github.com/rubygems/" rel="noopener noreferrer"&gt;GitHub org&lt;/a&gt;, not the website) &lt;a href="https://joel.drapper.me/p/ruby-central-security-measures/" rel="noopener noreferrer"&gt;suffered&lt;/a&gt; a &lt;a href="https://pup-e.com/blog/goodbye-rubygems/" rel="noopener noreferrer"&gt;hostile takeover&lt;/a&gt; in September 2025.&lt;/li&gt;
&lt;li&gt;Ultimately &lt;a href="https://www.reddit.com/r/ruby/s/gOk42POCaV" rel="noopener noreferrer"&gt;4 maintainers&lt;/a&gt; were &lt;a href="https://bsky.app/profile/martinemde.com/post/3m3occezxxs2q" rel="noopener noreferrer"&gt;hard removed&lt;/a&gt; and a (dubious) reason has been given for only 1 of those, while 2 others resigned in protest.&lt;/li&gt;
&lt;li&gt;It is a &lt;a href="https://joel.drapper.me/p/ruby-central-takeover/" rel="noopener noreferrer"&gt;complicated story&lt;/a&gt; which is difficult to &lt;a href="https://joel.drapper.me/p/ruby-central-fact-check/" rel="noopener noreferrer"&gt;parse quickly&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Simply put - there was active policy for adding or removing maintainers/owners of &lt;a href="https://github.com/ruby/rubygems/blob/b1ab33a3d52310a84d16b193991af07f5a6a07c0/doc/rubygems/POLICIES.md?plain=1#L187-L196" rel="noopener noreferrer"&gt;rubygems&lt;/a&gt; and &lt;a href="https://github.com/ruby/rubygems/blob/b1ab33a3d52310a84d16b193991af07f5a6a07c0/doc/bundler/playbooks/TEAM_CHANGES.md" rel="noopener noreferrer"&gt;bundler&lt;/a&gt;, and those &lt;a href="https://www.reddit.com/r/ruby/comments/1ove9vp/rubycentral_hates_this_one_fact/" rel="noopener noreferrer"&gt;policies were not followed&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;I'm adding a note linking to this post to all of my gems because I &lt;a href="https://joel.drapper.me/p/ruby-central/" rel="noopener noreferrer"&gt;don't condone theft&lt;/a&gt; of repositories or gems from their rightful owners.&lt;/li&gt;
&lt;li&gt;If a similar theft happened with my repos/gems, I'd hope some would stand up for me.&lt;/li&gt;
&lt;li&gt;Disenfranchised former-maintainers have started &lt;a href="https://gem.coop" rel="noopener noreferrer"&gt;gem.coop&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Once available I will publish there, or to my own server, exclusively; unless RubyCentral &amp;amp; Ruby Core make amends with the community.&lt;/li&gt;
&lt;li&gt;The &lt;a href="https://youtu.be/_H4qbtC5qzU?si=BvuBU90R2wAqD2E6" rel="noopener noreferrer"&gt;"Technology for Humans: Joel Draper"&lt;/a&gt; podcast episode by &lt;a href="https://reinteractive.com/ruby-on-rails" rel="noopener noreferrer"&gt;reinteractive&lt;/a&gt; is the most cogent summary I'm aware of.&lt;/li&gt;
&lt;li&gt;See &lt;a href="https://github.com/gem-coop/gem.coop/issues/12" rel="noopener noreferrer"&gt;here&lt;/a&gt;, &lt;a href="https://gem.coop" rel="noopener noreferrer"&gt;here&lt;/a&gt; and &lt;a href="https://martinemde.com/2025/10/05/announcing-gem-coop.html" rel="noopener noreferrer"&gt;here&lt;/a&gt; for more info on what comes next.&lt;/li&gt;
&lt;/ul&gt;

&lt;h1&gt;
  
  
  My thoughts
&lt;/h1&gt;

&lt;ol&gt;
&lt;li&gt;I no longer trust Ruby Central.&lt;/li&gt;
&lt;li&gt;I no longer trust certain members, but primarily HSBT, of the RubyGems core team.&lt;/li&gt;
&lt;li&gt;I no longer trust certain members, but primarily HSBT and Matz, of the Ruby core team.&lt;/li&gt;
&lt;/ol&gt;

&lt;blockquote&gt;
&lt;p&gt;Q: In what sense do I &lt;em&gt;not trust&lt;/em&gt; them?&lt;br&gt;
A: 📃 &lt;strong&gt;Governance&lt;/strong&gt; 📃&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;To be more specific, I no longer trust that they:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Hold people accountable for their actions according to written agreements and documentation around governance policy. &lt;/li&gt;
&lt;li&gt;Understand the community upset over point 1.&lt;/li&gt;
&lt;li&gt;Will ever do anything about it.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If they are added to your repository, you may wake up to find you have lost access to your own project.&lt;/p&gt;

&lt;p&gt;I'm not OK with this having already happened to others, and have taken steps to ensure it will not happen to me.&lt;/p&gt;

&lt;p&gt;Within my open source projects, I will reduce, to the degree possible, my reliance, on any project hosted under the Ruby org on GitHub. Since most of my projects are Ruby projects, I'll never get to complete exclusion, but I will be focusing much more on JRuby and Truffleruby.&lt;/p&gt;

&lt;p&gt;It has been pointed out to me in other discussions about this that we never had reason to trust them, but we did anyway, implicitly. We normally assume other people live by the same code of ethics that we ourselves live by. I will miss being able to rest on that assumption, but it is probably for the best that it get binned.&lt;/p&gt;

&lt;h1&gt;
  
  
  What I'm doing about it
&lt;/h1&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://github.com/appraisal-rb/appraisal2" rel="noopener noreferrer"&gt;appraisal2&lt;/a&gt; is a hard fork of the old, and nearly-dead, namesake Thoughtbot project, to which I've added many features, including support for:

&lt;ul&gt;
&lt;li&gt;Bundler's &lt;code&gt;eval_gemfile&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;frozen appraisal lockfiles w/ bundler version switching&lt;/li&gt;
&lt;li&gt;all versions of Ruby back to v1.8&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/contriboss/ore-light" rel="noopener noreferrer"&gt;ore&lt;/a&gt; (see below)&lt;/li&gt;
&lt;li&gt;More on the reasons behind the &lt;a href="https://dev.to/galtzo/ann-appraisal2-a-hard-fork-44dh"&gt;hard fork&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;a href="https://github.com/contriboss/ore-light" rel="noopener noreferrer"&gt;ore&lt;/a&gt; installs gems without Ruby, without bundler, and without rubygems. It is a GoLang implementation of (some parts of) Bundler (and adds some features bundler lacks). A project by &lt;a class="mentioned-user" href="https://dev.to/seuros"&gt;@seuros&lt;/a&gt; - and I'm now on the core team. It is &lt;em&gt;much&lt;/em&gt; faster than bundler.&lt;/li&gt;

&lt;li&gt;

&lt;a href="https://github.com/appraisal-rb/setup-ruby-flash" rel="noopener noreferrer"&gt;setup-ruby-flash&lt;/a&gt; is an alternative to the venerable setup-ruby GHA we've all been using for years. &lt;code&gt;setup-ruby-flash&lt;/code&gt; relies on &lt;a href="https://rv.dev/" rel="noopener noreferrer"&gt;rv&lt;/a&gt; and &lt;a href="https://github.com/contriboss/ore-light" rel="noopener noreferrer"&gt;ore&lt;/a&gt; for Ruby and Gem installs, and it falls back to &lt;a href="https://github.com/ruby/setup-ruby" rel="noopener noreferrer"&gt;setup-ruby&lt;/a&gt; on unsupported platforms/engines. I wrote more about it &lt;a href="https://dev.to/galtzo/setup-ruby-flash-25lb"&gt;here&lt;/a&gt;.&lt;/li&gt;

&lt;li&gt;A (WIP) proposal for &lt;a href="https://github.com/galtzo-floss/bundle-namespace" rel="noopener noreferrer"&gt;bundler/gem scopes&lt;/a&gt;
&lt;/li&gt;

&lt;li&gt;A (WIP) proposal for a federated &lt;a href="https://github.com/galtzo-floss/gem-server" rel="noopener noreferrer"&gt;gem server&lt;/a&gt;
&lt;/li&gt;

&lt;/ul&gt;

</description>
      <category>opensource</category>
      <category>ruby</category>
      <category>rails</category>
      <category>governance</category>
    </item>
    <item>
      <title>⚡️ setup-ruby-flash</title>
      <dc:creator>Peter H. Boling</dc:creator>
      <pubDate>Mon, 19 Jan 2026 03:03:27 +0000</pubDate>
      <link>https://dev.to/galtzo/setup-ruby-flash-25lb</link>
      <guid>https://dev.to/galtzo/setup-ruby-flash-25lb</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;Find out how fast my workflows can go!&lt;br&gt;
-You, possibly&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;GH Marketplace&lt;/th&gt;
&lt;th&gt;Dogfood&lt;/th&gt;
&lt;th&gt;Current Tag&lt;/th&gt;
&lt;th&gt;License&lt;/th&gt;
&lt;th&gt;Source&lt;/th&gt;
&lt;th&gt;Stars&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://github.com/marketplace/actions/setup-ruby-with-rv-and-ore" rel="noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraster.shields.io%2Fbadge%2FMarketplace-238636%3F%26logo%3DGithub%26logoColor%3Dgreen" alt="GH Marketplace" width="95" height="20"&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href="https://github.com/appraisal-rb/setup-ruby-flash/actions/workflows/ci.yml" rel="noopener noreferrer"&gt;&lt;img src="https://github.com/appraisal-rb/setup-ruby-flash/actions/workflows/ci.yml/badge.svg" alt="CI" width="83" height="20"&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href="http://github.com/appraisal-rb/setup-ruby-flash/releases" rel="noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraster.shields.io%2Fgithub%2Ftag%2Fappraisal-rb%2Fsetup-ruby-flash.png" alt="GitHub tag (latest SemVer)" width="62" height="20"&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href="https://opensource.org/licenses/MIT" rel="noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraster.shields.io%2Fbadge%2FLicense-MIT-259D6C.png" alt="License: MIT" width="82" height="20"&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href="https://github.com/appraisal-rb/setup-ruby-flash" rel="noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraster.shields.io%2Fbadge%2FGitHub-238636%3F%26logo%3DGithub%26logoColor%3Dgreen" alt="GH Source" width="65" height="20"&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href="https://github.com/appraisal-rb/setup-ruby-flash/stargazers" rel="noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraster.shields.io%2Fgithub%2Fstars%2Fappraisal-rb%2Fsetup-ruby-flash.png" alt="GH Repo Stars" width="82" height="20"&gt;&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;A &lt;em&gt;fast&lt;/em&gt; GitHub Action for fast Ruby environment setup using &lt;a href="https://github.com/spinel-coop/rv" rel="noopener noreferrer"&gt;rv&lt;/a&gt; for Ruby installation and &lt;a href="https://github.com/contriboss/ore-light" rel="noopener noreferrer"&gt;ore&lt;/a&gt; for gem management.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;⚡ Install Ruby in under 2 seconds&lt;/strong&gt; — no compilation required!&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;⚡ Install Gems 50% faster&lt;/strong&gt; — using ORE ✅️!&lt;/p&gt;

&lt;h2&gt;
  
  
  Features
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;🚀 &lt;strong&gt;Lightning-fast Ruby installation&lt;/strong&gt; via prebuilt binaries from rv&lt;/li&gt;
&lt;li&gt;📦 &lt;strong&gt;Rapid gem installation&lt;/strong&gt; with ore (Bundler-compatible, ~50% faster)&lt;/li&gt;
&lt;li&gt;💾 &lt;strong&gt;Intelligent caching&lt;/strong&gt; for both Ruby and gems&lt;/li&gt;
&lt;li&gt;🔒 &lt;strong&gt;Security auditing&lt;/strong&gt; via &lt;code&gt;ore audit&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;🐧 &lt;strong&gt;Linux &amp;amp; macOS support&lt;/strong&gt; (x86_64 and ARM64)&lt;/li&gt;
&lt;li&gt;☕️ &lt;strong&gt;Gitea &lt;a href="https://docs.gitea.com/usage/actions/overview" rel="noopener noreferrer"&gt;Actions&lt;/a&gt; support&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;🦊 &lt;strong&gt;Forgejo &lt;a href="https://forgejo.org/docs/next/admin/actions/" rel="noopener noreferrer"&gt;Actions&lt;/a&gt; support&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;🧊 &lt;strong&gt;Codeberg &lt;a href="https://docs.codeberg.org/ci/actions/" rel="noopener noreferrer"&gt;Actions&lt;/a&gt; support&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;🐙 &lt;strong&gt;GitHub &lt;a href="https://github.com/marketplace/actions/setup-ruby-with-rv-and-ore" rel="noopener noreferrer"&gt;Actions&lt;/a&gt; support&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Requirements
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Operating Systems&lt;/strong&gt;: Ubuntu 22.04+, macOS 14+&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Architectures&lt;/strong&gt;: x86_64, ARM64&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Ruby Versions&lt;/strong&gt;: 3.2, 3.3, 3.4, 4.0&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;#&lt;/th&gt;
&lt;th&gt;Important&lt;/th&gt;
&lt;th&gt;Alternative&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;Windows is not supported&lt;/td&gt;
&lt;td&gt;&lt;a href="https://github.com/ruby/setup-ruby" rel="noopener noreferrer"&gt;ruby/setup-ruby&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;Ruby &amp;lt;= 3.1 is not supported&lt;/td&gt;
&lt;td&gt;&lt;a href="https://github.com/ruby/setup-ruby" rel="noopener noreferrer"&gt;ruby/setup-ruby&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Key Differences with setup-ruby
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Feature&lt;/th&gt;
&lt;th&gt;setup-ruby&lt;/th&gt;
&lt;th&gt;setup-ruby-flash&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Ruby Install&lt;/td&gt;
&lt;td&gt;~5 seconds&lt;/td&gt;
&lt;td&gt;&amp;lt; 2 seconds&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Gem Install&lt;/td&gt;
&lt;td&gt;Bundler&lt;/td&gt;
&lt;td&gt;ore (~50% faster)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;ruby-version: ruby&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;✅ latest stable&lt;/td&gt;
&lt;td&gt;✅ latest stable&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;rubygems: latest&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;bundler: latest&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Windows&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Ruby &amp;lt; 3.2&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;JRuby&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;❌ (planned)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;TruffleRuby&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;❌ (planned)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Security Audit&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;✅ (&lt;code&gt;ore audit&lt;/code&gt;)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Basic Usage
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;appraisal-rb/setup-ruby-flash@v1&lt;/span&gt;
  &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;ruby-version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;3.4'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  With Gem Installation
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;appraisal-rb/setup-ruby-flash@v1&lt;/span&gt;
  &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;ruby-version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;3.4'&lt;/span&gt;
    &lt;span class="na"&gt;ore-install&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Using Version Files
&lt;/h3&gt;

&lt;p&gt;When &lt;code&gt;ruby-version&lt;/code&gt; is set to &lt;code&gt;default&lt;/code&gt; (the default), setup-ruby-flash reads from:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;.ruby-version&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;.tool-versions&lt;/code&gt; (asdf format)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;mise.toml&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;appraisal-rb/setup-ruby-flash@v1&lt;/span&gt;
  &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;ore-install&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Enough Talk, Where?
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/marketplace/actions/setup-ruby-with-rv-and-ore" rel="noopener noreferrer"&gt;GitHub Marketplace&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/appraisal-rb/setup-ruby-flash" rel="noopener noreferrer"&gt;Source&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://opencollective.com/appraisal-rb" rel="noopener noreferrer"&gt;Support the Project&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Inputs
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Input&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;th&gt;Default&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;ruby-version&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Ruby version to install (e.g., &lt;code&gt;3.4&lt;/code&gt;, &lt;code&gt;3.4.1&lt;/code&gt;). Use &lt;code&gt;ruby&lt;/code&gt; for latest stable version, or &lt;code&gt;default&lt;/code&gt; to read from version files.&lt;/td&gt;
&lt;td&gt;&lt;code&gt;default&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;rubygems&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;RubyGems version: &lt;code&gt;default&lt;/code&gt;, &lt;code&gt;latest&lt;/code&gt;, or a version number (e.g., &lt;code&gt;3.5.0&lt;/code&gt;)&lt;/td&gt;
&lt;td&gt;&lt;code&gt;default&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;bundler&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Bundler version: &lt;code&gt;Gemfile.lock&lt;/code&gt;, &lt;code&gt;default&lt;/code&gt;, &lt;code&gt;latest&lt;/code&gt;, &lt;code&gt;none&lt;/code&gt;, or a version number&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Gemfile.lock&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;ore-install&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Run &lt;code&gt;ore install&lt;/code&gt; and cache gems&lt;/td&gt;
&lt;td&gt;&lt;code&gt;false&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;working-directory&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Directory for version files and Gemfile&lt;/td&gt;
&lt;td&gt;&lt;code&gt;.&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;cache-version&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Cache version string for invalidation&lt;/td&gt;
&lt;td&gt;&lt;code&gt;v1&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;rv-version&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Version of rv to install (ignored if &lt;code&gt;rv-git-ref&lt;/code&gt; is set)&lt;/td&gt;
&lt;td&gt;&lt;code&gt;latest&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;rv-git-ref&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Git branch, tag, or commit SHA to build rv from source&lt;/td&gt;
&lt;td&gt;&lt;code&gt;''&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;ore-version&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Version of ore to install (ignored if &lt;code&gt;ore-git-ref&lt;/code&gt; is set)&lt;/td&gt;
&lt;td&gt;&lt;code&gt;latest&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;ore-git-ref&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Git branch, tag, or commit SHA to build ore from source&lt;/td&gt;
&lt;td&gt;&lt;code&gt;''&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;skip-extensions&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Skip building native extensions&lt;/td&gt;
&lt;td&gt;&lt;code&gt;false&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;without-groups&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Gem groups to exclude (comma-separated)&lt;/td&gt;
&lt;td&gt;&lt;code&gt;''&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;ruby-install-retries&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Number of retry attempts for Ruby installation (with exponential backoff)&lt;/td&gt;
&lt;td&gt;&lt;code&gt;3&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;no-document&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Skip generating documentation (ri/rdoc) for installed gems. Creates &lt;code&gt;~/.gemrc&lt;/code&gt; with &lt;code&gt;gem: --no-document&lt;/code&gt; if file doesn't exist&lt;/td&gt;
&lt;td&gt;&lt;code&gt;true&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;token&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;GitHub token for API calls&lt;/td&gt;
&lt;td&gt;&lt;code&gt;${{ github.token }}&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Outputs
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Output&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;ruby-version&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The installed Ruby version&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;ruby-prefix&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The path to the Ruby installation&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;rv-version&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The installed rv version&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;rubygems-version&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The installed RubyGems version&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;bundler-version&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The installed Bundler version&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;ore-version&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The installed ore version&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;cache-hit&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Whether gems were restored from cache&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Examples
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Matrix Build
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;CI&lt;/span&gt;
&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;push&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;pull_request&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;

&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;test&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;strategy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;fail-fast&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
      &lt;span class="na"&gt;matrix&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;os&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;ubuntu-latest&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;macos-latest&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
        &lt;span class="na"&gt;ruby&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;3.2"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;3.3"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;3.4"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;4.0"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ matrix.os }}&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v5&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;appraisal-rb/setup-ruby-flash@v1&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;ruby-version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ matrix.ruby }}&lt;/span&gt;
          &lt;span class="na"&gt;ore-install&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;bundle exec rake test&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Production Gems Only
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;appraisal-rb/setup-ruby-flash@v1&lt;/span&gt;
  &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;ruby-version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;3.4'&lt;/span&gt;
    &lt;span class="na"&gt;ore-install&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
    &lt;span class="na"&gt;without-groups&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;development,test'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Latest Ruby with Latest RubyGems and Bundler
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;appraisal-rb/setup-ruby-flash@v1&lt;/span&gt;
  &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;ruby-version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ruby&lt;/span&gt;
    &lt;span class="na"&gt;rubygems&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;latest&lt;/span&gt;
    &lt;span class="na"&gt;bundler&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;latest&lt;/span&gt;
    &lt;span class="na"&gt;ore-install&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Specific RubyGems Version
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;appraisal-rb/setup-ruby-flash@v1&lt;/span&gt;
  &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;ruby-version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;3.4'&lt;/span&gt;
    &lt;span class="na"&gt;rubygems&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;3.5.0'&lt;/span&gt;
    &lt;span class="na"&gt;ore-install&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Skip Native Extensions
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;appraisal-rb/setup-ruby-flash@v1&lt;/span&gt;
  &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;ruby-version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;3.4'&lt;/span&gt;
    &lt;span class="na"&gt;ore-install&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
    &lt;span class="na"&gt;skip-extensions&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Custom Working Directory
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;appraisal-rb/setup-ruby-flash@v1&lt;/span&gt;
  &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;ruby-version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;3.4'&lt;/span&gt;
    &lt;span class="na"&gt;ore-install&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
    &lt;span class="na"&gt;working-directory&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;./my-app'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Specific Tool Versions
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;appraisal-rb/setup-ruby-flash@v1&lt;/span&gt;
  &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;ruby-version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;3.4.1'&lt;/span&gt;
    &lt;span class="na"&gt;rv-version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;0.4.0'&lt;/span&gt;
    &lt;span class="na"&gt;ore-version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;0.1.0'&lt;/span&gt;
    &lt;span class="na"&gt;ore-install&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Custom Retry Configuration
&lt;/h3&gt;

&lt;p&gt;If you experience intermittent failures due to GitHub API rate limiting, you can adjust the number of retry attempts:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;appraisal-rb/setup-ruby-flash@v1&lt;/span&gt;
  &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;ruby-version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;3.4'&lt;/span&gt;
    &lt;span class="na"&gt;ruby-install-retries&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;5'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Enable Documentation Generation
&lt;/h3&gt;

&lt;p&gt;Include documentation (ri/rdoc) for installed gems (default skips documentation for faster installation):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;appraisal-rb/setup-ruby-flash@v1&lt;/span&gt;
  &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;ruby-version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;3.4"&lt;/span&gt;
    &lt;span class="na"&gt;ore-install&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
    &lt;span class="na"&gt;no-document&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Building rv or ore from Source
&lt;/h3&gt;

&lt;p&gt;You can build rv or ore from a git branch, tag, or commit SHA instead of using a released version.&lt;br&gt;
This is useful for testing unreleased features or bug fixes. Required toolchains (Rust for rv, Go for ore)&lt;br&gt;
are automatically installed. Fork syntax (&lt;code&gt;pboling:feat/myexperiment&lt;/code&gt;) is supported to test out feature branches in forks of ore or rv.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Test an ore feature branch&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;appraisal-rb/setup-ruby-flash@v1&lt;/span&gt;
  &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;ruby-version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;3.4"&lt;/span&gt;
    &lt;span class="na"&gt;ore-install&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
    &lt;span class="na"&gt;ore-git-ref&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;feat/bundle-gemfile-support"&lt;/span&gt;

&lt;span class="c1"&gt;# Test a pre-release rv tag&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;appraisal-rb/setup-ruby-flash@v1&lt;/span&gt;
  &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;ruby-version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;3.4"&lt;/span&gt;
    &lt;span class="na"&gt;rv-git-ref&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;v0.5.0-beta1"&lt;/span&gt;

&lt;span class="c1"&gt;# Test both from main branches&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;appraisal-rb/setup-ruby-flash@v1&lt;/span&gt;
  &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;ruby-version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;3.4"&lt;/span&gt;
    &lt;span class="na"&gt;rv-git-ref&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;main"&lt;/span&gt;
    &lt;span class="na"&gt;ore-install&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
    &lt;span class="na"&gt;ore-git-ref&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;main"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Migration from setup-ruby
&lt;/h2&gt;

&lt;p&gt;setup-ruby-flash is designed to be a near drop-in replacement for &lt;code&gt;ruby/setup-ruby&lt;/code&gt; on supported platforms:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Before (setup-ruby)&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ruby/setup-ruby@v1&lt;/span&gt;
  &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;ruby-version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;3.4'&lt;/span&gt;
    &lt;span class="na"&gt;bundler-cache&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;bundle exec rake test&lt;/span&gt;

&lt;span class="c1"&gt;# After (setup-ruby-flash)&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;appraisal-rb/setup-ruby-flash@v1&lt;/span&gt;
  &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;ruby-version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;3.4'&lt;/span&gt;
    &lt;span class="na"&gt;ore-install&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;bundle exec rake test&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  With Latest RubyGems and Bundler
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Before (setup-ruby)&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ruby/setup-ruby@v1&lt;/span&gt;
  &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;ruby-version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ruby&lt;/span&gt;
    &lt;span class="na"&gt;rubygems&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;latest&lt;/span&gt;
    &lt;span class="na"&gt;bundler&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;latest&lt;/span&gt;

&lt;span class="c1"&gt;# After (setup-ruby-flash)&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;appraisal-rb/setup-ruby-flash@v1&lt;/span&gt;
  &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;ruby-version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ruby&lt;/span&gt;
    &lt;span class="na"&gt;rubygems&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;latest&lt;/span&gt;
    &lt;span class="na"&gt;bundler&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;latest&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Acknowledgements
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://github.com/ruby/setup-ruby" rel="noopener noreferrer"&gt;setup-ruby&lt;/a&gt; the venerable mainstay for many years, and inspiration for this project.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/spinel-coop/rv" rel="noopener noreferrer"&gt;rv&lt;/a&gt; by Spinel Cooperative&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/contriboss/ore-light" rel="noopener noreferrer"&gt;ore&lt;/a&gt; by Contriboss&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Support &amp;amp; Funding Info
&lt;/h2&gt;

&lt;p&gt;I am a full-time FLOSS maintainer. If you find &lt;a href="//github.com/pboling"&gt;my work&lt;/a&gt; valuable I ask that you become a sponsor. Every dollar helps!&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;🥰 Support FLOSS work 🥰&lt;/th&gt;
&lt;th&gt;Get access&lt;/th&gt;
&lt;th&gt;"Sponsors" channel&lt;/th&gt;
&lt;th&gt;on Galtzo FLOSS&lt;/th&gt;
&lt;th&gt;Discord 👇️ &lt;a href="https://discord.gg/3qme4XHNKN" rel="noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraster.shields.io%2Fdiscord%2F1373797679469170758%3Fstyle%3Dfor-the-badge" alt="Live Chat on Discord" width="144" height="28"&gt;&lt;/a&gt;
&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;a href="https://opencollective.com/appraisal-rb#backer" rel="noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraster.shields.io%2Fopencollective%2Fbackers%2Fappraisal-rb.png%3Fstyle%3Dfor-the-badge" alt="OpenCollective Backers" width="112" height="28"&gt;&lt;/a&gt; &lt;a href="https://opencollective.com/appraisal-rb#sponsor" rel="noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraster.shields.io%2Fopencollective%2Fsponsors%2Fappraisal-rb.png%3Fstyle%3Dfor-the-badge" alt="OpenCollective Sponsors" width="122" height="28"&gt;&lt;/a&gt;
&lt;/td&gt;
&lt;td&gt;
&lt;a href="https://www.buymeacoffee.com/pboling" rel="noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraster.shields.io%2Fbadge%2Fbuy_me_a_coffee-%25E2%259C%2593-a51611.png%3Fstyle%3Dflat" alt="Buy me a coffee" width="118" height="20"&gt;&lt;/a&gt; &lt;a href="https://ko-fi.com/O5O86SNP4" rel="noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraster.shields.io%2Fbadge%2Fko--fi-%25E2%259C%2593-a51611.png%3Fstyle%3Dflat" alt="Donate at ko-fi.com" width="54" height="20"&gt;&lt;/a&gt;
&lt;/td&gt;
&lt;td&gt;&lt;a href="https://www.paypal.com/paypalme/peterboling" rel="noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraster.shields.io%2Fbadge%2Fdonate-paypal-a51611.png%3Fstyle%3Dflat%26logo%3Dpaypal" alt="Donate on PayPal" width="111" height="20"&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href="https://polar.sh/pboling" rel="noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraster.shields.io%2Fbadge%2Fpolar-donate-a51611.png%3Fstyle%3Dflat" alt="Donate on Polar" width="84" height="20"&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;
&lt;a href="https://github.com/sponsors/pboling" rel="noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraster.shields.io%2Fbadge%2FSponsor_Me%21-pboling.png%3Fstyle%3Dsocial%26logo%3Dgithub" alt="Sponsor Me on Github" width="107" height="20"&gt;&lt;/a&gt; &lt;a href="https://liberapay.com/pboling/donate" rel="noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraster.shields.io%2Fliberapay%2Fgoal%2Fpboling.png%3Flogo%3Dliberapay%26color%3Da51611%26style%3Dflat" alt="Liberapay Goal Progress" width="131" height="20"&gt;&lt;/a&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  About rv and ore
&lt;/h2&gt;

&lt;h3&gt;
  
  
  rv
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://github.com/spinel-coop/rv" rel="noopener noreferrer"&gt;rv&lt;/a&gt; is an extremely fast Ruby version manager written in Rust. It downloads prebuilt Ruby binaries, eliminating the need for compilation. Created by &lt;a href="https://github.com/indirect" rel="noopener noreferrer"&gt;@indirect&lt;/a&gt;, long-time project lead for Bundler and RubyGems.&lt;/p&gt;

&lt;h3&gt;
  
  
  ore
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://github.com/contriboss/ore-light" rel="noopener noreferrer"&gt;ore&lt;/a&gt; is a fast gem installer written in Go. It's Bundler-compatible but performs downloads significantly faster using Go's concurrency features. Use &lt;code&gt;bundle exec&lt;/code&gt; to run gem commands after ore installs your gems. Created by &lt;a href="https://github.com/seuros" rel="noopener noreferrer"&gt;@seuros&lt;/a&gt;, a long time Rubyist, and prolific &lt;a href="https://www.seuros.com/blog/rubygems-coup-when-parasites-take-the-host/" rel="noopener noreferrer"&gt;writer&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Cover photo (cropped) by &lt;a href="https://unsplash.com/@whereiskylenow?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText" rel="noopener noreferrer"&gt;Kyle Hinkson&lt;/a&gt; on &lt;a href="https://unsplash.com/photos/streetcar-passing-through-modern-city-buildings-with-cn-tower--NLQW5yTKYo?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText" rel="noopener noreferrer"&gt;Unsplash&lt;/a&gt;&lt;/p&gt;

</description>
      <category>ruby</category>
      <category>gha</category>
      <category>ore</category>
      <category>rv</category>
    </item>
    <item>
      <title>💲FLOSS Funding</title>
      <dc:creator>Peter H. Boling</dc:creator>
      <pubDate>Thu, 06 Nov 2025 23:39:10 +0000</pubDate>
      <link>https://dev.to/galtzo/floss-funding-2cfn</link>
      <guid>https://dev.to/galtzo/floss-funding-2cfn</guid>
      <description>&lt;h1&gt;
  
  
  Two things are true.
&lt;/h1&gt;

&lt;p&gt;Many open source contributors have been laid off recently, and have not been able to find new roles. Many of these have become the fabled "person in Nebraska", made famous by XKCD #2347.&lt;br&gt;
Every RubyGem, NPM, pip, cargo, etc, package you have ever used is worth at least $1 to you and the careers and products they helped build. Many people would contribute more financially to open source if more systems were in place to facilitate that.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://xkcd.com/2347/" rel="noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fh6a6f560xn8dvmhcpu1o.png" alt="All modern digital infrastructure (depending on) a project some random person in Nebraska has been thanklessly maintaining since 2003" width="385" height="489"&gt;&lt;/a&gt;&lt;br&gt;
﻿&lt;/p&gt;

&lt;p&gt;﻿&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;All modern digital infrastructure (depending on) a project some random person in Nebraska has been thanklessly maintaining since 2003 - &lt;a href="https://xkcd.com/2347/" rel="noopener noreferrer"&gt;xkcd.com/2347&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h1&gt;
  
  
  You may be saying:
&lt;/h1&gt;

&lt;blockquote&gt;
&lt;p&gt;Systems do exist to enable donations or subscriptions to developers.  In fact, I can name several!&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Yes, fine, three things are true. Those systems are excellent, and underutilized. Why?&lt;/p&gt;

&lt;p&gt;Finding the funding path for a project is hard. Unless you are the meticulous type to memorize a Gemfile.lock, yarn.lock, or package-lock.json, many, if not most, of the projects at the bottom of the tree get overlooked. Since they know they will be overlooked, they often don't even bother setting up paths for donations.&lt;/p&gt;

&lt;h1&gt;
  
  
  What kinds of dependencies?
&lt;/h1&gt;

&lt;p&gt;The nested, indirect, dependencies loaded by your direct dependencies.&lt;br&gt;
The development and test dependencies which you rely on for AI to validate its work when you are vibe coding&lt;br&gt;
The ones that take just as much effort to build, but fail to get recognized as valuable.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Aren't there tools that automatically dig into the dependency tree to help fund those indirect dependencies?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Yes, those are great, and I encourage people to use them. So apparently four things are true, but:&lt;/p&gt;

&lt;p&gt;None dig down more than 3 levels into a dependency tree, and trees can be much deeper than that. In Ruby, for example:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;bundle fund&lt;/code&gt; doesn't consider indirect dependencies 

&lt;ul&gt;
&lt;li&gt;In some contexts they only consider runtime dependencies&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;
&lt;code&gt;bundle fund&lt;/code&gt; doesn't consider development dependencies in Gemfile during RubyGem development&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;I love &lt;code&gt;bundle fund&lt;/code&gt;, but if the command isn't run, people don't see the requests for help with funding, and it is never run automatically.  I expect there are similar issues with &lt;code&gt;npm fund&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Currently, funding for the majority of free (libre) open source code (FLOSS) is hard.&lt;/p&gt;

&lt;h1&gt;
  
  
  What if it was easy?
&lt;/h1&gt;

&lt;blockquote&gt;
&lt;p&gt;What if a project could tell you how to support it precisely when you are using it, and deriving value from it?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;New #Ruby gem to fund open source developers whose projects get missed by other #FLOSS #funding tools which don’t cover dev deps, nor indirect deps &amp;gt; 3 levels down.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://ruby.social/@galtzo/114994584740434327" rel="noopener noreferrer"&gt;https://ruby.social/@galtzo/114994584740434327&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;👉 no tracking&lt;br&gt;
👉 no network calls&lt;br&gt;
👉 never forces or requires payment&lt;br&gt;
👉 supports buy-once, or per-version &lt;br&gt;
👉 easily silenced nags &lt;br&gt;
👉 sets of related libraries share activation keys &lt;br&gt;
👉 nags are opt-in for maintainers, opt-out for users&lt;br&gt;
👉 nags are throttled to customizable frequency&lt;br&gt;
👉 inert in non-TTY shells (i.e. production-like environments)&lt;br&gt;
👉 inert when exit status is non-zero&lt;br&gt;
👉 inert for many edge cases, such as an internal failure&lt;br&gt;
👉 inert when a global silencing ENV variable is set (you're welcome, haters!)&lt;/p&gt;

&lt;p&gt;Open source projects in which languages will have this ability?&lt;br&gt;
&lt;a href="https://github.com/floss-funding/floss_funding" rel="noopener noreferrer"&gt;Ruby&lt;/a&gt;&lt;br&gt;
Javascript&lt;br&gt;
Elixir&lt;br&gt;
Python&lt;br&gt;
Perl&lt;br&gt;
... Join the &lt;a href="https://discord.gg/3qme4XHNKN" rel="noopener noreferrer"&gt;discord&lt;/a&gt; if you have more ideas, and want to contribute. A lanugage just needs to have an "atexit()" style hook, i.e. a process termination hook. A bunch of languages meet the criteria, even Bash.&lt;/p&gt;

&lt;p&gt;Cover photo by me, CC-SA 4.0.&lt;/p&gt;

</description>
      <category>career</category>
      <category>discuss</category>
      <category>opensource</category>
    </item>
    <item>
      <title>💎 ANN: omniauth-identity v3.1.5 (Hanami/ROM Support)</title>
      <dc:creator>Peter H. Boling</dc:creator>
      <pubDate>Tue, 14 Oct 2025 00:49:55 +0000</pubDate>
      <link>https://dev.to/galtzo/ann-omniauth-identity-v315-hanamirom-support-4ha3</link>
      <guid>https://dev.to/galtzo/ann-omniauth-identity-v315-hanamirom-support-4ha3</guid>
      <description>&lt;h2&gt;
  
  
  &lt;a href="https://github.com/omniauth/omniauth-identity/compare/v3.1.4...v3.1.5" rel="noopener noreferrer"&gt;3.1.5&lt;/a&gt; - 2025-10-13
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;TAG: &lt;a href="https://github.com/omniauth/omniauth-identity/releases/tag/v3.1.5" rel="noopener noreferrer"&gt;v3.1.5&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;COVERAGE: 93.58% -- 437/467 lines in 14 files&lt;/li&gt;
&lt;li&gt;BRANCH COVERAGE: 81.00% -- 81/100 branches in 14 files&lt;/li&gt;
&lt;li&gt;92.39% documented&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Added
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Adapter support for Hanami and ROM&lt;/li&gt;
&lt;li&gt;Complete YARD documentation&lt;/li&gt;
&lt;li&gt;kettle-dev for easier maintenance &amp;amp; dev tooling&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The one where omniauth-identity gains a &lt;code&gt;rom&lt;/code&gt; adapter. Since &lt;code&gt;rom&lt;/code&gt; is the default DB adapter for Hanami, this also makes it &lt;em&gt;hanami-ready&lt;/em&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Identity&lt;/span&gt;
  &lt;span class="kp"&gt;include&lt;/span&gt; &lt;span class="no"&gt;OmniAuth&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Identity&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Models&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Rom&lt;/span&gt;

  &lt;span class="c1"&gt;# Configure the ROM container and relation using the DSL (no `self.`):&lt;/span&gt;
  &lt;span class="n"&gt;rom_container&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="no"&gt;MyDatabase&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;rom&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="c1"&gt;# accepts a proc or a container object&lt;/span&gt;
  &lt;span class="n"&gt;rom_relation_name&lt;/span&gt; &lt;span class="ss"&gt;:identities&lt;/span&gt; &lt;span class="c1"&gt;# optional, defaults to :identities&lt;/span&gt;
  &lt;span class="n"&gt;owner_relation_name&lt;/span&gt; &lt;span class="ss"&gt;:owners&lt;/span&gt;  &lt;span class="c1"&gt;# optional, for loading associated owner&lt;/span&gt;
  &lt;span class="c1"&gt;# Uses OmniAuth::Identity::Model.auth_key to set the auth key (defaults to :email)&lt;/span&gt;
  &lt;span class="n"&gt;auth_key&lt;/span&gt; &lt;span class="ss"&gt;:email&lt;/span&gt;
  &lt;span class="c1"&gt;# Optional: override the password digest field name (defaults to :password_digest)&lt;/span&gt;
  &lt;span class="n"&gt;password_field&lt;/span&gt; &lt;span class="ss"&gt;:password_digest&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Find a complete example after the funding info.&lt;/p&gt;

&lt;h2&gt;
  
  
  Support &amp;amp; Funding Info
&lt;/h2&gt;

&lt;p&gt;I am a full-time FLOSS maintainer. If you find &lt;a href="//github.com/pboling"&gt;my work&lt;/a&gt; valuable I ask that you become a sponsor. Every dollar helps!&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;🥰 Support FLOSS work 🥰&lt;/th&gt;
&lt;th&gt;Get access to "Sponsors" channel&lt;/th&gt;
&lt;th&gt;on Galtzo FLOSS&lt;/th&gt;
&lt;th&gt;Discord 👇️ &lt;a href="https://discord.gg/3qme4XHNKN" rel="noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraster.shields.io%2Fdiscord%2F1373797679469170758%3Fstyle%3Dfor-the-badge" alt="Live Chat on Discord" width="144" height="28"&gt;&lt;/a&gt;
&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;a href="https://github.com/sponsors/pboling" rel="noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraster.shields.io%2Fbadge%2FSponsor_Me%21-pboling.png%3Fstyle%3Dsocial%26logo%3Dgithub" alt="Sponsor Me on Github" width="107" height="20"&gt;&lt;/a&gt; &lt;a href="https://liberapay.com/pboling/donate" rel="noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraster.shields.io%2Fliberapay%2Fgoal%2Fpboling.png%3Flogo%3Dliberapay%26color%3Da51611%26style%3Dflat" alt="Liberapay Goal Progress" width="131" height="20"&gt;&lt;/a&gt;
&lt;/td&gt;
&lt;td&gt;
&lt;a href="https://www.buymeacoffee.com/pboling" rel="noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraster.shields.io%2Fbadge%2Fbuy_me_a_coffee-%25E2%259C%2593-a51611.png%3Fstyle%3Dflat" alt="Buy me a coffee" width="118" height="20"&gt;&lt;/a&gt; &lt;a href="https://ko-fi.com/O5O86SNP4" rel="noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraster.shields.io%2Fbadge%2Fko--fi-%25E2%259C%2593-a51611.png%3Fstyle%3Dflat" alt="Donate at ko-fi.com" width="54" height="20"&gt;&lt;/a&gt;
&lt;/td&gt;
&lt;td&gt;&lt;a href="https://www.paypal.com/paypalme/peterboling" rel="noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraster.shields.io%2Fbadge%2Fdonate-paypal-a51611.png%3Fstyle%3Dflat%26logo%3Dpaypal" alt="Donate on PayPal" width="111" height="20"&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href="https://polar.sh/pboling" rel="noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraster.shields.io%2Fbadge%2Fpolar-donate-a51611.png%3Fstyle%3Dflat" alt="Donate on Polar" width="84" height="20"&gt;&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;As an example I'll use the code straight from the specs.&lt;/p&gt;

&lt;h1&gt;
  
  
  Test Harness
&lt;/h1&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ROM_DB = if RUBY_ENGINE == "jruby"
  require "jdbc/sqlite3"
  require "sequel"
  Sequel.connect("jdbc:sqlite::memory:rom")
else
  require "sqlite3"
  require "sequel"
  Sequel.connect("sqlite::memory:rom")
end

require "rom"
require "rom-sql"

# Define the ROM relations
class RomTestIdentities &amp;lt; ROM::Relation[:sql]
  schema(:rom_test_identities) do
    attribute :id, ROM::SQL::Types::Serial
    attribute :email, ROM::SQL::Types::String
    attribute :login, ROM::SQL::Types::String
    attribute :password_digest, ROM::SQL::Types::String
    attribute :pwd_hash, ROM::SQL::Types::String
    attribute :owner_id, ROM::SQL::Types::Integer
  end
end

class RomTestOwners &amp;lt; ROM::Relation[:sql]
  schema(:rom_test_owners) do
    attribute :id, ROM::SQL::Types::Serial
    attribute :name, ROM::SQL::Types::String
  end
end

# Set up ROM container
ROM_CONFIG = ROM::Configuration.new(:sql, ROM_DB)
ROM_CONFIG.register_relation(RomTestIdentities)
ROM_CONFIG.register_relation(RomTestOwners)
ROM_CONTAINER = ROM.container(ROM_CONFIG)

class RomTestIdentity
  include OmniAuth::Identity::Models::Rom

  # Configure via the new DSL (no `self.`): accepts a value or a proc
  rom_container -&amp;gt; { ROM_CONTAINER }
  rom_relation_name :rom_test_identities
  # Use the standard Model.auth_key API to configure the auth key
  auth_key :email
  password_field :password_digest

  # Provide a simple reader for the :login attribute for specs that use a
  # non-standard auth key (e.g. `auth_key :login`). The ROM adapter stores the
  # underlying tuple in @identity_data, so delegate to it here.
  def login
    @identity_data &amp;amp;&amp;amp; @identity_data[:login]
  end
end
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h1&gt;
  
  
  Test
&lt;/h1&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;RSpec.describe(OmniAuth::Identity::Models::Rom, :sqlite3) do
  before(:all) do
    # Create the tables
    ROM_DB.create_table(:rom_test_identities) do
      primary_key :id
      String :email, null: false
      String :password_digest, null: false
      # Add the login column to support tests that use a non-standard auth_key
      String :login
      Integer :owner_id
    end

    ROM_DB.create_table(:rom_test_owners) do
      primary_key :id
      String :name, null: false
    end
  end

  before do
    # Clear the tables before each test
    ROM_DB[:rom_test_identities].delete
    ROM_DB[:rom_test_owners].delete
  end

  describe "model", type: :model do
    subject(:model_klass) { RomTestIdentity }

    describe "::authenticate" do
      it "authenticates with correct password" do
        # Insert test data
        password_digest = BCrypt::Password.create("password")
        identity_data = {email: "test@example.com", password_digest: password_digest}
        ROM_CONTAINER.relations[:rom_test_identities].insert(identity_data)

        authenticated = model_klass.authenticate({email: "test@example.com"}, "password")
        expect(authenticated).to(be_a(RomTestIdentity))
        expect(authenticated.email).to(eq("test@example.com"))
      end

      it "authenticates with custom auth_key (login) when auth_key is set to :login" do
        original = model_klass.auth_key
        model_klass.auth_key(:login)

        begin
          # Insert test data using login field
          password_digest = BCrypt::Password.create("password")
          identity_data = {login: "bob", email: "bob@example.com", password_digest: password_digest}
          ROM_CONTAINER.relations[:rom_test_identities].insert(identity_data)

          authenticated = model_klass.authenticate({login: "bob"}, "password")
          expect(authenticated).to(be_a(RomTestIdentity))
          expect(authenticated.login).to(eq("bob"))
        ensure
          model_klass.auth_key(original)
        end
      end

      it "returns false with incorrect password" do
        # Insert test data
        password_digest = BCrypt::Password.create("password")
        identity_data = {email: "test@example.com", password_digest: password_digest}
        ROM_CONTAINER.relations[:rom_test_identities].insert(identity_data)

        authenticated = model_klass.authenticate({email: "test@example.com"}, "wrong")
        expect(authenticated).to(be(false))
      end

      it "returns false for non-existent user" do
        authenticated = model_klass.authenticate({email: "nonexistent@example.com"}, "password")
        expect(authenticated).to(be(false))
      end
    end
  end
end
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Photo by &lt;a href="https://unsplash.com/@ryunosuke_kikuno?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText" rel="noopener noreferrer"&gt;Ryunosuke Kikuno&lt;/a&gt; on &lt;a href="https://unsplash.com/photos/a-white-swan-with-a-yellow-beak-dips-its-head-911slHGPFpY?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText" rel="noopener noreferrer"&gt;Unsplash&lt;/a&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>ruby</category>
      <category>rom</category>
      <category>authentication</category>
    </item>
    <item>
      <title>💎 ANN: oauth2 v2.0.17</title>
      <dc:creator>Peter H. Boling</dc:creator>
      <pubDate>Thu, 18 Sep 2025 06:30:38 +0000</pubDate>
      <link>https://dev.to/galtzo/ann-oauth2-v2017-h1a</link>
      <guid>https://dev.to/galtzo/ann-oauth2-v2017-h1a</guid>
      <description>&lt;h1&gt;
  
  
  Announcing oauth2 v2.0.17 — Static Hash for verb‑dependent token mode (Instagram‑friendly)
&lt;/h1&gt;

&lt;p&gt;The oauth2 v2.0.17 is a small but useful release: it adds support for configuring verb‑dependent token transmission with a static Hash in addition to the previously available Proc. This makes integrations like Instagram’s Graph API a little simpler and slightly more performant.&lt;/p&gt;

&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;v2.0.15 introduced verb‑dependent token mode, so you could decide per HTTP verb whether the access token should be sent in the query string or the Authorization header.&lt;/li&gt;
&lt;li&gt;v2.0.17 lets you provide that mapping as a static Hash instead of a Proc.&lt;/li&gt;
&lt;li&gt;Example mapping: &lt;code&gt;{get: :query, post: :header, delete: :header}&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Why this matters
&lt;/h2&gt;

&lt;p&gt;Some APIs (notably Instagram’s Graph API) require you to send the access token in the query for GET requests (e.g., &lt;code&gt;?access_token=...&lt;/code&gt;), but in the Authorization header for POST/DELETE (e.g., &lt;code&gt;Authorization: Bearer ...&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;In v2.0.15 we added support for a verb‑dependent mode via a Proc, like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;mode: &lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;verb&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;verb&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="ss"&gt;:get&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="ss"&gt;:query&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="ss"&gt;:header&lt;/span&gt; &lt;span class="p"&gt;}}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In v2.0.17 you can now configure the same behavior using a static Hash, which avoids calling a Proc for each request, keeps intent obvious at a glance, and can be trivially serialized or reused:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;verb_dependent_token&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;get: :query&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;post: :header&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;delete: :header&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Example: Instagram Graph API with a static Hash
&lt;/h2&gt;

&lt;p&gt;Below is a concrete example that exchanges a short‑lived token for a long‑lived token, refreshes it, and then makes API calls — all while automatically placing the token in the right place per HTTP verb using the static Hash mode.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s2"&gt;"oauth2"&lt;/span&gt;

&lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;OAuth2&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kp"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kp"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;site: &lt;/span&gt;&lt;span class="s2"&gt;"https://graph.instagram.com"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Start with a short‑lived token you already obtained via Facebook Login&lt;/span&gt;
&lt;span class="n"&gt;verb_dependent_token&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;get: :query&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;post: :header&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;delete: :header&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="n"&gt;short_lived&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;OAuth2&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;AccessToken&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="no"&gt;ENV&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"IG_SHORT_LIVED_TOKEN"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="ss"&gt;mode: &lt;/span&gt;&lt;span class="n"&gt;verb_dependent_token&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# 1) Exchange for a long‑lived token (GET with token in query)&lt;/span&gt;
&lt;span class="c1"&gt;#    Endpoint: GET https://graph.instagram.com/access_token&lt;/span&gt;
&lt;span class="c1"&gt;#    Params: grant_type=ig_exchange_token, client_secret=APP_SECRET&lt;/span&gt;
&lt;span class="n"&gt;exchange&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;short_lived&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="s2"&gt;"/access_token"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="ss"&gt;params: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="ss"&gt;grant_type: &lt;/span&gt;&lt;span class="s2"&gt;"ig_exchange_token"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="ss"&gt;client_secret: &lt;/span&gt;&lt;span class="no"&gt;ENV&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"IG_APP_SECRET"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="c1"&gt;# access_token will be added automatically in the query&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;long_lived_token_value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;exchange&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parsed&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"access_token"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="n"&gt;long_lived&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;OAuth2&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;AccessToken&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;long_lived_token_value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="ss"&gt;mode: &lt;/span&gt;&lt;span class="n"&gt;verb_dependent_token&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# 2) Refresh the long‑lived token (GET with token in query)&lt;/span&gt;
&lt;span class="c1"&gt;#    Endpoint: GET https://graph.instagram.com/refresh_access_token&lt;/span&gt;
&lt;span class="n"&gt;refresh_resp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;long_lived&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="s2"&gt;"/refresh_access_token"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="ss"&gt;params: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;grant_type: &lt;/span&gt;&lt;span class="s2"&gt;"ig_refresh_token"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;long_lived&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;OAuth2&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;AccessToken&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;refresh_resp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parsed&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"access_token"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="ss"&gt;mode: &lt;/span&gt;&lt;span class="n"&gt;verb_dependent_token&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# 3) Typical API GET request (token automatically in query)&lt;/span&gt;
&lt;span class="n"&gt;me&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;long_lived&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"/me"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;params: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;fields: &lt;/span&gt;&lt;span class="s2"&gt;"id,username"&lt;/span&gt;&lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nf"&gt;parsed&lt;/span&gt;

&lt;span class="c1"&gt;# 4) Example POST (token automatically in Authorization header)&lt;/span&gt;
&lt;span class="c1"&gt;# long_lived.post("/me/media", body: {image_url: "https://...", caption: "hello"})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notes&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Instagram is a special case that explicitly requires query‑string tokens for GET endpoints. For most providers you should prefer header‑based tokens when possible.&lt;/li&gt;
&lt;li&gt;If you need custom logic beyond a simple mapping, you can still use a Proc: &lt;code&gt;mode: -&amp;gt;(verb) { ... }&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Migrating from Proc to Hash
&lt;/h2&gt;

&lt;p&gt;If you already use the v2.0.15 Proc style:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;mode: &lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;verb&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;verb&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="ss"&gt;:get&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="ss"&gt;:query&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="ss"&gt;:header&lt;/span&gt; &lt;span class="p"&gt;}}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can switch to the Hash form in v2.0.17:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;mode: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;get: :query&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;post: :header&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;delete: :header&lt;/span&gt;&lt;span class="p"&gt;}}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Both are supported; choose whichever best fits your app. The Hash form is generally a bit faster and more explicit, while the Proc form is endlessly versatile!&lt;/p&gt;

&lt;h2&gt;
  
  
  Release links
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Changelog entry: &lt;a href="https://github.com/ruby-oauth/oauth2/blob/main/CHANGELOG.md#2017---2025-09-15" rel="noopener noreferrer"&gt;https://github.com/ruby-oauth/oauth2/blob/main/CHANGELOG.md#2017---2025-09-15&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Tag: &lt;a href="https://github.com/ruby-oauth/oauth2/releases/tag/v2.0.17" rel="noopener noreferrer"&gt;https://github.com/ruby-oauth/oauth2/releases/tag/v2.0.17&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;PR: Add Hash‑based verb‑dependent mode &lt;a href="https://github.com/ruby-oauth/oauth2/pull/682" rel="noopener noreferrer"&gt;https://github.com/ruby-oauth/oauth2/pull/682&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Thanks
&lt;/h2&gt;

&lt;p&gt;Thanks to everyone using oauth2 and filing issues. Keep the feedback coming!&lt;/p&gt;

&lt;h2&gt;
  
  
  Support &amp;amp; Funding Info
&lt;/h2&gt;

&lt;p&gt;I am a full-time FLOSS maintainer. If you find &lt;a href="//github.com/pboling"&gt;my work&lt;/a&gt; valuable I ask that you become a sponsor. Every dollar helps!&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;🥰 Support FLOSS work 🥰&lt;/th&gt;
&lt;th&gt;Get access&lt;/th&gt;
&lt;th&gt;"Sponsors" channel&lt;/th&gt;
&lt;th&gt;on Galtzo FLOSS&lt;/th&gt;
&lt;th&gt;Discord 👇️ &lt;a href="https://discord.gg/3qme4XHNKN" rel="noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraster.shields.io%2Fdiscord%2F1373797679469170758%3Fstyle%3Dfor-the-badge" alt="Live Chat on Discord" width="144" height="28"&gt;&lt;/a&gt;
&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;a href="https://opencollective.com/ruby-oauth#backer" rel="noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraster.shields.io%2Fopencollective%2Fbackers%2Fruby-oauth.png%3Fstyle%3Dfor-the-badge" alt="OpenCollective Backers" width="112" height="28"&gt;&lt;/a&gt; &lt;a href="https://opencollective.com/ruby-oauth#sponsor" rel="noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraster.shields.io%2Fopencollective%2Fsponsors%2Fruby-oauth.png%3Fstyle%3Dfor-the-badge" alt="OpenCollective Sponsors" width="122" height="28"&gt;&lt;/a&gt;
&lt;/td&gt;
&lt;td&gt;
&lt;a href="https://www.buymeacoffee.com/pboling" rel="noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraster.shields.io%2Fbadge%2Fbuy_me_a_coffee-%25E2%259C%2593-a51611.png%3Fstyle%3Dflat" alt="Buy me a coffee" width="118" height="20"&gt;&lt;/a&gt; &lt;a href="https://ko-fi.com/O5O86SNP4" rel="noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraster.shields.io%2Fbadge%2Fko--fi-%25E2%259C%2593-a51611.png%3Fstyle%3Dflat" alt="Donate at ko-fi.com" width="54" height="20"&gt;&lt;/a&gt;
&lt;/td&gt;
&lt;td&gt;&lt;a href="https://www.paypal.com/paypalme/peterboling" rel="noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraster.shields.io%2Fbadge%2Fdonate-paypal-a51611.png%3Fstyle%3Dflat%26logo%3Dpaypal" alt="Donate on PayPal" width="111" height="20"&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href="https://polar.sh/pboling" rel="noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraster.shields.io%2Fbadge%2Fpolar-donate-a51611.png%3Fstyle%3Dflat" alt="Donate on Polar" width="84" height="20"&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;
&lt;a href="https://github.com/sponsors/pboling" rel="noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraster.shields.io%2Fbadge%2FSponsor_Me%21-pboling.png%3Fstyle%3Dsocial%26logo%3Dgithub" alt="Sponsor Me on Github" width="107" height="20"&gt;&lt;/a&gt; &lt;a href="https://liberapay.com/pboling/donate" rel="noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraster.shields.io%2Fliberapay%2Fgoal%2Fpboling.png%3Flogo%3Dliberapay%26color%3Da51611%26style%3Dflat" alt="Liberapay Goal Progress" width="131" height="20"&gt;&lt;/a&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Photo (cropped) by &lt;a href="https://unsplash.com/@wonderkim?utm_content=creditCopyText&amp;amp;utm_medium=referral&amp;amp;utm_source=unsplash" rel="noopener noreferrer"&gt;Wonder KIM&lt;/a&gt; on &lt;a href="https://unsplash.com/photos/a-dog-wearing-a-sweater-and-boots-in-the-snow-puTqUxcMZz8?utm_content=creditCopyText&amp;amp;utm_medium=referral&amp;amp;utm_source=unsplash" rel="noopener noreferrer"&gt;Unsplash&lt;/a&gt;&lt;/p&gt;

</description>
      <category>ruby</category>
      <category>oauth</category>
      <category>webdev</category>
      <category>instagram</category>
    </item>
    <item>
      <title>💎 ANN: kettle-dev v1.1.20 w/ improved CHANGELOG handling</title>
      <dc:creator>Peter H. Boling</dc:creator>
      <pubDate>Mon, 15 Sep 2025 11:53:14 +0000</pubDate>
      <link>https://dev.to/galtzo/ann-kettle-dev-v1020-w-improved-changelog-handling-20hl</link>
      <guid>https://dev.to/galtzo/ann-kettle-dev-v1020-w-improved-changelog-handling-20hl</guid>
      <description>&lt;h2&gt;
  
  
  &lt;a href="https://github.com/kettle-rb/kettle-dev/compare/v1.1.19...v1.1.20" rel="noopener noreferrer"&gt;1.1.20&lt;/a&gt; - 2025-09-15
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;TAG: &lt;a href="https://github.com/kettle-rb/kettle-dev/releases/tag/v1.1.20" rel="noopener noreferrer"&gt;v1.1.20&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;COVERAGE: 96.80% -- 3660/3781 lines in 25 files&lt;/li&gt;
&lt;li&gt;BRANCH COVERAGE: 81.65% -- 1504/1842 branches in 25 files&lt;/li&gt;
&lt;li&gt;77.01% documented&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Added
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Allow reformating of CHANGELOG.md without version bump&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;--include=GLOB&lt;/code&gt; includes files not otherwise included in default template&lt;/li&gt;
&lt;li&gt;more test coverage&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Fixed
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Add .licenserc.yaml to gem package&lt;/li&gt;
&lt;li&gt;Handling of GFM fenced code blocks in CHANGELOG.md&lt;/li&gt;
&lt;li&gt;Handling of nested list items in CHANGELOG.md&lt;/li&gt;
&lt;li&gt;Handling of blank lines around all headings in CHANGELOG.md&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Support &amp;amp; Funding Info
&lt;/h2&gt;

&lt;p&gt;I am a full-time FLOSS maintainer. If you find &lt;a href="//github.com/pboling"&gt;my work&lt;/a&gt; valuable I ask that you become a sponsor. Every dollar helps!&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;🥰 Support FLOSS work 🥰&lt;/th&gt;
&lt;th&gt;Get access&lt;/th&gt;
&lt;th&gt;"Sponsors" channel&lt;/th&gt;
&lt;th&gt;on Galtzo FLOSS&lt;/th&gt;
&lt;th&gt;Discord 👇️ &lt;a href="https://discord.gg/3qme4XHNKN" rel="noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraster.shields.io%2Fdiscord%2F1373797679469170758%3Fstyle%3Dfor-the-badge" alt="Live Chat on Discord" width="144" height="28"&gt;&lt;/a&gt;
&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;a href="https://opencollective.com/ruby-oauth#backer" rel="noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraster.shields.io%2Fopencollective%2Fbackers%2Fruby-oauth.png%3Fstyle%3Dfor-the-badge" alt="OpenCollective Backers" width="112" height="28"&gt;&lt;/a&gt; &lt;a href="https://opencollective.com/ruby-oauth#sponsor" rel="noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraster.shields.io%2Fopencollective%2Fsponsors%2Fruby-oauth.png%3Fstyle%3Dfor-the-badge" alt="OpenCollective Sponsors" width="122" height="28"&gt;&lt;/a&gt;
&lt;/td&gt;
&lt;td&gt;
&lt;a href="https://www.buymeacoffee.com/pboling" rel="noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraster.shields.io%2Fbadge%2Fbuy_me_a_coffee-%25E2%259C%2593-a51611.png%3Fstyle%3Dflat" alt="Buy me a coffee" width="118" height="20"&gt;&lt;/a&gt; &lt;a href="https://ko-fi.com/O5O86SNP4" rel="noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraster.shields.io%2Fbadge%2Fko--fi-%25E2%259C%2593-a51611.png%3Fstyle%3Dflat" alt="Donate at ko-fi.com" width="54" height="20"&gt;&lt;/a&gt;
&lt;/td&gt;
&lt;td&gt;&lt;a href="https://www.paypal.com/paypalme/peterboling" rel="noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraster.shields.io%2Fbadge%2Fdonate-paypal-a51611.png%3Fstyle%3Dflat%26logo%3Dpaypal" alt="Donate on PayPal" width="111" height="20"&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href="https://polar.sh/pboling" rel="noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraster.shields.io%2Fbadge%2Fpolar-donate-a51611.png%3Fstyle%3Dflat" alt="Donate on Polar" width="84" height="20"&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;
&lt;a href="https://github.com/sponsors/pboling" rel="noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraster.shields.io%2Fbadge%2FSponsor_Me%21-pboling.png%3Fstyle%3Dsocial%26logo%3Dgithub" alt="Sponsor Me on Github" width="107" height="20"&gt;&lt;/a&gt; &lt;a href="https://liberapay.com/pboling/donate" rel="noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraster.shields.io%2Fliberapay%2Fgoal%2Fpboling.png%3Flogo%3Dliberapay%26color%3Da51611%26style%3Dflat" alt="Liberapay Goal Progress" width="131" height="20"&gt;&lt;/a&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

</description>
      <category>devtools</category>
      <category>ruby</category>
      <category>packaging</category>
      <category>automation</category>
    </item>
    <item>
      <title>💎 ANN: oauth2 v2.0.16 w/ full E2E example (&amp; Instagram Compat)</title>
      <dc:creator>Peter H. Boling</dc:creator>
      <pubDate>Sun, 14 Sep 2025 23:07:29 +0000</pubDate>
      <link>https://dev.to/galtzo/ann-oauth2-v2015-v2016-w-full-e2e-example-4f74</link>
      <guid>https://dev.to/galtzo/ann-oauth2-v2015-v2016-w-full-e2e-example-4f74</guid>
      <description>&lt;p&gt;Actually Instagram compatibility came in oauth2 v2.0.15, but I didn't make a separate post about it. So here's what you need to know:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;verb-dependent token transmission mode for &lt;code&gt;AccessToken&lt;/code&gt; makes it Instagram compatible.&lt;/li&gt;
&lt;li&gt;Complete &lt;a href="https://github.com/ruby-oauth/oauth2/blob/main/README.md#instagram-api-verbdependent-token-mode" rel="noopener noreferrer"&gt;example included&lt;/a&gt;!&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;OK, now on with v2.0.16!&lt;/p&gt;

&lt;h1&gt;
  
  
  Complete E2E single file script
&lt;/h1&gt;

&lt;ul&gt;
&lt;li&gt;runs against &lt;a href="https://github.com/navikt/mock-oauth2-server" rel="noopener noreferrer"&gt;navikt/mock-oauth2-server&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;E2E script &lt;a href="https://github.com/ruby-oauth/oauth2/blob/main/examples/e2e.rb" rel="noopener noreferrer"&gt;in source&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;NOTE: The mock test server was added to source in oauth v2.0.11, and has now been upgraded to the latest version of mock-oauth2-server. The mock test server is not in the packaged gem.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="go"&gt;docker compose -f docker-compose-ssl.yml up -d --wait
ruby examples/e2e.rb
&lt;/span&gt;&lt;span class="gp"&gt;#&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;If your machine is slow or Docker pulls are cold, increase the &lt;span class="nb"&gt;wait&lt;/span&gt;:
&lt;span class="go"&gt;E2E_WAIT_TIMEOUT=120 ruby examples/e2e.rb
&lt;/span&gt;&lt;span class="gp"&gt;#&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;The mock server serves HTTP on 8080&lt;span class="p"&gt;;&lt;/span&gt; the example points to http://localhost:8080 by default.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The output should be something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="go"&gt;➜  ruby examples/e2e.rb
Access token (truncated): eyJraWQiOiJkZWZhdWx0...
userinfo status: 200
&lt;/span&gt;&lt;span class="gp"&gt;userinfo body: {"sub" =&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"demo-sub"&lt;/span&gt;, &lt;span class="s2"&gt;"aud"&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"demo-aud"&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;, &lt;span class="s2"&gt;"nbf"&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; 1757816758000, &lt;span class="s2"&gt;"iss"&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;"http://localhost:8080/default"&lt;/span&gt;, &lt;span class="s2"&gt;"exp"&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; 1757820358000, &lt;span class="s2"&gt;"iat"&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; 1757816758000, &lt;span class="s2"&gt;"jti"&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;"d63b97a7-ebe5-4dea-93e6-d542caba6104"&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="go"&gt;E2E complete
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Make sure to shut down the mock server when you are done:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="go"&gt;docker compose -f docker-compose-ssl.yml down
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Troubleshooting: validate connectivity to the mock server
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Check container status and port mapping:

&lt;ul&gt;
&lt;li&gt;docker compose -f docker-compose-ssl.yml ps&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;From the host, try the discovery URL directly (this is what the example uses by default):

&lt;ul&gt;
&lt;li&gt;curl -v &lt;a href="http://localhost:8080/default/.well-known/openid-configuration" rel="noopener noreferrer"&gt;http://localhost:8080/default/.well-known/openid-configuration&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;If that fails immediately, also try: curl -v --connect-timeout 2 &lt;a href="http://127.0.0.1:8080/default/.well-known/openid-configuration" rel="noopener noreferrer"&gt;http://127.0.0.1:8080/default/.well-known/openid-configuration&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;From inside the container (to distinguish container vs host networking):

&lt;ul&gt;
&lt;li&gt;docker exec -it oauth2-mock-oauth2-server-1 curl -v &lt;a href="http://127.0.0.1:8080/default/.well-known/openid-configuration" rel="noopener noreferrer"&gt;http://127.0.0.1:8080/default/.well-known/openid-configuration&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;Simple TCP probe from the host:

&lt;ul&gt;
&lt;li&gt;nc -vz localhost 8080  # or: ruby -rsocket -e 'TCPSocket.new("localhost",8080).close; puts "tcp ok"'&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;Inspect which host port 8080 is bound to (should be 8080):

&lt;ul&gt;
&lt;li&gt;docker inspect -f '{{ (index (index .NetworkSettings.Ports "8080/tcp") 0).HostPort }}' oauth2-mock-oauth2-server-1&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;Look at server logs for readiness/errors:

&lt;ul&gt;
&lt;li&gt;docker logs -n 200 oauth2-mock-oauth2-server-1&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;On Linux, ensure nothing else is bound to 8080 and that firewall/SELinux aren’t blocking:

&lt;ul&gt;
&lt;li&gt;ss -ltnp | grep :8080&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;h2&gt;
  
  
  Notes
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Discovery URL pattern is: &lt;a href="http://localhost:8080/" rel="noopener noreferrer"&gt;http://localhost:8080/&lt;/a&gt;/.well-known/openid-configuration, where  defaults to "default".&lt;/li&gt;
&lt;li&gt;You can change these with env vars when running the example:

&lt;ul&gt;
&lt;li&gt;E2E_ISSUER_BASE (default: &lt;a href="http://localhost:8080" rel="noopener noreferrer"&gt;http://localhost:8080&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;E2E_REALM (default: default)&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;See the full changelogs after a word from (or is it for?) my sponsor (you!).&lt;/p&gt;

&lt;h2&gt;
  
  
  Support &amp;amp; Funding Info
&lt;/h2&gt;

&lt;p&gt;I am a full-time FLOSS maintainer. If you find &lt;a href="//github.com/pboling"&gt;my work&lt;/a&gt; valuable I ask that you become a sponsor. Every dollar helps!&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;🥰 Support FLOSS work 🥰&lt;/th&gt;
&lt;th&gt;Get access&lt;/th&gt;
&lt;th&gt;"Sponsors" channel&lt;/th&gt;
&lt;th&gt;on Galtzo FLOSS&lt;/th&gt;
&lt;th&gt;Discord 👇️ &lt;a href="https://discord.gg/3qme4XHNKN" rel="noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraster.shields.io%2Fdiscord%2F1373797679469170758%3Fstyle%3Dfor-the-badge" alt="Live Chat on Discord" width="144" height="28"&gt;&lt;/a&gt;
&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;a href="https://opencollective.com/ruby-oauth#backer" rel="noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraster.shields.io%2Fopencollective%2Fbackers%2Fruby-oauth.png%3Fstyle%3Dfor-the-badge" alt="OpenCollective Backers" width="112" height="28"&gt;&lt;/a&gt; &lt;a href="https://opencollective.com/ruby-oauth#sponsor" rel="noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraster.shields.io%2Fopencollective%2Fsponsors%2Fruby-oauth.png%3Fstyle%3Dfor-the-badge" alt="OpenCollective Sponsors" width="122" height="28"&gt;&lt;/a&gt;
&lt;/td&gt;
&lt;td&gt;
&lt;a href="https://www.buymeacoffee.com/pboling" rel="noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraster.shields.io%2Fbadge%2Fbuy_me_a_coffee-%25E2%259C%2593-a51611.png%3Fstyle%3Dflat" alt="Buy me a coffee" width="118" height="20"&gt;&lt;/a&gt; &lt;a href="https://ko-fi.com/O5O86SNP4" rel="noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraster.shields.io%2Fbadge%2Fko--fi-%25E2%259C%2593-a51611.png%3Fstyle%3Dflat" alt="Donate at ko-fi.com" width="54" height="20"&gt;&lt;/a&gt;
&lt;/td&gt;
&lt;td&gt;&lt;a href="https://www.paypal.com/paypalme/peterboling" rel="noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraster.shields.io%2Fbadge%2Fdonate-paypal-a51611.png%3Fstyle%3Dflat%26logo%3Dpaypal" alt="Donate on PayPal" width="111" height="20"&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href="https://polar.sh/pboling" rel="noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraster.shields.io%2Fbadge%2Fpolar-donate-a51611.png%3Fstyle%3Dflat" alt="Donate on Polar" width="84" height="20"&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;
&lt;a href="https://github.com/sponsors/pboling" rel="noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraster.shields.io%2Fbadge%2FSponsor_Me%21-pboling.png%3Fstyle%3Dsocial%26logo%3Dgithub" alt="Sponsor Me on Github" width="107" height="20"&gt;&lt;/a&gt; &lt;a href="https://liberapay.com/pboling/donate" rel="noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraster.shields.io%2Fliberapay%2Fgoal%2Fpboling.png%3Flogo%3Dliberapay%26color%3Da51611%26style%3Dflat" alt="Liberapay Goal Progress" width="131" height="20"&gt;&lt;/a&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  &lt;a href="https://github.com/ruby-oauth/oauth2/compare/v2.0.15...v2.0.16" rel="noopener noreferrer"&gt;2.0.16&lt;/a&gt; - 2025-09-14
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;TAG: &lt;a href="https://github.com/ruby-oauth/oauth2/releases/tag/v2.0.16" rel="noopener noreferrer"&gt;v2.0.16&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;COVERAGE: 100.00% -- 520/520 lines in 14 files&lt;/li&gt;
&lt;li&gt;BRANCH COVERAGE: 100.00% -- 176/176 branches in 14 files&lt;/li&gt;
&lt;li&gt;90.48% documented&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Added
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://github.com/ruby-oauth/oauth2/pull/680" rel="noopener noreferrer"&gt;gh!680&lt;/a&gt; - E2E example using mock test server added in v2.0.11 by @pboling

&lt;ul&gt;
&lt;li&gt;mock-oauth2-server upgraded to v2.3.0&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/navikt/mock-oauth2-server" rel="noopener noreferrer"&gt;https://github.com/navikt/mock-oauth2-server&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;docker compose -f docker-compose-ssl.yml up -d --wait&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ruby examples/e2e.rb&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;docker compose -f docker-compose-ssl.yml down&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;mock server readiness wait is 90s&lt;/li&gt;
&lt;li&gt;override via E2E_WAIT_TIMEOUT&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;a href="https://github.com/ruby-oauth/oauth2/pull/676" rel="noopener noreferrer"&gt;gh!676&lt;/a&gt;, &lt;a href="https://github.com/ruby-oauth/oauth2/pull/679" rel="noopener noreferrer"&gt;gh!679&lt;/a&gt; - Apache SkyWalking Eyes dependency license check by @pboling&lt;/li&gt;

&lt;/ul&gt;

&lt;h3&gt;
  
  
  Changed
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://github.com/ruby-oauth/oauth2/pull/678" rel="noopener noreferrer"&gt;gh!678&lt;/a&gt; - Many improvements to make CI more resilient (past/future proof) by @pboling&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/ruby-oauth/oauth2/pull/681" rel="noopener noreferrer"&gt;gh!681&lt;/a&gt; - Upgrade to kettle-dev v1.1.19&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  &lt;a href="https://github.com/ruby-oauth/oauth2/compare/v2.0.14...v2.0.15" rel="noopener noreferrer"&gt;2.0.15&lt;/a&gt; - 2025-09-08
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;TAG: &lt;a href="https://github.com/ruby-oauth/oauth2/releases/tag/v2.0.15" rel="noopener noreferrer"&gt;v2.0.15&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;COVERAGE: 100.00% -- 519/519 lines in 14 files&lt;/li&gt;
&lt;li&gt;BRANCH COVERAGE: 100.00% -- 174/174 branches in 14 files&lt;/li&gt;
&lt;li&gt;90.48% documented&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Added
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://github.com/ruby-oauth/oauth2/pull/671" rel="noopener noreferrer"&gt;gh!671&lt;/a&gt; - Complete documentation example for Instagram by @pboling&lt;/li&gt;
&lt;li&gt;.env.local.example for contributor happiness&lt;/li&gt;
&lt;li&gt;note lack of builds for JRuby 9.2, 9.3 &amp;amp; Truffleruby 22.3, 23.0

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/actions/runner/issues/2347" rel="noopener noreferrer"&gt;actions/runner - issues/2347&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/orgs/community/discussions/15452" rel="noopener noreferrer"&gt;community/discussions/15452&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;a href="https://github.com/ruby-oauth/oauth2/pull/670" rel="noopener noreferrer"&gt;gh!670&lt;/a&gt; - AccessToken: verb-dependent token transmission mode by &lt;a class="mentioned-user" href="https://dev.to/mrj"&gt;@mrj&lt;/a&gt;

&lt;ul&gt;
&lt;li&gt;e.g., Instagram GET=:query, POST/DELETE=:header&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;h3&gt;
  
  
  Changed
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://github.com/ruby-oauth/oauth2/pull/669" rel="noopener noreferrer"&gt;gh!669&lt;/a&gt; - Upgrade to kettle-dev v1.1.9 by @pboling&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Fixed
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Remove accidentally duplicated lines, and fix typos in CHANGELOG.md&lt;/li&gt;
&lt;li&gt;point badge to the correct workflow for Ruby 2.3 (caboose.yml)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Photo (cropped) by &lt;a href="https://unsplash.com/@zoeey97?utm_content=creditCopyText&amp;amp;utm_medium=referral&amp;amp;utm_source=unsplash" rel="noopener noreferrer"&gt;Zoha Gohar&lt;/a&gt; on &lt;a href="https://unsplash.com/photos/abstract-flowing-lines-with-yellow-light-accents-L8uRhNnkrM0?utm_content=creditCopyText&amp;amp;utm_medium=referral&amp;amp;utm_source=unsplash" rel="noopener noreferrer"&gt;Unsplash&lt;/a&gt;&lt;/p&gt;

</description>
      <category>docker</category>
      <category>security</category>
      <category>ruby</category>
      <category>instagram</category>
    </item>
    <item>
      <title>👩‍🔧 How to Check License Compatibility in GHA</title>
      <dc:creator>Peter H. Boling</dc:creator>
      <pubDate>Sat, 13 Sep 2025 22:27:37 +0000</pubDate>
      <link>https://dev.to/galtzo/how-to-check-license-compatibility-41h0</link>
      <guid>https://dev.to/galtzo/how-to-check-license-compatibility-41h0</guid>
      <description>&lt;p&gt;Other posts in my Compatibility Matrix Series&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://dev.to/galtzo/activerecord-sqlite3-compatibility-matrix-58id"&gt;ActiveRecord / SQlite3 Compatibility Matrix&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/galtzo/matrix-ruby-gem-bundler-etc-4kk7"&gt;RRRRb Matrix (Ruby, RubyGems, RuboCop, Rails, Bundler)&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You have a project made out of code, sprinkles, and spice, and you want to &lt;em&gt;validate compatibility&lt;/em&gt; between your project's &lt;em&gt;license&lt;/em&gt; and the licenses of its &lt;em&gt;dependencies&lt;/em&gt;, as defined by:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Apache Software Foundation (ASF) Categorizations &lt;a href="https://www.apache.org/legal/resolved.html" rel="noopener noreferrer"&gt;via apache.org&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Free Software Foundation's (FSF) Free/Libre designations &lt;a href="https://spdx.org/licenses" rel="noopener noreferrer"&gt;via spdx.org&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Open Source Initiative (OSI) Approved designations &lt;a href="https://spdx.org/licenses" rel="noopener noreferrer"&gt;via spdx.org&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Any of the above&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What is Compatible?
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Apache Software Foundation
&lt;/h3&gt;

&lt;p&gt;IANAL, but Apache Software Foundation has resolved many legal issues between licenses and determined their compatibility to my satisfaction. Take it up with them if your fractious children want to quarrel about it.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://www.apache.org/legal/resolved.html#category-a" rel="noopener noreferrer"&gt;Category A&lt;/a&gt; licenses are compatible with each other and with Apache Software Foundation projects generally.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.apache.org/legal/resolved.html#category-b" rel="noopener noreferrer"&gt;Category B&lt;/a&gt; licenses are compatible with each other, and with Apache Software Foundation projects when included as &lt;em&gt;binary code&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;Other licenses need manual validation, and compatibility can be configured and documented per project.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These categorizations are the backbone of the &lt;a href="https://github.com/apache/skywalking-eyes" rel="noopener noreferrer"&gt;Apache SkyWalking Eyes&lt;/a&gt; license checker, see further down for a configuration example.&lt;/p&gt;

&lt;h3&gt;
  
  
  FSF Free/Libre
&lt;/h3&gt;

&lt;p&gt;Supported. In your &lt;code&gt;licenserc.yaml&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;dependency:
  require_fsf_free: true # will only consider compatible if license is FSF Free/Libre; default false
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  OSI Approved
&lt;/h3&gt;

&lt;p&gt;Supported. In your &lt;code&gt;licenserc.yaml&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;dependency:
  require_osi_approved: true # will only consider compatible if license is OSI Approved; default false
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  How can we check compatibility?
&lt;/h2&gt;

&lt;p&gt;I've now pushed many PRs to a project called Apache SkyWalking Eyes (&lt;a href="https://github.com/apache/skywalking-eyes/pull/205" rel="noopener noreferrer"&gt;#205&lt;/a&gt;, &lt;a href="https://github.com/apache/skywalking-eyes/pull/207" rel="noopener noreferrer"&gt;#207&lt;/a&gt;, &lt;a href="https://github.com/apache/skywalking-eyes/pull/208" rel="noopener noreferrer"&gt;#208&lt;/a&gt;, &lt;a href="https://github.com/apache/skywalking-eyes/pull/209" rel="noopener noreferrer"&gt;#209&lt;/a&gt;, ... &lt;a href="https://github.com/apache/skywalking-eyes/pull/247" rel="noopener noreferrer"&gt;#247&lt;/a&gt;, &lt;a href="https://github.com/apache/skywalking-eyes/pull/248" rel="noopener noreferrer"&gt;#248&lt;/a&gt;, &lt;a href="https://github.com/apache/skywalking-eyes/pull/249" rel="noopener noreferrer"&gt;#249&lt;/a&gt;, &lt;a href="https://github.com/apache/skywalking-eyes/pull/250" rel="noopener noreferrer"&gt;#250&lt;/a&gt;). And now it is ready to write about.&lt;/p&gt;

&lt;p&gt;It works for a basic use case of a project with MIT and Ruby licensed dependencies, such as &lt;code&gt;oauth2&lt;/code&gt;:&lt;br&gt;
&lt;a href="https://github.com/ruby-oauth/oauth2/pull/676" rel="noopener noreferrer"&gt;https://github.com/ruby-oauth/oauth2/pull/676&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It also works for more complex use cases with many dependencies across a broad set of open source licenses.&lt;/p&gt;
&lt;h3&gt;
  
  
  Project Setup
&lt;/h3&gt;

&lt;p&gt;This example will use a Ruby project as an example, but there is support for Create two files:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# .licenserc.yaml&lt;/span&gt;

&lt;span class="na"&gt;header&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;license&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;spdx-id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;MIT&lt;/span&gt; &lt;span class="c1"&gt;# The license of your project!&lt;/span&gt;

&lt;span class="na"&gt;dependency&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;require_fsf_free&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt; &lt;span class="c1"&gt;# if "FSF Free/Libre" is required, set to true&lt;/span&gt;
  &lt;span class="na"&gt;require_osi_approved&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt; &lt;span class="c1"&gt;# if "OSI Approved" is required, set to true&lt;/span&gt;
  &lt;span class="na"&gt;files&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;Gemfile.lock&lt;/span&gt;      &lt;span class="c1"&gt;# If this is a Ruby project (Bundler). Ensure Gemfile.lock is committed.&lt;/span&gt;
    &lt;span class="c1"&gt;# - pom.xml           # If this is a maven project.&lt;/span&gt;
    &lt;span class="c1"&gt;# - Cargo.toml        # If this is a rust project.&lt;/span&gt;
    &lt;span class="c1"&gt;# - package.json      # If this is a npm project.&lt;/span&gt;
    &lt;span class="c1"&gt;# - go.mod            # If this is a Go project.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# .github/workflows/license-eye.yml&lt;/span&gt;

&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Apache SkyWalking Eyes&lt;/span&gt;

&lt;span class="na"&gt;permissions&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;contents&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;read&lt;/span&gt;

&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;push&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;branches&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;main'&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;*-stable'&lt;/span&gt;
    &lt;span class="na"&gt;tags&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;!*'&lt;/span&gt; &lt;span class="c1"&gt;# Do not execute on tags&lt;/span&gt;
  &lt;span class="na"&gt;pull_request&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;branches&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;*'&lt;/span&gt;
  &lt;span class="c1"&gt;# Allow manually triggering the workflow.&lt;/span&gt;
  &lt;span class="na"&gt;workflow_dispatch&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;

&lt;span class="c1"&gt;# Cancels all previous workflow runs for the same branch that have not yet completed.&lt;/span&gt;
&lt;span class="na"&gt;concurrency&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="c1"&gt;# The concurrency group contains the workflow name and the branch name.&lt;/span&gt;
  &lt;span class="na"&gt;group&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;${{&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;github.workflow&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;}}-${{&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;github.ref&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;}}"&lt;/span&gt;
  &lt;span class="na"&gt;cancel-in-progress&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;

&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;license-check&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;if&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;!contains(github.event.commits[0].message,&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;'[ci&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;skip]')&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;!contains(github.event.commits[0].message,&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;'[skip&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;ci]')"&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Checkout&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v5&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Check Dependencies' License&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;apache/skywalking-eyes/dependency@main&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;config&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;.licenserc.yaml&lt;/span&gt;
          &lt;span class="c1"&gt;# Ruby packages declared as dependencies in gemspecs or Gemfiles are&lt;/span&gt;
          &lt;span class="c1"&gt;#   typically consumed as binaries; enable weak-compatibility&lt;/span&gt;
          &lt;span class="c1"&gt;#   so permissive and weak-copyleft combinations are treated as compatible.&lt;/span&gt;
          &lt;span class="na"&gt;flags&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;--weak-compatible&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Result
&lt;/h3&gt;

&lt;p&gt;End result workflow runs look like:&lt;br&gt;
&lt;a href="https://github.com/ruby-oauth/oauth2/actions/workflows/license-eye.yml" rel="noopener noreferrer"&gt;https://github.com/ruby-oauth/oauth2/actions/workflows/license-eye.yml&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Experiment: if we mark the Ruby license (which is category B) as incompatible in our MIT (category A) projects, this is what would happen:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fipxo9c3zs6fnxxfysoqy.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fipxo9c3zs6fnxxfysoqy.png" alt=" " width="781" height="164"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Cool, right?  I'm not suggesting you do that, since in Ruby dependencies are normally included as binaries, so Category B is generally compatible with Category A licenses &lt;em&gt;in Ruby projects&lt;/em&gt;... &lt;/p&gt;

&lt;p&gt;Projects that declare &lt;em&gt;no&lt;/em&gt; license are &lt;em&gt;problematic&lt;/em&gt; and will fail in this same way, alerting you to the problem.&lt;/p&gt;

&lt;p&gt;You'll also get notified if downstream dependencies change their licenses to something incompatible.&lt;/p&gt;

&lt;h3&gt;
  
  
  Spread Awareness
&lt;/h3&gt;

&lt;p&gt;Because licenses matter...&lt;/p&gt;

&lt;p&gt;Now is a good time to make your community of users aware of your shiny license compliance via README.md badges...&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/ruby-oauth/oauth2/actions/workflows/license-eye.yml" rel="noopener noreferrer"&gt;&lt;img src="https://github.com/ruby-oauth/oauth2/actions/workflows/license-eye.yml/badge.svg" alt="Apache SkyWalking Eyes License Compatibility Check" width="214" height="20"&gt;&lt;/a&gt;&lt;br&gt;
&lt;a href="https://www.apache.org/legal/resolved.html#category-a" rel="noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraster.shields.io%2Fbadge%2FApache_Compatible%3A_Category_A-%25E2%259C%2593-259D6C.png%3Fstyle%3Dflat%26logo%3DApache" alt="Compatible with Apache Software Projects: Verified by SkyWalking Eyes" width="223" height="20"&gt;&lt;/a&gt;&lt;/p&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;![Apache SkyWalking Eyes License Compatibility Check&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="ss"&gt;🚎15-🪪-wfi&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;][🚎15-🪪-wf]
&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;![Compatible with Apache Software Projects: Verified by SkyWalking Eyes&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="ss"&gt;📄license-compat-img&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;][📄license-compat]

&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;🚎15-🪪-wf&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt; &lt;span class="sx"&gt;https://github.com/ruby-oauth/oauth2/actions/workflows/license-eye.yml&lt;/span&gt;
&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;🚎15-🪪-wfi&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt; &lt;span class="sx"&gt;https://github.com/ruby-oauth/oauth2/actions/workflows/license-eye.yml/badge.svg&lt;/span&gt;
&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;📄license-compat&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt; &lt;span class="sx"&gt;https://www.apache.org/legal/resolved.html#category-a&lt;/span&gt;
&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;📄license-compat-img&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt; &lt;span class="sx"&gt;https://img.shields.io/badge/Apache_Compatible:_Category_A-✓-259D6C.svg?style=flat&amp;amp;logo=Apache&lt;/span&gt;
&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;📄license-compat-img-raster&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt; &lt;span class="sx"&gt;https://raster.shields.io/badge/Apache_Compatible:_Category_A-✓-259D6C.png?style=flat&amp;amp;logo=Apache&lt;/span&gt;
&lt;span class="gh"&gt;# note: dev.to will only render the PNG rasterized version;&lt;/span&gt;
&lt;span class="gh"&gt;#       in READMEs you should use the SVG for better image clarity.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Support &amp;amp; Funding Info
&lt;/h2&gt;

&lt;p&gt;I am a full-time FLOSS maintainer. If you find &lt;a href="//github.com/pboling"&gt;my work&lt;/a&gt; valuable, I ask that you become a sponsor. Every dollar helps!&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;🥰 Support FLOSS work 🥰&lt;/th&gt;
&lt;th&gt;Get access&lt;/th&gt;
&lt;th&gt;"Sponsors" channel&lt;/th&gt;
&lt;th&gt;on Galtzo FLOSS&lt;/th&gt;
&lt;th&gt;Discord 👇️ &lt;a href="https://discord.gg/3qme4XHNKN" rel="noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraster.shields.io%2Fdiscord%2F1373797679469170758%3Fstyle%3Dfor-the-badge" alt="Live Chat on Discord" width="144" height="28"&gt;&lt;/a&gt;
&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;a href="https://opencollective.com/ruby-oauth#backer" rel="noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraster.shields.io%2Fopencollective%2Fbackers%2Fruby-oauth.png%3Fstyle%3Dfor-the-badge" alt="OpenCollective Backers" width="112" height="28"&gt;&lt;/a&gt; &lt;a href="https://opencollective.com/ruby-oauth#sponsor" rel="noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraster.shields.io%2Fopencollective%2Fsponsors%2Fruby-oauth.png%3Fstyle%3Dfor-the-badge" alt="OpenCollective Sponsors" width="122" height="28"&gt;&lt;/a&gt;
&lt;/td&gt;
&lt;td&gt;
&lt;a href="https://www.buymeacoffee.com/pboling" rel="noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraster.shields.io%2Fbadge%2Fbuy_me_a_coffee-%25E2%259C%2593-a51611.png%3Fstyle%3Dflat" alt="Buy me a coffee" width="118" height="20"&gt;&lt;/a&gt; &lt;a href="https://ko-fi.com/O5O86SNP4" rel="noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraster.shields.io%2Fbadge%2Fko--fi-%25E2%259C%2593-a51611.png%3Fstyle%3Dflat" alt="Donate at ko-fi.com" width="54" height="20"&gt;&lt;/a&gt;
&lt;/td&gt;
&lt;td&gt;&lt;a href="https://www.paypal.com/paypalme/peterboling" rel="noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraster.shields.io%2Fbadge%2Fdonate-paypal-a51611.png%3Fstyle%3Dflat%26logo%3Dpaypal" alt="Donate on PayPal" width="111" height="20"&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href="https://polar.sh/pboling" rel="noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraster.shields.io%2Fbadge%2Fpolar-donate-a51611.png%3Fstyle%3Dflat" alt="Donate on Polar" width="84" height="20"&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;
&lt;a href="https://github.com/sponsors/pboling" rel="noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraster.shields.io%2Fbadge%2FSponsor_Me%21-pboling.png%3Fstyle%3Dsocial%26logo%3Dgithub" alt="Sponsor Me on Github" width="107" height="20"&gt;&lt;/a&gt; &lt;a href="https://liberapay.com/pboling/donate" rel="noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraster.shields.io%2Fliberapay%2Fgoal%2Fpboling.png%3Flogo%3Dliberapay%26color%3Da51611%26style%3Dflat" alt="Liberapay Goal Progress" width="131" height="20"&gt;&lt;/a&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Photo (cropped) by &lt;a href="https://unsplash.com/@zoeey97?utm_content=creditCopyText&amp;amp;utm_medium=referral&amp;amp;utm_source=unsplash" rel="noopener noreferrer"&gt;Zoha Gohar&lt;/a&gt; on &lt;a href="https://unsplash.com/photos/pink-lion-resting-on-a-sofa-in-a-room-7sPd2qahtTw?utm_content=creditCopyText&amp;amp;utm_medium=referral&amp;amp;utm_source=unsplash" rel="noopener noreferrer"&gt;Unsplash&lt;/a&gt;&lt;/p&gt;

</description>
      <category>programming</category>
      <category>opensource</category>
      <category>githubactions</category>
      <category>howto</category>
    </item>
    <item>
      <title>💲ANN: awesome-sponsorships</title>
      <dc:creator>Peter H. Boling</dc:creator>
      <pubDate>Fri, 12 Sep 2025 08:18:46 +0000</pubDate>
      <link>https://dev.to/galtzo/ann-awesome-sponsorships-535m</link>
      <guid>https://dev.to/galtzo/ann-awesome-sponsorships-535m</guid>
      <description>&lt;p&gt;🪧 How do you get more open source sponsors? Now that I'm doing FLOSS full-time, I have to either starve or improve my marketing game.&lt;/p&gt;

&lt;p&gt;I've talked with people who do it well (they shall remain nameless, so you'll have to trust me on that), and distilled the ideas into 2 actionable checklists - a short one and a long one. Now I just need to implement. I'd love to hear your thoughts, and get your ⭐ of approval.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/floss-funding/awesome-sponsorships" rel="noopener noreferrer"&gt;github.com/floss-funding/awesome-sponsorships&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  #FLOSS #Funding #FiscalSponsor
&lt;/h1&gt;

&lt;h2&gt;
  
  
  Support &amp;amp; Funding Info
&lt;/h2&gt;

&lt;p&gt;I am a full-time FLOSS maintainer. If you find &lt;a href="//github.com/pboling"&gt;my work&lt;/a&gt; valuable I ask that you become a sponsor. Every dollar helps!&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;🥰 Support FLOSS work 🥰&lt;/th&gt;
&lt;th&gt;Get access&lt;/th&gt;
&lt;th&gt;"Sponsors" channel&lt;/th&gt;
&lt;th&gt;on Galtzo FLOSS&lt;/th&gt;
&lt;th&gt;Discord 👇️ &lt;a href="https://discord.gg/3qme4XHNKN" rel="noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraster.shields.io%2Fdiscord%2F1373797679469170758%3Fstyle%3Dfor-the-badge" alt="Live Chat on Discord" width="144" height="28"&gt;&lt;/a&gt;
&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;a href="https://opencollective.com/floss_funding#backer" rel="noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraster.shields.io%2Fopencollective%2Fbackers%2Ffloss_funding.png%3Fstyle%3Dfor-the-badge" alt="OpenCollective Backers" width="112" height="28"&gt;&lt;/a&gt; &lt;a href="https://opencollective.com/floss_funding#sponsor" rel="noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraster.shields.io%2Fopencollective%2Fsponsors%2Ffloss_funding.png%3Fstyle%3Dfor-the-badge" alt="OpenCollective Sponsors" width="122" height="28"&gt;&lt;/a&gt;
&lt;/td&gt;
&lt;td&gt;
&lt;a href="https://www.buymeacoffee.com/pboling" rel="noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraster.shields.io%2Fbadge%2Fbuy_me_a_coffee-%25E2%259C%2593-a51611.png%3Fstyle%3Dflat" alt="Buy me a coffee" width="118" height="20"&gt;&lt;/a&gt; &lt;a href="https://ko-fi.com/O5O86SNP4" rel="noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraster.shields.io%2Fbadge%2Fko--fi-%25E2%259C%2593-a51611.png%3Fstyle%3Dflat" alt="Donate at ko-fi.com" width="54" height="20"&gt;&lt;/a&gt;
&lt;/td&gt;
&lt;td&gt;&lt;a href="https://www.paypal.com/paypalme/peterboling" rel="noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraster.shields.io%2Fbadge%2Fdonate-paypal-a51611.png%3Fstyle%3Dflat%26logo%3Dpaypal" alt="Donate on PayPal" width="111" height="20"&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href="https://polar.sh/pboling" rel="noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraster.shields.io%2Fbadge%2Fpolar-donate-a51611.png%3Fstyle%3Dflat" alt="Donate on Polar" width="84" height="20"&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;
&lt;a href="https://github.com/sponsors/pboling" rel="noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraster.shields.io%2Fbadge%2FSponsor_Me%21-pboling.png%3Fstyle%3Dsocial%26logo%3Dgithub" alt="Sponsor Me on Github" width="107" height="20"&gt;&lt;/a&gt; &lt;a href="https://liberapay.com/pboling/donate" rel="noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraster.shields.io%2Fliberapay%2Fgoal%2Fpboling.png%3Flogo%3Dliberapay%26color%3Da51611%26style%3Dflat" alt="Liberapay Goal Progress" width="131" height="20"&gt;&lt;/a&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Photo (cropped) by &lt;a href="https://unsplash.com/@cajeo?utm_content=creditCopyText&amp;amp;utm_medium=referral&amp;amp;utm_source=unsplash" rel="noopener noreferrer"&gt;Cajeo Zhang&lt;/a&gt; on &lt;a href="https://unsplash.com/photos/two-astronauts-walk-through-pink-smoke-8jW6xCtmYsw?utm_content=creditCopyText&amp;amp;utm_medium=referral&amp;amp;utm_source=unsplash" rel="noopener noreferrer"&gt;Unsplash&lt;/a&gt;&lt;/p&gt;

</description>
      <category>opensource</category>
      <category>webmonetization</category>
      <category>sponsorships</category>
      <category>programming</category>
    </item>
    <item>
      <title>💎 ANN: oauth2 v2.0.14</title>
      <dc:creator>Peter H. Boling</dc:creator>
      <pubDate>Mon, 01 Sep 2025 03:24:45 +0000</pubDate>
      <link>https://dev.to/galtzo/ann-oauth2-v2014-2g52</link>
      <guid>https://dev.to/galtzo/ann-oauth2-v2014-2g52</guid>
      <description>&lt;p&gt;oauth2 v2.0.14 has been released with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;📝 Added OAuth 2.1 draft specification as inline documentation throughout in PR #662

&lt;ul&gt;
&lt;li&gt;PKCE required for auth code,&lt;/li&gt;
&lt;li&gt;exact redirect URI match,&lt;/li&gt;
&lt;li&gt;implicit/password grants omitted,&lt;/li&gt;
&lt;li&gt;avoid bearer tokens in query,&lt;/li&gt;
&lt;li&gt;refresh token guidance for public clients,&lt;/li&gt;
&lt;li&gt;simplified client definitions&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;📝 Added OIDC documentation, example, and spec references in &lt;a href="https://github.com/ruby-oauth/oauth2/blob/main/OIDC.md" rel="noopener noreferrer"&gt;OIDC.md&lt;/a&gt; in PR #663&lt;/li&gt;

&lt;li&gt;📝 Add Example for JHipster UAA Server (Spring Cloud) Password Grant Integration in PR #664&lt;/li&gt;

&lt;li&gt;📝 Document Mutual TLS (mTLS) usage with example in README in PR #665&lt;/li&gt;

&lt;li&gt;✅ Documentation with Example for Flat Params Usage, with specs in PR #666&lt;/li&gt;

&lt;li&gt;Purely a documentation release!&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;Yes, this is the second release in two days. There are no code changes in this release.&lt;/p&gt;

&lt;p&gt;This project is used by over 100k other projects.  It is downloaded millions of times per week. It currently has zero backers, and zero sponsors. Please consider supporting it.&lt;/p&gt;

&lt;p&gt;Release Notes: &lt;a href="https://github.com/ruby-oauth/oauth2/releases/tag/v2.0.14" rel="noopener noreferrer"&gt;github.com/ruby-oauth/oauth2/releases/tag/v2.0.14&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Open Collective: &lt;a href="https://opencollective.com/ruby-oauth" rel="noopener noreferrer"&gt;opencollective.com/ruby-oauth&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Support &amp;amp; Funding Info
&lt;/h2&gt;

&lt;p&gt;I am a full-time FLOSS maintainer. If you find &lt;a href="//github.com/pboling"&gt;my work&lt;/a&gt; valuable I ask that you become a sponsor. Every dollar helps!&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;🥰 Support FLOSS work 🥰&lt;/th&gt;
&lt;th&gt;Get access&lt;/th&gt;
&lt;th&gt;"Sponsors" channel&lt;/th&gt;
&lt;th&gt;on Galtzo FLOSS&lt;/th&gt;
&lt;th&gt;Discord 👇️ &lt;a href="https://discord.gg/3qme4XHNKN" rel="noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraster.shields.io%2Fdiscord%2F1373797679469170758%3Fstyle%3Dfor-the-badge" alt="Live Chat on Discord" width="144" height="28"&gt;&lt;/a&gt;
&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;a href="https://opencollective.com/ruby-oauth#backer" rel="noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraster.shields.io%2Fopencollective%2Fbackers%2Fruby-oauth.png%3Fstyle%3Dfor-the-badge" alt="OpenCollective Backers" width="112" height="28"&gt;&lt;/a&gt; &lt;a href="https://opencollective.com/ruby-oauth#sponsor" rel="noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraster.shields.io%2Fopencollective%2Fsponsors%2Fruby-oauth.png%3Fstyle%3Dfor-the-badge" alt="OpenCollective Sponsors" width="122" height="28"&gt;&lt;/a&gt;
&lt;/td&gt;
&lt;td&gt;
&lt;a href="https://www.buymeacoffee.com/pboling" rel="noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraster.shields.io%2Fbadge%2Fbuy_me_a_coffee-%25E2%259C%2593-a51611.png%3Fstyle%3Dflat" alt="Buy me a coffee" width="118" height="20"&gt;&lt;/a&gt; &lt;a href="https://ko-fi.com/O5O86SNP4" rel="noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraster.shields.io%2Fbadge%2Fko--fi-%25E2%259C%2593-a51611.png%3Fstyle%3Dflat" alt="Donate at ko-fi.com" width="54" height="20"&gt;&lt;/a&gt;
&lt;/td&gt;
&lt;td&gt;&lt;a href="https://www.paypal.com/paypalme/peterboling" rel="noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraster.shields.io%2Fbadge%2Fdonate-paypal-a51611.png%3Fstyle%3Dflat%26logo%3Dpaypal" alt="Donate on PayPal" width="111" height="20"&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href="https://polar.sh/pboling" rel="noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraster.shields.io%2Fbadge%2Fpolar-donate-a51611.png%3Fstyle%3Dflat" alt="Donate on Polar" width="84" height="20"&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;
&lt;a href="https://github.com/sponsors/pboling" rel="noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraster.shields.io%2Fbadge%2FSponsor_Me%21-pboling.png%3Fstyle%3Dsocial%26logo%3Dgithub" alt="Sponsor Me on Github" width="107" height="20"&gt;&lt;/a&gt; &lt;a href="https://liberapay.com/pboling/donate" rel="noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraster.shields.io%2Fliberapay%2Fgoal%2Fpboling.png%3Flogo%3Dliberapay%26color%3Da51611%26style%3Dflat" alt="Liberapay Goal Progress" width="131" height="20"&gt;&lt;/a&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Photo by &lt;a href="https://unsplash.com/@pawel_czerwinski?utm_content=creditCopyText&amp;amp;utm_medium=referral&amp;amp;utm_source=unsplash" rel="noopener noreferrer"&gt;Pawel Czerwinski&lt;/a&gt; on &lt;a href="https://unsplash.com/photos/abstract-geometric-shapes-with-dramatic-lighting-and-shadows-WtNk-Ab9JEY?utm_content=creditCopyText&amp;amp;utm_medium=referral&amp;amp;utm_source=unsplash" rel="noopener noreferrer"&gt;Unsplash&lt;/a&gt;&lt;/p&gt;

</description>
    </item>
  </channel>
</rss>
