:toc: macro
:toclevels: 5
:figure-caption!:

= Gemsmith

Gemsmith is a command line interface for smithing Ruby gems. Perfect for when you need a
professional and robust tool beyond link:https://bundler.io[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.

toc::[]

== Features

* Supports all link:https://www.alchemists.io/projects/rubysmith[Rubysmith] features.
* Supports basic gem skeletons or more advanced Command Line Interface (CLI) skeletons.
* Supports gem building, installing for local development, and publishing.
* Supports the editing and viewing of installed gems.

== Requirements

. A UNIX-based system.
. link:https://www.ruby-lang.org[Ruby].
. link:https://rubygems.org[RubyGems].

== Upgrade

If upgrading from 17.0.0 to 18.0.0, you'll want to be aware of the following changes:

* The `--dead_end` build option has been removed.
* The Milestoner configuration has been updated to support ASCII Doc format by default and the
  ability to sign tags has been removed (this happens dynamically based on your Git configuration
  which gives you more flexibility).

== Setup

To install, run:

[source,bash]
----
gem install gemsmith
----

== Usage

=== Command Line Interface (CLI)

From the command line, type: `gemsmith --help`

....
USAGE:
  -b, --build NAME [options]               Build new project.
  -c, --config ACTION                      Manage gem configuration: edit or view.
      --edit GEM                           Edit installed gem in default editor.
  -h, --help                               Show this message.
  -i, --install [NAME]                     Install gem for local development.
  -p, --publish [NAME]                     Publish gem to remote gem server.
  -v, --version                            Show gem version.
      --view GEM                           View installed gem in default browser.

BUILD OPTIONS:
      --[no-]amazing_print                 Add Amazing Print gem. Default: true.
      --[no-]bundler-leak                  Add Bundler Leak gem. Default: true.
      --[no-]caliber                       Add Caliber gem. Default: true.
      --[no-]circle_ci                     Add Circle CI configuration and badge. Default: false.
      --[no-]citation                      Add citation documentation. Default: true.
      --[no-]community                     Add community documentation. Default: false.
      --[no-]conduct                       Add code of conduct documentation. Default: true.
      --[no-]console                       Add console script. Default: true.
      --[no-]contributions                 Add contributions documentation. Default: true.
      --[no-]debug                         Add Debug gem. Default: true.
      --[no-]funding                       Add GitHub funding configuration. Default: false.
      --[no-]git                           Add Git. Default: true.
      --[no-]git_hub                       Add GitHub templates. Default: false.
      --[no-]git-lint                      Add Git Lint gem. Default: true.
      --[no-]guard                         Add Guard gem. Default: true.
      --[no-]license                       Add license documentation. Default: true.
      --max                                Use maximum/enabled options. Default: false.
      --min                                Use minimum/disabled options. Default: false.
      --[no-]rake                          Add Rake gem. Default: true.
      --[no-]readme                        Add readme documentation. Default: true.
      --[no-]reek                          Add Reek gem. Default: true.
      --[no-]refinements                   Add Refinements gem. Default: true.
      --[no-]rspec                         Add RSpec gem. Default: true.
      --[no-]security                      Add security. Default: true.
      --[no-]setup                         Add setup script. Default: true.
      --[no-]simple_cov                    Add SimpleCov gem. Default: true.
      --[no-]versions                      Add version history. Default: true.
      --[no-]yard                          Add Yard gem. Default: false.
      --[no-]zeitwerk                      Add Zeitwerk gem. Default: true.
      --[no-]cli                           Add command line interface. Default: false.
....

=== 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
link:https://www.alchemists.io/projects/rubysmith/#_build[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
link:https://dry-rb.org/gems/dry-container[Dry Container]. 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:

[source,bash]
----
gemsmith --build 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:

....
  -c, --config ACTION                      Manage gem configuration: edit or view.
  -h, --help                               Show this message.
  -v, --version                            Show gem version.
....

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:

[source,bash]
----
# 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:

[source,bash]
----
# Implicit
gemsmith --publish

# Explicit
gemsmith --publish demo
----

