├── .conventional-changelog-lintrc ├── .editorconfig ├── .eslintrc ├── .gitignore ├── .jsonlintignore ├── .jsonlintrc ├── .npmrc ├── changelog.md ├── contributing.md ├── index.js ├── jsonlint-cli-banner.svg ├── jsonlint-cli.svg ├── library ├── cli.js ├── fetch-schema.js ├── filter.js ├── format.js ├── get-configuration.js ├── get-input.js ├── help.js ├── lint.js ├── list-files.js ├── load-schema.js ├── print.js ├── resolve-keys.js ├── schema-error.js ├── sort.js └── unknown.js ├── license.md ├── package.json ├── pattern ├── .jsonlintrc ├── pattern-schema.json └── pattern.json └── readme.md /.conventional-changelog-lintrc: -------------------------------------------------------------------------------- 1 | { 2 | "rules": { 3 | "scope-enum": [ 4 | 2, 5 | "always", 6 | [ 7 | "release", 8 | "system", 9 | "validation", 10 | "parsing", 11 | "configuration", 12 | "cli" 13 | ] 14 | ] 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | [*] 2 | end_of_line = lf 3 | insert_final_newline = true 4 | trim_trailing_whitespace = true 5 | indent_style = tab 6 | 7 | [{.*rc,*.yml,*.md,package.json,*.svg}] 8 | indent_style = space 9 | 10 | [*.md] 11 | trim_trailing_whitespace = false 12 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["xo"] 3 | } 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | 6 | # Runtime data 7 | pids 8 | *.pid 9 | *.seed 10 | 11 | # Directory for instrumented libs generated by jscoverage/JSCover 12 | lib-cov 13 | 14 | # Coverage directory used by tools like istanbul 15 | coverage 16 | 17 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 18 | .grunt 19 | 20 | # node-waf configuration 21 | .lock-wscript 22 | 23 | # Compiled binary addons (http://nodejs.org/api/addons.html) 24 | build/Release 25 | 26 | # Dependency directory 27 | node_modules 28 | 29 | # Optional npm cache directory 30 | .npm 31 | 32 | # Optional REPL history 33 | .node_repl_history 34 | 35 | # jsonlint-cli cache 36 | .tmp 37 | -------------------------------------------------------------------------------- /.jsonlintignore: -------------------------------------------------------------------------------- 1 | node_modules/**/* 2 | -------------------------------------------------------------------------------- /.jsonlintrc: -------------------------------------------------------------------------------- 1 | { 2 | "validate": "http://json.schemastore.org/package" 3 | } 4 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | spin = false 2 | progress = false 3 | save-exact = true 4 | -------------------------------------------------------------------------------- /changelog.md: -------------------------------------------------------------------------------- 1 | 2 | ## [1.0.1](https://github.com/marionebl/jsonlint-cli/compare/v1.0.0...v1.0.1) (2016-03-17) 3 | 4 | 5 | ### Bug Fixes 6 | 7 | * **configuration:** employ correct precedence for configuration files ([742df22](https://github.com/marionebl/jsonlint-cli/commit/742df22)) 8 | * **configuration:** filter ignored files before reading ([17c32f4](https://github.com/marionebl/jsonlint-cli/commit/17c32f4)), closes [#2](https://github.com/marionebl/jsonlint-cli/issues/2) 9 | 10 | 11 | 12 | 13 | # [1.0.0](https://github.com/marionebl/jsonlint-cli/compare/v0.2.8...v1.0.0) (2016-03-13) 14 | 15 | 16 | ### Code Refactoring 17 | 18 | * split program into logical bits ([a7fe762](https://github.com/marionebl/jsonlint-cli/commit/a7fe762)), closes [#1](https://github.com/marionebl/jsonlint-cli/issues/1) 19 | 20 | 21 | ### BREAKING CHANGES 22 | 23 | * * removed the --quiet flag 24 | * errors are always printed 25 | 26 | 27 | 28 | 29 | ## [0.2.8](https://github.com/marionebl/jsonlint-cli/compare/v0.2.7...v0.2.8) (2016-03-01) 30 | 31 | 32 | ### Bug Fixes 33 | 34 | * add missing minimatch dependency ([1f72a5a](https://github.com/marionebl/jsonlint-cli/commit/1f72a5a)) 35 | 36 | 37 | 38 | 39 | ## [0.2.7](https://github.com/marionebl/jsonlint-cli/compare/v0.2.6...v0.2.7) (2016-02-11) 40 | 41 | 42 | ### Bug Fixes 43 | 44 | * actually expose jsonlint-cli ([63510de](https://github.com/marionebl/jsonlint-cli/commit/63510de)) 45 | * resolve local schemas properly ([fb345be](https://github.com/marionebl/jsonlint-cli/commit/fb345be)) 46 | * use schema for validation messages again ([89bcbf9](https://github.com/marionebl/jsonlint-cli/commit/89bcbf9)) 47 | 48 | 49 | 50 | 51 | ## [0.2.6](https://github.com/marionebl/jsonlint-cli/compare/v0.2.5...v0.2.6) (2016-02-11) 52 | 53 | 54 | 55 | 56 | 57 | ## [0.2.5](https://github.com/marionebl/jsonlint-cli/compare/v0.2.4...v0.2.5) (2016-02-11) 58 | 59 | 60 | 61 | 62 | 63 | ## [0.2.4](https://github.com/marionebl/jsonlint-cli/compare/v0.2.3...v0.2.4) (2016-02-11) 64 | 65 | 66 | 67 | 68 | 69 | ## 0.2.3 (2016-02-11) 70 | 71 | 72 | ### Features 73 | 74 | * add missing help command ([448e5ec](https://github.com/marionebl/jsonlint-cli/commit/448e5ec)) 75 | * enhance jsonlint cli with globbing, remote schemas ([f69d8c3](https://github.com/marionebl/jsonlint-cli/commit/f69d8c3)) 76 | 77 | 78 | 79 | 80 | ## 0.2.2 (2016-02-11) 81 | 82 | 83 | ### Features 84 | 85 | * add missing help command ([448e5ec](https://github.com/marionebl/jsonlint-cli/commit/448e5ec)) 86 | * enhance jsonlint cli with globbing, remote schemas ([f69d8c3](https://github.com/marionebl/jsonlint-cli/commit/f69d8c3)) 87 | 88 | 89 | 90 | -------------------------------------------------------------------------------- /contributing.md: -------------------------------------------------------------------------------- 1 | > cli wrapper for jsonlint 2 | 3 |

4 |

jsonlint-cli

5 |

6 | 7 | Yeay! You want to contribute to jsonlint-cli. That's amazing! 8 | To smoothen everyone's experience involved with the project please take note of the following guidelines and rules. 9 | 10 | ## Found an Issue? 11 | Thank you for reporting any issues you find. We do our best to test and make jsonlint-cli as solid as possible, but any reported issue is a real help. 12 | 13 | > jsonlint-cli issues 14 | 15 | Please follow these guidelines when reporting issues: 16 | * Provide a title in the format of ` when ` 17 | * Tag your issue with the tag `bug` 18 | * Provide a short summary of what you are trying to do 19 | * Provide the log of the encountered error if applicable 20 | * Provide the exact version of jsonlint-cli. Check `npm ls jsonlint-cli` when in doubt 21 | * Be awesome and consider contributing a [pull request](#want-to-contribute) 22 | 23 | ## Want to contribute? 24 | You consider contributing changes to jsonlint-cli – we dig that! 25 | Please consider these guidelines when filing a pull request: 26 | 27 | > jsonlint-cli pull requests 28 | 29 | * Follow the [Coding Rules](#coding-rules) 30 | * Follow the [Commit Rules](#commit-rules) 31 | * Make sure you rebased the current master branch when filing the pull request 32 | * Squash your commits when filing the pull request 33 | * Provide a short title with a maximum of 100 characters 34 | * Provide a more detailed description containing 35 | * What you want to achieve 36 | * What you changed 37 | * What you added 38 | * What you removed 39 | 40 | ## Coding Rules 41 | To keep the code base of jsonlint-cli neat and tidy the following rules apply to every change 42 | 43 | > Coding standards 44 | 45 | * [Happiness](/sindresorhus/xo) enforced via eslint 46 | * Favor micro library over swiss army knives (rimraf, ncp vs. fs-extra) 47 | * Coverage never drops below 90% 48 | * No change may lower coverage by more than 5% 49 | * Be awesome 50 | 51 | ## Commit Rules 52 | To help everyone with understanding the commit history of jsonlint-cli the following commit rules are enforced. 53 | To make your life easier jsonlint-cli is commitizen-friendly and provides the npm run-script `commit`. 54 | 55 | > Commit standards 56 | 57 | * [conventional-changelog](/commitizen/cz-conventional-changelog) 58 | * husky commit message hook available 59 | * present tense 60 | * maximum of 100 characters 61 | * message format of `$type($scope): $message` 62 | 63 | 64 | --- 65 | Copyright 2016 by [Mario Nebl](https://github.com/marionebl) and [contributors](./graphs/contributors). Released under the [MIT license]('./license.md'). 66 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | const merge = require('lodash').merge; 3 | 4 | const cli = require('./library/cli'); 5 | const listFiles = require('./library/list-files'); 6 | const fetchSchema = require('./library/fetch-schema'); 7 | const getConfiguration = require('./library/get-configuration'); 8 | const getInput = require('./library/get-input'); 9 | const resolveKeys = require('./library/resolve-keys'); 10 | const lint = require('./library/lint'); 11 | const format = require('./library/format'); 12 | const print = require('./library/print'); 13 | const filter = require('./library/filter'); 14 | const pkg = require('./package'); 15 | 16 | // Main program 17 | function main(options) { 18 | return listFiles(options.input) 19 | // Load file configurations 20 | .then(getConfiguration) 21 | // Filter files according to ignore config 22 | .then(filter) 23 | // Load file contents 24 | .then(getInput) 25 | // Fetch json schemas 26 | .then(fetchSchema) 27 | // Wait for resolution of async tasks 28 | .then(resolveKeys) 29 | .then(inputs => { 30 | // Merge cli options on file configuration 31 | return inputs.map(input => { 32 | input.configuration = merge({}, input.configuration, options.flags); 33 | return input; 34 | }); 35 | }) 36 | .then(inputs => { 37 | // Lint and validate files 38 | return inputs.map(input => { 39 | input.content.path = input.path; 40 | input.data = lint(input.content, input.configuration, input.schema); 41 | return input; 42 | }); 43 | }) 44 | .then(inputs => { 45 | // Format results 46 | return inputs.map(input => { 47 | input.formatted = format(input.data, input.configuration); 48 | return input; 49 | }); 50 | }) 51 | .then(inputs => { 52 | // Print results 53 | inputs.forEach(print); 54 | return inputs; 55 | }); 56 | } 57 | 58 | // Start the engines 59 | main(cli) 60 | .catch(error => 61 | setTimeout(() => { 62 | if (error.type === pkg.name) { 63 | console.error(error.message); 64 | process.exit(1); 65 | } 66 | throw error; 67 | }) 68 | ); 69 | 70 | // handle unhandled rejections 71 | process.on('unhandledRejection', (reason, promise) => { 72 | if (reason.type === pkg.name) { 73 | process.exit(1); 74 | } 75 | console.log('Unhandled Rejection at: Promise ', promise, ' reason: ', reason); 76 | throw reason; 77 | }); 78 | 79 | /* const fs = require('fs'); 80 | const path = require('path'); 81 | const url = require('url'); 82 | const crypto = require('crypto'); 83 | 84 | const minimist = require('minimist'); 85 | const rcNodeBack = require('cli-rc'); 86 | const globby = require('globby'); 87 | const parser = require('jsonlint').parser; 88 | const denodeify = require('denodeify'); 89 | const jjv = require('jjv'); 90 | const request = require('sync-request'); 91 | const mkdirp = require('mkdirp'); 92 | var memoize = require('lodash.memoize'); 93 | var minimatch = require('minimatch'); 94 | 95 | const read = denodeify(fs.readFile); 96 | const rc = denodeify(rcNodeBack); 97 | 98 | const pkg = require('./package.json'); 99 | 100 | const defaults = { 101 | ignore: ['node_modules'], 102 | validate: null, 103 | indent: ' ', 104 | env: 'json-schema-draft-04', 105 | quiet: false, 106 | pretty: false 107 | }; 108 | 109 | const aliases = { 110 | ignore: 'i', 111 | validate: 's', 112 | indent: 'w', 113 | env: 'e', 114 | quiet: 'q', 115 | pretty: 'p' 116 | }; 117 | 118 | const descriptions = { 119 | ignore: 'glob pattern to exclude from linting', 120 | validate: 'uri to schema to use for validation', 121 | indent: 'whitespace to use for pretty printing', 122 | env: 'json schema env to use for validation', 123 | quiet: 'surpress all output', 124 | pretty: 'pretty-print the input' 125 | }; 126 | 127 | function repeat(s, count) { 128 | return new Array(count + 1).join(s); 129 | } 130 | function formatJson(source, indent) { 131 | var i = 0; // eslint-disable-line no-var 132 | var il = 0; // eslint-disable-line no-var 133 | var tab = (typeof indent === 'undefined') ? ' ' : indent; // eslint-disable-line no-var 134 | var newJson = ''; // eslint-disable-line no-var 135 | var indentLevel = 0; // eslint-disable-line no-var 136 | var inString = false; // eslint-disable-line no-var 137 | var currentChar = null; // eslint-disable-line no-var 138 | 139 | for (i = 0, il = source.length; i < il; i += 1) { 140 | currentChar = source.charAt(i); 141 | 142 | switch (currentChar) { 143 | case '{': 144 | case '[': 145 | if (inString) { 146 | newJson += currentChar; 147 | } else { 148 | newJson += currentChar + '\n' + repeat(tab, indentLevel + 1); // eslint-disable-line prefer-template 149 | indentLevel += 1; 150 | } 151 | break; 152 | case '}': 153 | case ']': 154 | if (inString) { 155 | newJson += currentChar; 156 | } else { 157 | indentLevel -= 1; 158 | newJson += '\n' + repeat(tab, indentLevel) + currentChar; // eslint-disable-line prefer-template 159 | } 160 | break; 161 | case ',': 162 | if (inString) { 163 | newJson += currentChar; 164 | } else { 165 | newJson += ',\n' + repeat(tab, indentLevel); // eslint-disable-line prefer-template 166 | } 167 | break; 168 | case ':': 169 | if (inString) { 170 | newJson += currentChar; 171 | } else { 172 | newJson += ': '; 173 | } 174 | break; 175 | case ' ': 176 | case '\n': 177 | case '\t': 178 | if (inString) { 179 | newJson += currentChar; 180 | } 181 | break; 182 | case '"': 183 | if (i > 0 && source.charAt(i - 1) !== '\\') { 184 | inString = !inString; 185 | } 186 | newJson += currentChar; 187 | break; 188 | default: 189 | newJson += currentChar; 190 | break; 191 | } 192 | } 193 | return newJson; 194 | } 195 | 196 | function sort(o) { 197 | if (Array.isArray(o)) { 198 | return o.map(sort); 199 | } else if (Object.prototype.toString.call(o) !== '[object Object]') { 200 | return o; 201 | } 202 | 203 | const sorted = {}; 204 | const a = []; 205 | var key; // eslint-disable-line no-var 206 | 207 | for (key in o) { 208 | if (o.hasOwnProperty(key)) { 209 | a.push(key); 210 | } 211 | } 212 | 213 | a.sort(); 214 | 215 | for (key = 0; key < a.length; key++) { 216 | sorted[a[key]] = sort(o[a[key]]); 217 | } 218 | 219 | return sorted; 220 | } 221 | 222 | const lex = { 223 | type(key, value) { 224 | return `"${key}" must be of type "${value}"`; 225 | }, 226 | minLength(key, value, ruleName, ruleValue) { 227 | return `"${key}" must be at least "${ruleValue}" characters`; 228 | }, 229 | maxLength(key, value, ruleName, ruleValue) { 230 | return `"${key}" may be at most "${ruleValue}" characters`; 231 | }, 232 | minProperties(key, value, ruleName, ruleValue) { 233 | return `"${key}" must hold at least "${ruleValue}" properties`; 234 | }, 235 | maxProperties(key, value, ruleName, ruleValue) { 236 | return `"${key}" may hold at most "${ruleValue}" properties`; 237 | }, 238 | patternProperties(key, value, ruleName, ruleValue) { 239 | return `"${key}" must hold "${ruleValue}" properties`; 240 | }, 241 | minItems(key, value, ruleName, ruleValue) { 242 | return `"${key}" must have at leat "${ruleValue}" items`; 243 | }, 244 | maxItems(key, value, ruleName, ruleValue) { 245 | return `"${key}" may have at most "${ruleValue}" items`; 246 | }, 247 | required(key, _, name) { 248 | return `"${key}" is ${name} but unset`; 249 | }, 250 | additional(key, value, name) { 251 | return `"${key}" is not allowed as ${name} key `; 252 | }, 253 | fallback(key, value, ruleName, ruleValue, prop) { 254 | const ruleValueString = typeof ruleValue === 'string' ? JSON.stringify(ruleValue) : ruleValue; 255 | return `"${key}" does not meet rule "${ruleName}=${ruleValueString}" - ${prop.description}`; 256 | } 257 | }; 258 | 259 | function schemaError(error, schema) { 260 | return Object.keys(error.validation) 261 | .reduce((messages, key) => { 262 | const validation = error.validation[key]; 263 | const names = Object.keys(validation); 264 | 265 | return messages.concat( 266 | names 267 | .map(name => lex[name] || lex.fallback) 268 | .map((formatter, index) => { 269 | const name = names[index]; 270 | const props = schema.properties || {}; 271 | const prop = props[key] || {}; 272 | return formatter( 273 | key, 274 | validation[name], 275 | name, 276 | prop[name] || '', 277 | prop 278 | ); 279 | }) 280 | ); 281 | }, []); 282 | } 283 | 284 | function getSchemaCacheId(uri) { 285 | const sum = crypto.createHash('md5'); 286 | sum.update(uri); 287 | return sum.digest('hex'); 288 | } 289 | 290 | function readSchemaCache(uri) { 291 | const id = getSchemaCacheId(uri); 292 | const tmp = path.resolve(__dirname, '.tmp', `${id}.json`); 293 | 294 | try { 295 | return fs.readFileSync(tmp); 296 | } catch (error) { 297 | return null; 298 | } 299 | } 300 | 301 | function writeSchemaCache(uri, schema) { 302 | const id = getSchemaCacheId(uri); 303 | const tmp = path.resolve(__dirname, '.tmp', `${id}.json`); 304 | 305 | try { 306 | mkdirp.sync(path.dirname(tmp)); 307 | return fs.writeFileSync(tmp, schema); 308 | } catch (error) { 309 | return null; 310 | } 311 | } 312 | 313 | function getSchema(uri) { 314 | const parsed = url.parse(uri); 315 | 316 | if (parsed.protocol && parsed.host) { 317 | const buffer = readSchemaCache(uri); 318 | const response = buffer ? buffer.toString('utf-8') : request('GET', uri).getBody(); 319 | const data = JSON.parse(response); 320 | writeSchemaCache(uri, response); 321 | return data; 322 | } 323 | 324 | return require(uri); 325 | } 326 | 327 | const obtainSchema = memoize(getSchema); 328 | 329 | function lint(source, sourcePath, settings) { 330 | return new Promise((resolve, reject) => { 331 | const absSourcePath = sourcePath ? path.resolve(sourcePath) : null; 332 | 333 | try { 334 | const parsed = settings.source ? 335 | sort(parser.parse(source)) : 336 | parser.parse(source); 337 | 338 | if (settings.pretty && !settings.quiet) { 339 | console.log(formatJson(source, settings.indent)); 340 | } 341 | 342 | if (settings.validate) { 343 | const environment = jjv(settings.env); 344 | const schema = obtainSchema(settings.validate); 345 | environment.addSchema('default', schema); 346 | const errors = environment.validate('default', parsed); 347 | if (errors) { 348 | const jsonLintError = new Error([`"${absSourcePath}" fails against schema "${settings.validate}"`] 349 | .concat(schemaError(errors, schema) 350 | .filter(Boolean) 351 | .map(message => ` ${message}`) 352 | ).join('\n')); 353 | 354 | jsonLintError.type = 'jsonlint'; 355 | throw jsonLintError; 356 | } 357 | } 358 | } catch (error) { 359 | error.message = settings.quiet ? null : `${absSourcePath} ${error.message}`; 360 | error.file = absSourcePath; 361 | error.type = 'jsonlint'; 362 | reject(error); 363 | } 364 | }); 365 | } 366 | 367 | function readStdin() { 368 | return new Promise(resolve => { 369 | const source = []; 370 | const stdin = process.openStdin(); 371 | stdin.setEncoding('utf8'); 372 | stdin.on('data', chunk => { 373 | source.push(chunk.toString('utf8')); 374 | }); 375 | stdin.on('end', () => { 376 | resolve(source.join('')); 377 | }); 378 | }); 379 | } 380 | 381 | function getSettings(options, file) { 382 | const filePath = path.extname(file) ? 383 | path.dirname(file) : 384 | file; 385 | 386 | const loaders = [ 387 | { 388 | name: '.jsonlintrc', 389 | path: [filePath], 390 | prepend: [filePath] 391 | }, 392 | { 393 | name: '.jsonlintignore', 394 | path: [filePath], 395 | type: 'ini', 396 | prepend: [filePath] 397 | } 398 | ].map(loader => { 399 | return rc(loader) 400 | .then(config => Object.assign(config)) 401 | .catch(error => { 402 | setTimeout(() => { 403 | throw error; 404 | }); 405 | }); 406 | }); 407 | 408 | return Promise.all(loaders) 409 | .then(results => { 410 | const configuration = results[0]; 411 | configuration._file = file; 412 | 413 | const ignore = (options.ignore || []) 414 | .concat(results[0].ignore || []) 415 | .concat(Object.keys(results[1])); 416 | 417 | if (configuration.validate) { 418 | const parsed = url.parse(configuration.validate); 419 | configuration.validate = parsed.protocol && parsed.host ? 420 | configuration.validate : 421 | path.resolve(file, configuration.validate); 422 | } 423 | 424 | return Object.assign({}, defaults, options, configuration, { 425 | ignore 426 | }); 427 | }); 428 | } 429 | 430 | function execute(settings) { 431 | const files = settings._ || []; 432 | const ignored = settings.ignore.map(rule => `!${rule}`); 433 | const glob = files.concat(ignored); 434 | 435 | // read from stdin if no files are given 436 | if (files.length === 0) { 437 | return readStdin() 438 | .then(content => { 439 | return lint(content, null, settings); 440 | }); 441 | } 442 | 443 | // read the glob if files are given 444 | return globby(glob).then(paths => { 445 | Promise 446 | .all(paths.map(file => { 447 | return getSettings(settings, file); 448 | })) 449 | .then(configurations => { 450 | return Promise.all( 451 | configurations.map(configuration => { 452 | // ignore could be changed by config files 453 | const ignored = configuration.ignore 454 | .filter(pattern => { 455 | return minimatch(configuration._file, pattern) || 456 | path.basename(configuration._file) === pattern; 457 | }).length > 0; 458 | 459 | if (ignored) { 460 | return null; 461 | } 462 | return read(configuration._file) 463 | .then(content => { 464 | return { 465 | content: content.toString('utf-8'), 466 | path: configuration._file, 467 | configuration: configuration 468 | }; 469 | }); 470 | }) 471 | ); 472 | }) 473 | .then(payloads => { 474 | return Promise.all( 475 | payloads.map(payload => { 476 | return lint( 477 | payload.content, 478 | payload.path, 479 | payload.configuration 480 | ); 481 | }) 482 | ); 483 | }); 484 | }); 485 | } 486 | 487 | function printFlags() { 488 | const flags = Object.keys(defaults) 489 | .map(key => { 490 | return [`--${aliases[key]}, --${key}`, `${descriptions[key]}, defaults to: "${defaults[key]}"`]; 491 | }); 492 | 493 | const lines = [ 494 | [`--h, --help`, `show this help`], 495 | [`--v, --version`, `show jsonlint-cli version`] 496 | ].concat(flags); 497 | 498 | const longestKeyLine = lines.sort((a, b) => b[0].length - a[0].length)[0]; 499 | const longest = longestKeyLine[0].length; 500 | 501 | return lines 502 | .map(line => `${line[0]}${' '.repeat(4 + longest - line[0].length)}${line[1]}`) 503 | .join('\n'); 504 | } 505 | 506 | function help() { 507 | console.log(` 508 | ${pkg.name} [options] [file] - ${pkg.description} 509 | 510 | ${printFlags()} 511 | `); 512 | } 513 | 514 | function main(options) { 515 | if (options.help) { 516 | help(); 517 | return Promise.resolve(); 518 | } 519 | 520 | if (options.version) { 521 | console.log(pkg.version); 522 | return Promise.resolve(); 523 | } 524 | 525 | return new Promise((resolve, reject) => { 526 | getSettings(options, process.cwd()) 527 | .then(execute) 528 | .then(resolve) 529 | .catch(reject); 530 | }); 531 | } 532 | 533 | // parse cli flags 534 | const args = minimist(process.argv.slice(2)); 535 | 536 | // start the main function 537 | main(args) 538 | .catch(error => { 539 | if (error.type === 'jsonlint') { 540 | if (error.message !== null) { 541 | console.log(error.message); 542 | } 543 | process.exit(1); 544 | } else { 545 | setTimeout(() => { 546 | throw error; 547 | }); 548 | } 549 | }); 550 | 551 | // Catch unhandled rejections globally 552 | process.on('unhandledRejection', (reason, promise) => { 553 | console.log('Unhandled Rejection at: Promise ', promise, ' reason: ', reason); 554 | throw reason; 555 | }); 556 | */ 557 | -------------------------------------------------------------------------------- /jsonlint-cli-banner.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /jsonlint-cli.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /library/cli.js: -------------------------------------------------------------------------------- 1 | const meow = require('meow'); 2 | const pkg = require('../package'); 3 | 4 | const help = require('./help'); 5 | const unknown = require('./unknown'); 6 | 7 | const configuration = { 8 | // flags of string type 9 | string: ['ignore', 'validate', 'indent', 'env'], 10 | // flags of bool type 11 | boolean: ['pretty', 'sort', 'help', 'version'], 12 | // flag aliases 13 | alias: { 14 | i: 'ignore', 15 | s: 'validate', 16 | t: 'sort', 17 | w: 'indent', 18 | e: 'env', 19 | p: 'pretty', 20 | h: 'help', 21 | v: 'version' 22 | }, 23 | description: { 24 | ignore: 'glob pattern to exclude from linting', 25 | validate: 'uri to schema to use for validation', 26 | indent: 'whitespace to use for pretty printing', 27 | env: 'json schema env to use for validation', 28 | pretty: 'pretty-print the input', 29 | sort: 'sort json keys alphabetically', 30 | version: 'print the version', 31 | help: 'show this help' 32 | }, 33 | // flag defaults 34 | default: { 35 | ignore: ['node_modules'], 36 | validate: '', 37 | indent: '" "', 38 | env: 'json-schema-draft-04', 39 | quiet: true, 40 | pretty: false, 41 | sort: false 42 | }, 43 | unknown: unknown 44 | }; 45 | 46 | module.exports = meow({ 47 | help: `[input] reads from stdin if [files] are omitted\n${help(configuration)}`, 48 | description: `${pkg.name}@${pkg.version} - ${pkg.description}` 49 | }, configuration); 50 | -------------------------------------------------------------------------------- /library/fetch-schema.js: -------------------------------------------------------------------------------- 1 | const loadSchema = require('./load-schema'); 2 | 3 | module.exports = items => { 4 | return Promise.all(items.map(item => { 5 | return item.configuration 6 | .then(config => { 7 | item.configuration = config; 8 | if (config.validate) { 9 | item.schema = loadSchema(config.validate); 10 | } 11 | return item; 12 | }); 13 | })); 14 | }; 15 | -------------------------------------------------------------------------------- /library/filter.js: -------------------------------------------------------------------------------- 1 | const debuglog = require('util').debuglog; 2 | const relative = require('path').relative; 3 | const basename = require('path').basename; 4 | const minimatch = require('minimatch'); 5 | const merge = require('lodash').merge; 6 | 7 | const log = debuglog('jsonlint-cli'); 8 | 9 | module.exports = files => { 10 | const configuringFiles = Promise.all( 11 | files.map(file => { 12 | return file.configuration 13 | .then(configuration => { 14 | return merge({}, file, { 15 | configuration: configuration 16 | }); 17 | }); 18 | }) 19 | ); 20 | 21 | return configuringFiles 22 | .then(configuredFiles => { 23 | log('found files:', configuredFiles.length); 24 | return configuredFiles 25 | .filter(configuredFile => { 26 | const ignore = configuredFile.configuration.ignore || []; 27 | const config = Array.isArray(ignore) ? 28 | ignore : ignore.split(','); 29 | return !config.some(pattern => { 30 | return minimatch(configuredFile.path, pattern) || 31 | minimatch(relative(process.cwd(), configuredFile.path), pattern) || 32 | basename(configuredFile.path) === pattern; 33 | }); 34 | }); 35 | }); 36 | }; 37 | -------------------------------------------------------------------------------- /library/format.js: -------------------------------------------------------------------------------- 1 | const sort = require('./sort'); 2 | 3 | function repeat(s, count) { 4 | return new Array(count + 1).join(s); 5 | } 6 | 7 | function formatJSON(source, indent) { 8 | var i = 0; // eslint-disable-line no-var 9 | var il = 0; // eslint-disable-line no-var 10 | var tab = (typeof indent === 'undefined') ? ' ' : indent; // eslint-disable-line no-var 11 | var newJson = ''; // eslint-disable-line no-var 12 | var indentLevel = 0; // eslint-disable-line no-var 13 | var inString = false; // eslint-disable-line no-var 14 | var currentChar = null; // eslint-disable-line no-var 15 | 16 | for (i = 0, il = source.length; i < il; i += 1) { 17 | currentChar = source.charAt(i); 18 | 19 | switch (currentChar) { 20 | case '{': 21 | case '[': 22 | if (inString) { 23 | newJson += currentChar; 24 | } else { 25 | newJson += currentChar + '\n' + repeat(tab, indentLevel + 1); // eslint-disable-line prefer-template 26 | indentLevel += 1; 27 | } 28 | break; 29 | case '}': 30 | case ']': 31 | if (inString) { 32 | newJson += currentChar; 33 | } else { 34 | indentLevel -= 1; 35 | newJson += '\n' + repeat(tab, indentLevel) + currentChar; // eslint-disable-line prefer-template 36 | } 37 | break; 38 | case ',': 39 | if (inString) { 40 | newJson += currentChar; 41 | } else { 42 | newJson += ',\n' + repeat(tab, indentLevel); // eslint-disable-line prefer-template 43 | } 44 | break; 45 | case ':': 46 | if (inString) { 47 | newJson += currentChar; 48 | } else { 49 | newJson += ': '; 50 | } 51 | break; 52 | case ' ': 53 | case '\n': 54 | case '\t': 55 | if (inString) { 56 | newJson += currentChar; 57 | } 58 | break; 59 | case '"': 60 | if (i > 0 && source.charAt(i - 1) !== '\\') { 61 | inString = !inString; 62 | } 63 | newJson += currentChar; 64 | break; 65 | default: 66 | newJson += currentChar; 67 | break; 68 | } 69 | } 70 | return newJson; 71 | } 72 | 73 | module.exports = (data, settings) => { 74 | const sorted = settings.sort ? 75 | sort(data) : 76 | data; 77 | return formatJSON(JSON.stringify(sorted), settings.indent.replace(/["']/g, '')); 78 | }; 79 | -------------------------------------------------------------------------------- /library/get-configuration.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const rcNodeBack = require('cli-rc'); 3 | const denodeify = require('denodeify'); 4 | const entries = require('core-js/fn/object/entries'); 5 | const merge = require('lodash').merge; 6 | 7 | const rc = denodeify(rcNodeBack); 8 | 9 | const loaders = [ 10 | { 11 | name: '.jsonlintrc', 12 | process: config => config 13 | }, 14 | { 15 | name: '.jsonlintignore', 16 | type: 'ini', 17 | process: config => { 18 | return { 19 | ignore: entries(config) 20 | .filter(item => item[1]) 21 | .map(item => item[0]) 22 | }; 23 | } 24 | } 25 | ]; 26 | 27 | function getPaths(directory) { 28 | return directory 29 | .split(path.sep) 30 | .map((_, index) => { 31 | const args = [directory].concat(Array(index).fill('..')); 32 | return path.resolve.apply(null, args); 33 | }); 34 | } 35 | 36 | function load(directory) { 37 | return Promise.all( 38 | loaders 39 | .map(load => { 40 | const paths = getPaths(directory); 41 | const loader = merge({}, load, { 42 | path: [directory], 43 | prepend: paths 44 | }); 45 | return rc(loader) 46 | .then(result => { 47 | return result; 48 | }) 49 | .then(loader.process); 50 | }) 51 | ).then(configurations => { 52 | return configurations 53 | .reverse() 54 | .reduce((registry, config) => { 55 | return merge({}, registry, config); 56 | }, {}); 57 | }); 58 | } 59 | 60 | module.exports = inputs => { 61 | return inputs 62 | .map(input => { 63 | input.configuration = load(input.directory); 64 | return input; 65 | }); 66 | }; 67 | -------------------------------------------------------------------------------- /library/get-input.js: -------------------------------------------------------------------------------- 1 | const debuglog = require('util').debuglog; 2 | const readFileNodeback = require('fs').readFile; 3 | const denodeify = require('denodeify'); 4 | 5 | const readFile = denodeify(readFileNodeback); 6 | const log = debuglog('jsonlint-cli'); 7 | 8 | module.exports = files => { 9 | log('reading files:', files.length); 10 | return Promise.all( 11 | files.map(file => { 12 | return file.configuration 13 | .then(configuration => { 14 | if (file.content && file.piped) { 15 | return file; 16 | } 17 | return readFile(file.path) 18 | .then(content => { 19 | file.content = content; 20 | return file; 21 | }) 22 | }); 23 | }) 24 | ); 25 | }; 26 | -------------------------------------------------------------------------------- /library/help.js: -------------------------------------------------------------------------------- 1 | const entries = require('core-js/fn/object/entries'); 2 | 3 | module.exports = configuration => { 4 | const lines = entries(configuration.description) 5 | .map(entry => { 6 | const name = entry[0]; 7 | const desc = entry[1]; 8 | const alias = Object.entries(configuration.alias) 9 | .find(entry => entry[1] === name) 10 | .map(entry => entry[0])[0]; 11 | const defaults = configuration.default[name]; 12 | return [[name, alias].filter(Boolean), desc, defaults] 13 | .filter(item => item !== null && typeof item !== 'undefined'); 14 | }); 15 | 16 | const longest = lines 17 | .map(line => { 18 | const flags = line[0]; 19 | return flags.reduce((sum, flag) => sum + flag.length, 0); 20 | }) 21 | .sort(Number)[0]; 22 | 23 | return lines 24 | .map(line => { 25 | const flags = line[0]; 26 | const desc = line[1]; 27 | const defaults = line[2]; 28 | const fs = flags.map(flag => flag.length > 1 ? `--${flag}` : `-${flag}`); 29 | const ds = typeof defaults === 'undefined' ? '' : `, defaults to: ${defaults}`; 30 | const length = flags.reduce((sum, flag) => sum + flag.length, 0); 31 | return `${fs.join(',')}${' '.repeat(7 + longest - length)}${desc}${ds}`; 32 | }) 33 | .join('\n'); 34 | }; 35 | -------------------------------------------------------------------------------- /library/lint.js: -------------------------------------------------------------------------------- 1 | const parser = require('jsonlint').parser; 2 | const jjv = require('jjv'); 3 | const schemaError = require('./schema-error'); 4 | const pkg = require('../package'); 5 | 6 | function parse(source) { 7 | try { 8 | return parser.parse(source); 9 | } catch (error) { 10 | error.type = pkg.name; 11 | error.step = 'parse'; 12 | throw error; 13 | } 14 | } 15 | 16 | function validate(data, settings, schema) { 17 | if (!schema) { 18 | return data; 19 | } 20 | 21 | const environment = jjv(settings.env); 22 | environment.addSchema('default', schema); 23 | const errors = environment.validate('default', data); 24 | 25 | if (errors) { 26 | const message = schemaError(errors, schema) 27 | .filter(Boolean).join('\n'); 28 | const error = new Error(message); 29 | error.type = pkg.name; 30 | error.step = 'validation'; 31 | throw error; 32 | } 33 | 34 | return data; 35 | } 36 | 37 | module.exports = (source, settings, schema) => { 38 | try { 39 | const json = source.toString(); 40 | const parsed = parse(json, settings); 41 | return validate(parsed, settings, schema); 42 | } catch (error) { 43 | const intro = `${source.path} ${error.step}`; 44 | error.file = error.file || source.path; 45 | error.message = `${intro}:\n${error.message}`; 46 | throw error; 47 | } 48 | }; 49 | -------------------------------------------------------------------------------- /library/list-files.js: -------------------------------------------------------------------------------- 1 | const dirname = require('path').dirname; 2 | const resolve = require('path').resolve; 3 | const globby = require('globby'); 4 | const getStdin = require('get-stdin'); 5 | 6 | module.exports = input => { 7 | if (input.length === 0) { 8 | return Promise.resolve( 9 | [{ 10 | content: getStdin.buffer(), 11 | directory: process.cwd(), 12 | path: process.cwd(), 13 | piped: true 14 | }] 15 | ); 16 | } 17 | return globby(input) 18 | .then(paths => { 19 | return paths.map(path => { 20 | return { 21 | content: null, 22 | directory: dirname(resolve(process.cwd(), path)), 23 | path: resolve(process.cwd(), path), 24 | piped: false 25 | }; 26 | }); 27 | }); 28 | }; 29 | -------------------------------------------------------------------------------- /library/load-schema.js: -------------------------------------------------------------------------------- 1 | const createHash = require('crypto').createHash; 2 | const denodeify = require('denodeify'); 3 | const readFileNodeback = require('fs').readFile; 4 | const writeFileNodeback = require('fs').writeFile; 5 | const parse = require('url').parse; 6 | const resolve = require('path').resolve; 7 | const dirname = require('path').dirname; 8 | const mkdirpNodeback = require('mkdirp'); 9 | const fetch = require('omni-fetch'); 10 | 11 | const readFile = denodeify(readFileNodeback); 12 | const writeFile = denodeify(writeFileNodeback); 13 | const mkdirp = denodeify(mkdirpNodeback); 14 | 15 | function getSchemaCacheId(uri) { 16 | const sum = createHash('md5'); 17 | sum.update(uri); 18 | return sum.digest('hex'); 19 | } 20 | 21 | function readSchemaCache(uri) { 22 | const id = getSchemaCacheId(uri); 23 | const tmp = resolve(__dirname, '.tmp', `${id}.json`); 24 | return readFile(tmp) 25 | .catch(() => {}); 26 | } 27 | 28 | function writeSchemaCache(uri, schema) { 29 | const id = getSchemaCacheId(uri); 30 | const tmp = resolve(__dirname, '..', '.tmp', `${id}.json`); 31 | 32 | mkdirp(dirname(tmp)) 33 | .then(() => { 34 | return writeFile(tmp, schema); 35 | }) 36 | .catch(err => { 37 | console.log(err); 38 | }); 39 | } 40 | 41 | module.exports = schema => { 42 | const parsed = parse(schema); 43 | 44 | if (parsed.protocol && parsed.host) { 45 | return readSchemaCache(schema) 46 | .then(buffer => { 47 | return buffer ? 48 | buffer.toString('utf-8') : 49 | fetch(schema) 50 | .then(resp => resp.text()) 51 | .then(data => { 52 | setTimeout(() => { 53 | writeSchemaCache(schema, data); 54 | }, 0); 55 | return data; 56 | }); 57 | }) 58 | .then(data => JSON.parse(data)); 59 | } 60 | 61 | return require(schema); 62 | }; 63 | -------------------------------------------------------------------------------- /library/print.js: -------------------------------------------------------------------------------- 1 | module.exports = input => { 2 | if (input.configuration.pretty) { 3 | console.log(input.formatted); 4 | } 5 | }; 6 | -------------------------------------------------------------------------------- /library/resolve-keys.js: -------------------------------------------------------------------------------- 1 | const entries = require('core-js/fn/object/entries'); 2 | const merge = require('lodash').merge; 3 | 4 | module.exports = items => { 5 | return Promise.all( 6 | items 7 | .map(item => { 8 | return Promise 9 | .all(entries(item).map(entry => { 10 | return Promise 11 | .resolve(entry[1]) 12 | .then(result => { 13 | const resolved = {}; 14 | resolved[entry[0]] = result; 15 | return resolved; 16 | }); 17 | })) 18 | .then(resolved => { 19 | return resolved.reduce((registry, entry) => { 20 | return merge(registry, entry); 21 | }, {}); 22 | }); 23 | }) 24 | ); 25 | }; 26 | -------------------------------------------------------------------------------- /library/schema-error.js: -------------------------------------------------------------------------------- 1 | const lex = { 2 | type(key, value) { 3 | return `"${key}" must be of type "${value}"`; 4 | }, 5 | minLength(key, value, ruleName, ruleValue) { 6 | return `"${key}" must be at least "${ruleValue}" characters`; 7 | }, 8 | maxLength(key, value, ruleName, ruleValue) { 9 | return `"${key}" may be at most "${ruleValue}" characters`; 10 | }, 11 | minProperties(key, value, ruleName, ruleValue) { 12 | return `"${key}" must hold at least "${ruleValue}" properties`; 13 | }, 14 | maxProperties(key, value, ruleName, ruleValue) { 15 | return `"${key}" may hold at most "${ruleValue}" properties`; 16 | }, 17 | patternProperties(key, value, ruleName, ruleValue) { 18 | return `"${key}" must hold "${ruleValue}" properties`; 19 | }, 20 | minItems(key, value, ruleName, ruleValue) { 21 | return `"${key}" must have at leat "${ruleValue}" items`; 22 | }, 23 | maxItems(key, value, ruleName, ruleValue) { 24 | return `"${key}" may have at most "${ruleValue}" items`; 25 | }, 26 | required(key, _, name) { 27 | return `"${key}" is ${name} but unset`; 28 | }, 29 | additional(key, value, name) { 30 | return `"${key}" is not allowed as ${name} key `; 31 | }, 32 | fallback(key, value, ruleName, ruleValue, prop) { 33 | const ruleValueString = typeof ruleValue === 'string' ? JSON.stringify(ruleValue) : ruleValue; 34 | return `"${key}" does not meet rule "${ruleName}=${ruleValueString}" - ${prop.description}`; 35 | } 36 | }; 37 | 38 | module.exports = function schemaError(error, schema) { 39 | return Object.keys(error.validation) 40 | .reduce((messages, key) => { 41 | const validation = error.validation[key]; 42 | const names = Object.keys(validation); 43 | 44 | return messages.concat( 45 | names 46 | .map(name => lex[name] || lex.fallback) 47 | .map((formatter, index) => { 48 | const name = names[index]; 49 | const props = schema.properties || {}; 50 | const prop = props[key] || {}; 51 | return formatter( 52 | key, 53 | validation[name], 54 | name, 55 | prop[name] || '', 56 | prop 57 | ); 58 | }) 59 | ); 60 | }, []); 61 | }; 62 | -------------------------------------------------------------------------------- /library/sort.js: -------------------------------------------------------------------------------- 1 | const entries = require('core-js/fn/object/entries'); 2 | 3 | function entrySorter(a, b) { 4 | const aKey = a[0]; 5 | const bKey = b[0]; 6 | if (aKey < bKey) { 7 | return -1; 8 | } else if (aKey > bKey) { 9 | return 1; 10 | } 11 | return 0; 12 | } 13 | 14 | module.exports = function sort(data) { 15 | if (Array.isArray(data)) { 16 | return data.map(sort); 17 | } else if (Object.prototype.toString.call(data) !== '[object Object]') { 18 | return data; 19 | } 20 | 21 | return entries(data) 22 | .sort(entrySorter) 23 | .reduce((sorted, item) => { 24 | sorted[item[0]] = item[1]; 25 | return sorted; 26 | }, {}); 27 | }; 28 | -------------------------------------------------------------------------------- /library/unknown.js: -------------------------------------------------------------------------------- 1 | const pkg = require('../package'); 2 | 3 | function isFlag(arg) { 4 | return arg[0] === '-' || 5 | arg.slice(0, 2) === '--'; 6 | } 7 | 8 | module.exports = arg => { 9 | if (isFlag(arg) === false) { 10 | return; 11 | } 12 | const error = new RangeError(`unknown flags: ${arg}`); 13 | error.type = pkg.name; 14 | throw error; 15 | }; 16 | -------------------------------------------------------------------------------- /license.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Mario Nebl 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 all 13 | 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 THE 21 | SOFTWARE. 22 | 23 | --- 24 | Copyright 2016 by [Mario Nebl](https://github.com/marionebl) and [contributors](./graphs/contributors). Released under the [MIT license]('./license.md'). 25 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "jsonlint-cli", 3 | "version": "1.0.1", 4 | "description": "cli wrapper for jsonlint", 5 | "main": "index.js", 6 | "scripts": { 7 | "commit": "git-cz", 8 | "commitmsg": "conventional-changelog-lint -e", 9 | "changelog": "conventional-changelog --preset angular --infile changelog.md --same-file --output-unreleased", 10 | "push": "git push && git push --tags && hub release create \"v$(cat .git/RELEASE_VERSION.tmp)\" --message=\"v$(cat .git/RELEASE_VERSION.tmp)\n$(cat .git/COMMITMSG.tmp)\" && npm publish && rm .git/RELEASE_VERSION.tmp && rm .git/COMMITMSG.tmp", 11 | "release": "npm version $(conventional-recommended-bump -p angular)", 12 | "test": "parallelshell \"eslint index.js\" \"node index.js **/*.json *.json\" ", 13 | "preversion": "npm test", 14 | "version": "npm run changelog && git add . && echo \"$(conventional-changelog -p angular)\" > .git/COMMITMSG.tmp", 15 | "postversion": "echo $(git log -1 --pretty=%B HEAD^..HEAD) > .git/RELEASE_VERSION.tmp && git tag -d v$(cat .git/RELEASE_VERSION.tmp) && git commit --amend -m \"chore(release): $(cat .git/RELEASE_VERSION.tmp)\n$(cat .git/COMMITMSG.tmp)\" && git tag -a v$(cat .git/RELEASE_VERSION.tmp) -m \"$(cat .git/COMMITMSG.tmp)\"" 16 | }, 17 | "config": { 18 | "commitizen": { 19 | "path": "cz-conventional-changelog-lint" 20 | } 21 | }, 22 | "bin": { 23 | "jsonlint-cli": "./index.js" 24 | }, 25 | "repository": { 26 | "type": "git", 27 | "url": "git+https://github.com/marionebl/jsonlint-cli.git" 28 | }, 29 | "keywords": [ 30 | "jsonlint", 31 | "cli", 32 | "jsonschema" 33 | ], 34 | "author": { 35 | "name": "Mario Nebl", 36 | "email": "hello@herebecode.com" 37 | }, 38 | "license": "MIT", 39 | "bugs": { 40 | "url": "https://github.com/marionebl/jsonlint-cli/issues" 41 | }, 42 | "homepage": "https://github.com/marionebl/jsonlint-cli#readme", 43 | "devDependencies": { 44 | "commitizen": "2.7.2", 45 | "conventional-changelog-cli": "1.0.0", 46 | "conventional-changelog-lint": "0.3.2", 47 | "conventional-recommended-bump": "0.1.0", 48 | "cz-conventional-changelog-lint": "0.1.3", 49 | "eslint": "1.10.3", 50 | "eslint-config-xo": "0.9.2", 51 | "husky": "0.11.3", 52 | "parallelshell": "2.0.0" 53 | }, 54 | "dependencies": { 55 | "cli-rc": "1.0.12", 56 | "core-js": "2.1.5", 57 | "denodeify": "1.2.1", 58 | "get-stdin": "5.0.1", 59 | "globby": "4.0.0", 60 | "isomorphic-fetch": "2.2.1", 61 | "jjv": "1.0.2", 62 | "jsonlint": "1.6.2", 63 | "lodash": "4.6.1", 64 | "meow": "3.7.0", 65 | "minimatch": "3.0.4", 66 | "mkdirp": "0.5.1", 67 | "omni-fetch": "0.1.0", 68 | "path-exists": "2.1.0" 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /pattern/.jsonlintrc: -------------------------------------------------------------------------------- 1 | { 2 | "validate": "./pattern-schema.json", 3 | "ignore": ["pattern-schema.json"] 4 | } 5 | -------------------------------------------------------------------------------- /pattern/pattern-schema.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-04/schema#", 3 | "title": "pattern manifest", 4 | "type": "object", 5 | "additionalProperties": false, 6 | "required": [ 7 | "name", 8 | "version" 9 | ], 10 | "properties": { 11 | "id": { 12 | "description": "Unique id of this pattern", 13 | "type": "string", 14 | "minLength": 1 15 | }, 16 | "name": { 17 | "description": "Machine readable name of the pattern", 18 | "type": "string", 19 | "minLength": 1, 20 | "pattern": "^[[a-z]*[-]?[a-z]*]*$" 21 | }, 22 | "displayName": { 23 | "description": "Human readable name of the pattern", 24 | "type": "string", 25 | "minLength": 1 26 | }, 27 | "version": { 28 | "description": "Semantic version of the pattern", 29 | "type": "string", 30 | "pattern": "^\\d\\.\\d\\.\\d(-[a-z]*){0,1}$" 31 | }, 32 | "versions": { 33 | "description": "Available semantic versions of the pattern", 34 | "type": "array", 35 | "minItems": 1, 36 | "items": { 37 | "description": "Semantic version of the pattern", 38 | "type": "string", 39 | "pattern": "^\\d\\.\\d\\.\\d(-[a-z]*){0,1}$" 40 | } 41 | }, 42 | "flag": { 43 | "description": "Stability flag of the pattern", 44 | "type": "string", 45 | "pattern": "^alpha|beta|rc|stable$" 46 | }, 47 | "tags": { 48 | "description": "Array of tags describing the pattern", 49 | "type": "array", 50 | "minItems": 1, 51 | "items": { 52 | "description": "Tag describing the pattern", 53 | "type": "string", 54 | "minLength": 1 55 | }, 56 | "uniqueItems": true 57 | }, 58 | "data": { 59 | "description": "Custom data object supplied by user", 60 | "type": "object", 61 | "minProperties": 1 62 | }, 63 | "meta": { 64 | "description": "Custom meta data object supplied by user", 65 | "type": "object", 66 | "minProperties": 1 67 | }, 68 | "options": { 69 | "description": "Custom options object supplied by user", 70 | "type": "object", 71 | "minProperties": 1 72 | }, 73 | "patterns": { 74 | "description": "Dependencies of the pattern", 75 | "type": "object", 76 | "minProperties": 1, 77 | "patternProperties": { 78 | "^.+$": { 79 | "type": "string", 80 | "pattern": "^(/)?([^/\u0000]+(/)?)+$" 81 | } 82 | } 83 | }, 84 | "demoPatterns": { 85 | "description": "Dependencies of the pattern used for demo purposes", 86 | "minProperties": 1, 87 | "patternProperties": { 88 | "^.+$": { 89 | "type": "string", 90 | "pattern": "^(/)?([^/\u0000]+(/)?)+$" 91 | } 92 | } 93 | }, 94 | "overrides": { 95 | "description": "Options for overriding of core pattern behaviour", 96 | "type": "object", 97 | "minProperties": 1, 98 | "properties": { 99 | "files": { 100 | "description": "Custom mapping between patternplate files and paths to use in exchange for this pattern", 101 | "type": "object", 102 | "minProperties": 1, 103 | "patternProperties": { 104 | "^.+$": { 105 | "type": "string", 106 | "pattern": "^(/)?([^/\u0000]+(/)?)+$" 107 | } 108 | } 109 | }, 110 | "demo": { 111 | "description": "Custom url to use as demo for this pattern", 112 | "type": "string" 113 | } 114 | } 115 | }, 116 | "_patternplate": { 117 | "description": "Technical values saved by patternplate core", 118 | "type": "object" 119 | } 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /pattern/pattern.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "pattern", 3 | "version": "0.1.0" 4 | } 5 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # jsonlint-cli 2 | 3 | ![jsonlint-cli logo](https://cdn.rawgit.com/marionebl/jsonlint-cli/master/jsonlint-cli-banner.svg) 4 | 5 | > jsonlint-cli - cli wrapper for jsonlint 6 | 7 | Thin wrapper around [jsonlint](https://github.com/zaach/jsonlint) 8 | improving on its cli. 9 | It introduces glob expansion and advanced schema validation. 10 | Borrows heavily from `jsonlint` in every regard. 11 | 12 | ## Feature comparison 13 | 14 | `jsonlint-cli` introduces valuable improvements 15 | and additions to the cli shipping with [jsonlint](https://github.com/zaach/jsonlint). 16 | 17 | |Feature |jsonlint |jsonlint-cli |Description | 18 | |------------------------|:----------------:|:----------------:|:-----------------------------------------------| 19 | |json validity checking |:heavy_check_mark:|:heavy_check_mark:|jsonlint-cli uses jsonlint to parse and validate| 20 | |local schema validation |:heavy_check_mark:|:heavy_check_mark:|specify local schemas to validate input against | 21 | |read from stdin |:heavy_check_mark:|:heavy_check_mark:|stream json in via stdin | 22 | |read from fs |:heavy_check_mark:|:heavy_check_mark:|specify file's path to lint | 23 | |glob expansion |:x: |:heavy_check_mark:|specify globs of files to lint, e.g. `**/*.json`| 24 | |remote schema validation|:x: |:heavy_check_mark:|specify [remote schemas][1] to validate against | 25 | |v4 schema validation |:x: |:heavy_check_mark:|use v4 jsonschema | 26 | |config files |:x: |:heavy_check_mark:|support for `eslint` style config files | 27 | 28 | ## Installation 29 | 30 | ```shell 31 | # Install it from npm 32 | npm install -g jsonlint-cli 33 | ``` 34 | 35 | ### Usage 36 | 37 | `jsonlint-cli` exposes a command line interface 38 | 39 | ```shell 40 | ❯ jsonlint-cli --help 41 | jsonlint-cli@1.0.0 - cli wrapper for jsonlint 42 | 43 | [input] reads from stdin if [files] are omitted 44 | --ignore,-i glob pattern to exclude from linting, defaults to: node_modules 45 | --validate,-s uri to schema to use for validation, defaults to: 46 | --indent,-w whitespace to use for pretty printing, defaults to: " " 47 | --env,-e json schema env to use for validation, defaults to: json-schema-draft-04 48 | --pretty,-p pretty-print the input, defaults to: false 49 | --sort,-t sort json keys alphabetically, defaults to: false 50 | --version,-v print the version 51 | --help,-h show this help 52 | ``` 53 | 54 | ## Configuration 55 | 56 | `jsonlint-cli` picks up configuration files, 57 | searching upwards from `process.cwd()` or the file path if specified. 58 | 59 | ### .jsonlintrc 60 | 61 | ```js 62 | { 63 | "validate": "", // schema uri to validate against 64 | "ignore": ["node_modules/**/*"], // glob patterns to ignore 65 | "indent": "", // indent to use for pretty-printed output 66 | "env": "json-schema-draft-04", // json schema env version to use 67 | "pretty": true // pretty-print formatted json if quiet is false 68 | } 69 | ``` 70 | 71 | ### .jsonlintignore 72 | 73 | ```ini 74 | node_modules/ # ignored by default 75 | distribution/ 76 | ``` 77 | 78 | --- 79 | 80 | Copyright 2016 by [Mario Nebl](https://github.com/marionebl) 81 | and [contributors](./graphs/contributors). 82 | Released under the [MIT license]('./license.md'). 83 | 84 | [1]: http://schemastore.org/json/ 85 | --------------------------------------------------------------------------------