Skip to content

whereFromPlugin/whereToPlugin never match prerelease versions, silently skipping migrations #66

Description

@taylortom

Subject of the issue

whereFromPlugin and whereToPlugin never match a plugin whose version is a semver prerelease, so any migration gated on a version range is silently skipped for those courses.

Both call semver.satisfies without includePrerelease (api/where.js):

if (config.version && !semver.satisfies(plugin.version, config.version)) return false

node-semver excludes prerelease versions from every range unless a comparator in that range carries a prerelease with the same major.minor.patch. A plugin at 3.1.0-alpha.1 therefore matches no ordinary range at all:

version              range               satisfies
3.1.0-alpha.1        <=4.0.0             false
3.1.0-alpha.1        >=3.0.0             false
3.1.0-alpha.1        >=3.1.0             false
3.1.0-alpha.1        *                   false
3.1.0-alpha.1        x                   false
3.1.0-alpha.1        >=3.1.0-0           true
3.1.0               <=4.0.0             true

* and x returning false is the part that makes this hard to spot by reading a script.

Why it matters

It fails silently, and in the direction that looks like success. A where* step returning false is how a script says "not applicable here" — so nothing throws, nothing is logged at error level, the migration run reports success, and the content arrives unmigrated. It surfaces much later as missing or misshapen data, with no signal pointing back at the migration.

The affected versions are not exotic. Two ordinary cases hit it:

  • Prerelease contrib builds deployed to production, e.g. adapt-contrib-slider@3.1.0-alpha.1 — the same example that prompted addPlugin and updatePlugin with alpha/beta version numbers #36.
  • Forked builds, where an install carries a locally patched plugin versioned 3.0.5-myfork-0.0.1. These are common in long-lived estates, and a fork is exactly the case where a migration matters most.

There is no workaround at the range level. Because node-semver requires a comparator sharing the version's exact major.minor.patch, and forks span arbitrary tuples, no single range can cover them — authors have to abandon the config form and reimplement the comparison in a callback.

Precedent

#36 established that prerelease versions are legitimate plugin versions in this tool: addPlugin and updatePlugin validate with semver.valid, which accepts them. api/where.js did not get the same treatment, so a version that is valid enough to write is not matchable to read.

Suggested fix

--- a/api/where.js
+++ b/api/where.js
       return fromPlugins.some(plugin => {
         if (config.name && plugin.name !== config.name) return false
-        if (config.version && !semver.satisfies(plugin.version, config.version)) return false
+        if (config.version && !semver.satisfies(plugin.version, config.version, { includePrerelease: true })) return false
         return true
       })

and the identical line in whereToPlugin.

includePrerelease keeps normal ordering intact, so this stays correct at the boundaries — 4.0.0-beta.1 satisfies <=4.0.0 (it precedes the release, and pre-4.0.0 data should migrate), while 4.0.1-alpha.1 does not.

I don't think this needs an opt-out. A migration author writing <=4.0.0 means "versions up to 4.0.0", and there is no plausible reading where a fork or beta of 3.0.5 should be excluded from that.

Test coverage

Worth adding a fixture with a prerelease fromPlugins version, since a script can pass its whole suite on clean versions while failing in production on a fork — which is what makes this invisible today.

Happy to raise a PR for this if it's welcome.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions