Skip to content

Commit

Permalink
Add ProjectWideDependencyChecker check API
Browse files Browse the repository at this point in the history
This allows someone to make broader assertions against the project’s dependencies.

```js
const checker VersionChecker.forProject(project);

checker.check({
  ‘ember-data’: ‘>= 3.16.0’,
  ‘ember-resolver’: ‘*’,
}) === {
  isSatisfied: true | false,
  message: ‘’ || ‘useful canned error message’,
  node_modules: {
   ‘ember-data’: {
     isSatisfied: true | false,
     message: ‘’ || ‘useful canned error message’,
     Versions: [/* list of versions found */]
   },
   ‘Ember-resolver: { /* basically same as ember-datas blob */ },
   assert(message?) { } // throw a canned error, with an optional description
  }
}
```

This Commit also fixes/cleans up the testing infrastructure to simplify the testing of the above new feature
  • Loading branch information
stefanpenner committed May 11, 2020
1 parent 99226a4 commit a9d7642
Show file tree
Hide file tree
Showing 6 changed files with 387 additions and 37 deletions.
59 changes: 57 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -236,7 +236,7 @@ module.exports = {

### allAddons

An iterator which gives acccess to all addon instances
An iterator which gives access to all addon instances

```js
const VersionChecker = require('ember-cli-version-checker');
Expand All @@ -249,7 +249,62 @@ module.exports = {
let checker = VersionChecker.forProject(this.project);

for (let { name, root } = checker.allAddons()) {
// access to the add-on, in this case root + name
// access to the addon, in this case root + name
}
}
};
```
### check

A utility to verify that addons are installed at appropriate versions.
Although `npm` and `yarn` typically manage dependencies for us, they do not
provide a mechanism for an addon to specific a hard constraint. When `npm` or
`yarn `see and invalid dependency, they resolve this by duplicating said
dependency. In-general, the node ecosystem works fine with duplication;
unfortunately, this strategy doesn't always work.

Every addon in the ember ecosystem implicitly depends `ember-source` and most
likely a specific version range. If that dependency is specified as a
`package.json` dependency, a mismatch between application and addon would
result in duplicating `ember-source`. This is both unhelpful and unwanted.

For example, as of today `ember-data` supports `ember-source` `>= 3.4.8`, if it
where to use this addon, it could specify this constraint and provide good
error messages to users.

```
const VersionChecker = require('ember-cli-version-checker');
module.exports = {
name: 'awesome-addon',
included() {
this._super.included.apply(this, arguments);
const checker = VersionChecker.forProject(this.project);
const check = checker.check({
'ember-source': '>= 3.4.8'
});
// if it would like to simply assert
check.assert('[awesome-addon] dependency check failed');
// will throw an error message similar to the following if the check was not satisfied:
// [awesome-addon] dependency check failed:
// - 'ember-source' expected version [>= 3.4.8] but got version: [2.0.0]
// if the check is more advanced, we can inspect the resulting check.
if (!check.isSatisfied) {
const altCheck = checker.check({
'magical-polyfil': '>= 1.0.0',
'ember-source': '>= 3.0.0'
})
check.assert('[awesome-addon] dependency check failed:');
// will throw error message similar to the following if the check was not satisfied:
// [awesome-addon] dependency check failed:
// - 'magical-polyfil' expected version [>= 1.0.0] but got version: [0.0.1]
// - 'ember-source' expected version [>= 3.0.0] but got version: [2.0.-]
}
}
};
Expand Down
90 changes: 89 additions & 1 deletion src/project-wide-dependency-checker.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,9 @@ const {
hasSingleImplementation,
allAddons,
} = require('./utils/single-implementation');
const semver = require('semver');
const SilentError = require('silent-error');

const { EOL } = require('os');
/* global Set */

module.exports = class ProjectWideDependencyChecker {
Expand Down Expand Up @@ -48,6 +49,22 @@ module.exports = class ProjectWideDependencyChecker {
return addons;
}

filterAddonsByNames(names) {
const result = Object.create(null);
for (let name of names) {
result[name] = [];
}

for (let addon of this.allAddons()) {
const addonResult = result[addon.name];
if (addonResult !== undefined) {
addonResult.push(addon);
}
}

return result;
}

assertSingleImplementation(name, customMessage) {
const uniqueImplementations = new Set();

Expand Down Expand Up @@ -77,4 +94,75 @@ module.exports = class ProjectWideDependencyChecker {

throw new SilentError(message);
}

check(constraints) {
const names = Object.keys(constraints);
const addons = this.filterAddonsByNames(names);
const node_modules = Object.create(null);

for (let name in addons) {
const found = addons[name];
const versions = found.map(addon => addon.pkg.version);

const constraint = constraints[name];
const missing = versions.length === 0;
const isSatisfied =
!missing &&
versions.every(version => semver.satisfies(version, constraint));

let message;
if (isSatisfied) {
message = '';
} else if (missing) {
message = `'${name}' was not found, expected version: [${constraint}]`;
} else {
message = `'${name}' expected version: [${constraint}] but got version${
versions.length > 1 ? 's' : ''
}: [${versions.join(', ')}]`;
}

node_modules[name] = {
versions,
isSatisfied,
message,
};
}

return new Check(node_modules);
}
};

class Check {
constructor(node_modules) {
this.node_modules = node_modules;
Object.freeze(this);
}

get isSatisfied() {
return Object.values(this.node_modules).every(
node_module => node_module.isSatisfied
);
}

get message() {
let result = '';

for (const name in this.node_modules) {
const { message } = this.node_modules[name];
if (message !== '') {
result += ` - ${message}${EOL}`;
}
}

return result;
}

assert(description = 'Checker Assertion Failed') {
if (this.isSatisfied) {
return;
}
throw new Error(
`[Ember-cli-version-checker] ${description}\n${this.message}`
);
}
}
Loading

0 comments on commit a9d7642

Please sign in to comment.