Skip to content

Commit

Permalink
Fixes #2758 by normalizing references for .canary to .alpha.
Browse files Browse the repository at this point in the history
  • Loading branch information
zachleat committed Jan 19, 2023
1 parent a79788c commit 3810ccf
Show file tree
Hide file tree
Showing 3 changed files with 325 additions and 11 deletions.
20 changes: 9 additions & 11 deletions src/UserConfig.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
const chalk = require("kleur");
const semver = require("semver");
const { DateTime } = require("luxon");

const EventEmitter = require("./Util/AsyncEventEmitter");
const EleventyCompatibility = require("./Util/Compatibility");
const EleventyBaseError = require("./EleventyBaseError");
const BenchmarkManager = require("./BenchmarkManager");
const merge = require("./Util/Merge");

const debug = require("debug")("Eleventy:UserConfig");
const pkg = require("../package.json");

class UserConfigError extends EleventyBaseError {}

Expand Down Expand Up @@ -106,15 +107,12 @@ class UserConfig {
this.dataFileDirBaseNameOverride = false;
}

versionCheck(expected) {
if (
!semver.satisfies(pkg.version, expected, {
includePrerelease: true,
})
) {
throw new UserConfigError(
`This project requires the Eleventy version to match '${expected}' but found ${pkg.version}. Use \`npm update @11ty/eleventy -g\` to upgrade the eleventy global or \`npm update @11ty/eleventy --save\` to upgrade your local project version.`
);
// compatibleRange is optional in 2.0.0-beta.2
versionCheck(compatibleRange) {
let compat = new EleventyCompatibility(compatibleRange);

if (!compat.isCompatible()) {
throw new UserConfigError(compat.getErrorMessage());
}
}

Expand Down
53 changes: 53 additions & 0 deletions src/Util/Compatibility.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
const semver = require("semver");
const path = require("path");
const { TemplatePath } = require("@11ty/eleventy-utils");

const pkg = require("../../package.json");
const debug = require("debug")("Eleventy:Compatibility");

class Compatibility {
static NORMALIZE_PRERELEASE_REGEX = /\-canary\b/g;

constructor(compatibleRange) {
this.compatibleRange = Compatibility.getCompatibilityValue(compatibleRange);
}

static normalizeIdentifier(identifier) {
return identifier.replace(Compatibility.NORMALIZE_PRERELEASE_REGEX, "-alpha");
}

static getCompatibilityValue(compatibleRange) {
if (compatibleRange) {
return compatibleRange;
}

try {
// fetch from project’s package.json
let projectPackageJson = require(path.join(TemplatePath.getWorkingDir(), "package.json"));
return projectPackageJson["11ty"]?.compatibility;
} catch (e) {
debug("Could not find a project package.json for compatibility version check: %O", e);
return; // do nothing, no compatibility information to check
}
}

isCompatible() {
return Compatibility.satisfies(pkg.version, this.compatibleRange);
}

static satisfies(version, compatibleRange) {
return semver.satisfies(
Compatibility.normalizeIdentifier(version),
Compatibility.normalizeIdentifier(compatibleRange),
{
includePrerelease: true,
}
);
}

getErrorMessage() {
return `We found Eleventy version '${pkg.version}' which does not meet the required version range: '${this.compatibleRange}'. Use \`npm install @11ty/eleventy\` to upgrade your local project to the latest Eleventy version (or \`npm install @11ty/eleventy -g\` to upgrade the globally installed version).`;
}
}

module.exports = Compatibility;
263 changes: 263 additions & 0 deletions test/CompatibilityTest.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,263 @@
const test = require("ava");
const EleventyCompatibility = require("../src/Util/Compatibility");

test(".canary- to .alpha- normalization (because pre-releases are alphabetic comparisons 😭)", (t) => {
t.is(EleventyCompatibility.normalizeIdentifier("2.0.0"), "2.0.0");
t.is(EleventyCompatibility.normalizeIdentifier("2.0.0-beta.1"), "2.0.0-beta.1");
t.is(EleventyCompatibility.normalizeIdentifier("2.0.0-canary.1"), "2.0.0-alpha.1");
t.is(EleventyCompatibility.normalizeIdentifier("2.0.0-alpha.1"), "2.0.0-alpha.1");
t.is(EleventyCompatibility.normalizeIdentifier(">=2.0.0-beta.1"), ">=2.0.0-beta.1");
t.is(
EleventyCompatibility.normalizeIdentifier(">=2.0.0-beta.1 || >=2.0.0-canary.1"),
">=2.0.0-beta.1 || >=2.0.0-alpha.1"
);
});

test("Version checking for plugin compatibility >=0.5.4", (t) => {
let range = ">=0.5.4";
t.true(EleventyCompatibility.satisfies("1.0.0-beta.1", range));
t.true(EleventyCompatibility.satisfies("1.0.2", range));
t.true(EleventyCompatibility.satisfies("2.0.0-canary.3", range));
t.true(EleventyCompatibility.satisfies("2.0.0-canary.18", range));
t.true(EleventyCompatibility.satisfies("2.0.0-canary.19", range));
t.true(EleventyCompatibility.satisfies("2.0.0-beta.1", range));
t.true(EleventyCompatibility.satisfies("2.0.0", range));
t.true(EleventyCompatibility.satisfies("2.0.1", range));
t.true(EleventyCompatibility.satisfies("3.0.0-canary.1", range));
t.true(EleventyCompatibility.satisfies("3.0.0-beta.1", range));
t.true(EleventyCompatibility.satisfies("3.0.0", range));
});

test("Version checking for plugin compatibility >=0.6", (t) => {
let range = ">=0.6";
t.true(EleventyCompatibility.satisfies("1.0.0-beta.1", range));
t.true(EleventyCompatibility.satisfies("1.0.2", range));
t.true(EleventyCompatibility.satisfies("2.0.0-canary.3", range));
t.true(EleventyCompatibility.satisfies("2.0.0-canary.18", range));
t.true(EleventyCompatibility.satisfies("2.0.0-canary.19", range));
t.true(EleventyCompatibility.satisfies("2.0.0-beta.1", range));
t.true(EleventyCompatibility.satisfies("2.0.0", range));
t.true(EleventyCompatibility.satisfies("2.0.1", range));
t.true(EleventyCompatibility.satisfies("3.0.0-canary.1", range));
t.true(EleventyCompatibility.satisfies("3.0.0-beta.1", range));
t.true(EleventyCompatibility.satisfies("3.0.0", range));
});

test("Version checking for plugin compatibility >=1", (t) => {
let range = ">=1"; // **not** the same as >=1.0.0
t.true(EleventyCompatibility.satisfies("1.0.0-beta.1", range));
t.true(EleventyCompatibility.satisfies("1.0.2", range));
t.true(EleventyCompatibility.satisfies("2.0.0-canary", range));
t.true(EleventyCompatibility.satisfies("2.0.0-canary.18", range));
t.true(EleventyCompatibility.satisfies("2.0.0-canary.19", range));
t.true(EleventyCompatibility.satisfies("2.0.0-beta.1", range));
t.true(EleventyCompatibility.satisfies("2.0.0", range));
t.true(EleventyCompatibility.satisfies("2.0.1", range));
t.true(EleventyCompatibility.satisfies("3.0.0-canary.1", range));
t.true(EleventyCompatibility.satisfies("3.0.0-beta.1", range));
t.true(EleventyCompatibility.satisfies("3.0.0", range));
});

test("Version checking for plugin compatibility >=1.0.0", (t) => {
let range = ">=1.0.0"; // **not** the same as >=1
t.false(EleventyCompatibility.satisfies("1.0.0-beta.1", range));
t.true(EleventyCompatibility.satisfies("1.0.2", range));
t.true(EleventyCompatibility.satisfies("2.0.0-canary", range));
t.true(EleventyCompatibility.satisfies("2.0.0-canary.18", range));
t.true(EleventyCompatibility.satisfies("2.0.0-canary.19", range));
t.true(EleventyCompatibility.satisfies("2.0.0-beta.1", range));
t.true(EleventyCompatibility.satisfies("2.0.0", range));
t.true(EleventyCompatibility.satisfies("2.0.1", range));
t.true(EleventyCompatibility.satisfies("3.0.0-canary.1", range));
t.true(EleventyCompatibility.satisfies("3.0.0-beta.1", range));
t.true(EleventyCompatibility.satisfies("3.0.0", range));
});

test("Version checking for plugin compatibility >=1.0.0 || >=1.0.0-beta || >=1.0.0-canary", (t) => {
// could be simplified to >=1
// noting that pre-1.0 versions of Eleventy did not match prereleases—which doesn’t matter because 0.12 would return false here anyway.
let range = ">=1.0.0 || >=1.0.0-beta || >=1.0.0-canary";
t.true(EleventyCompatibility.satisfies("1.0.0-beta.1", range));
t.true(EleventyCompatibility.satisfies("1.0.2", range));
t.true(EleventyCompatibility.satisfies("2.0.0-canary", range));
t.true(EleventyCompatibility.satisfies("2.0.0-canary.18", range));
t.true(EleventyCompatibility.satisfies("2.0.0-canary.19", range));
t.true(EleventyCompatibility.satisfies("2.0.0-beta.1", range));
t.true(EleventyCompatibility.satisfies("2.0.0", range));
t.true(EleventyCompatibility.satisfies("2.0.1", range));
t.true(EleventyCompatibility.satisfies("3.0.0-canary.1", range));
t.true(EleventyCompatibility.satisfies("3.0.0-beta.1", range));
t.true(EleventyCompatibility.satisfies("3.0.0", range));
});

test("Version checking for plugin compatibility >=2", (t) => {
let range = ">=2";
t.false(EleventyCompatibility.satisfies("1.0.0-beta.1", range));
t.false(EleventyCompatibility.satisfies("1.0.2", range));
t.true(EleventyCompatibility.satisfies("2.0.0-canary.3", range));
t.true(EleventyCompatibility.satisfies("2.0.0-canary.18", range));
t.true(EleventyCompatibility.satisfies("2.0.0-canary.19", range));
t.true(EleventyCompatibility.satisfies("2.0.0-beta.1", range));
t.true(EleventyCompatibility.satisfies("2.0.0", range));
t.true(EleventyCompatibility.satisfies("2.0.1", range));
t.true(EleventyCompatibility.satisfies("3.0.0-canary.1", range));
t.true(EleventyCompatibility.satisfies("3.0.0-beta.1", range));
t.true(EleventyCompatibility.satisfies("3.0.0", range));
});

test("Version checking for plugin compatibility >=2.0.0", (t) => {
// Recommend to use >=2 instead of this
let range = ">=2.0.0";
t.false(EleventyCompatibility.satisfies("1.0.0-beta.1", range));
t.false(EleventyCompatibility.satisfies("1.0.2", range));
t.false(EleventyCompatibility.satisfies("2.0.0-canary.3", range)); // Contentious! I wish this were true
t.false(EleventyCompatibility.satisfies("2.0.0-canary.18", range)); // Contentious! I wish this were true
t.false(EleventyCompatibility.satisfies("2.0.0-canary.19", range)); // Contentious! I wish this were true
t.false(EleventyCompatibility.satisfies("2.0.0-beta.1", range)); // Contentious! I wish this were true
t.true(EleventyCompatibility.satisfies("2.0.0", range));
t.true(EleventyCompatibility.satisfies("2.0.1", range));
t.true(EleventyCompatibility.satisfies("3.0.0-canary.1", range));
t.true(EleventyCompatibility.satisfies("3.0.0-beta.1", range));
t.true(EleventyCompatibility.satisfies("3.0.0", range));
});

test("Version checking for plugin compatibility >=2.0.0-canary", (t) => {
// Recommend to use >=2 instead of this
let range = ">=2.0.0-canary";
t.false(EleventyCompatibility.satisfies("1.0.0-beta.1", range));
t.false(EleventyCompatibility.satisfies("1.0.2", range));
t.true(EleventyCompatibility.satisfies("2.0.0-canary.3", range));
t.true(EleventyCompatibility.satisfies("2.0.0-canary.18", range));
t.true(EleventyCompatibility.satisfies("2.0.0-canary.19", range));
t.true(EleventyCompatibility.satisfies("2.0.0-beta.1", range));
t.true(EleventyCompatibility.satisfies("2.0.0", range));
t.true(EleventyCompatibility.satisfies("2.0.1", range));
t.true(EleventyCompatibility.satisfies("3.0.0-canary.1", range));
t.true(EleventyCompatibility.satisfies("3.0.0-beta.1", range));
t.true(EleventyCompatibility.satisfies("3.0.0", range));
});

test("Version checking for plugin compatibility >=2.0.0-canary.1", (t) => {
// Recommend to use >=2 instead of this
let range = ">=2.0.0-canary.1";
t.false(EleventyCompatibility.satisfies("1.0.0-beta.1", range));
t.false(EleventyCompatibility.satisfies("1.0.2", range));
t.true(EleventyCompatibility.satisfies("2.0.0-canary.3", range));
t.true(EleventyCompatibility.satisfies("2.0.0-canary.18", range));
t.true(EleventyCompatibility.satisfies("2.0.0-canary.19", range));
t.true(EleventyCompatibility.satisfies("2.0.0-beta.1", range));
t.true(EleventyCompatibility.satisfies("2.0.0", range));
t.true(EleventyCompatibility.satisfies("2.0.1", range));
t.true(EleventyCompatibility.satisfies("3.0.0-canary.1", range));
t.true(EleventyCompatibility.satisfies("3.0.0-beta.1", range));
t.true(EleventyCompatibility.satisfies("3.0.0", range));
});

test("Version checking for plugin compatibility >=2.0.0-beta", (t) => {
let range = ">=2.0.0-beta";
t.false(EleventyCompatibility.satisfies("1.0.0-beta.1", range));
t.false(EleventyCompatibility.satisfies("1.0.2", range));
t.false(EleventyCompatibility.satisfies("2.0.0-canary", range));
t.false(EleventyCompatibility.satisfies("2.0.0-canary.18", range));
t.false(EleventyCompatibility.satisfies("2.0.0-canary.19", range));
t.true(EleventyCompatibility.satisfies("2.0.0-beta.1", range));
t.true(EleventyCompatibility.satisfies("2.0.0", range));
t.true(EleventyCompatibility.satisfies("2.0.1", range));
t.true(EleventyCompatibility.satisfies("3.0.0-canary.1", range));
t.true(EleventyCompatibility.satisfies("3.0.0-beta.1", range));
t.true(EleventyCompatibility.satisfies("3.0.0", range));
});

test("Version checking for plugin compatibility >=2.0.0-beta.1", (t) => {
let range = ">=2.0.0-beta.1";
t.false(EleventyCompatibility.satisfies("1.0.0-beta.1", range));
t.false(EleventyCompatibility.satisfies("1.0.2", range));
t.false(EleventyCompatibility.satisfies("2.0.0-canary", range));
t.false(EleventyCompatibility.satisfies("2.0.0-canary.18", range));
t.false(EleventyCompatibility.satisfies("2.0.0-canary.19", range));
t.true(EleventyCompatibility.satisfies("2.0.0-beta.1", range));
t.true(EleventyCompatibility.satisfies("2.0.0-beta.2", range));
t.true(EleventyCompatibility.satisfies("2.0.0", range));
t.true(EleventyCompatibility.satisfies("2.0.1", range));
t.true(EleventyCompatibility.satisfies("3.0.0-canary.1", range));
t.true(EleventyCompatibility.satisfies("3.0.0-beta.1", range));
t.true(EleventyCompatibility.satisfies("3.0.0", range));
});

test("Version checking for plugin compatibility >=2.0.0-beta.2", (t) => {
let range = ">=2.0.0-beta.2";
t.false(EleventyCompatibility.satisfies("1.0.0-beta.1", range));
t.false(EleventyCompatibility.satisfies("1.0.2", range));
t.false(EleventyCompatibility.satisfies("2.0.0-canary", range));
t.false(EleventyCompatibility.satisfies("2.0.0-canary.18", range));
t.false(EleventyCompatibility.satisfies("2.0.0-canary.19", range));
t.false(EleventyCompatibility.satisfies("2.0.0-beta.1", range));
t.true(EleventyCompatibility.satisfies("2.0.0-beta.2", range));
t.true(EleventyCompatibility.satisfies("2.0.0", range));
t.true(EleventyCompatibility.satisfies("2.0.1", range));
t.true(EleventyCompatibility.satisfies("3.0.0-canary.1", range));
t.true(EleventyCompatibility.satisfies("3.0.0-beta.1", range));
t.true(EleventyCompatibility.satisfies("3.0.0", range));
});

// TODO eleventy-upgrade-help
test("Version checking for plugin compatibility >=2 <3", (t) => {
let range = ">=2 <3";
t.false(EleventyCompatibility.satisfies("1.0.0-beta.1", range));
t.false(EleventyCompatibility.satisfies("1.0.2", range));
t.true(EleventyCompatibility.satisfies("2.0.0-canary", range));
t.true(EleventyCompatibility.satisfies("2.0.0-canary.18", range));
t.true(EleventyCompatibility.satisfies("2.0.0-canary.19", range));
t.true(EleventyCompatibility.satisfies("2.0.0-beta.1", range));
t.true(EleventyCompatibility.satisfies("2.0.0", range));
t.true(EleventyCompatibility.satisfies("2.0.1", range));
t.false(EleventyCompatibility.satisfies("3.0.0-canary.1", range));
t.false(EleventyCompatibility.satisfies("3.0.0-beta.1", range));
t.false(EleventyCompatibility.satisfies("3.0.0", range));
});

test("Version checking for plugin compatibility >=2.0.0-canary.19", (t) => {
let range = ">=2.0.0-canary.19";
t.false(EleventyCompatibility.satisfies("1.0.0-beta.1", range));
t.false(EleventyCompatibility.satisfies("1.0.2", range));
t.false(EleventyCompatibility.satisfies("2.0.0-canary", range));
t.false(EleventyCompatibility.satisfies("2.0.0-canary.18", range));
t.true(EleventyCompatibility.satisfies("2.0.0-canary.19", range));
t.true(EleventyCompatibility.satisfies("2.0.0-beta.1", range));
t.true(EleventyCompatibility.satisfies("2.0.0", range));
t.true(EleventyCompatibility.satisfies("2.0.1", range));
t.true(EleventyCompatibility.satisfies("3.0.0-canary.1", range));
t.true(EleventyCompatibility.satisfies("3.0.0-beta.1", range));
t.true(EleventyCompatibility.satisfies("3.0.0", range));
});

test("Version checking for plugin compatibility >=2.0.0-canary.19 || >=2.0.0-beta.1", (t) => {
let range = ">=2.0.0-canary.19 || >=2.0.0-beta.1"; // can be simplified to >=2.0.0-canary.19
t.false(EleventyCompatibility.satisfies("1.0.0-beta.1", range));
t.false(EleventyCompatibility.satisfies("1.0.2", range));
t.false(EleventyCompatibility.satisfies("2.0.0-canary", range));
t.false(EleventyCompatibility.satisfies("2.0.0-canary.18", range));
t.true(EleventyCompatibility.satisfies("2.0.0-canary.19", range));
t.true(EleventyCompatibility.satisfies("2.0.0-beta.1", range));
t.true(EleventyCompatibility.satisfies("2.0.0", range));
t.true(EleventyCompatibility.satisfies("2.0.1", range));
t.true(EleventyCompatibility.satisfies("3.0.0-canary.1", range));
t.true(EleventyCompatibility.satisfies("3.0.0-beta.1", range));
t.true(EleventyCompatibility.satisfies("3.0.0", range));
});

test("Version checking for plugin compatibility >=2.0.0-canary.19 || >=2.0.0-beta.2", (t) => {
let range = ">=2.0.0-canary.19 || >=2.0.0-beta.2"; // same as ">=2.0.0-canary.19"
t.false(EleventyCompatibility.satisfies("1.0.0-beta.1", range));
t.false(EleventyCompatibility.satisfies("1.0.2", range));
t.false(EleventyCompatibility.satisfies("2.0.0-canary", range));
t.false(EleventyCompatibility.satisfies("2.0.0-canary.18", range));
t.true(EleventyCompatibility.satisfies("2.0.0-canary.19", range));
t.true(EleventyCompatibility.satisfies("2.0.0-beta.1", range)); // warning: this matches because of >=2.0.0-canary.19
t.true(EleventyCompatibility.satisfies("2.0.0-beta.2", range));
t.true(EleventyCompatibility.satisfies("2.0.0", range));
t.true(EleventyCompatibility.satisfies("2.0.1", range));
t.true(EleventyCompatibility.satisfies("3.0.0-canary.1", range));
t.true(EleventyCompatibility.satisfies("3.0.0-beta.1", range));
t.true(EleventyCompatibility.satisfies("3.0.0", range));
});

0 comments on commit 3810ccf

Please sign in to comment.