Security is important which requires a GPG key for signing your Git tags and
link:https://www.alchemists.io/articles/ruby_gems_multi_factor_authentication/[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:

[source,bash]
----
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:

[source,bash]
----
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 link:https://www.alchemists.io/projects/xdg[XDG] environment
variables.

The default configuration is everything provided in the
link:https://www.alchemists.io/projects/rubysmith/#_configuration[Rubysmith] with the addition of
the following:

[source,yaml]
----
:build:
  :cli: false
----

Feel free to take the combined Rubysmith + Gemsmith configuration, modify, and save as your own
custom `configuration.yml`.

It is recommended that you provide URLs for your project which would be all keys found in this
section:

[source,yaml]
----
:project:
  :url:
    # 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:

[source,bash]
----
# Build
gemsmith --build 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 link:https://www.gnupg.org[GPG]:

[source,bash]
----
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:

[source,bash]
----
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:

[source,bash]
----
cd ~/.ssh
gem cert --build you@example.com
chmod 600 gem-*.pem
----

The resulting `.pem` key files can be referenced via the `signing_key` and `cert_chain` of your
`.gemspec` which Gemsmith provides for you via the `--security` build option. Example:

[source,ruby]
----
# 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
----

To learn more about gem certificates, read about RubyGems
link:https://guides.rubygems.org/security[Security].

== Private Gem Servers

By default, the following command will publicly publish your gem to
link:https://rubygems.org[RubyGems]:

[source,bash]
----
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:

[source,ruby]
----
Gem::Specification.new do |spec|
  spec.metadata = {"allowed_push_host" => "https://private.example.com"}
end
----

💡 The gemspec metadata (i.e. keys and values) _must_ be strings per the
link:https://guides.rubygems.org/specification-reference/#metadata[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:

[source,yaml]
----
: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.metadata = {"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:

[source,bash]
----
gemsmith --publish
----

This is especially powerful when publishing to
link:https://docs.github.com/en/packages/working-with-a-github-packages-registry/working-with-the-rubygems-registry[GitHub
Packages] which would look like this when properly configured (truncated for brevity while using
fake data):

....
# Gem specification
spec.metadata = {"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
link:https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/creating-a-personal-access-token[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 link:https://bundler.io[Bundler] can authenticate to
the private GitHub Packages gem server by running the following:

[source,bash]
----
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,ruby]
----
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 metadata from https://rubygems.pkg.github.com/alchemists/...
Resolving dependencies...Fetching gem metadata 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:

``` yaml
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@v3
        with:
          fetch-depth: '0'
          ref: ${{github.head_ref}}
      - name: Setup
        run: |
          git config user.email "engineering@example.com"
          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
link:https://www.alchemists.io/talks/ruby_git_hub_packages[talk] on this exact subject too.

== Promotion

Once your gem is released, let the world know about your accomplishment by posting an update to
these sites:

* link:https://rubyflow.com[RubyFlow]
* link:https://ruby.libhunt.com[Ruby Library Hunt]
* link:https://awesome-ruby.com[Awesome Ruby]
* link:https://www.ruby-toolbox.com[Ruby Toolbox]
* link:https://www.ruby-lang.org/en/community[Ruby Community]

== Development

To contribute, run:

[source,bash]
----
git clone https://github.com/bkuhlmann/gemsmith.git
cd gemsmith
bin/setup
----

You can also use the IRB console for direct access to all objects:

[source,bash]
----
bin/console
----

== Tests

To test, run:

[source,bash]
----
bundle exec rake
----

== link:https://www.alchemists.io/policies/license[License]

== link:https://www.alchemists.io/policies/security[Security]

== link:https://www.alchemists.io/policies/code_of_conduct[Code of Conduct]

== link:https://www.alchemists.io/policies/contributions[Contributions]

== link:https://www.alchemists.io/projects/gemsmith/versions[Versions]

== link:https://www.alchemists.io/community[Community]

== Credits

Engineered by link:https://www.alchemists.io/team/brooke_kuhlmann[Brooke Kuhlmann].