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
|
||||
|
||||
# production
|
||||
build
|
||||
/build
|
||||
.swc
|
||||
|
||||
# testing
|
||||
|
@ -1,4 +1,4 @@
|
||||
build
|
||||
/build
|
||||
node_modules
|
||||
coverage
|
||||
package.json
|
||||
|
@ -1,5 +1,5 @@
|
||||
{
|
||||
"branches": [{ "name": "master", "channel": "beta", "prerelease": true }],
|
||||
"branches": ["master"],
|
||||
"plugins": [
|
||||
[
|
||||
"@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
|
||||
{
|
||||
@ -79,18 +79,22 @@ npm install --save-dev html-w3c-validator start-server-and-test
|
||||
npm run test:html-w3c-validator
|
||||
```
|
||||
|
||||
Example of output:
|
||||
Example of output (in case of success):
|
||||
|
||||
```txt
|
||||
✔ Validating http://localhost:3000/
|
||||
✔ Validating http://localhost:3000/about
|
||||
|
||||
Success: HTML validation (W3C) passed! 🎉
|
||||
```
|
||||
|
||||
See the [./example](./example) folder for practical usage.
|
||||
|
||||
### Options
|
||||
|
||||
```text
|
||||
-V, --version Output the version number.
|
||||
-h, --help Display help for command.
|
||||
--config Path to a configuration file (default: `.html-w3c-validator.json`).
|
||||
```
|
||||
|
||||
## 💡 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"]
|
||||
},
|
||||
"rootDir": "./src",
|
||||
"collectCoverage": true,
|
||||
"coverageDirectory": "../coverage/",
|
||||
"coverageReporters": ["text", "cobertura"]
|
||||
"setupFiles": ["<rootDir>/__test__/setup.ts"]
|
||||
}
|
||||
|
15570
package-lock.json
generated
15570
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",
|
||||
"clipanion": "3.1.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": {
|
||||
"@commitlint/cli": "16.0.1",
|
||||
@ -54,7 +56,9 @@
|
||||
"@swc/cli": "0.1.55",
|
||||
"@swc/core": "1.2.127",
|
||||
"@swc/jest": "0.2.15",
|
||||
"@types/html-validator": "5.0.2",
|
||||
"@types/jest": "27.4.0",
|
||||
"@types/mock-fs": "4.13.1",
|
||||
"@types/node": "17.0.8",
|
||||
"editorconfig-checker": "4.0.2",
|
||||
"eslint": "7.32.0",
|
||||
@ -65,16 +69,20 @@
|
||||
"eslint-plugin-prettier": "4.0.0",
|
||||
"eslint-plugin-promise": "5.1.1",
|
||||
"eslint-plugin-unicorn": "40.0.0",
|
||||
"execa": "5.1.1",
|
||||
"husky": "7.0.4",
|
||||
"jest": "27.4.7",
|
||||
"jest-mock-extended": "2.0.4",
|
||||
"jest-ts-webcompat-resolver": "1.0.0",
|
||||
"lint-staged": "12.1.5",
|
||||
"markdownlint-cli": "0.30.0",
|
||||
"mock-fs": "5.1.2",
|
||||
"npm": "8.3.0",
|
||||
"pinst": "2.1.6",
|
||||
"prettier": "2.5.1",
|
||||
"rimraf": "3.0.2",
|
||||
"semantic-release": "18.0.1",
|
||||
"serve": "13.0.2",
|
||||
"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 {
|
||||
static usage = {
|
||||
@ -9,7 +28,90 @@ export class HTMLValidatorCommand extends Command {
|
||||
}
|
||||
|
||||
async execute(): Promise<number> {
|
||||
console.log('html-w3c-validator')
|
||||
const configPath = path.join(CURRENT_DIRECTORY, CONFIG_FILE_NAME)
|
||||
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 { HTMLValidatorCommand } from '../HTMLValidatorCommand.js'
|
||||
|
||||
@ -12,11 +16,11 @@ describe('html-w3c-validator', () => {
|
||||
})
|
||||
|
||||
it('succeeds and validate the html correctly', async () => {
|
||||
console.log = jest.fn()
|
||||
const exitCode = await cli.run([], {
|
||||
stdin: process.stdin
|
||||
})
|
||||
expect(console.log).toHaveBeenCalledWith('html-w3c-validator')
|
||||
const examplePath = path.join(__dirname, '..', '..', 'example')
|
||||
process.chdir(examplePath)
|
||||
await execa('rimraf', ['node_modules'])
|
||||
await execa('npm', ['install'])
|
||||
const { exitCode } = await execa('npm', ['run', 'test:html-w3c-validator'])
|
||||
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