Skip to content

Commit f3f8d09

Browse files
committed
Using semver to enforce our plugin versions a bit better
1 parent 2e53737 commit f3f8d09

File tree

4 files changed

+103
-33
lines changed

4 files changed

+103
-33
lines changed

lib/features.js

Lines changed: 17 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -19,61 +19,61 @@ const features = {
1919
sass: {
2020
method: 'enableSassLoader()',
2121
packages: [
22-
{ name: 'sass-loader', version: 7 },
22+
{ name: 'sass-loader', enforce_version: true },
2323
{ name: 'node-sass' }
2424
],
2525
description: 'load Sass files'
2626
},
2727
less: {
2828
method: 'enableLessLoader()',
2929
packages: [
30-
{ name: 'less-loader', version: 4 },
30+
{ name: 'less-loader', enforce_version: true },
3131
],
3232
description: 'load LESS files'
3333
},
3434
stylus: {
3535
method: 'enableStylusLoader()',
3636
packages: [
37-
{ name: 'stylus-loader', version: 3 },
37+
{ name: 'stylus-loader', enforce_version: true },
3838
{ name: 'stylus' }
3939
],
4040
description: 'load Stylus files'
4141
},
4242
postcss: {
4343
method: 'enablePostCssLoader()',
4444
packages: [
45-
{ name: 'postcss-loader', version: 2 }
45+
{ name: 'postcss-loader', enforce_version: true }
4646
],
4747
description: 'process through PostCSS'
4848
},
4949
react: {
5050
method: 'enableReactPreset()',
5151
packages: [
52-
{ name: '@babel/preset-react', version: 7 }
52+
{ name: '@babel/preset-react', enforce_version: true }
5353
],
5454
description: 'process React JS files'
5555
},
5656
preact: {
5757
method: 'enablePreactPreset()',
5858
packages: [
59-
{ name: '@babel/plugin-transform-react-jsx', version: 7 }
59+
{ name: '@babel/plugin-transform-react-jsx', enforce_version: true }
6060
],
6161
description: 'process Preact JS files'
6262
},
6363
typescript: {
6464
method: 'enableTypeScriptLoader()',
6565
packages: [
6666
{ name: 'typescript' },
67-
{ name: 'ts-loader', version: 4 },
67+
{ name: 'ts-loader', enforce_version: true },
6868
],
6969
description: 'process TypeScript files'
7070
},
7171
forkedtypecheck: {
7272
method: 'enableForkedTypeScriptTypesChecking()',
7373
packages: [
7474
{ name: 'typescript' },
75-
{ name: 'ts-loader', version: 4 },
76-
{ name: 'fork-ts-checker-webpack-plugin', version: 0 },
75+
{ name: 'ts-loader', enforce_version: true },
76+
{ name: 'fork-ts-checker-webpack-plugin', enforce_version: true },
7777
],
7878
description: 'check TypeScript types in a separate process'
7979
},
@@ -83,7 +83,7 @@ const features = {
8383
// vue-template-compiler is a peer dep of vue-loader
8484
packages: [
8585
{ name: 'vue' },
86-
{ name: 'vue-loader', version: 15 },
86+
{ name: 'vue-loader', enforce_version: true },
8787
{ name: 'vue-template-compiler' }
8888
],
8989
description: 'load VUE files'
@@ -93,30 +93,30 @@ const features = {
9393
// eslint is needed so the end-user can do things
9494
packages: [
9595
{ name: 'eslint' },
96-
{ name: 'eslint-loader', version: 1 },
97-
{ name: 'babel-eslint', version: 8 },
96+
{ name: 'eslint-loader', enforce_version: true },
97+
{ name: 'babel-eslint', enforce_version: true },
9898
],
9999
description: 'Enable ESLint checks'
100100
},
101101
notifier: {
102102
method: 'enableBuildNotifications()',
103103
packages: [
104-
{ name: 'webpack-notifier', version: 1 },
104+
{ name: 'webpack-notifier', enforce_version: true },
105105
],
106106
description: 'display build notifications'
107107
},
108108
urlloader: {
109109
method: 'configureUrlLoader()',
110110
packages: [
111-
{ name: 'url-loader', version: 1 },
111+
{ name: 'url-loader', enforce_version: true },
112112
],
113113
description: 'use the url-loader'
114114
},
115115
handlebars: {
116116
method: 'enableHandlebarsLoader()',
117117
packages: [
118118
{ name: 'handlebars' },
119-
{ name: 'handlebars-loader', version: 1 }
119+
{ name: 'handlebars-loader', enforce_version: true }
120120
],
121121
description: 'load Handlebars files'
122122
}
@@ -135,7 +135,7 @@ module.exports = {
135135
const config = getFeatureConfig(featureName);
136136

137137
packageHelper.ensurePackagesExist(
138-
config.packages,
138+
packageHelper.addPackageVersions(config.packages),
139139
config.method
140140
);
141141
},
@@ -144,7 +144,7 @@ module.exports = {
144144
const config = getFeatureConfig(featureName);
145145

146146
return packageHelper.getMissingPackageRecommendations(
147-
config.packages,
147+
packageHelper.addPackageVersions(config.packages),
148148
config.method
149149
);
150150
},

lib/package-helper.js

Lines changed: 42 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
const chalk = require('chalk');
1313
const fs = require('fs');
1414
const logger = require('./logger');
15+
const semver = require('semver');
1516

1617
function ensurePackagesExist(packagesConfig, requestedFeature) {
1718
const missingPackagesRecommendation = getMissingPackageRecommendations(packagesConfig, requestedFeature);
@@ -39,7 +40,14 @@ function getInstallCommand(packageConfigs) {
3940
return packageConfig.name;
4041
}
4142

42-
return `${packageConfig.name}@^${packageConfig.version}.0`;
43+
// e.g. ^4.0|^5.0: use the latest version
44+
let recommendedVersion = packageConfig.version;
45+
if (recommendedVersion.indexOf('|') !== -1) {
46+
recommendedVersion = recommendedVersion.split('|').pop();
47+
}
48+
49+
// recommend the version included in our package.json file
50+
return `${packageConfig.name}@${recommendedVersion}`;
4351
});
4452

4553
if (hasNpmLockfile && !hasYarnLockfile) {
@@ -91,29 +99,56 @@ function getInvalidPackageVersionRecommendations(packagesConfig, requestedFeatur
9199

92100
let version;
93101
try {
94-
version = /^(\d+)/.exec(require(`${packageConfig.name}/package.json`).version).pop();
102+
version = require(`${packageConfig.name}/package.json`).version;
95103
} catch (e) {
96104
// should not happen because this functions is meant to be
97105
// called only after verifying a package is actually installed
98106
throw new Error(`Could not find package.json file for ${packageConfig.name}`);
99107
}
100108

101-
if (Number(version) < packageConfig.version) {
109+
if (semver.satisfies(version, packageConfig.version)) {
110+
continue;
111+
}
112+
113+
if (semver.gtr(version, packageConfig.version)) {
102114
badVersionMessages.push(
103-
`Webpack Encore requires version ${chalk.green(packageConfig.version)} of ${chalk.green(packageConfig.name)}, but your version (${chalk.green(version)}) is too old. The related feature will probably *not* work correctly.`
115+
`Webpack Encore requires version ${chalk.green(packageConfig.version)} of ${chalk.green(packageConfig.name)}. Your version ${chalk.green(version)} is too new. The related feature *may* still work properly. If you have issues, try downgrading the library, or upgrading Encore.`
104116
);
105-
} else if (Number(version) > packageConfig.version) {
117+
} else {
106118
badVersionMessages.push(
107-
`Webpack Encore requires version ${chalk.green(packageConfig.version)} of ${chalk.green(packageConfig.name)}. Your version ${chalk.green(version)} is too new. The related feature *may* still work properly. If you have issues, try downgrading the library, or upgrading Encore.`
119+
`Webpack Encore requires version ${chalk.green(packageConfig.version)} of ${chalk.green(packageConfig.name)}, but your version (${chalk.green(version)}) is too old. The related feature will probably *not* work correctly.`
108120
);
109121
}
110122
}
111123

112124
return badVersionMessages;
113125
}
114126

127+
function addPackagesVersionConstraint(packages) {
128+
const packageJsonData = require('../package.json');
129+
130+
return packages.map(packageData => {
131+
const newData = Object.assign({}, packageData);
132+
133+
if (packageData.enforce_version) {
134+
// this method only supports devDependencies due to how it's used:
135+
// it's mean to inform the user what deps they need to install
136+
// for optional features
137+
if (!packageJsonData.devDependencies[packageData.name]) {
138+
throw new Error(`Count not find package ${packageData.name}`);
139+
}
140+
141+
newData.version = packageJsonData.devDependencies[packageData.name];
142+
delete newData['enforce_version'];
143+
}
144+
145+
return newData;
146+
});
147+
}
148+
115149
module.exports = {
116150
ensurePackagesExist,
117151
getMissingPackageRecommendations,
118-
getInvalidPackageVersionRecommendations
152+
getInvalidPackageVersionRecommendations,
153+
addPackagesVersionConstraint,
119154
};

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@
4343
"pkg-up": "^1.0.0",
4444
"pretty-error": "^2.1.1",
4545
"resolve-url-loader": "^2.3.0",
46+
"semver": "^5.5.0",
4647
"style-loader": "^0.21.0",
4748
"uglifyjs-webpack-plugin": "^1.2.5",
4849
"webpack": "^4.0.0",

test/package-helper.js

Lines changed: 43 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -57,30 +57,47 @@ describe('package-helper', () => {
5757

5858
describe('check messaging on install commands', () => {
5959
it('Make sure the major version is included in the install command', () => {
60-
process.chdir(path.join(__dirname , '../fixtures/package-helper/empty'));
6160
const packageRecommendations = packageHelper.getMissingPackageRecommendations([
62-
{ name: 'foo' }, { name: 'bar', version: 3 }
61+
{ name: 'foo' }, { name: 'bar', version: '^3.0' }
6362
]);
6463

6564
expect(packageRecommendations.installCommand).to.contain('yarn add foo bar@^3.0');
6665
});
6766

67+
it('Recommends correct install on 0 version', () => {
68+
const packageRecommendations = packageHelper.getMissingPackageRecommendations([
69+
{ name: 'foo', version: '^0.1.0' },
70+
{ name: 'bar' }
71+
]);
72+
73+
expect(packageRecommendations.installCommand).to.contain('yarn add foo@^0.1.0 bar');
74+
});
75+
76+
it.only('Recommends correct install with a more complex constraint', () => {
77+
// e.g. ^7.0|^8.0
78+
const packageRecommendations = packageHelper.getMissingPackageRecommendations([
79+
{ name: 'foo', version: '^7.0|^8.0' },
80+
{ name: 'bar' }
81+
]);
82+
83+
expect(packageRecommendations.installCommand).to.contain('yarn add foo@^8.0 bar');
84+
});
6885
});
6986

7087
describe('The getInvalidPackageVersionRecommendations correctly checks installed versions', () => {
7188
it('Check package that *is* the correct version', () => {
7289
const versionProblems = packageHelper.getInvalidPackageVersionRecommendations([
73-
{ name: 'sass-loader', version: 7 },
74-
{ name: 'preact', version: 8 }
90+
{ name: 'sass-loader', version: '^7.0.1' },
91+
{ name: 'preact', version: '^8.1.0' }
7592
]);
7693

7794
expect(versionProblems).to.be.empty;
7895
});
7996

8097
it('Check package with a version too low', () => {
8198
const versionProblems = packageHelper.getInvalidPackageVersionRecommendations([
82-
{ name: 'sass-loader', version: 8 },
83-
{ name: 'preact', version: 9 }
99+
{ name: 'sass-loader', version: '^8.0.1' },
100+
{ name: 'preact', version: '9.0.0' }
84101
]);
85102

86103
expect(versionProblems).to.have.length(2);
@@ -89,8 +106,8 @@ describe('package-helper', () => {
89106

90107
it('Check package with a version too low', () => {
91108
const versionProblems = packageHelper.getInvalidPackageVersionRecommendations([
92-
{ name: 'sass-loader', version: 6 },
93-
{ name: 'preact', version: 7 }
109+
{ name: 'sass-loader', version: '^6.9.11' },
110+
{ name: 'preact', version: '7.0.0' }
94111
]);
95112

96113
expect(versionProblems).to.have.length(2);
@@ -99,12 +116,29 @@ describe('package-helper', () => {
99116

100117
it('Missing "version" key is ok', () => {
101118
const versionProblems = packageHelper.getInvalidPackageVersionRecommendations([
102-
{ name: 'sass-loader', version: 6 },
119+
{ name: 'sass-loader', version: '^6.9.9' },
103120
{ name: 'preact' }
104121
]);
105122

106123
// just sass-loader
107124
expect(versionProblems).to.have.length(1);
108125
});
109126
});
127+
128+
describe('addPackagesVersionConstraint', () => {
129+
it('Lookup a version constraint', () => {
130+
// hardcoding sass-loader: test WILL break when this changes
131+
132+
const inputPackages = [
133+
{ name: 'sass-loader', enforce_version: 7 },
134+
{ name: 'node-sass' }
135+
];
136+
const expectedPackages = [
137+
{ name: 'sass-loader', version: '^7.0.1' },
138+
{ name: 'node-sass' }
139+
]
140+
const actualPackages = packageHelper.addPackagesVersionConstraint(inputPackages);
141+
expect(JSON.stringify(actualPackages)).to.equal(JSON.stringify(expectedPackages));
142+
});
143+
});
110144
});

0 commit comments

Comments
 (0)