├── .gitignore ├── LICENSE ├── README.md ├── bin └── kwalitee.js ├── docs ├── README.md └── checkers │ ├── Addon.md │ ├── Package.md │ └── Tests.md ├── lib ├── base.js ├── checkers │ ├── addon.js │ ├── package.js │ └── tests.js └── index.js ├── package.json └── test ├── basic_operation.js ├── checkers ├── addon.js ├── addon │ └── test-dir │ │ ├── binding.gyp │ │ └── package.json ├── package.js ├── package │ └── test-dir │ │ └── package.json ├── tests.js └── tests │ ├── test-dir │ └── package.json │ ├── test-folder-does-exist │ ├── package.json │ └── test │ │ └── .gitignore │ └── test-folder-does-not-exist │ └── package.json └── data └── basic-operation └── package.json /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | coverage/ 3 | .nyc_output/ 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Cloudtone and Contributors. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | docs/README.md -------------------------------------------------------------------------------- /bin/kwalitee.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | var mm = require('minimist'); 4 | var fs = require('fs'); 5 | var path = require('path'); 6 | var util = require('util'); 7 | var kwal = require('../lib'); 8 | 9 | var argv = mm(process.argv.slice(2), { 10 | "boolean": [ 'verbose', 'no-color', 'parseable', 'help' ], 11 | "alias": { 'verbose': 'v', 'no-color': 'C', 'parseable': 'H', 'help': 'h' } 12 | }); 13 | 14 | var directory = argv._[0]; 15 | var print_usage = false; 16 | var error_usage = false; 17 | 18 | if(argv.help) print_usage = true; 19 | if(!directory) print_usage = true; 20 | 21 | if(directory) { 22 | var dir_stat; 23 | try { 24 | dir_stat = fs.statSync(directory); 25 | } catch (e) {}; 26 | if(!dir_stat || !dir_stat.isDirectory()) { 27 | error_usage = "Supplied path does not exist or is not a directory"; 28 | print_usage = true; 29 | var pkg_json_stat; 30 | try { 31 | pkg_json_stat = fs.statSync( path.join(directory, 'package.json') ); 32 | } catch (e) {}; 33 | if(!pkg_json_stat || !pkg_json_stat.isFile()) { 34 | error_usage = "Supplied path does not have a package.json file within"; 35 | } 36 | } 37 | } 38 | 39 | if(error_usage) { 40 | console.error("\n" + error_usage + "\n"); 41 | } 42 | 43 | if(print_usage) { 44 | var script_name = path.basename(process.argv[1], '.js'); 45 | var usage = util.format("\ 46 | %s [-h|--help] [-v|--verbose] [-C|--no-color] [-H|--parseable] \n\n\ 47 | Options:\n\ 48 | --help: Display this usage information\n\ 49 | --verbose: Display extended information about kwalitee scoring\n\ 50 | --no-color: Don't display scoring information with color\n\ 51 | --parseable: Print scoring information in a machine readable way\n\ 52 | ", 53 | script_name); 54 | 55 | console.log(usage); 56 | } else { 57 | var foo = new kwal({ "path": path.resolve(directory) }); 58 | foo.init(function(){ 59 | foo.score(print_scores); 60 | }); 61 | } 62 | 63 | function print_scores (scores) { 64 | if(!argv.verbose) { 65 | console.log("Kwalitee Score: " + ((scores.overall.score / scores.overall.total) * 100) + "%"); 66 | console.log(" (score/total): " + scores.overall.score + " / " + scores.overall.total); 67 | } else { 68 | console.log("Kwalitee Score: " + ((scores.overall.score / scores.overall.total) * 100) + "%"); 69 | 70 | scores.checkers_used.forEach(function(item) { 71 | console.log("\n" + item + ":"); 72 | Object.getOwnPropertyNames(scores.scores[item]).forEach(function(check) { 73 | var check_scores = scores.scores[item][check]; 74 | var amount = (check_scores[0] / check_scores[1]); 75 | 76 | var msg = util.format(" %s:\n %s (%s / %s)", check, amount, check_scores[0], check_scores[1]); 77 | console.log(msg); 78 | }); 79 | }); 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | # kwalitee 2 | ### Version 1.0.0 3 | 4 | ## Description 5 | 6 | This package is designed to give a community biased and opinionated metric to packages on NPM. 7 | 8 | Over time, the community has built up a series of best practices in regards to what constitutes great packaging on NPM. As a lot of these are practices are unwritten or unknown to new comers (or even old comers); this tool should provide them with a great way in which to incrementally improve their package for wider use by the community. 9 | 10 | This module is **EXPECTED** to change over time, with new metrics, improved metrics and perhaps even complete reversal on metrics that may be considered bad in future. 11 | 12 | The hope is that this information will be used by the NPM registry directly so that the "_kwalitee_" metric can be tracked over time and improve with community involvement. 13 | 14 | > **NB** The `kwalitee` package is "**self-hosting**". kwalitee is run against itself to ensure that it is held to it's own opinions. 15 | 16 | Please see [Bugs](#Bugs) if you have questions or concerns about these metrics. 17 | 18 | ## Introduction 19 | 20 | Use the kwalitee script with a path to a directory containing an NPM package. This directory _must_ contain a package.json file. 21 | 22 | kwalitee 23 | 24 | Options: 25 | --help: Display this usage information 26 | --verbose: Display extended information about kwalitee scoring 27 | --no-color: Don't display scoring information with color 28 | --parseable: Print scoring information in a machine readable way 29 | 30 | The default output from the script is a percentage based on the score of the package out of the possible scoring available. For a breakdown of how the scoring was put together, use the `verbose` flag to get a full listing. 31 | 32 | ### Checkers 33 | 34 | Kwalitee uses a group of "checkers" to validate packages. These are broken out into functional aspects like so: 35 | 36 | * [Package](docs/checkers/Package.md): static checks based on package.json 37 | * [Tests](docs/checkers/Tests.md): checks based around package testing 38 | * [Addon](docs/checkers/Addon.md): checks around use of native addon packages 39 | 40 | > If you have additional checks that you would like to see here, please submit a PR againt [the github repository](https://github.com/konobi/kwalitee/). While issues are appreciated, 41 | > due to the nature of how specific these checks must be, PRs are far more likely to be brought in in a timely manor. 42 | 43 | ## Additional 44 | 45 | ### Bugs 46 | 47 | Please report any bugs, issues or feature requests to [the github repository](https://github.com/konobi/kwalitee/issues/) for the project. The project author and collaborators will be notified. 48 | 49 | ### Author 50 | 51 | Scott "konobi" McWhirter (npm: konobi) 52 | 53 | ### Collaborators 54 | 55 | * Isaac Z. Schlueter 56 | * Forrest L. Norvell 57 | 58 | ### Contributors 59 | 60 | To have your name here, please submit a PR! 61 | 62 | ### Copyright & License 63 | 64 | © 2015 Cloudtone and Contributors, All rights reserved. 65 | 66 | This software package is released under the conditions of the **MIT** license. Please see the _"LICENSE"_ file distributed within this package for details. 67 | -------------------------------------------------------------------------------- /docs/checkers/Addon.md: -------------------------------------------------------------------------------- 1 | Addon Checker 2 | ============== 3 | 4 | This checker is used to assess various practices around native addon packages. 5 | 6 | ## addon_depends_on_nan_and_bindings **[2.0 or 4.0]** 7 | 8 | The core node.js team have declared that using the "nan" and "bindings" package are the suggested way to handle developing and runtime use of native addons. Currently there may be addons that may need to use a mechanism other than "bindings" for loading the addon object, so you can score either half or full points for this check. 9 | 10 | ## addon_depends_on_nodegyp_devdependency **[3.0]** 11 | 12 | Many addon packages don't declare "node-gyp" as an item under the devDependencies field. Since this may cause confusion to users, especially in terms of which version of node-gyp the author requires, this should be defined in the package.json. 13 | 14 | -------------------------------------------------------------------------------- /docs/checkers/Package.md: -------------------------------------------------------------------------------- 1 | Package Checker 2 | ============== 3 | 4 | This checker is used to statically assess a package's **package.json** to ensure that it follows community best practices. 5 | 6 | ## packagename_does_not_include_node **[1.0]** 7 | 8 | Node and NPM have commonly stated that it is redundant to use "node" in the name of a package, since it will be installed from a registry for node. The following patterns will be cause a negative scoring: 9 | ``` 10 | node-shinypackage 11 | node_shinypackage 12 | shinypackage-node 13 | shinypackage_node 14 | ``` 15 | 16 | ## packagename_does_not_include_js **[1.0]** 17 | 18 | Node and NPM have commonly stated that it is redundant to use a "js" prefix or suffix in the name of a package, since it is a given that it is JS. The following patterns will be cause a negative scoring: 19 | ``` 20 | js-shinypackage 21 | js_shinypackage 22 | shinypackage-js 23 | shinypackage_js 24 | ``` 25 | 26 | ## package_has_repo **[1.0]** 27 | 28 | Packages that are published to the NPM registry should have a repository assigned in package.json so that users can refer to commit history, changes, branches, etc. 29 | 30 | (**NB:** This check does not currently check formatting of the repository, but over time, will be updated to check for specific formatting as NPM requires.) 31 | 32 | ## package_has_sufficient_description **[2.0]** 33 | 34 | When browsing through the NPM registry, the description assigned in package.json is used to show the purpose of the package. Unfortunately, many authors provide little to no information in this description that is useful for users. This check makes sure that there is at least a reasonable description. 35 | 36 | (**NB:** Currently this is a very simple check, however in the future it is expected that this will become more complex.) 37 | 38 | ## package_has_spdx_license **[3.0 or 4.0]** 39 | 40 | NPM now expects the license field in package.json to match [SPDX specifications](http://spdx.org/) for license names. This check ensures that the license assigned in package.json is formatted appropriately, with extra credit for use of a license that is OSI approved. 41 | 42 | ## package_has_valid_semver **[6.0]** 43 | 44 | The version field now needs to comply with the [Semantic Versioning](http://semver.org) specifications. Since this field is used for version comparisons for dependencies, etc. it is scored highly to encourage authors to switch to this versioning method as soon as possible. 45 | 46 | ## package_has_valid_semver_with_base_value **[3.0]** 47 | 48 | Due to how the semver spec works, NPM has decided that any version number for a package that is less than '1.0.0' is problematic. For this reason, we ensure that the version is equal to or higher than this. This is also now the default behaviour of `npm init`. 49 | 50 | ## package_has_minimum_keywords **[2.0]** 51 | 52 | Packages on the NPM registry use keywords assigned from the "keywords" fields for extra information for searching and to provide extra indicators as to a packages use. As many packages may have similar names but entirely different uses, we want to make sure that we have at least a few keywords for a package. 53 | 54 | (**NB:** Currently the minimum is set at **3** keywords, but this may change in future. It is best to add as many appropriate keywords that you see fit to indicate your packages use.) 55 | 56 | ## package_has_author **[1.0]** 57 | 58 | NPM prefers to have the package author to be defined in the package.json. 59 | 60 | (**NB:** This currently only accepts the "object" format of a "person" and not the shorthand string version. This is based on a preference from NPM staff.) 61 | 62 | ## package_has_test_script **[5.0]** 63 | 64 | Well tested packages are a great boon for users, however there are many different test frameworks all with different ways to invoke tests to be run. To help all users, we want to be able to run all applicable tests by invoking `npm test` for a package. This check ensures that the "test" npm script is defined in the package.json. 65 | 66 | 67 | -------------------------------------------------------------------------------- /docs/checkers/Tests.md: -------------------------------------------------------------------------------- 1 | Tests Checker 2 | ============== 3 | 4 | This checker is used to assess various practices around testing for packages. 5 | 6 | ## test_folder_exists **[10.0]** 7 | 8 | Within the package there should be a folder "test" that contains test scripts and data for the package. The decision of the name is based on what is currently most common on the NPM registry. Use of a folder is preferred for simple separation of concerns. 9 | 10 | ## test_script_is_not_default **[10.0]** 11 | 12 | When initially creating a package using `npm init`, a default "test" script is inserted into package.json. This check ensures that the script has been changed from this spurious default. 13 | 14 | -------------------------------------------------------------------------------- /lib/base.js: -------------------------------------------------------------------------------- 1 | 2 | function base_checker () { 3 | } 4 | 5 | base_checker.prototype.get_score_test_names = function () { 6 | var self = this; 7 | var score_test_funcs = Object.getOwnPropertyNames(Object.getPrototypeOf(self)).filter(function(value) { 8 | if(value.match('^_score_') ){ 9 | return true; 10 | } 11 | return false; 12 | }); 13 | 14 | return score_test_funcs; 15 | } 16 | 17 | base_checker.prototype.gather_scores = function (ret){ 18 | var self = this; 19 | 20 | self.get_score_test_names().forEach(function(test_function) { 21 | var score = self[test_function](); 22 | ret.overall.score += score[0]; 23 | ret.overall.total += score[1]; 24 | var test_name = test_function.replace(/^_score_/, ''); 25 | ret.scores[test_name] = score; 26 | }); 27 | 28 | return ret; 29 | } 30 | 31 | module.exports = base_checker; 32 | -------------------------------------------------------------------------------- /lib/checkers/addon.js: -------------------------------------------------------------------------------- 1 | 2 | var async = require('async'); 3 | var path = require('path'); 4 | var fs = require('fs'); 5 | var util = require('util'); 6 | var basechecker = require('../base'); 7 | 8 | function AddonChecker (path, _test_pkg_json) { 9 | var self = this; 10 | 11 | basechecker.call(this); 12 | 13 | self.path = path; 14 | self.data = {}; 15 | 16 | self.package_json = require(path + '/package.json'); 17 | 18 | // Purely for easier testing 19 | if(typeof _test_pkg_json === 'object') { 20 | self.package_json = _test_pkg_json; 21 | } 22 | 23 | return self; 24 | } 25 | util.inherits(AddonChecker, basechecker); 26 | 27 | AddonChecker.prototype.name = "addon_checker"; 28 | 29 | AddonChecker.prototype.score = function score (cb) { 30 | var self = this; 31 | 32 | var ret = { 33 | overall: { score: 0, total: 0}, 34 | scores: {} 35 | }; 36 | 37 | var score_test_funcs = self.get_score_test_names(); 38 | 39 | async.series([ 40 | function check_for_test_directory ($cb) { 41 | var check_path = path.resolve(path.join(self.path, 'binding.gyp')); 42 | fs.stat( check_path, function(err, stat) { 43 | self.data.is_addon = (!err && stat && stat.isFile()) ? true : false; 44 | $cb(); 45 | }); 46 | } 47 | ], function(err) { 48 | 49 | if(self.data.is_addon === false) { 50 | return cb(ret); 51 | } 52 | 53 | cb(self.gather_scores(ret)); 54 | }); 55 | 56 | }; 57 | 58 | 59 | AddonChecker.prototype._score_addon_depends_on_nodegyp_devdependency = function() { 60 | var self = this; 61 | 62 | var ret_score = 0; 63 | if(self.package_json.devDependencies 64 | && typeof self.package_json.devDependencies === 'object'){ 65 | 66 | if('node-gyp' in self.package_json.devDependencies) { 67 | ret_score = 3.0; 68 | } 69 | } 70 | 71 | return [ ret_score, 3.0 ]; 72 | }; 73 | 74 | AddonChecker.prototype._score_addon_depends_on_nan_and_bindings = function() { 75 | var self = this; 76 | 77 | var ret_score = 0; 78 | if(self.package_json.dependencies 79 | && typeof self.package_json.dependencies === 'object'){ 80 | 81 | if('nan' in self.package_json.dependencies) { 82 | ret_score += 2.0; 83 | } 84 | if('bindings' in self.package_json.dependencies) { 85 | ret_score += 2.0; 86 | } 87 | } 88 | 89 | return [ ret_score, 4.0 ]; 90 | }; 91 | 92 | module.exports = AddonChecker; 93 | -------------------------------------------------------------------------------- /lib/checkers/package.js: -------------------------------------------------------------------------------- 1 | var spdx = require('spdx-licenses'); 2 | var semver = require('semver'); 3 | var util = require('util'); 4 | var basechecker = require('../base'); 5 | 6 | function PackageChecker (path, _test_pkg_json) { 7 | var self = this; 8 | 9 | basechecker.call(this); 10 | self.path = path; 11 | 12 | self.package_json = require(path + '/package.json'); 13 | 14 | // Purely for easier testing 15 | if(typeof _test_pkg_json === 'object') { 16 | self.package_json = _test_pkg_json; 17 | } 18 | 19 | return self; 20 | } 21 | util.inherits(PackageChecker, basechecker); 22 | 23 | PackageChecker.prototype.name = "package_checker"; 24 | 25 | PackageChecker.prototype.score = function score (cb) { 26 | var self = this; 27 | 28 | var ret = { 29 | overall: { score: 0, total: 0}, 30 | scores: {} 31 | }; 32 | 33 | cb(self.gather_scores(ret)); 34 | }; 35 | 36 | /* 37 | There are lots of packages out there with "node-" or "-node" as a component of the 38 | package name. This is redundant though. The name of a git repo shouldn't influence 39 | the package name. 40 | */ 41 | PackageChecker.prototype._score_packagename_does_not_include_node = function () { 42 | var self = this; 43 | 44 | if(!self.package_json.name.match(/(?:^node(?:-|_)|(?:-|_)node$)/)) { 45 | return [ 1.0, 1.0 ]; 46 | } else { 47 | return [ 0.0, 1.0 ]; 48 | } 49 | }; 50 | 51 | PackageChecker.prototype._score_packagename_does_not_include_js = function () { 52 | var self = this; 53 | 54 | if(!self.package_json.name.match(/(?:^js(?:-|_)|(?:-|_)js$)/)) { 55 | return [ 1.0, 1.0 ]; 56 | } else { 57 | return [ 0.0, 1.0 ]; 58 | } 59 | }; 60 | 61 | /* 62 | Should have a repo defined. 63 | XXX - We should also add additional tests to check for validity of repo location 64 | and it's availability over time (repo rename, organization moves, etc. 65 | */ 66 | PackageChecker.prototype._score_package_has_repo = function () { 67 | var self = this; 68 | 69 | if(!self.package_json.repository) { 70 | return [ 0.0, 1.0 ]; 71 | } else { 72 | return [ 1.0, 1.0 ]; 73 | } 74 | }; 75 | 76 | /* 77 | The description should be verbose about the purpose of the package 78 | XXX - This is currently arbitraryly set at 30 characters length. A better 79 | method to define the readability would be next. 80 | */ 81 | PackageChecker.prototype._score_package_has_sufficient_description = function () { 82 | var self = this; 83 | 84 | if(!self.package_json.description || self.package_json.description.length < 30) { 85 | return [ 0.0, 2.0 ]; 86 | } else { 87 | return [ 2.0, 2.0 ]; 88 | } 89 | }; 90 | 91 | /* 92 | The package should have an spdx registered licence. We give extra 93 | credence to licences that are OSI approved. 94 | */ 95 | PackageChecker.prototype._score_package_has_spdx_license = function () { 96 | var self = this; 97 | 98 | if(self.package_json.license) { 99 | var score = 0.0; 100 | var info = spdx.spdx(self.package_json.license); 101 | if(info) { 102 | score = score + 3.0; 103 | if(info.OSIApproved) { 104 | score = score + 1.0; 105 | } 106 | } 107 | 108 | return [ score, 4.0 ]; 109 | } else { 110 | return [ 0.0, 4.0 ]; 111 | } 112 | }; 113 | 114 | /* 115 | Valid semver is needed, of course 116 | */ 117 | PackageChecker.prototype._score_package_has_valid_semver = function () { 118 | var self = this; 119 | 120 | if(self.package_json.version) { 121 | if(semver.valid(self.package_json.version)) { 122 | return [ 6.0, 6.0 ]; 123 | } 124 | } 125 | return [ 0.0, 6.0 ]; 126 | }; 127 | 128 | PackageChecker.prototype._score_package_has_valid_semver_with_base_value = function () { 129 | var self = this; 130 | 131 | var version = self.package_json.version; 132 | 133 | if(version) { 134 | if( 135 | semver.valid(version) 136 | && semver.satisfies(version, '>=1.0.0') 137 | ) { 138 | return [ 3.0, 3.0 ]; 139 | } 140 | } 141 | return [ 0.0, 3.0 ]; 142 | }; 143 | 144 | /* 145 | While there's no real idea of what constitutes a good sequence of keywords, 146 | having at least 3 keywords is a good idea. At least then, it's thought about. 147 | */ 148 | PackageChecker.prototype._score_package_has_minimum_keywords = function () { 149 | var self = this; 150 | 151 | if(self.package_json.keywords) { 152 | if(self.package_json.keywords.length >= 3){ 153 | return [ 2.0, 2.0 ]; 154 | } 155 | } 156 | return [ 0.0, 2.0 ]; 157 | }; 158 | 159 | /* 160 | XXX - should probably add better checks and maybe different scoring for 161 | more information 162 | */ 163 | PackageChecker.prototype._score_package_has_author = function () { 164 | var self = this; 165 | 166 | if(self.package_json.author) { 167 | if(typeof self.package_json.author === 'object') { 168 | if(self.package_json.author.name 169 | && typeof self.package_json.author.name === 'string' 170 | && self.package_json.author.name.length > 5) { 171 | 172 | return [ 1.0, 1.0 ]; 173 | } 174 | } 175 | } 176 | 177 | return [ 0.0, 1.0 ]; 178 | }; 179 | 180 | PackageChecker.prototype._score_package_has_test_script = function () { 181 | var self = this; 182 | 183 | if(self.package_json.scripts 184 | && typeof self.package_json.scripts === 'object' 185 | && self.package_json.scripts.test 186 | && typeof self.package_json.scripts.test === 'string' 187 | && self.package_json.scripts.test.length > 0) 188 | { 189 | return [ 5.0, 5.0 ]; 190 | } 191 | 192 | return [ 0.0, 5.0 ]; 193 | }; 194 | 195 | module.exports = PackageChecker; 196 | -------------------------------------------------------------------------------- /lib/checkers/tests.js: -------------------------------------------------------------------------------- 1 | var async = require('async'); 2 | var path = require('path'); 3 | var fs = require('fs'); 4 | var util = require('util'); 5 | var basechecker = require('../base'); 6 | 7 | function TestsChecker (path, _test_pkg_json) { 8 | var self = this; 9 | 10 | basechecker.call(this); 11 | 12 | self.path = path; 13 | self.data = {}; 14 | 15 | self.package_json = require(path + '/package.json'); 16 | 17 | // Purely for easier testing 18 | if(typeof _test_pkg_json === 'object') { 19 | self.package_json = _test_pkg_json; 20 | } 21 | 22 | return self; 23 | } 24 | util.inherits(TestsChecker, basechecker); 25 | 26 | TestsChecker.prototype.name = "tests_checker"; 27 | 28 | TestsChecker.prototype.score = function score (cb) { 29 | var self = this; 30 | 31 | var ret = { 32 | overall: { score: 0, total: 0}, 33 | scores: {} 34 | }; 35 | 36 | var score_test_funcs = self.get_score_test_names(); 37 | 38 | async.series([ 39 | function check_for_test_directory ($cb) { 40 | var check_path = path.join(self.path, 'test'); 41 | fs.stat( check_path, function(err, stat) { 42 | self.data.has_test_directory = (!err && stat && stat.isDirectory()) ? true : false; 43 | $cb(); 44 | }); 45 | } 46 | ], function(err) { 47 | cb(self.gather_scores(ret)); 48 | }); 49 | 50 | }; 51 | 52 | /* 53 | We should have a folder containing tests. Based on input from community and npm, 54 | this has been decided as "test" 55 | */ 56 | TestsChecker.prototype._score_test_folder_exists = function () { 57 | var self = this; 58 | 59 | if(self.data.has_test_directory) { 60 | return [ 10.0, 10.0 ]; 61 | } 62 | 63 | return [ 0.0, 10.0 ]; 64 | }; 65 | 66 | /* 67 | `npm init` will fill in a default test script that ends with "&& exit 1". We want to 68 | make sure that it's not that. 69 | */ 70 | TestsChecker.prototype._score_test_script_is_not_default = function() { 71 | var self = this; 72 | 73 | if(self.package_json.scripts 74 | && typeof self.package_json.scripts === 'object' 75 | && self.package_json.scripts.test 76 | && typeof self.package_json.scripts.test === 'string' 77 | && !self.package_json.scripts.test.match(/&& exit 1\s*?$/) ) 78 | { 79 | return [ 10.0, 10.0 ]; 80 | } 81 | 82 | return [ 0.0, 10.0 ]; 83 | }; 84 | 85 | module.exports = TestsChecker; 86 | -------------------------------------------------------------------------------- /lib/index.js: -------------------------------------------------------------------------------- 1 | 2 | var fs = require('fs'); 3 | var path = require('path'); 4 | var async = require('async'); 5 | 6 | function Package (opts) { 7 | var self = this; 8 | 9 | self.path = opts.path; 10 | self.checkers = opts.checkers || []; 11 | self.checks = []; 12 | 13 | return self; 14 | } 15 | 16 | Package.prototype.init = function init (cb) { 17 | var self = this; 18 | 19 | if(self.checkers.length <= 0){ 20 | fs.readdir(path.dirname(module.filename) + '/checkers/', function(err, files) { 21 | var names = files.filter(function(item){ 22 | return item.match(/\.js$/); 23 | }); 24 | self.checks = checker_names_to_modules(names); 25 | cb(); 26 | }); 27 | } else { 28 | self.checks = checker_names_to_modules(self.checkers); 29 | cb(); 30 | } 31 | 32 | }; 33 | 34 | function checker_names_to_modules (names) { 35 | var mapping = {}; 36 | 37 | names.forEach(function(item){ 38 | var checker_module = require('./checkers/' + item); 39 | mapping[item] = checker_module; 40 | }); 41 | 42 | return mapping; 43 | } 44 | 45 | Package.prototype.score = function score (cb) { 46 | var self = this; 47 | 48 | var names = Object.getOwnPropertyNames(self.checks); 49 | var ret = { 50 | overall: { score: 0, total: 0 }, 51 | scores: {}, 52 | checkers_used: [] 53 | }; 54 | 55 | var list_of_checkers = []; 56 | var i = 0; 57 | for(i=0; i < names.length; i++){ 58 | var name = names[i]; 59 | list_of_checkers.push(self.checks[name]); 60 | } 61 | 62 | async.each( 63 | list_of_checkers, 64 | function __score_iterator (item, callback) { 65 | var checker = new item(self.path); 66 | checker.score(function(scores) { 67 | ret.overall.score += scores.overall.score; 68 | ret.overall.total += scores.overall.total; 69 | 70 | ret.scores[ checker.name ] = scores.scores; 71 | ret.checkers_used.push(checker.name); 72 | callback(); 73 | }); 74 | }, 75 | function __score_finalize (err) { 76 | // XXX - should we add more processing here? like percentages or 77 | // the like? 78 | cb(ret); 79 | } 80 | ); 81 | }; 82 | 83 | module.exports = Package; 84 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "kwalitee", 3 | "version": "1.0.0", 4 | "description": "Kwalitee scoring for npm packages, providing tooling to help package authors", 5 | "main": "lib/index.js", 6 | "scripts": { 7 | "test": "tap test" 8 | }, 9 | "author": "", 10 | "license": "MIT", 11 | "devDependencies": { 12 | "async": "^1.4.2", 13 | "nyc": "^3.1.0", 14 | "tap": "^1.3.2" 15 | }, 16 | "dependencies": { 17 | "async": "^1.4.2", 18 | "minimist": "^1.2.0", 19 | "semver": "^5.0.1", 20 | "spdx-licenses": "0.0.3" 21 | }, 22 | "bin": { 23 | "kwalitee": "bin/kwalitee.js" 24 | }, 25 | "repository": "github:konobi/kwalitee", 26 | "keywords": [ "package", "npm", "author", "authoring", "test", "testing", "publish", "publishing" ], 27 | "author": { 28 | "name": "Scott 'konobi' McWhirter" 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /test/basic_operation.js: -------------------------------------------------------------------------------- 1 | 2 | var tap = require('tap'); 3 | var kwalitee = require('../lib/index.js'); 4 | 5 | var check = new kwalitee({ path: '../../test/data/basic-operation/' }); 6 | 7 | check.init(function() { 8 | check.score(function (scores) { 9 | tap.equals(scores.overall.total, 46.0, 'got the correct overall'); 10 | }); 11 | }); 12 | 13 | -------------------------------------------------------------------------------- /test/checkers/addon.js: -------------------------------------------------------------------------------- 1 | var tap = require('tap'); 2 | var async = require('async'); 3 | var path = require('path'); 4 | 5 | var AddonChecker = require('../../lib/checkers/addon'); 6 | var fake_path = path.join(__dirname, 'addon/test-dir/') 7 | function reset () { 8 | delete require.cache[ path.resolve(fake_path) ]; 9 | } 10 | function fresh (stuff ) { 11 | reset(); 12 | 13 | return new AddonChecker(fake_path, stuff); 14 | } 15 | 16 | tap.test('basic functionality', function(t) { 17 | t.type(AddonChecker, 'function', "Ensure checker is function"); 18 | 19 | var obj = new AddonChecker(fake_path); 20 | t.type(obj, 'object', "AddonTestsChecker is a constructor"); 21 | reset(); 22 | 23 | var obj2 = new AddonChecker(fake_path); 24 | t.type(obj2, 'object', "Uses package.json in directory when available"); 25 | reset(); 26 | 27 | t.done(); 28 | }); 29 | 30 | tap.test('Addon requires nan and bindings as dependencies', function(t) { 31 | 32 | async.series([ 33 | function(cb){ 34 | var obj = fresh({ 35 | name: 'addon-without-dependencies' 36 | }); 37 | obj.score(function(score) { 38 | var s = score.scores; 39 | t.equals( s.addon_depends_on_nan_and_bindings[0], 0.0, 40 | 'Correct scoring for addon without dependencies'); 41 | t.equals( s.addon_depends_on_nan_and_bindings[1], 4.0, 42 | 'Correct possible score for addon with dependencies'); 43 | 44 | cb(); 45 | }); 46 | }, 47 | function(cb){ 48 | var obj = fresh({ 49 | name: 'addon-with-dependencies', 50 | dependencies: { 51 | "nan": "1.2.3", 52 | "bindings": "4.5.6" 53 | } 54 | }); 55 | obj.score(function(score) { 56 | var s = score.scores; 57 | t.equals( s.addon_depends_on_nan_and_bindings[0], 4.0, 58 | 'Correct scoring for addon with dependencies'); 59 | 60 | cb(); 61 | }); 62 | }, 63 | function(cb){ 64 | var obj = fresh({ 65 | name: 'addon-with-only-one-dependency', 66 | dependencies: { 67 | "nan": "1.2.3", 68 | } 69 | }); 70 | obj.score(function(score) { 71 | var s = score.scores; 72 | t.equals( s.addon_depends_on_nan_and_bindings[0], 2.0, 73 | 'Correct scoring for addon with one dependency'); 74 | 75 | cb(); 76 | }); 77 | } 78 | ], function(err) { 79 | t.done(); 80 | }); 81 | 82 | }); 83 | 84 | tap.test('Addon requires node-gyp as devdependency', function(t) { 85 | 86 | async.series([ 87 | function(cb){ 88 | var obj = fresh({ 89 | name: 'addon-without-devdependencies' 90 | }); 91 | obj.score(function(score) { 92 | var s = score.scores; 93 | t.equals( s.addon_depends_on_nodegyp_devdependency[0], 0.0, 94 | 'Correct scoring for addon without devdependencies'); 95 | t.equals( s.addon_depends_on_nodegyp_devdependency[1], 3.0, 96 | 'Correct possible score for addon with node-gyp devdependency'); 97 | 98 | cb(); 99 | }); 100 | }, 101 | function(cb){ 102 | var obj = fresh({ 103 | name: 'addon-with-nodegyp-devdependency', 104 | devDependencies: { 105 | "node-gyp": "2.4.6" 106 | } 107 | }); 108 | obj.score(function(score) { 109 | var s = score.scores; 110 | t.equals( s.addon_depends_on_nodegyp_devdependency[0], 3.0, 111 | 'Correct scoring for addon with node-gyp devdependency'); 112 | 113 | cb(); 114 | }); 115 | } 116 | ], function(err) { 117 | t.done(); 118 | }); 119 | 120 | }); 121 | 122 | -------------------------------------------------------------------------------- /test/checkers/addon/test-dir/binding.gyp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/konobi/kwalitee/b467ab91b44a86b8480a1c60d9dd595142cf8654/test/checkers/addon/test-dir/binding.gyp -------------------------------------------------------------------------------- /test/checkers/addon/test-dir/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "yupworks" 3 | } 4 | -------------------------------------------------------------------------------- /test/checkers/package.js: -------------------------------------------------------------------------------- 1 | 2 | var tap = require('tap'); 3 | var async = require('async'); 4 | var path = require('path'); 5 | 6 | var PackageChecker = require('../../lib/checkers/package'); 7 | var fake_path = '../../test/checkers/package/test-dir/'; 8 | function reset () { 9 | delete require.cache[ path.resolve(fake_path) ]; 10 | } 11 | function fresh (stuff) { 12 | reset(); 13 | return new PackageChecker(fake_path, stuff); 14 | } 15 | 16 | tap.test('basic functionality', function(t) { 17 | t.type(PackageChecker, 'function', "Ensure checker is function"); 18 | 19 | var obj = new PackageChecker(fake_path); 20 | t.type(obj, 'object', "PackageChecker is a constructor"); 21 | reset(); 22 | 23 | var obj2 = new PackageChecker(fake_path); 24 | t.type(obj2, 'object', "Uses package.json in directory when available"); 25 | reset(); 26 | 27 | t.done(); 28 | }); 29 | 30 | tap.test('package does not include name', function(t) { 31 | 32 | async.series([ 33 | function(cb){ 34 | var obj = fresh({ 35 | name: 'node-this-should-fail' 36 | }); 37 | obj.score(function(score) { 38 | var s = score.scores; 39 | t.equals( s.packagename_does_not_include_node[0], 0.0, 40 | 'Correct scoring for package with node in name'); 41 | t.equals( s.packagename_does_not_include_node[1], 1.0, 42 | 'Correct possible score for package with node in name'); 43 | 44 | cb(); 45 | }); 46 | }, 47 | function(cb){ 48 | var obj = fresh({ 49 | name: 'this-is-okay-as-a-name' 50 | }); 51 | obj.score(function(score) { 52 | var s = score.scores; 53 | t.equals( s.packagename_does_not_include_node[0], 1.0, 54 | 'Correct scoring for package without node in name'); 55 | 56 | cb(); 57 | }); 58 | } 59 | ], function(err) { 60 | t.done(); 61 | }); 62 | 63 | }); 64 | 65 | tap.test('package does not include "js" in name', function(t) { 66 | 67 | async.series([ 68 | function(cb){ 69 | var obj = fresh({ 70 | name: 'this-should-fail-js' 71 | }); 72 | obj.score(function(score) { 73 | var s = score.scores; 74 | t.equals( s.packagename_does_not_include_js[0], 0.0, 75 | 'Correct scoring for package with "js" in name'); 76 | t.equals( s.packagename_does_not_include_js[1], 1.0, 77 | 'Correct possible score for package with "js" in name'); 78 | 79 | cb(); 80 | }); 81 | }, 82 | function(cb){ 83 | var obj = fresh({ 84 | name: 'this-is-okay-as-a-name' 85 | }); 86 | obj.score(function(score) { 87 | var s = score.scores; 88 | t.equals( s.packagename_does_not_include_js[0], 1.0, 89 | 'Correct scoring for package without "js" in name'); 90 | 91 | cb(); 92 | }); 93 | } 94 | ], function(err) { 95 | t.done(); 96 | }); 97 | 98 | }); 99 | 100 | tap.test('package has repo', function(t) { 101 | 102 | async.series([ 103 | function(cb){ 104 | var obj = fresh({ 105 | name: "without-repo" 106 | }); 107 | obj.score(function(score) { 108 | var s = score.scores; 109 | t.equals(s.package_has_repo[0], 0.0, 110 | 'Correct scoring for package without repo'); 111 | t.equals(s.package_has_repo[1], 1.0, 112 | 'Correct possible score for package without repo'); 113 | 114 | cb(); 115 | }); 116 | }, 117 | function(cb){ 118 | var obj = fresh({ 119 | name: "with-repo", 120 | repository: "repoexists" 121 | }); 122 | obj.score(function(score) { 123 | var s = score.scores; 124 | t.equals(s.package_has_repo[0], 1.0, 125 | 'Correct scoring for package with repo'); 126 | 127 | cb(); 128 | }); 129 | } 130 | ], function(err) { 131 | t.done(); 132 | }); 133 | 134 | }); 135 | 136 | tap.test('package has sufficient description', function(t) { 137 | 138 | async.series([ 139 | function(cb){ 140 | var obj = fresh({ 141 | name: "without-sufficient-description", 142 | description: "yeah no" 143 | }); 144 | obj.score(function(score) { 145 | var s = score.scores; 146 | t.equals(s.package_has_sufficient_description[0], 0.0, 147 | 'Correct scoring for package without repo'); 148 | t.equals(s.package_has_sufficient_description[1], 2.0, 149 | 'Correct possible score for package without repo'); 150 | 151 | cb(); 152 | }); 153 | }, 154 | function(cb){ 155 | var obj = fresh({ 156 | name: 'is-sufficient-description', 157 | description: "This is an exmaple of a descritpion which is sufficient for kwalitee" 158 | }); 159 | obj.score(function(score) { 160 | var s = score.scores; 161 | t.equals(s.package_has_sufficient_description[0], 2.0, 162 | 'Correct scoring for package with repo'); 163 | 164 | cb(); 165 | }); 166 | } 167 | ], function(err) { 168 | t.done(); 169 | }); 170 | 171 | }); 172 | 173 | tap.test('package has spdx licensing', function(t) { 174 | 175 | async.series([ 176 | function(cb){ 177 | var obj = fresh({ 178 | name: "without-spdx-license", 179 | license: "MegaCorpLicense" 180 | }); 181 | obj.score(function(score) { 182 | var s = score.scores; 183 | t.equals(s.package_has_spdx_license[0], 0.0, 184 | 'Correct scoring for package without spdx license'); 185 | t.equals(s.package_has_spdx_license[1], 4.0, 186 | 'Correct possible score for package without spdx license'); 187 | 188 | cb(); 189 | }); 190 | }, 191 | function(cb){ 192 | var obj = fresh({ 193 | name: "spdx-non-osi-license", 194 | license: "YPL-1.0" 195 | }); 196 | obj.score(function(score) { 197 | var s = score.scores; 198 | t.equals(s.package_has_spdx_license[0], 3.0, 199 | 'Correct scoring for package with spdx license (non-osi-approved)'); 200 | 201 | cb(); 202 | }); 203 | }, 204 | function(cb) { 205 | var obj = fresh({ 206 | name: "valid-spdx-osi", 207 | license: "MIT" 208 | }); 209 | obj.score(function(score) { 210 | var s = score.scores; 211 | t.equals(s.package_has_spdx_license[0], 4.0, 212 | 'Correct scoring for package with spdx license (osi-approved)'); 213 | 214 | cb(); 215 | }); 216 | } 217 | ], function(err) { 218 | t.done(); 219 | }); 220 | 221 | }); 222 | 223 | tap.test('package has valid semver', function(t) { 224 | 225 | async.series([ 226 | function(cb){ 227 | var obj = fresh({ 228 | name: "not-valid-semver", 229 | version: "ahahahaha" 230 | }); 231 | obj.score(function(score) { 232 | var s = score.scores; 233 | t.equals(s.package_has_valid_semver[0], 0.0, 234 | 'Correct scoring for package without valid semver'); 235 | t.equals(s.package_has_valid_semver[1], 6.0, 236 | 'Correct possible score for package without valid semver'); 237 | 238 | cb(); 239 | }); 240 | }, 241 | function(cb){ 242 | var obj = fresh({ 243 | name: "valid-semver", 244 | version: "1.2.3" 245 | }); 246 | obj.score(function(score) { 247 | var s = score.scores; 248 | t.equals(s.package_has_valid_semver[0], 6.0, 249 | 'Correct scoring for package with valid semver'); 250 | 251 | cb(); 252 | }); 253 | }, 254 | ], function(err) { 255 | t.done(); 256 | }); 257 | 258 | }); 259 | 260 | tap.test('package uses semver of 1.0.0 or above', function(t) { 261 | async.series([ 262 | function(cb){ 263 | var obj = fresh({ 264 | name: "valid-semver-not-1.0.0-or-over", 265 | version: "0.2.3" 266 | }); 267 | obj.score(function(score) { 268 | var s = score.scores; 269 | t.equals(s.package_has_valid_semver_with_base_value[0], 0.0, 270 | 'Correct scoring for package without valid semver'); 271 | t.equals(s.package_has_valid_semver_with_base_value[1], 3.0, 272 | 'Correct possible score for package without valid semver'); 273 | 274 | cb(); 275 | }); 276 | }, 277 | function(cb){ 278 | var obj = fresh({ 279 | name: "valid-semver-over-1.0.0", 280 | version: "1.2.3" 281 | }); 282 | obj.score(function(score) { 283 | var s = score.scores; 284 | t.equals(s.package_has_valid_semver_with_base_value[0], 3.0, 285 | 'Correct scoring for package with valid semver'); 286 | 287 | cb(); 288 | }); 289 | }, 290 | ], function(err) { 291 | t.done(); 292 | }); 293 | }); 294 | 295 | tap.test('package has minimum keywords', function(t) { 296 | 297 | async.series([ 298 | function(cb){ 299 | var obj = fresh({ 300 | name: "without-minimum-keywords", 301 | keywords: [ 'just-one' ] 302 | }); 303 | obj.score(function(score) { 304 | var s = score.scores; 305 | t.equals(s.package_has_minimum_keywords[0], 0.0, 306 | 'Correct scoring for package without minimum keywords'); 307 | t.equals(s.package_has_minimum_keywords[1], 2.0, 308 | 'Correct possible score for package without minimum keywords'); 309 | 310 | cb(); 311 | }); 312 | }, 313 | function(cb){ 314 | var obj = fresh({ 315 | name: "with-minimum-keywords", 316 | keywords: [ "one", "two", "three", "more" ] 317 | }); 318 | obj.score(function(score) { 319 | var s = score.scores; 320 | t.equals(s.package_has_minimum_keywords[0], 2.0, 321 | 'Correct scoring for package with minimum keywords'); 322 | 323 | cb(); 324 | }); 325 | }, 326 | ], function(err) { 327 | t.done(); 328 | }); 329 | 330 | }); 331 | 332 | tap.test('package has author', function(t) { 333 | 334 | async.series([ 335 | function(cb){ 336 | var obj = fresh({ 337 | name: "no-author" 338 | }); 339 | obj.score(function(score) { 340 | var s = score.scores; 341 | t.equals(s.package_has_author[0], 0.0, 342 | 'Correct scoring for package without author'); 343 | t.equals(s.package_has_author[1], 1.0, 344 | 'Correct possible score for package without author'); 345 | 346 | cb(); 347 | }); 348 | }, 349 | function(cb){ 350 | var obj = fresh({ 351 | name: "with-author", 352 | author: { 353 | name: "A. Uthor" 354 | } 355 | }); 356 | obj.score(function(score) { 357 | var s = score.scores; 358 | t.equals(s.package_has_author[0], 1.0, 359 | 'Correct scoring for package with author'); 360 | 361 | cb(); 362 | }); 363 | }, 364 | ], function(err) { 365 | t.done(); 366 | }); 367 | 368 | }); 369 | 370 | tap.test('package has test script', function(t) { 371 | 372 | async.series([ 373 | function(cb){ 374 | var obj = fresh({ 375 | name: "without-test-script" 376 | }); 377 | obj.score(function(score) { 378 | var s = score.scores; 379 | t.equals(s.package_has_test_script[0], 0.0, 380 | 'Correct scoring for package without test script'); 381 | t.equals(s.package_has_test_script[1], 5.0, 382 | 'Correct possible score for package without test script'); 383 | 384 | cb(); 385 | }); 386 | }, 387 | function(cb){ 388 | 389 | var obj = fresh({ 390 | name: "with-test-script", 391 | scripts: { 392 | test: "echo 'we have some sort of test script here'" 393 | } 394 | }); 395 | obj.score(function(score) { 396 | var s = score.scores; 397 | t.equals(s.package_has_test_script[0], 5.0, 398 | 'Correct scoring for package with test script'); 399 | 400 | cb(); 401 | }); 402 | }, 403 | ], function(err) { 404 | t.done(); 405 | }); 406 | 407 | }); 408 | 409 | -------------------------------------------------------------------------------- /test/checkers/package/test-dir/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "yupworks" 3 | } 4 | -------------------------------------------------------------------------------- /test/checkers/tests.js: -------------------------------------------------------------------------------- 1 | var tap = require('tap'); 2 | var async = require('async'); 3 | var path = require('path'); 4 | 5 | var TestsChecker = require('../../lib/checkers/tests'); 6 | var fake_path = '../../test/checkers/tests/test-dir/'; 7 | function reset () { 8 | delete require.cache[ path.resolve(fake_path) ]; 9 | } 10 | function fresh (stuff, test_path) { 11 | reset(); 12 | 13 | var path_to_use = test_path 14 | ? path.join(__dirname, 'tests/', test_path) 15 | : fake_path; 16 | 17 | return new TestsChecker(path_to_use, stuff); 18 | } 19 | 20 | tap.test('basic functionality', function(t) { 21 | t.type(TestsChecker, 'function', "Ensure checker is function"); 22 | 23 | var obj = new TestsChecker(fake_path); 24 | t.type(obj, 'object', "TestsChecker is a constructor"); 25 | reset(); 26 | 27 | var obj2 = new TestsChecker(fake_path); 28 | t.type(obj2, 'object', "Uses package.json in directory when available"); 29 | reset(); 30 | 31 | t.done(); 32 | }); 33 | 34 | tap.test('test folder exists', function(t) { 35 | 36 | async.series([ 37 | function(cb){ 38 | var obj = fresh({ 39 | name: 'test-folder-does-not-exist' 40 | }, "test-folder-does-not-exist"); 41 | obj.score(function(score) { 42 | var s = score.scores; 43 | t.equals( s.test_folder_exists[0], 0.0, 44 | 'Correct scoring for package without test folder'); 45 | t.equals( s.test_folder_exists[1], 10.0, 46 | 'Correct possible score for package without test folder'); 47 | 48 | cb(); 49 | }); 50 | }, 51 | function(cb){ 52 | var obj = fresh({ 53 | name: 'test-folder-does-exist' 54 | }, 'test-folder-does-exist'); 55 | obj.score(function(score) { 56 | var s = score.scores; 57 | t.equals( s.test_folder_exists[0], 10.0, 58 | 'Correct scoring for package with test folder'); 59 | 60 | cb(); 61 | }); 62 | } 63 | ], function(err) { 64 | t.done(); 65 | }); 66 | 67 | }); 68 | 69 | tap.test('test script is not default', function(t) { 70 | 71 | async.series([ 72 | function(cb){ 73 | var obj = fresh({ 74 | name: 'test-is-default', 75 | scripts: { 76 | "test": "echo 'bad author, no default' && exit 1" 77 | } 78 | }); 79 | obj.score(function(score) { 80 | var s = score.scores; 81 | t.equals( s.test_script_is_not_default[0], 0.0, 82 | 'Correct scoring for default test script'); 83 | t.equals( s.test_script_is_not_default[1], 10.0, 84 | 'Correct possible score for non default test script'); 85 | 86 | cb(); 87 | }); 88 | }, 89 | function(cb){ 90 | var obj = fresh({ 91 | name: 'test-is-not-default', 92 | scripts: { 93 | "test": "echo 'not the default test'" 94 | } 95 | }); 96 | obj.score(function(score) { 97 | var s = score.scores; 98 | t.equals( s.test_script_is_not_default[0], 10.0, 99 | 'Correct scoring for non default test script'); 100 | 101 | cb(); 102 | }); 103 | } 104 | ], function(err) { 105 | t.done(); 106 | }); 107 | 108 | }); 109 | 110 | -------------------------------------------------------------------------------- /test/checkers/tests/test-dir/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "yupworks" 3 | } 4 | -------------------------------------------------------------------------------- /test/checkers/tests/test-folder-does-exist/package.json: -------------------------------------------------------------------------------- 1 | {} 2 | -------------------------------------------------------------------------------- /test/checkers/tests/test-folder-does-exist/test/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore -------------------------------------------------------------------------------- /test/checkers/tests/test-folder-does-not-exist/package.json: -------------------------------------------------------------------------------- 1 | {} 2 | -------------------------------------------------------------------------------- /test/data/basic-operation/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "basic-operations", 3 | "repository": "none-for-now", 4 | "description": "This is a very interesting but non-descript description for this package. It doesn't really exist, but we should be verbose" 5 | } 6 | --------------------------------------------------------------------------------