├── .gitignore
├── .travis.yml
├── LICENSE
├── README.md
├── index.js
├── lib
├── getHelpText.js
└── helptext.json
├── package-lock.json
├── package.json
├── renovate.json
└── test
├── cli-test.js
├── data
├── invalid.html
└── valid.html
└── getHelpText-test.js
/.gitignore:
--------------------------------------------------------------------------------
1 | # IDE
2 | .idea
3 | .vs
4 | .vscode
5 |
6 | # Mac OS
7 | .DS_Store
8 |
9 | # Logs
10 | logs
11 | *.log
12 |
13 | # Runtime data
14 | pids
15 | *.pid
16 | *.seed
17 |
18 | # Directory for instrumented libs generated by jscoverage/JSCover
19 | lib-cov
20 |
21 | # Coverage directory used by tools like istanbul
22 | coverage
23 | .nyc_output
24 |
25 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
26 | .grunt
27 |
28 | # node-waf configuration
29 | .lock-wscript
30 |
31 | # Compiled binary addons (http://nodejs.org/api/addons.html)
32 | build/Release
33 |
34 | # Dependency directory
35 | # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git
36 | node_modules
37 |
38 | # Env
39 | .env
40 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 | node_js:
3 | - "10"
4 | after_success:
5 | - npm run coveralls
6 | deploy:
7 | provider: npm
8 | email: $NPM_EMAIL
9 | api_key: $NPM_TOKEN
10 | on:
11 | tags: true
12 | branch: master
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2015 Geir Gåsodden
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 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | [](https://travis-ci.org/zrrrzzt/html-validator-cli)
2 | [](https://coveralls.io/github/zrrrzzt/html-validator-cli?branch=master)
3 | [](https://github.com/feross/standard)
4 |
5 | # html-validator-cli
6 |
7 | CLI for validating html using [validator.w3.org/nu](http://validator.w3.org/nu/)
8 |
9 | Requires Node >= 8.15.3 for older versions use v5.0.0
10 |
11 | Sends ```Page is valid```to ```STDOUT``` and exits with code 0 if page is valid.
12 |
13 | Sends ```Page is not valid``` to ```STDOUT``` and exits with code 1 if page is not valid.
14 |
15 | Sends ```Page not found``` to ```STDOUT``` and exits with code 1 if page is not found.
16 |
17 | ## Installation
18 |
19 | ```
20 | $ npm i html-validator-cli -g
21 | ```
22 |
23 | ## Usage
24 |
25 | ```
26 | $ html-validator
27 | ```
28 |
29 | With file
30 |
31 | ```
32 | $ html-validator --file=
33 | ```
34 |
35 | With data
36 |
37 | ```
38 | $ html-validator --data=data
39 | ```
40 |
41 | Optional pass in format for returned data.
42 |
43 | Valid options: json, html, xhtml, xml, gnu and text (default).
44 |
45 | ```
46 | $ html-validator --format=gnu
47 | ```
48 |
49 | Optional pass in another validator.
50 |
51 | It needs to expose the same REST interface.
52 |
53 | ```
54 | $ html-validator --validator='http://html5.validator.nu'
55 | ```
56 |
57 | Optional pass in strings to ignore
58 |
59 | ```
60 | $ html-validator --ignore='Error: Stray end tag “div”.' --ignore='Error: Stray end tag “body”.'
61 | ```
62 | Optional pass in headers
63 |
64 | ```
65 | $ html-validator --headers='{"foo":"doo"}'
66 | ```
67 |
68 | To get full result from validator use --verbose
69 |
70 | ```
71 | $ html-validator --verbose
72 | ```
73 |
74 | Optional, only get errors use --quiet
75 |
76 | ```
77 | $ html-validator --quiet
78 | ```
79 |
80 | Validate a local document without setting up a tunnel
81 |
82 | ```
83 | $ html-validator --islocal
84 | ```
85 |
86 | returns array of error messages
87 |
88 | ```JavaScript
89 | [
90 | {
91 | "type": "error",
92 | "lastLine": 8,
93 | "lastColumn": 32,
94 | "firstColumn": 27,
95 | "message": "Stray end tag “div”.",
96 | "extract": "aaaad code
\n<",
97 | "hiliteStart": 10,
98 | "hiliteLength": 6
99 | }
100 | ]
101 | ```
102 |
103 | ## Related
104 |
105 | - [site-validator-cli](https://github.com/p1ho/site-validator-cli) CLI for validating a whole site or multiple pages
106 | - [html-validator](https://github.com/zrrrzzt/html-validator) API for this module
107 |
108 | ## License
109 |
110 | [MIT](LICENSE)
111 |
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 | (async () => {
3 | const fs = require('fs')
4 | const validator = require('html-validator')
5 | const getHelpText = require('./lib/getHelpText')
6 | const pkg = require('./package.json')
7 | const query = process.argv[2]
8 | const argv = require('minimist')((process.argv.slice(2)))
9 | const options = {
10 | format: 'text',
11 | ignore: argv.ignore
12 | }
13 |
14 | const isError = item => item.type === 'error'
15 | const pageNotFound = item => item.type === 'non-document-error'
16 |
17 | if (!query || process.argv.indexOf('-h') !== -1 || process.argv.indexOf('--help') !== -1) {
18 | console.log(getHelpText())
19 | process.exit(0)
20 | }
21 |
22 | if (process.argv.indexOf('-v') !== -1 || process.argv.indexOf('--version') !== -1) {
23 | console.log(pkg.version)
24 | process.exit(0)
25 | }
26 |
27 | if (query.indexOf('http') !== -1) {
28 | options.url = argv._[0]
29 | }
30 |
31 | if (argv.format && !argv.ignore) {
32 | options.format = argv.format
33 | }
34 |
35 | if (argv.url) {
36 | options.url = argv.url
37 | }
38 |
39 | if (argv.islocal) {
40 | options.isLocal = true
41 | }
42 |
43 | if (argv.validator) {
44 | options.validator = argv.validator
45 | }
46 |
47 | if (argv.headers) {
48 | options.headers = JSON.parse(argv.headers)
49 | }
50 |
51 | if (argv.file) {
52 | options.data = fs.readFileSync(argv.file)
53 | }
54 |
55 | if (argv.data) {
56 | options.data = argv.data
57 | }
58 |
59 | try {
60 | const data = await validator(options)
61 | let msg
62 | let validationFailed = false
63 | let documentNotFound = false
64 |
65 | if (options.format === 'json') {
66 | const errors = data.messages.filter(isError)
67 | const notFound = data.messages.filter(pageNotFound)
68 | msg = JSON.stringify(data, null, 2)
69 | if (errors.length > 0 || notFound.length > 0) {
70 | validationFailed = true
71 | documentNotFound = notFound.length > 0
72 | if (argv.quiet) {
73 | msg = JSON.stringify(errors, null, 2)
74 | }
75 | }
76 | } else if (options.ignore) {
77 | msg = data
78 | if (data.includes('Error')) {
79 | validationFailed = true
80 | }
81 | if (data.includes('non-document-error')) {
82 | documentNotFound = true
83 | }
84 | } else {
85 | msg = data
86 | if (data.includes('There were errors') || data.includes('non-document-error')) {
87 | validationFailed = true
88 | documentNotFound = data.includes('non-document-error')
89 | }
90 | }
91 | if (validationFailed) {
92 | if (!argv.verbose && !argv.quiet) {
93 | console.log(documentNotFound ? 'Page not found' : 'Page is not valid')
94 | }
95 | if (argv.verbose || argv.quiet) {
96 | console.log(msg)
97 | }
98 | process.exitCode = 1
99 | } else {
100 | if (!argv.verbose && !argv.quiet) {
101 | console.log('Page is valid')
102 | }
103 | if (argv.verbose) {
104 | console.log(msg)
105 | }
106 | }
107 | } catch (error) {
108 | console.error(error)
109 | process.exitCode = 1
110 | }
111 | })()
112 |
--------------------------------------------------------------------------------
/lib/getHelpText.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 |
3 | module.exports = () => {
4 | const help = require('./helptext.json')
5 | return help.join('\n')
6 | }
7 |
--------------------------------------------------------------------------------
/lib/helptext.json:
--------------------------------------------------------------------------------
1 | [
2 | "Usage: ",
3 | "",
4 | "$ html-validator ",
5 | "",
6 | "or",
7 | "$ html-validator --file=",
8 | "or",
9 | "$ html-validator --data=",
10 | "",
11 | "Optional, specify format of returned data",
12 | "Valid options: json, html, xhtml, xml, gnu and text (default)",
13 | "",
14 | "$ html-validator --format=",
15 | "",
16 | "Use another validator (requires the same api)",
17 | "",
18 | "$ html-validator --validator='http://html5.validator.nu'",
19 | "",
20 | "Optional, pass in strings to ignore",
21 | "",
22 | "$ html-validator --ignore='Error: Stray end tag “div”.' --ignore='Error: Stray end tag “body”.'",
23 | "",
24 | "Get full result from validator use --verbose",
25 | "",
26 | "$ html-validator --verbose",
27 | "",
28 | "Optional, only get errors use --quiet",
29 | "",
30 | "$ html-validator --quiet"
31 | ]
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "html-validator-cli",
3 | "version": "7.0.1",
4 | "description": "CLI for validating html using validator.w3.org/nu",
5 | "license": "MIT",
6 | "author": {
7 | "name": "Geir Gåsodden",
8 | "email": "geir.gasodden@pythonia.no",
9 | "url": "https://github.com/zrrrzzt"
10 | },
11 | "main": "index.js",
12 | "bin": {
13 | "html-validator": "index.js"
14 | },
15 | "engines": {
16 | "node": ">=10.22.0"
17 | },
18 | "scripts": {
19 | "test": "standard && tap --reporter=spec test/*.js",
20 | "coverage": "tap test/*.js --coverage",
21 | "coveralls": "tap --cov --coverage-report=lcov test/*.js && cat coverage/lcov.info | coveralls",
22 | "standard-fix": "standard --fix",
23 | "refresh": "rm -rf node_modules && rm package-lock.json && npm install"
24 | },
25 | "keywords": [
26 | "html-validator",
27 | "validation",
28 | "cli"
29 | ],
30 | "repository": {
31 | "type": "git",
32 | "url": "git+https://github.com/zrrrzzt/html-validator-cli.git"
33 | },
34 | "bugs": {
35 | "url": "https://github.com/zrrrzzt/html-validator-cli/issues"
36 | },
37 | "homepage": "https://github.com/zrrrzzt/html-validator-cli#readme",
38 | "dependencies": {
39 | "html-validator": "5.0.1",
40 | "minimist": "1.2.5"
41 | },
42 | "devDependencies": {
43 | "coveralls": "3.1.0",
44 | "standard": "16.0.4",
45 | "tap": "15.1.6"
46 | },
47 | "files": [
48 | "lib/*.{js,json}",
49 | "index.js"
50 | ]
51 | }
52 |
--------------------------------------------------------------------------------
/renovate.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://docs.renovatebot.com/renovate-schema.json",
3 | "extends": [
4 | "github>zrrrzzt/renovate-config"
5 | ]
6 | }
7 |
--------------------------------------------------------------------------------
/test/cli-test.js:
--------------------------------------------------------------------------------
1 | const tap = require('tap')
2 | const fs = require('fs')
3 | const exec = require('child_process').execFile
4 | const getHelpText = require('../lib/getHelpText')
5 | const pkgVersion = require('../package.json').version
6 |
7 | tap.test('It returns helptext with -h flag', function helpTextWithH (test) {
8 | exec('./index.js', ['-h'], function helpTextWithH (error, stdout, stderr) {
9 | if (error) {
10 | console.error(stderr.toString())
11 | throw error
12 | } else {
13 | test.equal(stdout.toString().trim(), getHelpText().toString().trim())
14 | test.end()
15 | }
16 | })
17 | })
18 |
19 | tap.test('It returns helptext with --help flag', function helpTextWithH (test) {
20 | exec('./index.js', ['--help'], function helpTextWithH (error, stdout, stderr) {
21 | if (error) {
22 | throw error
23 | } else {
24 | test.equal(stdout.toString().trim(), getHelpText().toString().trim())
25 | test.end()
26 | }
27 | })
28 | })
29 |
30 | tap.test('It returns version with -v flag', function versionWithV (test) {
31 | exec('./index.js', ['-v'], function versionWithV (error, stdout, stderr) {
32 | if (error) {
33 | throw error
34 | } else {
35 | test.equal(stdout.toString().trim(), pkgVersion)
36 | test.end()
37 | }
38 | })
39 | })
40 |
41 | tap.test('It returns error on error', function testError (test) {
42 | exec('./index.js', ['npmlovesyou-do-you-love-npm'], function versionWithV (error, stdout, stderr) {
43 | test.ok(error, 'Error OK')
44 | test.end()
45 | })
46 | })
47 |
48 | tap.test('It returns error on validation failure', function testError (test) {
49 | exec('./index.js', ['--file=test/data/invalid.html'], function versionWithV (error, stdout, stderr) {
50 | tap.equal(error.code, 1, 'It returns error.code 1')
51 | test.equal(stdout.toString().trim(), 'Page is not valid', 'Expected message to stdout')
52 | test.end()
53 | })
54 | })
55 |
56 | tap.test('It returns not found for non-existing pages', function testError (test) {
57 | exec('./index.js', ['https://adsf'], function versionWithV (error, stdout, stderr) {
58 | tap.equal(error.code, 1, 'It returns error.code 1')
59 | test.equal(stdout.toString().trim(), 'Page not found', 'Expected message to stdout')
60 | test.end()
61 | })
62 | })
63 |
64 | tap.test('It returns correct message on validation success', function testSuccess (test) {
65 | exec('./index.js', ['--file=test/data/valid.html'], (error, stdout, stderr) => {
66 | if (error) {
67 | throw error
68 | }
69 | test.equal(stdout.toString().trim(), 'Page is valid', 'Expected message to stdout')
70 | test.end()
71 | })
72 | })
73 |
74 | tap.test('You can supply headers', function testSuccess (test) {
75 | exec('./index.js', ['--file=test/data/valid.html', `--headers=${JSON.stringify({ foo: 'bar' })}`], (error, stdout, stderr) => {
76 | if (error) {
77 | throw error
78 | }
79 | test.ok(stdout.toString().trim(), 'Data OK')
80 | test.end()
81 | })
82 | })
83 |
84 | tap.test('It returns data if file supplied', function testError (test) {
85 | exec('./index.js', ['--file=test/data/valid.html'], function versionWithV (error, stdout, stderr) {
86 | if (error) {
87 | throw error
88 | }
89 | test.ok(stdout.toString().trim(), 'Data OK')
90 | test.end()
91 | })
92 | })
93 |
94 | tap.test('It returns data if data supplied', function testError (test) {
95 | const data = fs.readFileSync('test/data/valid.html', 'utf-8')
96 | const cmd = `--data=${data}`
97 | exec('./index.js', [cmd], (error, stdout, stderr) => {
98 | if (error) {
99 | throw error
100 | }
101 | test.ok(stdout.toString().trim(), 'Data OK')
102 | test.end()
103 | })
104 | })
105 |
106 | tap.test('It returns data in supplied format for success', function testError (test) {
107 | exec('./index.js', ['--file=test/data/valid.html', '--format=json', '--verbose'], function versionWithV (error, stdout, stderr) {
108 | if (error) {
109 | throw error
110 | }
111 | test.equal(JSON.parse(stdout.toString().trim()).messages.length, 0)
112 | test.end()
113 | })
114 | })
115 |
116 | tap.test('It returns data in supplied format for failures', function testError (test) {
117 | exec('./index.js', ['--file=test/data/invalid.html', '--format=json', '--verbose'], function versionWithV (error, stdout, stderr) {
118 | if (error) {
119 | console.error(error)
120 | }
121 | test.equal(JSON.parse(stdout.toString().trim()).messages.length, 2)
122 | test.end()
123 | })
124 | })
125 |
126 | tap.test('It returns no data for success if --quiet', function testSuccessQuiet (test) {
127 | exec('./index.js', ['--file=test/data/valid.html', '--format=json', '--quiet'], function successQuiet (error, stdout, stderr) {
128 | if (error) {
129 | throw error
130 | }
131 | test.equal(stdout.toString().trim().length, 0)
132 | test.end()
133 | })
134 | })
135 |
136 | tap.test('It errors for failures if --quiet', function testErrorQuiet (test) {
137 | exec('./index.js', ['--file=test/data/invalid.html', '--format=json', '--quiet'], function errorQuiet (error, stdout, stderr) {
138 | if (error) {
139 | console.error(error)
140 | }
141 | test.equal(JSON.parse(stdout.toString().trim()).length, 1)
142 | test.end()
143 | })
144 | })
145 |
146 | tap.test('You can supply url by flag', function testError (test) {
147 | exec('./index.js', ['--url=https://www.npm.com', '--verbose'], function versionWithV (error, stdout, stderr) {
148 | if (error) {
149 | console.error(error)
150 | }
151 | test.ok(stdout.toString().trim(), 'Data OK')
152 | test.end()
153 | })
154 | })
155 |
156 | tap.test('You can use another validator', function testError (test) {
157 | exec('./index.js', ['--file=test/data/valid.html', '--validator=https://html5.validator.nu'], function versionWithV (error, stdout, stderr) {
158 | if (error) {
159 | throw error
160 | }
161 | test.ok(stdout.toString().trim(), 'Data OK')
162 | test.end()
163 | })
164 | })
165 |
166 | tap.test('It supports ignore', function testError (test) {
167 | const str = 'Error: Stray end tag “div”.'
168 | exec('./index.js', ['--file=test/data/invalid.html', `--ignore=${str}`], function versionWithV (error, stdout, stderr) {
169 | if (error) {
170 | throw error
171 | }
172 | test.equal(stdout.toString().trim(), 'Page is valid', 'Expected message to stdout')
173 | test.end()
174 | })
175 | })
176 |
--------------------------------------------------------------------------------
/test/data/invalid.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Invalid
6 |
7 |
8 | I'm baaaaaaaaaaaad code
9 |
10 |
--------------------------------------------------------------------------------
/test/data/valid.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Title
6 |
7 |
8 | I'm a very good page I am
9 |
10 |
--------------------------------------------------------------------------------
/test/getHelpText-test.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 |
3 | const tap = require('tap')
4 | const getHelpText = require('../lib/getHelpText')
5 | const helpText = require('../lib/helptext.json').join('\n')
6 |
7 | tap.equal(helpText, getHelpText(), 'It returns correct helptext')
8 |
--------------------------------------------------------------------------------