Dependabot helps you keep your gems updated, and it also supports other package managers such as NPM. However, it doesn’t know anything about the JavaScript packages in a Rails app that uses importmap-rails, and the feature request for that has been open since February 2023.
That means that when you dropped Node from your Rails app, you also dropped the tooling that would tell you a pinned package had fallen behind or picked up a published advisory.
importmap-update is a GitHub Action that closes that gap. It runs
bin/importmap outdated and bin/importmap audit, opens pull requests for what
it finds, and keeps those pull requests in sync on every run after that.
Why importmaps get left behind
An importmap-rails app records its JavaScript dependencies in
config/importmap.rb. Running bin/importmap pin react resolves the package
through a CDN like JSPM, downloads it into vendor/javascript, and writes a
one-line pin with the version in a trailing comment:
pin "react" # @19.1.0
Your own asset pipeline serves the vendored copy from there. What lands in
source control is that comment and the JavaScript file itself, so there’s no
package.json and no lockfile for Dependabot to read. The information it would
want is available, just somewhere else: bin/importmap outdated compares your
pins against the npm registry, and bin/importmap audit checks them for known
advisories.
Half of that is already wired up for you. Since Rails 7.2, new apps are
generated with a .github/workflows/ci.yml that includes a scan_js job:
- name: Scan for security vulnerabilities in JavaScript dependencies
run: bin/importmap audit
A published advisory against one of your pins will fail the build, which is the part you’d want to hear about first. It still stops at telling you, though. Someone has to look up the fixed version and pin it by hand.
Nothing runs bin/importmap outdated. Ordinary version drift stays invisible
until it turns into an advisory, and by then you’re upgrading in a hurry.
Getting started
Allow GitHub Actions to create pull requests in your repository settings (Actions > General > Workflow permissions), then add a workflow:
# .github/workflows/importmap-updates.yml
name: Importmap updates
on:
schedule:
- cron: "0 9 * * 1" # Mondays 09:00 UTC
workflow_dispatch:
permissions:
contents: write
pull-requests: write
jobs:
update:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: ruby/setup-ruby@v1
with:
bundler-cache: true
- uses: thoughtbot/importmap-update@v1
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
That’s the whole setup. Configuration is optional, and the defaults match what you’d probably configure anyway.
Grouping
Opening a pull request for every outdated package gets noisy fast, and rolling
all of them into one pull request makes for a diff nobody enjoys reviewing. The
action splits updates into security, patch, minor, and major buckets, and you
choose a strategy for each one in .github/importmap-updates.yml:
version: 1
grouping:
security: { strategy: individual } # one PR per vulnerable package
patch: { strategy: grouped } # one PR for all patch bumps
minor: { strategy: grouped } # one PR for all minor bumps
major: { strategy: individual } # one PR per major bump
open_pull_requests_limit: 10
labels: [dependencies, javascript, importmap]
branch_prefix: "importmap-updates"
Every field is optional. Both open_pull_requests_limit and
open-pull-requests-limit work, which saves you a round of debugging if you’re
copying from a Dependabot config.
Security updates are handled differently
A vulnerable package becomes a security pull request even when the fix happens to be a major version bump. The body still records the bump kind, so reviewers know to expect breaking changes before they open the diff.
Security pull requests are also exempt from open_pull_requests_limit. When the
budget is tight, we fill the remaining slots with major bumps first, then minor,
then patch, since the bigger bumps are the ones somebody needs to set aside time
for.
What happens on the next run
An open pull request goes stale as soon as another version ships. On each run, the action brings its own pull requests back in line with what’s actually outdated: the ones whose versions have moved get updated in place, and the ones whose packages aren’t outdated anymore get closed with a comment saying why. Pull requests it didn’t open are left alone, even on a branch matching your configured prefix.
Try it in dry-run mode first
Set dry-run: "true" and the action runs end to end without side effects. It
reads your importmap, builds the plan, compares it against your open pull
requests, and logs every operation it would have performed. That’s worth doing
on a repository whose JavaScript dependencies haven’t been looked at in a while,
before it opens 10 pull requests at once.
Alternatives
Depfu is the only one I could find. It has supported import maps since 2023, it’s free for public repos, and it’s a paid hosted service for private ones. If you’d rather have one service watching your gems and your JavaScript together, it’s worth a look.
Issues and pull requests are welcome on GitHub.