├── gulpfile.js ├── .travis.yml ├── .eslintrc ├── test ├── .eslintrc ├── data │ ├── unexpected.json │ ├── satisfy.json │ ├── check.json │ └── component.json └── spec │ └── check-dependencies.spec.js ├── package.json ├── README.md ├── bin └── ecd.js ├── check-dependencies.js └── .gitignore /gulpfile.js: -------------------------------------------------------------------------------- 1 | require("electrode-archetype-njs-module-dev")(); 2 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | 3 | node_js: 4 | - v6 5 | 6 | script: 7 | - npm test 8 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | --- 2 | extends: 3 | - "./node_modules/electrode-archetype-njs-module-dev/config/eslint/.eslintrc-node" 4 | -------------------------------------------------------------------------------- /test/.eslintrc: -------------------------------------------------------------------------------- 1 | --- 2 | extends: 3 | - "../node_modules/electrode-archetype-njs-module-dev/config/eslint/.eslintrc-test" 4 | -------------------------------------------------------------------------------- /test/data/unexpected.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": { 3 | "a": "^1.0.0", 4 | "b": "^1.0.1", 5 | "caret-0-maj-ok": "^0.5.0", 6 | "extra": "^1.0.0" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /test/data/satisfy.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": { 3 | "a": "^1.0.0", 4 | "b": "^1.0.1", 5 | "tilde-1-ok": "~1.5.0", 6 | "caret-0-maj-ok": "^0.5.0" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /test/data/check.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": { 3 | "a": "^1.0.0", 4 | "b": "^1.0.1", 5 | "tilde-1-ok": "~1.5.0", 6 | "tilde-1-bad": "~0.1.0", 7 | "not-finite": "^1.0.0", 8 | "caret-0-maj-ok": "^0.5.0", 9 | "caret-0-maj-bad": "^1.0.0", 10 | "any": "^1.0.0" 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /test/data/component.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": { 3 | "@walmart/test": "~0.0.1", 4 | "a": "^1.9.0", 5 | "b": "^1.5.1", 6 | "not-finite": "^1.0.0 || ^2.0.0", 7 | "any": "*", 8 | "caret-0-maj-ok": "^0.5.0", 9 | "caret-0-maj-bad": "^0.6.0", 10 | "tilde-1-ok": "~1.5.6", 11 | "tilde-1-bad": "~0.0.6" 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "electrode-check-dependencies", 3 | "version": "1.0.3", 4 | "description": "Maintain uniform dependencies for components", 5 | "main": "check-dependencies.js", 6 | "scripts": { 7 | "test": "gulp check" 8 | }, 9 | "bin": { 10 | "ecd": "./bin/ecd.js" 11 | }, 12 | "repository": { 13 | "type": "git", 14 | "url": "https://github.com/electrode-io/electrode-check-dependencies.git" 15 | }, 16 | "keywords": [], 17 | "author": "Joel Chen ", 18 | "license": "Apache-2.0", 19 | "dependencies": { 20 | "bluebird": "^3.4.6", 21 | "lodash": "^4.2.1", 22 | "optimist": "^0.6.1", 23 | "semver": "^5.1.0" 24 | }, 25 | "devDependencies": { 26 | "electrode-archetype-njs-module-dev": "^1.0.0" 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Electrode Check Dependencies 2 | 3 | [![NPM version][npm-image]][npm-url] [![Build Status][travis-image]][travis-url] [![Dependency Status][daviddm-image]][daviddm-url] 4 | 5 | This module provides function to check a `package.json`'s dependencies against another list to make sure the package doesn't deviates from a uniform dependencies. This is mainly for ensuring React components use the same version of a common module when being consumed by an app. 6 | 7 | ## Install 8 | 9 | ``` 10 | npm install electrode-check-dependencies 11 | ``` 12 | 13 | ## Usage 14 | 15 | ```js 16 | 17 | const CheckDep = require("electrode-check-dependencies"); 18 | 19 | CheckDep.checkPkgFile( "", "" ).catch( (err) => { 20 | console.log("component deviates from uniform dependencies"); 21 | }); 22 | ``` 23 | 24 | or 25 | 26 | ```js 27 | 28 | const CheckDep = require("electrode-check-dependencies"); 29 | const component = require("./package.json").dependencies; 30 | const check = require("./check.json").dependencies; 31 | 32 | 33 | const result = CheckDep.checkDependencies( component, check ); 34 | if ( result.unsatisfyCommon.length > 0 || result.unexpected.length > 0 ) { 35 | console.log("component deviates from uniform dependencies"); 36 | } 37 | ``` 38 | 39 | Built with :heart: by [Team Electrode](https://github.com/orgs/electrode-io/people) @WalmartLabs. 40 | 41 | [npm-image]: https://badge.fury.io/js/electrode-check-dependencies.svg 42 | [npm-url]: https://npmjs.org/package/electrode-check-dependencies 43 | [travis-image]: https://travis-ci.org/electrode-io/electrode-check-dependencies.svg?branch=master 44 | [travis-url]: https://travis-ci.org/electrode-io/electrode-check-dependencies 45 | [daviddm-image]: https://david-dm.org/electrode-io/electrode-check-dependencies.svg?theme=shields.io 46 | [daviddm-url]: https://david-dm.org/electrode-io/electrode-check-dependencies 47 | -------------------------------------------------------------------------------- /test/spec/check-dependencies.spec.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const check = require("../.."); 4 | const _ = require("lodash"); 5 | const Path = require("path"); 6 | 7 | describe("check-dependencies", function () { 8 | 9 | it("should return failed for deviated component dependencies", (done) => { 10 | check.checkPkgFile("./test/data/component.json", "./test/data/check.json") 11 | .then(() => { 12 | throw new Error("expected error"); 13 | }) 14 | .catch((error) => { 15 | if (error.result) { 16 | expect(error.result.unsatisfyCommon).to.not.include("@walmart/test"); 17 | const x = _.map(error.result.unsatisfyCommon, (r) => r.name).sort(); 18 | expect(x).to.deep.equal(["any", "caret-0-maj-bad", "not-finite", "tilde-1-bad"]); 19 | expect(error.result.unexpected).to.be.empty; 20 | } else { 21 | throw error; 22 | } 23 | }) 24 | .then(() => done()) 25 | .catch(done); 26 | }); 27 | 28 | 29 | it("should return failed for unexpected component dependencies", (done) => { 30 | check.checkPkgFile("./test/data/unexpected.json", "./test/data/check.json") 31 | .then(() => { 32 | throw new Error("expected error"); 33 | }) 34 | .catch((error) => { 35 | if (!error.result) { 36 | throw error; 37 | } else { 38 | const x = error.result.unexpected.map((e) => e.name).sort(); 39 | expect(x).to.deep.equal(["extra"]); 40 | } 41 | }) 42 | .then(() => done()) 43 | .catch(done); 44 | }); 45 | 46 | 47 | it("should skip extra dependencies check when allowExtra flag is true", (done) => { 48 | check.checkPkgFile("./test/data/unexpected.json", "./test/data/check.json", {allowExtra: true}) 49 | .then(() => done()) 50 | .catch(done); 51 | }); 52 | 53 | 54 | it("should return no error for good component dependencies", (done) => { 55 | check.checkPkgFile(Path.resolve("test/data/satisfy.json"), "./test/data/check.json") 56 | .then(() => done()) 57 | .catch(done); 58 | }); 59 | 60 | }); 61 | -------------------------------------------------------------------------------- /bin/ecd.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | "use strict"; 3 | 4 | const fs = require("fs"); 5 | const Optimist = require("optimist"); 6 | const CheckDep = require(".."); 7 | const Path = require("path"); 8 | const _ = require("lodash"); 9 | 10 | const args = Optimist.options("filename", { 11 | default: "package.json", 12 | alias: "f" 13 | }) 14 | .describe("filename", "component package.json to verify") 15 | .options("checkFile", { 16 | default: "", 17 | alias: "cf" 18 | }) 19 | .describe("checkFile", "check list json file to verify against") 20 | .options("allowExtra", { 21 | default: false, 22 | alias: "ae" 23 | }) 24 | .describe("allowExtra", "allow extra dependencies not in check list") 25 | .options("help", { 26 | default: false, 27 | alias: "h" 28 | }) 29 | .options("warn", { 30 | default: false, 31 | alias: "w" 32 | }) 33 | .describe("warn", "show warning but exit with status OK") 34 | .describe("help", "help") 35 | .alias("help", "?"); 36 | 37 | const options = args.argv; 38 | 39 | if (options.help) { 40 | args.showHelp(); 41 | process.exit(0); 42 | } 43 | 44 | function resolveFile(file) { 45 | try { 46 | require(file); 47 | return file; 48 | } catch (e) { 49 | file = Path.resolve(file); 50 | if (fs.existsSync(file)) { 51 | return file; 52 | } 53 | } 54 | } 55 | 56 | const filename = resolveFile(options.filename); 57 | if (!filename) { 58 | console.error(`File does not exist: ${options.filename}`); 59 | process.exit(1); 60 | } 61 | 62 | const checkFile = resolveFile(options.checkFile); 63 | if (!checkFile) { 64 | console.error(`Check file does not exist: ${options.checkFile}`); 65 | process.exit(1); 66 | } 67 | 68 | CheckDep 69 | .checkPkgFile(filename, checkFile, {allowExtra: options.allowExtra}) 70 | .then(() => { 71 | console.log("Check dependencies passed"); 72 | process.exit(0); 73 | }) 74 | .catch((error) => { 75 | console.error("Check dependencies failed."); 76 | const result = error.result; 77 | if (!result) { 78 | console.error("No result.") 79 | } else { 80 | if (!_.isEmpty(result.unsatisfyCommon)) { 81 | console.error("These dependencies are using versions that are not expected:"); 82 | _.each(result.unsatisfyCommon, (x) => { 83 | console.error(` - ${x.name}: version: "${x.want}" expected: "${x.expected}"`); 84 | }); 85 | } 86 | 87 | if (!_.isEmpty(result.unexpected)) { 88 | console.error("These dependencies are not expected:"); 89 | _.each(result.unexpected, (x) => { 90 | console.error(` - ${x.name}: version: "${x.version}"`); 91 | }) 92 | } 93 | } 94 | process.exit(options.warn ? 0 : 1); 95 | }); 96 | -------------------------------------------------------------------------------- /check-dependencies.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | const Promise = require("bluebird"); 3 | const Path = require("path"); 4 | const _ = require("lodash"); 5 | const assert = require("assert"); 6 | const Semver = require("semver"); 7 | 8 | function checkDependencies(component, check, options) { 9 | assert(_.isObject(component), "component dependencies missing"); 10 | assert(_.isObject(check), "dependencies check list missing"); 11 | 12 | const wantNames = Object.keys(component); 13 | const expectNames = Object.keys(check); 14 | const common = _.intersection(wantNames, expectNames); 15 | const isWml = (name) => name.startsWith("@walmart"); 16 | 17 | const unsatisfyCommon = common.filter((n) => !isWml(n)).map((name) => { 18 | 19 | const want = component[name]; 20 | const expected = check[name]; 21 | const wantRange = new Semver.Range(want); 22 | 23 | // version must have a finite range. ie: can't have * or latest 24 | // do not allow multiple ranges. ie: can't have ^1.0.0 || ^2.0.0 25 | if (!wantRange || !wantRange.range || wantRange.set.length !== 1) { 26 | return {name, want, expected}; 27 | } 28 | 29 | const expectedRange = new Semver.Range(expected); 30 | const wantMax = wantRange.set[0][1].toString(); 31 | const expectedMax = expectedRange.set[0][1].toString(); 32 | 33 | // the max version in the range must equal 34 | // don't care about min version since NPM will always match latest available less than max 35 | if (wantMax !== expectedMax) { 36 | return {name, want, expected}; 37 | } 38 | 39 | return undefined; 40 | 41 | }).filter(_.identity); 42 | 43 | const getUnexpected = () => 44 | (!(options && options.allowExtra) && wantNames.filter((n) => (!isWml(n) && !check[n])) || []); 45 | 46 | const unexpected = getUnexpected().map((name) => ({name, version: component[name]})); 47 | 48 | return {unsatisfyCommon, unexpected}; 49 | } 50 | 51 | 52 | function checkPkgFile(pkgFile, checkFile, options) { 53 | 54 | options = options || {}; 55 | 56 | const resolve = (file) => 57 | file.startsWith(".") ? Path.resolve(file) : file; 58 | 59 | const load = (file, field) => Promise.try(() => resolve(file)) 60 | .then(require).then((pkg) => pkg[field || "dependencies"]); 61 | 62 | return Promise.join(load(pkgFile, options.depField), load(checkFile, options.checkField), 63 | (component, check) => checkDependencies(component, check, options)) 64 | .then((result) => { 65 | const failed = () => { 66 | if (!_.isEmpty(result.unsatisfyCommon)) { 67 | return true; 68 | } 69 | 70 | return !options.allowExtra && !_.isEmpty(result.unexpected); 71 | }; 72 | 73 | if (failed()) { 74 | const error = new Error("component dependencies deviated"); 75 | error.result = result; 76 | throw error; 77 | } 78 | 79 | return result; 80 | }); 81 | } 82 | 83 | module.exports = { 84 | checkDependencies, 85 | checkPkgFile 86 | }; 87 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # Created by https://www.gitignore.io/api/gitbook,osx,webstorm,node 3 | 4 | ### GitBook ### 5 | # Node rules: 6 | ## Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 7 | .grunt 8 | 9 | ## Dependency directory 10 | ## Commenting this out is preferred by some people, see 11 | ## https://docs.npmjs.com/misc/faq#should-i-check-my-node_modules-folder-into-git 12 | node_modules 13 | 14 | # Book build output 15 | _book 16 | 17 | # eBook build output 18 | *.epub 19 | *.mobi 20 | *.pdf 21 | 22 | 23 | ### OSX ### 24 | .DS_Store 25 | .AppleDouble 26 | .LSOverride 27 | 28 | # Icon must end with two \r 29 | Icon 30 | 31 | 32 | # Thumbnails 33 | ._* 34 | 35 | # Files that might appear in the root of a volume 36 | .DocumentRevisions-V100 37 | .fseventsd 38 | .Spotlight-V100 39 | .TemporaryItems 40 | .Trashes 41 | .VolumeIcon.icns 42 | 43 | # Directories potentially created on remote AFP share 44 | .AppleDB 45 | .AppleDesktop 46 | Network Trash Folder 47 | Temporary Items 48 | .apdisk 49 | 50 | 51 | ### WebStorm ### 52 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm 53 | 54 | *.iml 55 | 56 | ## Directory-based project format: 57 | .idea/ 58 | # if you remove the above rule, at least ignore the following: 59 | 60 | # User-specific stuff: 61 | # .idea/workspace.xml 62 | # .idea/tasks.xml 63 | # .idea/dictionaries 64 | # .idea/shelf 65 | 66 | # Sensitive or high-churn files: 67 | # .idea/dataSources.ids 68 | # .idea/dataSources.xml 69 | # .idea/sqlDataSources.xml 70 | # .idea/dynamic.xml 71 | # .idea/uiDesigner.xml 72 | 73 | # Gradle: 74 | # .idea/gradle.xml 75 | # .idea/libraries 76 | 77 | # Mongo Explorer plugin: 78 | # .idea/mongoSettings.xml 79 | 80 | ## File-based project format: 81 | *.ipr 82 | *.iws 83 | 84 | ## Plugin-specific files: 85 | 86 | # IntelliJ 87 | /out/ 88 | 89 | # mpeltonen/sbt-idea plugin 90 | .idea_modules/ 91 | 92 | # JIRA plugin 93 | atlassian-ide-plugin.xml 94 | 95 | # Crashlytics plugin (for Android Studio and IntelliJ) 96 | com_crashlytics_export_strings.xml 97 | crashlytics.properties 98 | crashlytics-build.properties 99 | fabric.properties 100 | 101 | 102 | ### Node ### 103 | # Logs 104 | logs 105 | *.log 106 | npm-debug.log* 107 | 108 | # Runtime data 109 | pids 110 | *.pid 111 | *.seed 112 | 113 | # Directory for instrumented libs generated by jscoverage/JSCover 114 | lib-cov 115 | 116 | # Coverage directory used by tools like istanbul 117 | coverage 118 | 119 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 120 | .grunt 121 | 122 | # node-waf configuration 123 | .lock-wscript 124 | 125 | # Compiled binary addons (http://nodejs.org/api/addons.html) 126 | build/Release 127 | 128 | # Dependency directory 129 | # https://docs.npmjs.com/misc/faq#should-i-check-my-node-modules-folder-into-git 130 | node_modules 131 | 132 | # Optional npm cache directory 133 | .npm 134 | 135 | # Optional REPL history 136 | .node_repl_history 137 | 138 | .tmp 139 | .npmrc 140 | --------------------------------------------------------------------------------