mirror of
https://github.com/theoludwig/html-w3c-validator.git
synced 2025-05-21 23:21:29 +02:00
feat: add html-w3c-validator
command
This commit is contained in:
@ -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')
|
||||
return 0
|
||||
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
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user