Vendoring

Locking versions

Hold a package at a version. update, pristine and pin leave it there until you say otherwise.

Why#

Some packages you want to hold: a major you haven't migrated to yet, a release that broke something, a dependency whose author moves fast. With importmap-rails the only way to keep update off a package was to remember not to run it. A lock is that intent, written down where the tooling reads it.

Locking a package#

Pass --lock when pinning, or lock a package already pinned. Either way the lock lands in the pin comment:

$ ./bin/importmap pin [email protected] --lock
Pinning "luxon" to vendor/javascript/luxon.js via download from https://ga.jspm.io/npm:[email protected]/build/es6/luxon.mjs
Locked "luxon" at 3.7.2

$ ./bin/importmap lock @hotwired/stimulus
Locked "@hotwired/stimulus" at 3.2.2

$ ./bin/importmap unlock luxon
Unlocked "luxon"
config/importmap.rb
pin "luxon" # @3.7.2 (locked)
pin "@hotwired/stimulus", to: "@hotwired--stimulus.js" # @3.2.2 (esm.run, locked)
pin "md5", to: "https://cdn.jsdelivr.net/npm/[email protected]/md5.js", preload: false # @2.2.0 (locked)

lock and unlock rewrite only the comment — no network, nothing else on the line is touched, single quotes and all. A remote pin gets a version comment carrying the version from its URL. A pin with no version to lock at (a custom URL, a local file) is refused with a message. To lock at a different version than the one pinned, pin that version: bin/importmap pin [email protected] --lock.

Only the packages you name are locked. The dependencies a CDN resolves alongside them keep floating, so pin md5 --lock locks md5 and leaves charenc and crypt to update.

What each command does with a lock#

CommandLocked package
updateSkipped, with a note. update --force updates it and keeps the lock at the new version.
pin luxonSkipped before any network call. --force, --lock or --no-lock proceed (see below).
pristineRedownloaded at the locked version; the lock stays. A CDN that resolves a different version than the one locked is skipped, with both versions in the message.
outdatedListed with yes in the Locked column; doesn't make the command exit 1.
pin stimulus-use --from esm.runA locked dependency pin the bundle needs is kept as-is, like any existing pin.
unpinRemoved, lock included — explicit intent.
auditUnaffected.
$ ./bin/importmap pin [email protected]
Skipping "luxon" (locked at 3.7.2; run bin/importmap unlock luxon or pass --force)

$ ./bin/importmap update
Skipping "luxon" (locked at 3.7.2; run bin/importmap unlock luxon or pass --force)
Nothing to update (every outdated package is locked; pass --force)

Moving a locked package#

CommandResult
pin [email protected] --forceRe-pins and keeps the lock, now at 4.0.0.
pin [email protected] --lockThe same — the lock moves with the pin.
pin [email protected] --no-lockRe-pins and drops the lock.
update --forceUpdates every outdated package, locked ones included, and re-locks each at its new version.
unlock luxonRemoves the lock; the next update moves the package.

The lock survives a move on purpose: "don't drift" is the intent, and moving deliberately to a new version doesn't change it.

outdated and CI#

outdated still lists a locked package that has a newer version, so you can see what you are holding back, but a lock is a version the app chose, not drift. The command exits 1 only when an unlocked package is outdated, so a CI step that runs it stays green for what you locked.

$ ./bin/importmap outdated
| Package | Current | Latest | Locked |
|---------|---------|--------|--------|
| luxon   | 3.7.2   | 3.7.3  | yes    |
| md5     | 2.2.0   | 2.3.0  |        |
  2 outdated packages found (1 locked)