mirror of
https://github.com/theoludwig/html-w3c-validator.git
synced 2024-12-08 00:45:37 +01:00
feat: add html-w3c-validator
command
This commit is contained in:
parent
39b05c66a7
commit
77cefcd53f
11
.github/dependabot.yml
vendored
11
.github/dependabot.yml
vendored
@ -1,11 +0,0 @@
|
|||||||
version: 2
|
|
||||||
updates:
|
|
||||||
- package-ecosystem: 'github-actions'
|
|
||||||
directory: '/'
|
|
||||||
schedule:
|
|
||||||
interval: 'daily'
|
|
||||||
|
|
||||||
- package-ecosystem: 'npm'
|
|
||||||
directory: '/'
|
|
||||||
schedule:
|
|
||||||
interval: 'daily'
|
|
2
.gitignore
vendored
2
.gitignore
vendored
@ -3,7 +3,7 @@ node_modules
|
|||||||
.npm
|
.npm
|
||||||
|
|
||||||
# production
|
# production
|
||||||
build
|
/build
|
||||||
.swc
|
.swc
|
||||||
|
|
||||||
# testing
|
# testing
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
build
|
/build
|
||||||
node_modules
|
node_modules
|
||||||
coverage
|
coverage
|
||||||
package.json
|
package.json
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"branches": [{ "name": "master", "channel": "beta", "prerelease": true }],
|
"branches": ["master"],
|
||||||
"plugins": [
|
"plugins": [
|
||||||
[
|
[
|
||||||
"@semantic-release/commit-analyzer",
|
"@semantic-release/commit-analyzer",
|
||||||
|
10
README.md
10
README.md
@ -65,7 +65,7 @@ npm install --save-dev html-w3c-validator start-server-and-test
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
### `.html-w3c-validator.json`
|
### `.html-w3c-validatorrc.json`
|
||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
@ -79,18 +79,22 @@ npm install --save-dev html-w3c-validator start-server-and-test
|
|||||||
npm run test:html-w3c-validator
|
npm run test:html-w3c-validator
|
||||||
```
|
```
|
||||||
|
|
||||||
Example of output:
|
Example of output (in case of success):
|
||||||
|
|
||||||
```txt
|
```txt
|
||||||
|
✔ Validating http://localhost:3000/
|
||||||
|
✔ Validating http://localhost:3000/about
|
||||||
|
|
||||||
|
Success: HTML validation (W3C) passed! 🎉
|
||||||
```
|
```
|
||||||
|
|
||||||
|
See the [./example](./example) folder for practical usage.
|
||||||
|
|
||||||
### Options
|
### Options
|
||||||
|
|
||||||
```text
|
```text
|
||||||
-V, --version Output the version number.
|
-V, --version Output the version number.
|
||||||
-h, --help Display help for command.
|
-h, --help Display help for command.
|
||||||
--config Path to a configuration file (default: `.html-w3c-validator.json`).
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## 💡 Contributing
|
## 💡 Contributing
|
||||||
|
3
example/.html-w3c-validatorrc.json
Normal file
3
example/.html-w3c-validatorrc.json
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"urls": ["http://localhost:3000/", "http://localhost:3000/about"]
|
||||||
|
}
|
11
example/build/about.html
Normal file
11
example/build/about.html
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<title>About</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
9
example/build/index.html
Normal file
9
example/build/index.html
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<title>Home</title>
|
||||||
|
</head>
|
||||||
|
<body></body>
|
||||||
|
</html>
|
27688
example/package-lock.json
generated
Normal file
27688
example/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
13
example/package.json
Normal file
13
example/package.json
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
{
|
||||||
|
"name": "example",
|
||||||
|
"private": true,
|
||||||
|
"scripts": {
|
||||||
|
"start": "serve ./build",
|
||||||
|
"test:html-w3c-validator": "start-server-and-test 'start' 'http://localhost:3000' 'html-w3c-validator'"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"serve": "13.0.2",
|
||||||
|
"start-server-and-test": "1.14.0",
|
||||||
|
"html-w3c-validator": "file:.."
|
||||||
|
}
|
||||||
|
}
|
@ -5,7 +5,5 @@
|
|||||||
"^.+\\.(t|j)sx?$": ["@swc/jest"]
|
"^.+\\.(t|j)sx?$": ["@swc/jest"]
|
||||||
},
|
},
|
||||||
"rootDir": "./src",
|
"rootDir": "./src",
|
||||||
"collectCoverage": true,
|
"setupFiles": ["<rootDir>/__test__/setup.ts"]
|
||||||
"coverageDirectory": "../coverage/",
|
|
||||||
"coverageReporters": ["text", "cobertura"]
|
|
||||||
}
|
}
|
||||||
|
7154
package-lock.json
generated
7154
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
10
package.json
10
package.json
@ -46,7 +46,9 @@
|
|||||||
"chalk": "4.1.2",
|
"chalk": "4.1.2",
|
||||||
"clipanion": "3.1.0",
|
"clipanion": "3.1.0",
|
||||||
"html-validator": "6.0.0",
|
"html-validator": "6.0.0",
|
||||||
"read-pkg": "5.2.0"
|
"ora": "5.4.1",
|
||||||
|
"read-pkg": "5.2.0",
|
||||||
|
"table": "6.8.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@commitlint/cli": "16.0.1",
|
"@commitlint/cli": "16.0.1",
|
||||||
@ -54,7 +56,9 @@
|
|||||||
"@swc/cli": "0.1.55",
|
"@swc/cli": "0.1.55",
|
||||||
"@swc/core": "1.2.127",
|
"@swc/core": "1.2.127",
|
||||||
"@swc/jest": "0.2.15",
|
"@swc/jest": "0.2.15",
|
||||||
|
"@types/html-validator": "5.0.2",
|
||||||
"@types/jest": "27.4.0",
|
"@types/jest": "27.4.0",
|
||||||
|
"@types/mock-fs": "4.13.1",
|
||||||
"@types/node": "17.0.8",
|
"@types/node": "17.0.8",
|
||||||
"editorconfig-checker": "4.0.2",
|
"editorconfig-checker": "4.0.2",
|
||||||
"eslint": "7.32.0",
|
"eslint": "7.32.0",
|
||||||
@ -65,16 +69,20 @@
|
|||||||
"eslint-plugin-prettier": "4.0.0",
|
"eslint-plugin-prettier": "4.0.0",
|
||||||
"eslint-plugin-promise": "5.1.1",
|
"eslint-plugin-promise": "5.1.1",
|
||||||
"eslint-plugin-unicorn": "40.0.0",
|
"eslint-plugin-unicorn": "40.0.0",
|
||||||
|
"execa": "5.1.1",
|
||||||
"husky": "7.0.4",
|
"husky": "7.0.4",
|
||||||
"jest": "27.4.7",
|
"jest": "27.4.7",
|
||||||
"jest-mock-extended": "2.0.4",
|
"jest-mock-extended": "2.0.4",
|
||||||
"jest-ts-webcompat-resolver": "1.0.0",
|
"jest-ts-webcompat-resolver": "1.0.0",
|
||||||
"lint-staged": "12.1.5",
|
"lint-staged": "12.1.5",
|
||||||
"markdownlint-cli": "0.30.0",
|
"markdownlint-cli": "0.30.0",
|
||||||
|
"mock-fs": "5.1.2",
|
||||||
|
"npm": "8.3.0",
|
||||||
"pinst": "2.1.6",
|
"pinst": "2.1.6",
|
||||||
"prettier": "2.5.1",
|
"prettier": "2.5.1",
|
||||||
"rimraf": "3.0.2",
|
"rimraf": "3.0.2",
|
||||||
"semantic-release": "18.0.1",
|
"semantic-release": "18.0.1",
|
||||||
|
"serve": "13.0.2",
|
||||||
"typescript": "4.5.4"
|
"typescript": "4.5.4"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,25 @@
|
|||||||
import { Command } from 'clipanion'
|
import path from 'node:path'
|
||||||
|
import fs from 'node:fs'
|
||||||
|
|
||||||
// const CURRENT_DIRECTORY = process.cwd()
|
import { Command } from 'clipanion'
|
||||||
|
import chalk from 'chalk'
|
||||||
|
import ora from 'ora'
|
||||||
|
import validateHTML, { ValidationMessageLocationObject } from 'html-validator'
|
||||||
|
import { table } from 'table'
|
||||||
|
|
||||||
|
import { isExistingPath } from './utils/isExistingPath.js'
|
||||||
|
|
||||||
|
const CURRENT_DIRECTORY = process.cwd()
|
||||||
|
const CONFIG_FILE_NAME = '.html-w3c-validatorrc.json'
|
||||||
|
|
||||||
|
interface Config {
|
||||||
|
urls: string[]
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Error {
|
||||||
|
url: string
|
||||||
|
messagesTable: string[][]
|
||||||
|
}
|
||||||
|
|
||||||
export class HTMLValidatorCommand extends Command {
|
export class HTMLValidatorCommand extends Command {
|
||||||
static usage = {
|
static usage = {
|
||||||
@ -9,7 +28,90 @@ export class HTMLValidatorCommand extends Command {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async execute(): Promise<number> {
|
async execute(): Promise<number> {
|
||||||
console.log('html-w3c-validator')
|
const configPath = path.join(CURRENT_DIRECTORY, CONFIG_FILE_NAME)
|
||||||
return 0
|
try {
|
||||||
|
if (!(await isExistingPath(configPath))) {
|
||||||
|
throw new Error(
|
||||||
|
`No config file found at ${configPath}. Please create ${CONFIG_FILE_NAME}.`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const configData = await fs.promises.readFile(configPath, {
|
||||||
|
encoding: 'utf-8'
|
||||||
|
})
|
||||||
|
let config: Config = { urls: [] }
|
||||||
|
let isValidConfig = true
|
||||||
|
try {
|
||||||
|
config = JSON.parse(configData)
|
||||||
|
} catch {
|
||||||
|
isValidConfig = false
|
||||||
|
}
|
||||||
|
isValidConfig = isValidConfig && Array.isArray(config.urls)
|
||||||
|
if (!isValidConfig) {
|
||||||
|
throw new Error(
|
||||||
|
`Invalid config file at ${configPath}. Please check the syntax.`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const errors: Error[] = []
|
||||||
|
let isValid = true
|
||||||
|
for (const url of config.urls) {
|
||||||
|
const loader = ora(`Validating ${url}`).start()
|
||||||
|
const result = await validateHTML({
|
||||||
|
url,
|
||||||
|
format: 'json',
|
||||||
|
isLocal: true
|
||||||
|
})
|
||||||
|
const isValidHTML = result.messages.length === 0
|
||||||
|
if (isValidHTML) {
|
||||||
|
loader.succeed()
|
||||||
|
} else {
|
||||||
|
loader.fail()
|
||||||
|
const messagesTable: string[][] = []
|
||||||
|
for (const message of result.messages) {
|
||||||
|
const row: string[] = []
|
||||||
|
if (message.type === 'error') {
|
||||||
|
row.push(chalk.red(message.type))
|
||||||
|
} else {
|
||||||
|
row.push(chalk.yellow(message.type))
|
||||||
|
}
|
||||||
|
row.push(message.message)
|
||||||
|
const violation = message as ValidationMessageLocationObject
|
||||||
|
if (violation.extract != null) {
|
||||||
|
row.push(
|
||||||
|
`line: ${violation.lastLine}, column: ${violation.firstColumn}-${violation.lastColumn}`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
messagesTable.push(row)
|
||||||
|
}
|
||||||
|
errors.push({ url, messagesTable })
|
||||||
|
isValid = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isValid) {
|
||||||
|
for (const error of errors) {
|
||||||
|
console.error(`\n${error.url}`)
|
||||||
|
console.error(table(error.messagesTable))
|
||||||
|
console.error('------------------------------')
|
||||||
|
}
|
||||||
|
console.error()
|
||||||
|
throw new Error('HTML validation (W3C) failed!')
|
||||||
|
}
|
||||||
|
console.log()
|
||||||
|
console.log(
|
||||||
|
`${chalk.bold.green('Success:')} HTML validation (W3C) passed! 🎉`
|
||||||
|
)
|
||||||
|
return 0
|
||||||
|
} catch (error) {
|
||||||
|
if (error instanceof Error) {
|
||||||
|
console.error(`${chalk.bold.red('Error:')} ${error.message}`)
|
||||||
|
} else {
|
||||||
|
console.error(
|
||||||
|
`${chalk.bold.red('Error:')} HTML validation (W3C) failed!`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return 1
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,3 +1,7 @@
|
|||||||
|
import path from 'node:path'
|
||||||
|
|
||||||
|
import execa from 'execa'
|
||||||
|
|
||||||
import { cli } from '../cli.js'
|
import { cli } from '../cli.js'
|
||||||
import { HTMLValidatorCommand } from '../HTMLValidatorCommand.js'
|
import { HTMLValidatorCommand } from '../HTMLValidatorCommand.js'
|
||||||
|
|
||||||
@ -12,11 +16,11 @@ describe('html-w3c-validator', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it('succeeds and validate the html correctly', async () => {
|
it('succeeds and validate the html correctly', async () => {
|
||||||
console.log = jest.fn()
|
const examplePath = path.join(__dirname, '..', '..', 'example')
|
||||||
const exitCode = await cli.run([], {
|
process.chdir(examplePath)
|
||||||
stdin: process.stdin
|
await execa('rimraf', ['node_modules'])
|
||||||
})
|
await execa('npm', ['install'])
|
||||||
expect(console.log).toHaveBeenCalledWith('html-w3c-validator')
|
const { exitCode } = await execa('npm', ['run', 'test:html-w3c-validator'])
|
||||||
expect(exitCode).toEqual(0)
|
expect(exitCode).toEqual(0)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
1
src/__test__/setup.ts
Normal file
1
src/__test__/setup.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
jest.setTimeout(60000)
|
29
src/utils/__test__/isExistingPath.test.ts
Normal file
29
src/utils/__test__/isExistingPath.test.ts
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
import fsMock from 'mock-fs'
|
||||||
|
|
||||||
|
import { isExistingPath } from '../isExistingPath.js'
|
||||||
|
|
||||||
|
describe('utils/isExistingFile', () => {
|
||||||
|
afterEach(async () => {
|
||||||
|
fsMock.restore()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should return true if the file exists', async () => {
|
||||||
|
fsMock(
|
||||||
|
{
|
||||||
|
'/file.txt': ''
|
||||||
|
},
|
||||||
|
{ createCwd: false }
|
||||||
|
)
|
||||||
|
expect(await isExistingPath('/file.txt')).toBeTruthy()
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should return false if the file doesn't exists", async () => {
|
||||||
|
fsMock(
|
||||||
|
{
|
||||||
|
'/file.txt': ''
|
||||||
|
},
|
||||||
|
{ createCwd: false }
|
||||||
|
)
|
||||||
|
expect(await isExistingPath('/randomfile.txt')).toBeFalsy()
|
||||||
|
})
|
||||||
|
})
|
10
src/utils/isExistingPath.ts
Normal file
10
src/utils/isExistingPath.ts
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
import fs from 'node:fs'
|
||||||
|
|
||||||
|
export const isExistingPath = async (path: string): Promise<boolean> => {
|
||||||
|
try {
|
||||||
|
await fs.promises.access(path, fs.constants.F_OK)
|
||||||
|
return true
|
||||||
|
} catch {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user