Gemsmith is a command line interface for smithing Ruby gems. Perfect for when you need a professional and robust tool beyond Bundler's basic gem skeletons. While Bundler is great for creating your first gem, you’ll quickly outgrow Bundler when creating and maintaining multiple gems. This is where Gemsmith can increase your productivity by providing much of the tooling you need from the start with the ability to customize as desired.
Features
Setup
To install with security, run:
# 💡 Skip this line if you already have the public certificate installed.
gem cert --add <(curl --compressed --location https://alchemists.io/gems.pem)
gem install gemsmith --trust-policy HighSecurity
To install without security, run:
gem install gemsmith
Usage
Command Line Interface (CLI)
From the command line, type: gemsmith --help
Build
The core functionality of this gem centers around the build
command and associated flags. The build options allow you to further customize the kind of gem you want to build. Most build options
are enabled by default. For detailed documentation on all supported flags, see the Rubysmith documentation.
The build option which is unique to Gemsmith is the --cli
option. This allows you to build a gem which has a Command Line Interface (CLI). There are multiple ways a CLI can be built in Ruby but Gemsmith takes an approach which builds upon Ruby’s native OptionParser
with help from the Containable and Infusible gems. All of this culminates in a design that is mix of Objected Oriented + Functional Programming design. Building a gem with CLI support is a simple as running:
gemsmith build --name demo --cli
The above will give you a new gem with CLI support which includes working specs. It’s the same design used to build this Gemsmith gem. You’ll have both a configuration
and CLI
namespace for configuring your gem and adding additional CLI support. Out of the box, the CLI gem generated for you supports the following options:
-v, --version Show version.
-h, --help Show this .
config Manage configuration.
From here you can add whatever you wish to make an awesome CLI gem for others to enjoy.
Install
After you’ve designed, implemented, and built your gem, you’ll want to test it out within your local environment by installing it. You can do this by running:
# Implicit
gemsmith --install
# Explicit
gemsmith --install demo
Gemsmith can be used to install any gem, in fact. Doesn’t matter if the gem was built by Gemsmith,
Bundler, or some other tool. As long as your gem has a *.gemspec
file, Gemsmith will be able to
install it.
Publish
Once you’ve built your gem; installed it locally; and thoroughly tested it, you’ll want to publish your gem so anyone in the world can make use of it. You can do this by running the following:
# Implicit
gemsmith --publish
# Explicit
gemsmith --publish demo
Security is important which requires a GPG key for signing your Git tags and RubyGems Multi-Factor Authentication for publishing to RubyGems. Both of which are enabled by default. You’ll want to read through the linked article which delves into how Gemsmith automatically makes use of your YubiKey to authenticate with RubyGems. Spending the time to set this up will allow Gemsmith to use of your YubiKey for effortless and secure publishing of new versions of your gems so I highly recommend doing this.
As with installing a gem, Gemsmith can be used to publish existing gems which were not built by
Gemsmith too. As long as your gem has a *.gemspec
file with a valid version, Gemsmith will be able
to publish it.
Edit
Gemsmith can be used to edit existing gems on your local system. You can do this by running:
gemsmith --edit <name of gem>
If multiple versions of the same gem are detected, you’ll be prompted to pick which gem you want to
edit. Otherwise, the gem will immediately be opened within your default editor (or whatever you
have set in your EDITOR
environment variable).
Editing a local gem is a great way to learn from others or quickly debug issues.
View
Gemsmith can be used to view existing gem documentation. You can do this by running:
gemsmith --view <name of gem>
If multiple versions of the same gem are detected, you’ll be prompted to pick which gem you want to view. Otherwise, the gem will immediately be opened within your default browser.
Viewing a gem is a great way to learn more about the gem and documentation in general.
Configuration
This gem can be configured via a global configuration:
$HOME/.config/gemsmith/configuration.yml
It can also be configured via XDG environment variables.
The default configuration is everything provided in the Rubysmith with the addition of the following:
build:
cli: false
It is recommended that you provide URLs for your project which would be all keys found in this section:
project:
uri:
# Add sub-key values here.
When these values exist, you’ll benefit from having this information added to your generated
gemspec
and project documentation. Otherwise — if these values are empty — they are removed from
new gem generation.
Workflows
When building/testing your gem locally, a typical workflow is:
# Build
gemsmith build --name demo
# Design, Implement and Test.
cd demo
bundle exec rake
# Install
gemsmith --install
# Publish
gemsmith --publish
Security
Git Signing Key
To securely sign your Git tags, install and configure GPG:
brew install gpg
gpg --gen-key
When creating your GPG key, choose these settings:
-
Key kind: RSA and RSA (default)
-
Key size: 4096
-
Key validity: 0
-
Real Name:
<your name>
-
Email:
<your email>
-
Passphrase:
<your passphrase>
To obtain your key, run the following and take the part after the forward slash:
gpg --list-keys | grep pub
Add your key to your global Git configuration in the [user]
section. Example:
[user]
signingkey = <your GPG key>
Now, when publishing your gems with Gemsmith (i.e. bundle exec rake publish
), signing of your Git
tag will happen automatically.
Gem Certificates
To create a certificate for your gems, run the following:
cd ~/.gem
gem cert build [email protected] --days 730
gem cert --add gem-public_cert.pem
cp gem-public_cert.pem <path/to/server/public/folder>/gems.pem
The above breaks down as follows:
-
Source: The
~/.gem
directory is where your credentials and certificates are stored. This is also where theGem.default_key_path
andGem.default_cert_path
methods look for your certificates. I’ll talk more about these shortly. -
Build: Builds your
gem-private_key.pem
andgem-public_cert.pem
certificates with a two year duration (i.e.365 * 2
) before expiring. You can also see this information on the RubyGems page for your gem (scroll to the bottom). Security-wise, this isn’t great but the way RubyGems certification is implemented and enforced is weak to begin with. Regardless, this is important to do in order to be a good citizen within the ecosystem. You’ll also be prompted for a private key passphrase so make sure it is long and complicated and then store it in your favorite password manager. -
Add: Once your public certificate has been built, you’ll need to add it to your registry so RubyGems can look up and verify your certificate upon gem install.
-
Web: You’ll need to copy your public certificate to the public folder of your web server so you can host this certificate for others to install. I rename my public certificate as
gems.pem
to keep the URL simple but you can name it how you like and document usage for others. For example, here’s how you’d add my public certificate (same as done locally but via a URL this time):gem cert --add <(curl --compressed --location https://alchemists.io/gems.pem)
.
Earlier, I mentioned Gem.default_key_path
and Gem.default_cert_path
are paths to where your certificates are stored in your ~/.gem
directory. Well, the signing_key
and cert_chain
of your .gemspec
needs to use these paths. Gemsmith automates for you when the --security
build option is used (enabled by default). For example, when using Gemsmith to build a new gem, you’ll see the following configuration generated in your .gemspec
:
# frozen_string_literal: true
Gem::Specification.new do |spec|
# Truncated for brevity.
spec.signing_key = Gem.default_key_path
spec.cert_chain = [Gem.default_cert_path]
end
The above wires all of this functionality together so you can easily build and publish your gems with minimal effort while increasing your security. 🎉 To test the security of your newly minted gem, you can install it with the --trust-policy
set to high security for maximum benefit. Example:
gem install <your_gem> --trust-policy HighSecurity
To learn more about gem certificates, check out the RubyGems Security documentation.
Private Gem Servers
By default, the following command will publicly publish your gem to RubyGems:
gemsmith --publish
You can change this behavior by adding metadata to your gemspec that will allow Gemsmith to publish your gem to an alternate/private gem server instead. This can be done by updating your gem specification and RubyGems credentials.
Gem Specification Metadata
Add the following gemspec metadata to privately publish new versions of your gem:
Gem::Specification.new do |spec|
spec. = {"allowed_push_host" => "https://private.example.com"}
end
💡 The gemspec metadata (i.e. keys and values) must be strings per the RubyGems Specification.
Use of the allowed_push_host
key provides two important capabilities:
-
Prevents you from accidentally publishing your private gem to the public RubyGems server (default behavior).
-
Defines the lookup key in your
$HOME/.gem/credentials
file which contains your private credentials for authentication to your private server (more on this below).
Gem Credentials
With your gem specification metadata established, you are ready to publish your gem to a public or
private server. If this is your first time publishing a gem and no gem credentials have been
configured, you’ll be prompted for them. Gem credentials are stored in the RubyGems
$HOME/.gem/credentials
file. From this point forward, future gem publishing will use your stored
credentials instead.
Multiple credentials can be stored in the $HOME/.gem/credentials
file as well. Example:
:rubygems_api_key: 2a0b460650e67d9b85a60e183defa376
https://private.example.com: Basic dXNlcjpwYXNzd29yZA==
Notice how the first line contains credentials for the public RubyGems server while the second line is for our private example server. You’ll also notice that the key is not a symbol but a URL string to our private server. This is important because this is how we link our gem specification metadata to our private credentials. To illustrate further, here are both files truncated and shown together:
# Gem Specification: The metadata which defines the private host to publish to.
spec. = {"allowed_push_host" => "https://private.example.com"}
# Gem Credentials: The URL value -- shown above -- which becomes the key for enabling authentication.
https://private.example.com: Basic dXNlcjpwYXNzd29yZA==
When the above are linked together, you enable Gemsmith to publish your gem using only the following command:
gemsmith --publish
This is especially powerful when publishing to GitHub Packages which would look like this when properly configured (truncated for brevity while using fake data):
# Gem specification
spec. = {"allowed_push_host" => "https://rubygems.pkg.github.com/alchemists"}
# Gem credentials
https://rubygems.pkg.github.com/alchemists: Bearer ghp_c5b8d394abefebbf45c7b27b379c74978923
Lastly, should you need to delete a credential (due to a bad login/password for example), you can
open the $HOME/.gem/credentials
in your default editor and remove the line(s) you don’t need. Upon
next publish of your gem, you’ll be prompted for the missing credentials.
Bundler Configuration
So far, I’ve shown how to privately publish a gem but now we need to teach Bundler how to install the gem as dependency within your upstream project. For demonstration purposes, I’m going to assume you are using GitHub Packages as your private gem server. You should be able to quickly translate this documentation if using an alternate private gem server, though.
The first step is to create your own GitHub Personal Access Token (PAT) which is fast to do by following GitHub’s own documentation. At a minimum, you’ll need to enable repo and packages scopes with read/write access.
With your PAT in hand, you’ll need to ensure Bundler can authenticate to the private GitHub Packages gem server by running the following:
bundle config set --global rubygems.pkg.github.com <your GitHub handle>:<PAT>
# Example: bundle config set --global rubygems.pkg.github.com jdoe:ghp_c5b8d394abefebbf45c7b27b379c74978923
💡 Using Bundler’s --global
flag ensures you only have to define these credentials once for all
projects which reduces maintenance burden on you. The path to this global configuration can be found
here: $HOME/.config/bundler/configuration.yml
.
Lastly, you can add this gem to your Gemfile
as follows:
source "https://rubygems.pkg.github.com/alchemists" do
gem "demo", "~> 0.0"
end
At this point — if you run bundle install
— you should see the following in your console:
Fetching gem from https://rubygems.pkg.github.com/alchemists/...
Resolving dependencies...Fetching gem from https://rubygems.org/.....
If so, you’re all set!
GitHub Actions/Packages Automation
Earlier, I hinted at using GitHub Packages but what if you could automate the entire publishing process? Well, good news, you can by using GitHub Actions to publish your packages. Here’s the YAML necessary to accomplish this endeavor:
name: Gemsmith
on:
push:
branches: main
jobs:
build:
runs-on: ubuntu-latest
container:
image: ruby:latest
permissions:
contents: write
packages: write
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: '0'
ref: ${{github.head_ref}}
- name: Setup
run: |
git config user.email "[email protected]"
git config user.name "Gemsmith Publisher"
mkdir -p $HOME/.gem
printf "%s\n" "https://rubygems.pkg.github.com/example: Bearer ${{secrets.GITHUB_TOKEN}}" > $HOME/.gem/credentials
chmod 0600 $HOME/.gem/credentials
- name: Install
run: gem install gemsmith
- name: Publish
run: |
if git describe --tags --abbrev=0 > /dev/null 2>&1; then
gemsmith --publish
else
printf "%s\n" "First gem version must be manually created. Skipping."
fi
The above will ensure the following:
-
Only the first version requires manual publishing (hence the check for existing Git tags).
-
Duplicate versions are always skipped.
-
Only when a new version is detected (by changing your gemspec version) and you are on the
main
branch will a new version be automatically published.
This entire workflow is explained in my talk on this exact subject too.
Development
To contribute, run:
git clone https://github.com/bkuhlmann/gemsmith
cd gemsmith
bin/setup
You can also use the IRB console for direct access to all objects:
bin/console
Tests
To test, run:
bin/rake
Credits
Engineered by Brooke Kuhlmann.