diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c96f019 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +.vscode +node_modules/ \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..581a0ba --- /dev/null +++ b/README.md @@ -0,0 +1,56 @@ +# Compatible 🛠📦 + +Tiny (has no dependencies) package to check how compatible a version of dependency is with another version. +This package gets the data from Dependabot which supports many languages (JavaScript, Python, Java, ...) and package managers (npm, pip, mvn, ...). + +## Installation + +Install latest release from npm: + +``` shell +$ npm install compatible +``` + +Install latest updates from source: + +``` shell +$ npm install ssmirr/compatible +``` + +## Using _Compatible_ + +You can fetch compatibility status of a package and all the versions: +``` js +await compatible.update('', '', '', '')); +``` + +, specific version of a package: + +``` js +await compatible.dependency('', '')); +``` + +, or updating from version a to version b of a package: + +``` js +await compatible.version('', '', '')); +``` + +## Example + +``` js +const compatible = require('compatible'); + +let results = await compatible.update('django', 'pip', '1.10.3', '1.11.7')); +console.log(results); + +// results is a json object: +// [ { candidate_updates: 1, +// successful_updates: 0, +// previous_version: '1.10.3', +// updated_version: '1.11.7', +// non_breaking_if_semver: true +// success_rate: 0.94 } ] +``` + +> Note: `candidate_updates` is the number of pull requests Dependabot made, and `successful_updates` is how many were merged. \ No newline at end of file diff --git a/index.js b/index.js new file mode 100644 index 0000000..c63fe48 --- /dev/null +++ b/index.js @@ -0,0 +1,81 @@ +const http = require('http'); + +class Compatible { + static async dependency(pkgName, pkgManager, calculateSuccessRate = false) { + let updateData = await this.getUpdateData(pkgName, pkgManager); + let result = updateData.updates.concat(updateData.single_version_updates); + + if (calculateSuccessRate) { + result = result.map(update => { + return { + ...update, + success_rate: this.getSuccessRate(update.candidate_updates, update.successful_updates) + } + }); + } + + return result; + } + + static async version(pkgName, pkgManager, ToVersion) { + let updateData = await this.getUpdateData(pkgName, pkgManager); + let result = updateData.semver_updates.filter(update => update.updated_version === ToVersion)[0]; + result.success_rate = this.getSuccessRate(result.candidate_updates, result.successful_updates); + return result; + } + + static async update(pkgName, pkgManager, FromVersion, ToVersion) { + let updateData = await this.dependency(pkgName, pkgManager); + let result = updateData.filter(update => update.previous_version === FromVersion && update.updated_version === ToVersion)[0]; + result.success_rate = this.getSuccessRate(result.candidate_updates, result.successful_updates); + return result; + } + + static getSuccessRate(candidate_updates, successful_updates) { + return (successful_updates / candidate_updates).toFixed(2); + } + + static async getUpdateData(pkgName, pkgManager) { + return new Promise((resolve, reject) => { + http.get(`http://api.dependabot.com/compatibility_scores?dependency-name=${pkgName}&package-manager=${pkgManager}&version-scheme=semver`, (res) => { + const { + statusCode + } = res; + const contentType = res.headers['content-type']; + + let error; + if (statusCode !== 200) { + error = new Error('Request Failed.\n' + + `Status Code: ${statusCode}`); + } else if (!/^application\/json/.test(contentType)) { + error = new Error('Invalid content-type.\n' + + `Expected application/json but received ${contentType}`); + } + if (error) { + console.error(error.message); + // consume response data to free up memory + res.resume(); + return; + } + + res.setEncoding('utf8'); + let rawData = ''; + res.on('data', (chunk) => { + rawData += chunk; + }); + res.on('end', () => { + try { + const parsedData = JSON.parse(rawData); + resolve(parsedData); + } catch (e) { + reject(e.message); + } + }); + }).on('error', (e) => { + console.error(`Got error: ${e.message}`); + }); + }); + } +} + +module.exports = Compatible; \ No newline at end of file diff --git a/package.json b/package.json new file mode 100644 index 0000000..421b8e9 --- /dev/null +++ b/package.json @@ -0,0 +1,26 @@ +{ + "name": "compatible", + "version": "0.0.1", + "description": "Check how compatible two versions of a dependency are", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/ssmirr/compatible.git" + }, + "keywords": [ + "dependancy", + "dependancy", + "management", + "npm", + "pip", + "yarn", + "nuget", + "docker", + "maven" + ], + "author": "Samim Mirhosseini (https://github.com/ssmirr/)", + "license": "Apache-2.0" +} diff --git a/test.js b/test.js new file mode 100644 index 0000000..2783c53 --- /dev/null +++ b/test.js @@ -0,0 +1,7 @@ +const compatible = require('./index'); + +(async () => { + console.log(await compatible.update('django', 'pip', '2.1.4', '2.1.5')) + console.log(await compatible.version('got', 'npm_and_yarn', '9.5.0')) + console.log(await compatible.dependency('got', 'npm_and_yarn', true)) +})();