├── .editorconfig ├── .gitattributes ├── .gitignore ├── .travis.yml ├── cli.js ├── get-reporter.js ├── index.js ├── package.json ├── readme.md ├── reporters ├── default.js ├── json.js └── tap.js ├── rules ├── coffeescript.js ├── missing-package-props.js ├── tests.js ├── typescript.js └── valid-version.js ├── test.js └── test ├── package.json ├── test.coffee └── test.ts /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = tab 5 | end_of_line = lf 6 | charset = utf-8 7 | trim_trailing_whitespace = true 8 | insert_final_newline = true 9 | 10 | [*.yml] 11 | indent_style = space 12 | indent_size = 2 13 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto 2 | *.js text eol=lf 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - '6' 4 | - '4' 5 | -------------------------------------------------------------------------------- /cli.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 'use strict'; 3 | const sudoBlock = require('sudo-block'); 4 | const meow = require('meow'); 5 | const updateNotifier = require('update-notifier'); 6 | const getReporter = require('./get-reporter'); 7 | const npmlint = require('.'); 8 | 9 | const cli = meow(` 10 | Usage 11 | $ npmlint 12 | 13 | Options 14 | -r, --reporter Custom reporter (json|tap||) 15 | -v, --verbose Verbose output 16 | `, { 17 | boolean: ['verbose'], 18 | alias: { 19 | v: '--verbose', 20 | r: '--reporter' 21 | } 22 | }); 23 | 24 | sudoBlock(); 25 | updateNotifier({pkg: cli.pkg}).notify(); 26 | 27 | // TODO: support remote packages 28 | // if (/\w+\/\w+/.test(cmd)) { 29 | // download('https://api.github.com/repos/' + cmd + '/tarball', 'bar', {extract: true}); 30 | // request.get('https://api.github.com/repos/' + cmd + '/tarball', function () { 31 | // init(); 32 | // }); 33 | // return; 34 | // } 35 | 36 | npmlint(null, result => { 37 | console.log(getReporter(cli.flags.reporter, cli.flags)(result, cli.flags)); 38 | }); 39 | -------------------------------------------------------------------------------- /get-reporter.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const fs = require('fs'); 3 | const path = require('path'); 4 | const resolve = require('resolve'); 5 | 6 | module.exports = (reporter, opts) => { 7 | if (!reporter) { 8 | return require('./reporters/default'); 9 | } 10 | 11 | // Cwd file 12 | if (fs.existsSync(reporter)) { 13 | return require(path.resolve(reporter)); 14 | } 15 | 16 | // Cwd node module 17 | try { 18 | return require(resolve.sync(reporter, {basedir: process.cwd()})); 19 | } catch (err) { 20 | if (!/Cannot find module/.test(err)) { 21 | throw err; 22 | } 23 | } 24 | 25 | // File 26 | try { 27 | return require(`./reporters/${reporter}`); 28 | } catch (err) { 29 | if (err.code !== 'MODULE_NOT_FOUND') { 30 | throw err; 31 | } 32 | } 33 | 34 | if (opts.verbose) { 35 | console.error(`Couldn't find custom reporter. Falling back to default.`); 36 | } 37 | }; 38 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const fs = require('fs'); 3 | const path = require('path'); 4 | const findup = require('findup-sync'); 5 | const async = require('async'); 6 | 7 | module.exports = (dir, cb) => { 8 | const pkgPath = findup('package.json', {cwd: dir || process.cwd()}); 9 | const pkg = require(pkgPath); 10 | const cwd = path.dirname(pkgPath); 11 | const rulesPath = path.join(__dirname, 'rules'); 12 | let ret = []; 13 | 14 | if (!pkgPath) { 15 | throw new Error('Couldn\'t find a package.json.'); 16 | } 17 | 18 | async.each(fs.readdirSync(rulesPath), (file, next) => { 19 | if (path.extname(file) !== '.js') { 20 | next(); 21 | return; 22 | } 23 | 24 | const rule = require(path.join(rulesPath, file)); 25 | 26 | rule({cwd, pkg}, (err, result) => { 27 | if (result) { 28 | [].push.apply(ret, Array.isArray(result) ? result : [result]); 29 | } 30 | 31 | next(err); 32 | }); 33 | }, err => { 34 | if (err) { 35 | cb(err); 36 | return; 37 | } 38 | 39 | ret = ret.sort((a, b) => { 40 | const severity = { 41 | info: 0, 42 | warn: 1, 43 | error: 2 44 | }; 45 | 46 | return severity[a.severity] < severity[b.severity]; 47 | }); 48 | 49 | cb(ret); 50 | }); 51 | }; 52 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "npmlint", 3 | "version": "0.1.0", 4 | "description": "Lint your npm package", 5 | "license": "MIT", 6 | "repository": "sindresorhus/npmlint", 7 | "author": { 8 | "name": "Sindre Sorhus", 9 | "email": "sindresorhus@gmail.com", 10 | "url": "sindresorhus.com" 11 | }, 12 | "engines": { 13 | "node": ">=4" 14 | }, 15 | "scripts": { 16 | "test": "xo && ava" 17 | }, 18 | "files": [ 19 | "index.js", 20 | "cli.js", 21 | "get-reporter.js", 22 | "rules", 23 | "reporters" 24 | ], 25 | "keywords": [ 26 | "npm", 27 | "lint", 28 | "linter", 29 | "validate", 30 | "report", 31 | "check", 32 | "package", 33 | "json", 34 | "module" 35 | ], 36 | "dependencies": { 37 | "async": "^0.2.9", 38 | "chalk": "^1.1.1", 39 | "findup-sync": "^0.1.2", 40 | "glob": "^3.2.7", 41 | "meow": "^3.6.0", 42 | "resolve": "^0.5.1", 43 | "semver": "^5.1.0", 44 | "sudo-block": "^1.2.0", 45 | "update-notifier": "^0.5.0", 46 | "yamlish": "0.0.7" 47 | }, 48 | "devDependencies": { 49 | "ava": "*", 50 | "xo": "*" 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # Deprecated 2 | 3 | This is a code dump of something I made a long time ago, but never had the time to finish. There are lots of things that could have been done better. 4 | 5 | --- 6 | 7 | 8 | # npmlint [![Build Status](https://travis-ci.org/sindresorhus/npmlint.svg?branch=master)](https://travis-ci.org/sindresorhus/npmlint) 9 | 10 | > Lint your npm package 11 | 12 | Makes sure your package is unicorn good. 13 | 14 | 15 | ## Install 16 | 17 | ``` 18 | $ npm install --global npmlint 19 | ``` 20 | 21 | 22 | ## Usage 23 | 24 | Go to the package you want to lint and run `npmlint`. 25 | 26 | 27 | ## API 28 | 29 | ``` 30 | $ npm install --save npmlint 31 | ``` 32 | 33 | ```js 34 | const npmlint = require('npmlint'); 35 | 36 | console.log(npmlint()); 37 | //=> ['suggestion', 'another suggestion'] 38 | ``` 39 | 40 | 41 | ## Dev 42 | 43 | You can try it out by `cd`'ing into the `test` folder and running `../cli.js` 44 | 45 | 46 | ### Creating rules 47 | 48 | Rules are located in a `rules` directory. These are loaded automatically. 49 | 50 | A rule is initiated with an object containing: 51 | 52 | - `cwd`: the current working directory 53 | - `pkg`: the target package' package.json 54 | 55 | It's expected to return an object or an array of object containing: 56 | 57 | - `name`: a slug name for the rule 58 | - `severity`: `info`, `warn`, `error`. Use your best judgement 59 | - `message`: a message describing the violation 60 | 61 | 62 | ## License 63 | 64 | MIT © [Sindre Sorhus](http://sindresorhus.com) 65 | -------------------------------------------------------------------------------- /reporters/default.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const chalk = require('chalk'); 3 | 4 | const severity = { 5 | info: chalk.white, 6 | warn: chalk.yellow, 7 | error: chalk.red 8 | }; 9 | 10 | module.exports = (messages, opts) => { 11 | if (messages.length === 0) { 12 | console.log(chalk.green('✔︎ Looking good!')); 13 | return null; 14 | } 15 | 16 | return messages.map(el => { 17 | const name = opts.verbose ? `${el.name}\n` : ''; 18 | const msg = severity[el.severity](`• ${el.message}`); 19 | return `\n${chalk.gray(name)}${msg}\n`; 20 | }).join(''); 21 | }; 22 | -------------------------------------------------------------------------------- /reporters/json.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | module.exports = messages => JSON.stringify(messages); 3 | -------------------------------------------------------------------------------- /reporters/tap.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const yamlish = require('yamlish'); 3 | 4 | module.exports = messages => { 5 | let ret = '\nTAP version 13\n'; 6 | let total = 0; 7 | 8 | ret += messages.map(x => { 9 | const yaml = yamlish.encode({ 10 | name: x.name, 11 | severity: x.severity, 12 | message: x.message 13 | }).join('\n'); 14 | 15 | return `not ok ${++total}\n ---${yaml}\n`; 16 | }); 17 | 18 | ret += `1..${total}`; 19 | 20 | return ret; 21 | }; 22 | -------------------------------------------------------------------------------- /rules/coffeescript.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const glob = require('glob'); 3 | 4 | module.exports = (data, cb) => { 5 | const pkg = data.pkg; 6 | const ret = []; 7 | const csFiles = glob.sync('**/*.coffee', {cwd: data.cwd}).length; 8 | 9 | if (csFiles && !(pkg.scripts && pkg.scripts.prepublish)) { 10 | ret.push({ 11 | name: 'coffeescript-prepublish', 12 | severity: 'error', 13 | message: 'Compile your CoffeScript before publishing using `scripts.prepublish` in package.json.\n\tSee: https://npmjs.org/doc/misc/npm-scripts.html' 14 | }); 15 | } 16 | 17 | if (pkg.dependencies['coffee-script']) { 18 | ret.push({ 19 | name: 'coffeescript-dependency', 20 | severity: 'warn', 21 | message: '`coffee-script` should be a `devDependency` and not a `dependency`. Compile your CoffeScript before publishing using `scripts.prepublish` in package.json.\n\tSee: https://npmjs.org/doc/misc/npm-scripts.html' 22 | }); 23 | } 24 | 25 | cb(null, ret); 26 | }; 27 | -------------------------------------------------------------------------------- /rules/missing-package-props.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable prefer-template */ 2 | 'use strict'; 3 | module.exports = (data, cb) => { 4 | const pkg = data.pkg; 5 | const ret = []; 6 | 7 | const recommendedProps = [ 8 | 'name', 9 | 'version', 10 | 'description', 11 | 'keywords', 12 | 'author', 13 | 'main', 14 | 'files', 15 | 'repository', 16 | 'engines' 17 | ]; 18 | 19 | const missingProps = recommendedProps.filter(x => !pkg[x]); 20 | 21 | if (!pkg.license && !pkg.licenses) { 22 | missingProps.push('license'); 23 | ret.push({ 24 | name: 'license-property', 25 | severity: 'warn', 26 | message: 'Specify a "license" in package.json so people know how they are permitted to use your package.' 27 | }); 28 | } 29 | 30 | if (missingProps.length > 0) { 31 | ret.push({ 32 | name: 'package-properties', 33 | severity: 'info', 34 | message: 'Missing recommended package.json properties:' + 35 | missingProps.map(x => `\n\t• ${x}`).join('') 36 | }); 37 | } 38 | 39 | cb(null, ret); 40 | }; 41 | -------------------------------------------------------------------------------- /rules/tests.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const exec = require('child_process').exec; 3 | 4 | module.exports = (data, cb) => { 5 | const pkg = data.pkg; 6 | 7 | if (!(pkg.scripts && pkg.scripts.test)) { 8 | cb(null, { 9 | name: 'package-test-property', 10 | severity: 'error', 11 | message: 'Missing property `scripts.test` in package.json.' 12 | }); 13 | return; 14 | } 15 | 16 | if (pkg.scripts && /no test specified/.test(pkg.scripts.test)) { 17 | cb(null, { 18 | name: 'package-test-not-implemented', 19 | severity: 'error', 20 | message: 'Property `scripts.test` in package.json is not implemented.' 21 | }); 22 | return; 23 | } 24 | 25 | if (pkg.scripts && pkg.scripts.test) { 26 | exec('npm test', err => { 27 | if (!err) { 28 | cb(); 29 | return; 30 | } 31 | 32 | cb(null, { 33 | name: 'package-test-fail', 34 | severity: 'error', 35 | message: 'Tests are failing.' 36 | }); 37 | }); 38 | return; 39 | } 40 | 41 | cb(); 42 | }; 43 | -------------------------------------------------------------------------------- /rules/typescript.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const glob = require('glob'); 3 | 4 | module.exports = (data, cb) => { 5 | const pkg = data.pkg; 6 | const ret = []; 7 | const files = glob.sync('**/*.ts', {cwd: data.cwd}).length; 8 | 9 | if (files && !(pkg.scripts && pkg.scripts.prepublish)) { 10 | ret.push({ 11 | name: 'typescript-prepublish', 12 | severity: 'error', 13 | message: 'Compile your TypeScript before publishing using `scripts.prepublish` in package.json.\n\tSee: https://npmjs.org/doc/misc/npm-scripts.html' 14 | }); 15 | } 16 | 17 | if (pkg.dependencies.typescript) { 18 | ret.push({ 19 | name: 'typescript-dependency', 20 | severity: 'warn', 21 | message: '`typescript` should be a `devDependency` and not a `dependency`. Compile your TypeScript before publishing using `scripts.prepublish` in package.json.\n\tSee: https://npmjs.org/doc/misc/npm-scripts.html' 22 | }); 23 | } 24 | 25 | cb(null, ret); 26 | }; 27 | -------------------------------------------------------------------------------- /rules/valid-version.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const semver = require('semver'); 3 | 4 | module.exports = (data, cb) => { 5 | if (!semver.valid(data.pkg.version)) { 6 | cb(null, { 7 | name: 'valid-version', 8 | severity: 'error', 9 | message: 'The specified `version` in package.json is invalid.\n\tSee: https://github.com/isaacs/node-semver' 10 | }); 11 | return; 12 | } 13 | 14 | cb(); 15 | }; 16 | -------------------------------------------------------------------------------- /test.js: -------------------------------------------------------------------------------- 1 | import test from 'ava'; 2 | 3 | test(t => { 4 | t.pass(); 5 | }); 6 | -------------------------------------------------------------------------------- /test/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "test", 3 | "version": "0.0.0-beta", 4 | "keywords": [ 5 | "npm", 6 | "lint", 7 | "linter", 8 | "validate", 9 | "report", 10 | "check", 11 | "package", 12 | "package.json", 13 | "module" 14 | ], 15 | "author": "Sindre Sorhus (http://sindresorhus.com)", 16 | "files": [ 17 | "npmlint", 18 | "npmlint.js", 19 | "cli.js" 20 | ], 21 | "main": "npmlint", 22 | "bin": { 23 | "npmlint": "cli.js" 24 | }, 25 | "repository": { 26 | "type": "git", 27 | "url": "git://github.com/sindresorhus/npmlint" 28 | }, 29 | "scripts": { 30 | "test": "node ../test.j" 31 | }, 32 | "dependencies": { 33 | "findup-sync": "~0.1.2", 34 | "chalk": "~0.3.0", 35 | "update-notifier": "~0.1.7", 36 | "sudo-block": "~0.2.1", 37 | "coffee-script": "~1.6.3", 38 | "typescript": "~1.9.0" 39 | }, 40 | "devDependencies": { 41 | "tape": "~2.1.0" 42 | }, 43 | "engines": { 44 | "node": ">=0.8.0" 45 | }, 46 | "preferGlobal": true, 47 | "bugs": { 48 | "url": "https://github.com/sindresorhus/npmlint/issues" 49 | }, 50 | "homepage": "https://github.com/sindresorhus/npmlint", 51 | "license": "BSD-2-Clause" 52 | } 53 | -------------------------------------------------------------------------------- /test/test.coffee: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sindresorhus/npmlint/5e77eeeb21c9d64e50acbf8f775afaf1fc58a0f4/test/test.coffee -------------------------------------------------------------------------------- /test/test.ts: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sindresorhus/npmlint/5e77eeeb21c9d64e50acbf8f775afaf1fc58a0f4/test/test.ts --------------------------------------------------------------------------------