{"id":13484078,"url":"https://github.com/bkuhlmann/gemsmith","last_synced_at":"2025-04-08T09:09:52.489Z","repository":{"id":665344,"uuid":"1848617","full_name":"bkuhlmann/gemsmith","owner":"bkuhlmann","description":"A command line interface for smithing Ruby gems.","archived":false,"fork":false,"pushed_at":"2024-05-19T22:43:28.000Z","size":2230,"stargazers_count":447,"open_issues_count":0,"forks_count":15,"subscribers_count":15,"default_branch":"main","last_synced_at":"2024-05-19T23:32:46.171Z","etag":null,"topics":["gem-skeleton","rubygems"],"latest_commit_sha":null,"homepage":"https://alchemists.io/projects/gemsmith","language":"Ruby","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"other","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/bkuhlmann.png","metadata":{"files":{"readme":"README.adoc","changelog":null,"contributing":null,"funding":".github/FUNDING.yml","license":"LICENSE.adoc","code_of_conduct":null,"threat_model":null,"audit":null,"citation":"CITATION.cff","codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null},"funding":{"github":["bkuhlmann"]}},"created_at":"2011-06-04T23:31:01.000Z","updated_at":"2024-05-28T02:25:15.835Z","dependencies_parsed_at":"2023-07-08T18:18:24.846Z","dependency_job_id":"3d9e69be-e304-49d4-b82e-efffb726e9c4","html_url":"https://github.com/bkuhlmann/gemsmith","commit_stats":{"total_commits":1433,"total_committers":3,"mean_commits":477.6666666666667,"dds":"0.20446615491974873","last_synced_commit":"37385f3325f89ed39903e7468bab721dd9d8ca5d"},"previous_names":[],"tags_count":153,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bkuhlmann%2Fgemsmith","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bkuhlmann%2Fgemsmith/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bkuhlmann%2Fgemsmith/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bkuhlmann%2Fgemsmith/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/bkuhlmann","download_url":"https://codeload.github.com/bkuhlmann/gemsmith/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247809964,"owners_count":20999816,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["gem-skeleton","rubygems"],"created_at":"2024-07-31T17:01:19.083Z","updated_at":"2025-04-08T09:09:52.460Z","avatar_url":"https://github.com/bkuhlmann.png","language":"Ruby","funding_links":["https://github.com/sponsors/bkuhlmann"],"categories":["Gem Generators","Ruby"],"sub_categories":[],"readme":":toc: macro\n:toclevels: 5\n:figure-caption!:\n\n:containable_link: link:https://alchemists.io/projects/containable[Containable]\n:infusible_link: link:https://alchemists.io/projects/infusible[Infusible]\n:ruby_gems_link: link:https://rubygems.org[RubyGems]\n:sod_link: link:https://alchemists.io/projects/sod[Sod]\n\n= Gemsmith\n\nGemsmith 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.\n\ntoc::[]\n\n== Features\n\n* Supports all link:https://alchemists.io/projects/rubysmith[Rubysmith] features.\n* Supports basic gem or more advanced {sod_link}-based Command Line Interface (CLI) skeletons.\n* Supports gem building, installing for local development, and publishing.\n* Supports the editing and viewing of installed gems.\n\n== Requirements\n\n. A UNIX-based system.\n. link:https://www.ruby-lang.org[Ruby].\n. {ruby_gems_link}.\n\n== Setup\n\nTo install _with_ security, run:\n\n[source,bash]\n----\n# 💡 Skip this line if you already have the public certificate installed.\ngem cert --add \u003c(curl --compressed --location https://alchemists.io/gems.pem)\ngem install gemsmith --trust-policy HighSecurity\n----\n\nTo install _without_ security, run:\n\n[source,bash]\n----\ngem install gemsmith\n----\n\n== Usage\n\n=== Command Line Interface (CLI)\n\nFrom the command line, type: `gemsmith --help`\n\nimage:https://alchemists.io/images/projects/gemsmith/screenshots/usage.png[Usage,width=729,height=462,role=focal_point]\n\n==== Build\n\nThe 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\nare enabled by default. For detailed documentation on all supported flags, see the link:https://alchemists.io/projects/rubysmith/#_build[Rubysmith] documentation.\n\nThe 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_link} and {infusible_link} 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:\n\n[source,bash]\n----\ngemsmith build --name demo --cli\n----\n\nThe 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:\n\n....\n  -v, --version     Show version.\n  -h, --help        Show this message.\n  config            Manage configuration.\n....\n\nFrom here you can add whatever you wish to make an awesome CLI gem for others to enjoy.\n\n==== Install\n\nAfter you've designed, implemented, and built your gem, you'll want to test it out within your local\nenvironment by installing it. You can do this by running:\n\n[source,bash]\n----\n# Implicit\ngemsmith --install\n\n# Explicit\ngemsmith --install demo\n----\n\nGemsmith can be used to install any gem, in fact. Doesn't matter if the gem was built by Gemsmith,\nBundler, or some other tool. As long as your gem has a `*.gemspec` file, Gemsmith will be able to\ninstall it.\n\n==== Publish\n\nOnce you've built your gem; installed it locally; and thoroughly tested it, you'll want to publish\nyour gem so anyone in the world can make use of it. You can do this by running the following:\n\n[source,bash]\n----\n# Implicit\ngemsmith --publish\n\n# Explicit\ngemsmith --publish demo\n----\n\nSecurity is important which requires a GPG key for signing your Git tags and\nlink:https://alchemists.io/articles/ruby_gems_multi_factor_authentication/[RubyGems Multi-Factor\nAuthentication] for publishing to RubyGems. Both of which are enabled by default. You'll want to\nread through the linked article which delves into how Gemsmith automatically makes use of your\nYubiKey to authenticate with RubyGems. Spending the time to set this up will allow Gemsmith to use\nof your YubiKey for effortless and secure publishing of new versions of your gems so I highly\nrecommend doing this.\n\nAs with installing a gem, Gemsmith can be used to publish existing gems which were not built by\nGemsmith too. As long as your gem has a `*.gemspec` file with a valid version, Gemsmith will be able\nto publish it.\n\n==== Edit\n\nGemsmith can be used to edit existing gems on your local system. You can do this by running:\n\n[source,bash]\n----\ngemsmith --edit \u003cname of gem\u003e\n----\n\nIf multiple versions of the same gem are detected, you'll be prompted to pick which gem you want to\nedit. Otherwise, the gem will immediately be opened within your default editor (or whatever you\nhave set in your `EDITOR` environment variable).\n\nEditing a local gem is a great way to learn from others or quickly debug issues.\n\n==== View\n\nGemsmith can be used to view existing gem documentation. You can do this by running:\n\n[source,bash]\n----\ngemsmith --view \u003cname of gem\u003e\n----\n\nIf multiple versions of the same gem are detected, you'll be prompted to pick which gem you want to\nview. Otherwise, the gem will immediately be opened within your default browser.\n\nViewing a gem is a great way to learn more about the gem and documentation in general.\n\n=== Configuration\n\nThis gem can be configured via a global configuration:\n\n....\n$HOME/.config/gemsmith/configuration.yml\n....\n\nIt can also be configured via link:https://alchemists.io/projects/xdg[XDG] environment\nvariables.\n\nThe default configuration is everything provided in the\nlink:https://alchemists.io/projects/rubysmith/#_configuration[Rubysmith] with the addition of\nthe following:\n\n[source,yaml]\n----\nbuild:\n  cli: false\n----\n\nIt is recommended that you provide URLs for your project which would be all keys found in this\nsection:\n\n[source,yaml]\n----\nproject:\n  uri:\n    # Add sub-key values here.\n----\n\nWhen these values exist, you'll benefit from having this information added to your generated\n`gemspec` and project documentation. Otherwise -- if these values are empty -- they are removed from\nnew gem generation.\n\n=== Workflows\n\nWhen building/testing your gem locally, a typical workflow is:\n\n[source,bash]\n----\n# Build\ngemsmith build --name demo\n\n# Design, Implement and Test.\ncd demo\nbundle exec rake\n\n# Install\ngemsmith --install\n\n# Publish\ngemsmith --publish\n----\n\n=== Security\n\n==== Git Signing Key\n\nTo securely sign your Git tags, install and configure link:https://www.gnupg.org[GPG]:\n\n[source,bash]\n----\nbrew install gpg\ngpg --gen-key\n----\n\nWhen creating your GPG key, choose these settings:\n\n* Key kind: RSA and RSA (default)\n* Key size: 4096\n* Key validity: 0\n* Real Name: `\u003cyour name\u003e`\n* Email: `\u003cyour email\u003e`\n* Passphrase: `\u003cyour passphrase\u003e`\n\nTo obtain your key, run the following and take the part after the forward slash:\n\n[source,bash]\n----\ngpg --list-keys | grep pub\n----\n\nAdd your key to your global Git configuration in the `[user]` section. Example:\n\n....\n[user]\n  signingkey = \u003cyour GPG key\u003e\n....\n\nNow, when publishing your gems with Gemsmith (i.e. `bundle exec rake publish`), signing of your Git\ntag will happen automatically.\n\n==== Gem Certificates\n\nTo create a certificate for your gems, run the following:\n\n[source,bash]\n----\ncd ~/.gem\ngem cert build you@example.com --days 730\ngem cert --add gem-public_cert.pem\ncp gem-public_cert.pem \u003cpath/to/server/public/folder\u003e/gems.pem\n----\n\nThe above breaks down as follows:\n\n* *Source*: The `~/.gem` directory is where your credentials and certificates are stored. This is also where the `Gem.default_key_path` and `Gem.default_cert_path` methods look for your certificates. I'll talk more about these shortly.\n* *Build*: Builds your `gem-private_key.pem` and `gem-public_cert.pem` certificates with a two year duration (i.e. `365 * 2`) before expiring. You can also see this information on the {ruby_gems_link} page for your gem (scroll to the bottom). Security-wise, this isn't great but the way {ruby_gems_link} 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.\n* *Add*: Once your public certificate has been built, you'll need to add it to your registry so {ruby_gems_link} can look up and verify your certificate upon gem install.\n* *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 \u003c(curl --compressed --location https://alchemists.io/gems.pem)`.\n\nEarlier, 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`:\n\n[source,ruby]\n----\n# frozen_string_literal: true\n\nGem::Specification.new do |spec|\n  # Truncated for brevity.\n  spec.signing_key = Gem.default_key_path\n  spec.cert_chain = [Gem.default_cert_path]\nend\n----\n\nThe 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:\n\n[source,bash]\n----\ngem install \u003cyour_gem\u003e --trust-policy HighSecurity\n----\n\nTo learn more about gem certificates, check out the RubyGems\nlink:https://guides.rubygems.org/security[Security] documentation.\n\n=== Private Gem Servers\n\nBy default, the following command will publicly publish your gem to {ruby_gems_link}:\n\n[source,bash]\n----\ngemsmith --publish\n----\n\nYou can change this behavior by adding metadata to your gemspec that will allow Gemsmith to publish\nyour gem to an alternate/private gem server instead. This can be done by updating your gem\nspecification and RubyGems credentials.\n\n==== Gem Specification Metadata\n\nAdd the following gemspec metadata to privately publish new versions of your gem:\n\n[source,ruby]\n----\nGem::Specification.new do |spec|\n  spec.metadata = {\"allowed_push_host\" =\u003e \"https://private.example.com\"}\nend\n----\n\n💡 The gemspec metadata (i.e. keys and values) _must_ be strings per the\nlink:https://guides.rubygems.org/specification-reference/#metadata[RubyGems Specification].\n\nUse of the `allowed_push_host` key provides two important capabilities:\n\n* Prevents you from accidentally publishing your private gem to the public RubyGems server (default\n  behavior).\n* Defines the lookup key in your `$HOME/.gem/credentials` file which contains your private\n  credentials for authentication to your private server (more on this below).\n\n==== Gem Credentials\n\nWith your gem specification metadata established, you are ready to publish your gem to a public or\nprivate server. If this is your first time publishing a gem and no gem credentials have been\nconfigured, you'll be prompted for them. Gem credentials are stored in the RubyGems\n`$HOME/.gem/credentials` file. From this point forward, future gem publishing will use your stored\ncredentials instead.\n\nMultiple credentials can be stored in the `$HOME/.gem/credentials` file as well. Example:\n\n[source,yaml]\n----\n:rubygems_api_key: 2a0b460650e67d9b85a60e183defa376\nhttps://private.example.com: Basic dXNlcjpwYXNzd29yZA==\n----\n\nNotice how the first line contains credentials for the public RubyGems server while the second line\nis for our private example server. You'll also notice that the key is not a symbol but a URL string\nto our private server. This is important because this is how we link our gem specification metadata\nto our private credentials. To illustrate further, here are both files truncated and shown together:\n\n....\n# Gem Specification: The metadata which defines the private host to publish to.\nspec.metadata = {\"allowed_push_host\" =\u003e \"https://private.example.com\"}\n\n# Gem Credentials: The URL value -- shown above -- which becomes the key for enabling authentication.\nhttps://private.example.com: Basic dXNlcjpwYXNzd29yZA==\n....\n\nWhen the above are linked together, you enable Gemsmith to publish your gem using only the following\ncommand:\n\n[source,bash]\n----\ngemsmith --publish\n----\n\nThis is especially powerful when publishing to\nlink:https://docs.github.com/en/packages/working-with-a-github-packages-registry/working-with-the-rubygems-registry[GitHub\nPackages] which would look like this when properly configured (truncated for brevity while using\nfake data):\n\n....\n# Gem specification\nspec.metadata = {\"allowed_push_host\" =\u003e \"https://rubygems.pkg.github.com/alchemists\"}\n\n# Gem credentials\nhttps://rubygems.pkg.github.com/alchemists: Bearer ghp_c5b8d394abefebbf45c7b27b379c74978923\n....\n\nLastly, should you need to delete a credential (due to a bad login/password for example), you can\nopen the `$HOME/.gem/credentials` in your default editor and remove the line(s) you don't need. Upon\nnext publish of your gem, you'll be prompted for the missing credentials.\n\n==== Bundler Configuration\n\nSo far, I've shown how to privately _publish_ a gem but now we need to teach Bundler how to install\nthe gem as dependency within your upstream project. For demonstration purposes, I'm going to assume\nyou are using GitHub Packages as your private gem server. You should be able to quickly translate\nthis documentation if using an alternate private gem server, though.\n\nThe first step is to create your own GitHub Personal Access Token (PAT) which is fast to do by\nfollowing GitHub's own\nlink:https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/creating-a-personal-access-token[documentation].\nAt a minimum, you'll need to enable _repo_ and _packages_ scopes with read/write access.\n\nWith your PAT in hand, you'll need to ensure link:https://bundler.io[Bundler] can authenticate to\nthe private GitHub Packages gem server by running the following:\n\n[source,bash]\n----\nbundle config set --global rubygems.pkg.github.com \u003cyour GitHub handle\u003e:\u003cPAT\u003e\n# Example: bundle config set --global rubygems.pkg.github.com jdoe:ghp_c5b8d394abefebbf45c7b27b379c74978923\n----\n\n💡 Using Bundler's `--global` flag ensures you only have to define these credentials once for _all_\nprojects which reduces maintenance burden on you. The path to this global configuration can be found\nhere: `$HOME/.config/bundler/configuration.yml`.\n\nLastly, you can add this gem to your `Gemfile` as follows:\n\n[source,ruby]\n----\nsource \"https://rubygems.pkg.github.com/alchemists\" do\n  gem \"demo\", \"~\u003e 0.0\"\nend\n----\n\nAt this point -- if you run `bundle install` -- you should see the following in your console:\n\n....\nFetching gem metadata from https://rubygems.pkg.github.com/alchemists/...\nResolving dependencies...Fetching gem metadata from https://rubygems.org/.....\n....\n\nIf so, you're all set!\n\n==== GitHub Actions/Packages Automation\n\nEarlier, I hinted at using GitHub Packages but what if you could automate the entire publishing\nprocess? Well, good news, you can by using GitHub Actions to publish your packages. Here's the YAML\nnecessary to accomplish this endeavor:\n\n``` yaml\nname: Gemsmith\n\non:\n  push:\n    branches: main\n\njobs:\n  build:\n    runs-on: ubuntu-latest\n    container:\n      image: ruby:latest\n    permissions:\n      contents: write\n      packages: write\n\n    steps:\n      - name: Checkout\n        uses: actions/checkout@v4\n        with:\n          fetch-depth: '0'\n          ref: ${{github.head_ref}}\n      - name: Setup\n        run: |\n          git config user.email \"engineering@example.com\"\n          git config user.name \"Gemsmith Publisher\"\n          mkdir -p $HOME/.gem\n          printf \"%s\\n\" \"https://rubygems.pkg.github.com/example: Bearer ${{secrets.GITHUB_TOKEN}}\" \u003e $HOME/.gem/credentials\n          chmod 0600 $HOME/.gem/credentials\n      - name: Install\n        run: gem install gemsmith\n      - name: Publish\n        run: |\n          if git describe --tags --abbrev=0 \u003e /dev/null 2\u003e\u00261; then\n            gemsmith --publish\n          else\n            printf \"%s\\n\" \"First gem version must be manually created. Skipping.\"\n          fi\n```\n\nThe above will ensure the following:\n\n* Only the first version requires manual publishing (hence the check for existing Git tags).\n* Duplicate versions are always skipped.\n* Only when a new version is detected (by changing your gemspec version) and you are on the `main`\n  branch will a new version be automatically published.\n\nThis entire workflow is explained in my\nlink:https://alchemists.io/talks/ruby_git_hub_packages[talk] on this exact subject too.\n\n== Development\n\nTo contribute, run:\n\n[source,bash]\n----\ngit clone https://github.com/bkuhlmann/gemsmith\ncd gemsmith\nbin/setup\n----\n\nYou can also use the IRB console for direct access to all objects:\n\n[source,bash]\n----\nbin/console\n----\n\n== Tests\n\nTo test, run:\n\n[source,bash]\n----\nbin/rake\n----\n\n== link:https://alchemists.io/policies/license[License]\n\n== link:https://alchemists.io/policies/security[Security]\n\n== link:https://alchemists.io/policies/code_of_conduct[Code of Conduct]\n\n== link:https://alchemists.io/policies/contributions[Contributions]\n\n== link:https://alchemists.io/policies/developer_certificate_of_origin[Developer Certificate of Origin]\n\n== link:https://alchemists.io/projects/gemsmith/versions[Versions]\n\n== link:https://alchemists.io/community[Community]\n\n== Credits\n\nEngineered by link:https://alchemists.io/team/brooke_kuhlmann[Brooke Kuhlmann].\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbkuhlmann%2Fgemsmith","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fbkuhlmann%2Fgemsmith","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbkuhlmann%2Fgemsmith/lists"}