Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add new Travis jobs to test lowest/highest versions of all the dependencies #403

Merged
merged 3 commits into from
Oct 18, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,21 @@ cache:

matrix:
include:
- name: 'Lowest versions of the dependencies'
os: linux
node_js: "10"
env: JOB_PART=test
install:
- rm yarn.lock
- node ./scripts/force-lowest-dependencies
- yarn
- name: 'Highest versions of the dependencies'
os: linux
node_js: "10"
env: JOB_PART=test
install:
- rm yarn.lock
- yarn
- os: linux
node_js: "10"
env: JOB_PART=travis:lint
Expand Down
128 changes: 128 additions & 0 deletions scripts/force-lowest-dependencies.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
/*
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would it be worth to put this into a separate npm package? This sounds like something that more developers would run into when maintaining packages.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

as this is meant to run before a npm install, it might be harder 😄

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe something like a global install? Or a phar like equivalent for js

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm always interested in re-using existing libs or extracting Encore into new libs. But, with limited resources, we need to stay focused on Encore itself (i.e. things like this would need to be done by some community member).

* This file is part of the Symfony Webpack Encore package.
*
* (c) Fabien Potencier <[email protected]>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

'use strict';

const fs = require('fs');
const childProcess = require('child_process');

function getLowestVersion(dependency, range) {
return new Promise((resolve, reject) => {
childProcess.exec(
`npm view "${dependency}@${range}" version`,
{ encoding: 'utf-8' },
(error, stdout) => {
if (error) {
reject(`Could not retrieve versions list for "${dependency}@${range}"`);
return;
}

const versions = stdout
.split('\n')
.filter(line => line);

if (versions.length === 0) {
reject(`Could not find a lowest version for "${dependency}@${range}"`);
return;
}

const parts = versions[0].split(' ');

// If there is only one version available that version
// is directly printed as the output of npm view.
if (parts.length === 1) {
resolve([dependency, parts[0]]);
return;
}

// If multiple versions are available then it outputs
// multiple lines matching the following format:
// <package>@<version> '<version>'
if (parts.length === 2) {
resolve([dependency, parts[1].replace(/'/g, '')]);
return;
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wow, yea, this is nuts! But, if it works, and it's just a build process, let's use it until there is a better way

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I wasn't really happy with that part... I guess calling the npm.js API directly would have been a cleaner way to do it but then we would also have had to filter the versions ourselves (and since it happens before npm install/yarn, we don't have access to the semver package at that point).

Thank you for highlighting that part by the way, I noticed that I forgot some return in promises... not a big issue since a promise won't be able to change its state after being resolved/rejected but still something that had to be fixed :)


reject(`Unexpected response for "${dependency}@${range}": ${versions[0]}`);
}
);
});
}

fs.readFile('package.json', (error, data) => {
if (error) {
throw error;
}

const packageInfo = JSON.parse(data);

const dependencyPromises = [];
if (packageInfo.dependencies) {
for (const dependency in packageInfo.dependencies) {
dependencyPromises.push(getLowestVersion(
dependency,
packageInfo.dependencies[dependency]
));
}
}

const devDependencyPromises = [];
if (packageInfo.devDependencies) {
for (const devDependency in packageInfo.devDependencies) {
devDependencyPromises.push(getLowestVersion(
devDependency,
packageInfo.devDependencies[devDependency]
));
}
}

const dependenciesUpdate = Promise.all(dependencyPromises).then(versions => {
versions.forEach(version => {
packageInfo.dependencies[version[0]] = version[1];
});
});

const devDependenciesUpdate = Promise.all(devDependencyPromises).then(versions => {
versions.forEach(version => {
packageInfo.devDependencies[version[0]] = version[1];
});
});

// Once all the lowest versions have been resolved, update the
// package.json file accordingly.
Promise
.all([dependenciesUpdate, devDependenciesUpdate])
.then(() => new Promise((resolve, reject) => {
fs.writeFile('package.json', JSON.stringify(packageInfo, null, 2), (error) => {
if (error) {
reject(error);
return;
}

resolve();
});
}))
.then(() => {
console.log('Updated package.json file with lowest dependency versions: ');

console.log('Dependencies:');
for (const dependency in packageInfo.dependencies) {
console.log(` - ${dependency}: ${packageInfo.dependencies[dependency]}`);
}

console.log('Dev dependencies:');
for (const dependency in packageInfo.devDependencies) {
console.log(` - ${dependency}: ${packageInfo.devDependencies[dependency]}`);
}
})
.catch(error => {
console.error(error);
process.exit(1); // eslint-disable-line
});
});
10 changes: 7 additions & 3 deletions test/package-helper.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ const expect = require('chai').expect;
const packageHelper = require('../lib/package-helper');
const path = require('path');
const process = require('process');
const fs = require('fs');

describe('package-helper', () => {
const baseCwd = process.cwd();
Expand Down Expand Up @@ -135,14 +136,17 @@ describe('package-helper', () => {

describe('addPackagesVersionConstraint', () => {
it('Lookup a version constraint', () => {
// hardcoding sass-loader: test WILL break when this changes

const inputPackages = [
{ name: 'sass-loader', enforce_version: 7 },
{ name: 'node-sass' }
];

const packageInfo = JSON.parse(
fs.readFileSync(path.join(__dirname, '../package.json'))
);

const expectedPackages = [
{ name: 'sass-loader', version: '^7.0.1' },
{ name: 'sass-loader', version: packageInfo.devDependencies['sass-loader'] },
{ name: 'node-sass' }
];

Expand Down