1
1
mirror of https://github.com/theoludwig/html-w3c-validator.git synced 2025-05-21 23:21:29 +02:00

13 Commits

27 changed files with 6416 additions and 19541 deletions

View File

@ -1,6 +1,6 @@
<!-- Please first discuss the change you wish to make via issue before making a change. It might avoid a waste of your time. --> <!-- Please first discuss the change you wish to make via issue before making a change. It might avoid a waste of your time. -->
## What changes this PR introduce? # What changes this PR introduce?
## List any relevant issue numbers ## List any relevant issue numbers

View File

@ -10,16 +10,18 @@ jobs:
build: build:
runs-on: 'ubuntu-latest' runs-on: 'ubuntu-latest'
steps: steps:
- uses: 'actions/checkout@v3.0.0' - uses: 'actions/checkout@v3.5.3'
- name: 'Use Node.js' - name: 'Setup Node.js'
uses: 'actions/setup-node@v3.1.0' uses: 'actions/setup-node@v3.6.0'
with: with:
node-version: 'lts/*' node-version: 'lts/*'
cache: 'npm' cache: 'npm'
- name: 'Install' - name: 'Install dependencies'
run: 'npm install' run: 'npm clean-install'
- name: 'Build' - name: 'Build'
run: 'npm run build' run: 'npm run build'
- run: 'npm run build:typescript'

View File

@ -10,19 +10,19 @@ jobs:
lint: lint:
runs-on: 'ubuntu-latest' runs-on: 'ubuntu-latest'
steps: steps:
- uses: 'actions/checkout@v3.0.0' - uses: 'actions/checkout@v3.5.3'
- name: 'Use Node.js' - name: 'Setup Node.js'
uses: 'actions/setup-node@v3.1.0' uses: 'actions/setup-node@v3.6.0'
with: with:
node-version: 'lts/*' node-version: 'lts/*'
cache: 'npm' cache: 'npm'
- name: 'Install' - name: 'Install dependencies'
run: 'npm install' run: 'npm clean-install'
- run: 'npm run lint:commit -- --to "${{ github.sha }}"' - run: 'npm run lint:commit -- --to "${{ github.sha }}"'
- run: 'npm run lint:editorconfig' - run: 'npm run lint:editorconfig'
- run: 'npm run lint:markdown' - run: 'npm run lint:markdown'
- run: 'npm run lint:typescript' - run: 'npm run lint:eslint'
- run: 'npm run lint:prettier' - run: 'npm run lint:prettier'

View File

@ -7,24 +7,34 @@ on:
jobs: jobs:
release: release:
runs-on: 'ubuntu-latest' runs-on: 'ubuntu-latest'
permissions:
contents: 'write'
issues: 'write'
pull-requests: 'write'
id-token: 'write'
steps: steps:
- uses: 'actions/checkout@v3.0.0' - uses: 'actions/checkout@v3.5.3'
with: with:
fetch-depth: 0 fetch-depth: 0
persist-credentials: false persist-credentials: false
- name: 'Use Node.js' - name: 'Setup Node.js'
uses: 'actions/setup-node@v3.1.0' uses: 'actions/setup-node@v3.6.0'
with: with:
node-version: 'lts/*' node-version: 'lts/*'
cache: 'npm' cache: 'npm'
- name: 'Install' - name: 'Install dependencies'
run: 'npm install' run: 'npm clean-install'
- name: 'Build' - name: 'Build'
run: 'npm run build' run: 'npm run build'
- run: 'npm run build:typescript'
- name: 'Verify the integrity of provenance attestations and registry signatures for installed dependencies'
run: 'npm audit signatures'
- name: 'Release' - name: 'Release'
run: 'npm run release' run: 'npm run release'
env: env:

View File

@ -10,16 +10,16 @@ jobs:
test: test:
runs-on: 'ubuntu-latest' runs-on: 'ubuntu-latest'
steps: steps:
- uses: 'actions/checkout@v3.0.0' - uses: 'actions/checkout@v3.5.3'
- name: 'Use Node.js' - name: 'Setup Node.js'
uses: 'actions/setup-node@v3.1.0' uses: 'actions/setup-node@v3.6.0'
with: with:
node-version: 'lts/*' node-version: 'lts/*'
cache: 'npm' cache: 'npm'
- name: 'Install' - name: 'Install dependencies'
run: 'npm install' run: 'npm clean-install'
- name: 'Build' - name: 'Build'
run: 'npm run build' run: 'npm run build'

View File

@ -1,4 +0,0 @@
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"
npm run lint:commit -- --edit

View File

@ -1,5 +0,0 @@
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"
npm run lint:staged
npm run build

11
.markdownlint-cli2.jsonc Normal file
View File

@ -0,0 +1,11 @@
{
"config": {
"extends": "markdownlint/style/prettier",
"relative-links": true,
"default": true,
"MD033": false
},
"globs": ["**/*.{md,mdx}"],
"ignores": ["**/node_modules"],
"customRules": ["markdownlint-rule-relative-links"]
}

View File

@ -1,6 +0,0 @@
{
"default": true,
"MD013": false,
"MD033": false,
"MD041": false
}

1
.npmrc
View File

@ -1 +1,2 @@
save-exact=true save-exact=true
provenance=true

1
.swcrc
View File

@ -1,4 +1,5 @@
{ {
"sourceMaps": true,
"jsc": { "jsc": {
"parser": { "parser": {
"syntax": "typescript", "syntax": "typescript",

9
.taprc
View File

@ -1,9 +0,0 @@
ts: false
jsx: false
flow: false
check-coverage: false
coverage: false
timeout: 120000
files:
- 'build/**/*.test.js'

View File

@ -3,7 +3,6 @@
"editorconfig.editorconfig", "editorconfig.editorconfig",
"esbenp.prettier-vscode", "esbenp.prettier-vscode",
"dbaeumer.vscode-eslint", "dbaeumer.vscode-eslint",
"davidanson.vscode-markdownlint", "davidanson.vscode-markdownlint"
"ms-azuretools.vscode-docker"
] ]
} }

View File

@ -6,5 +6,9 @@
"editor.formatOnSave": true, "editor.formatOnSave": true,
"editor.codeActionsOnSave": { "editor.codeActionsOnSave": {
"source.fixAll": true "source.fixAll": true
} },
"eslint.options": {
"ignorePath": ".gitignore"
},
"prettier.ignorePath": ".gitignore"
} }

View File

@ -60,7 +60,7 @@ representative at an online or offline event.
Instances of abusive, harassing, or otherwise unacceptable behavior may be Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported to the community leaders responsible for enforcement at reported to the community leaders responsible for enforcement at
contact@divlo.fr. <contact@theoludwig.fr>.
All complaints will be reviewed and investigated promptly and fairly. All complaints will be reviewed and investigated promptly and fairly.
All community leaders are obligated to respect the privacy and security of the All community leaders are obligated to respect the privacy and security of the

View File

@ -2,6 +2,14 @@
Thanks a lot for your interest in contributing to **html-w3c-validator**! 🎉 Thanks a lot for your interest in contributing to **html-w3c-validator**! 🎉
## Code of Conduct
**html-w3c-validator** adopted the [Contributor Covenant](https://www.contributor-covenant.org/) as its Code of Conduct, and we expect project participants to adhere to it. Please read [the full text](./CODE_OF_CONDUCT.md) so that you can understand what actions will and will not be tolerated.
## Open Development
All work on **html-w3c-validator** happens directly on this repository. Both core team members and external contributors send pull requests which go through the same review process.
## Types of contributions ## Types of contributions
- Reporting a bug. - Reporting a bug.
@ -11,7 +19,7 @@ Thanks a lot for your interest in contributing to **html-w3c-validator**! 🎉
## Pull Requests ## Pull Requests
- **Please first discuss** the change you wish to make via [issue](https://github.com/Divlo/html-w3c-validator/issues) before making a change. It might avoid a waste of your time. - **Please first discuss** the change you wish to make via [issue](https://github.com/theoludwig/html-w3c-validator/issues) before making a change. It might avoid a waste of your time.
- Ensure your code respect linting. - Ensure your code respect linting.
@ -21,26 +29,4 @@ If you're adding new features to **html-w3c-validator**, please include tests.
## Commits ## Commits
The commit message guidelines respect [@commitlint/config-conventional](https://github.com/conventional-changelog/commitlint/tree/master/%40commitlint/config-conventional) and [Semantic Versioning](https://semver.org/) for releases. The commit message guidelines adheres to [Conventional Commits](https://www.conventionalcommits.org/) and [Semantic Versioning](https://semver.org/) for releases.
### Types
Types define which kind of changes you made to the project.
| Types | Description |
| -------- | ------------------------------------------------------------------------------------------------------------ |
| feat | A new feature. |
| fix | A bug fix. |
| docs | Documentation only changes. |
| style | Changes that do not affect the meaning of the code (white-space, formatting, missing semi-colons, etc). |
| refactor | A code change that neither fixes a bug nor adds a feature. |
| perf | A code change that improves performance. |
| test | Adding missing tests or correcting existing tests. |
| build | Changes that affect the build system or external dependencies (example scopes: gulp, broccoli, npm). |
| ci | Changes to our CI configuration files and scripts (example scopes: Travis, Circle, BrowserStack, SauceLabs). |
| chore | Other changes that don't modify src or test files. |
| revert | Reverts a previous commit. |
### Scopes
Scopes define what part of the code changed.

View File

@ -1,6 +1,6 @@
MIT License MIT License
Copyright (c) Divlo Copyright (c) Théo LUDWIG
Permission is hereby granted, free of charge, to any person obtaining a copy Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal of this software and associated documentation files (the "Software"), to deal

View File

@ -11,9 +11,9 @@
<a href="./LICENSE"><img src="https://img.shields.io/badge/licence-MIT-blue.svg" alt="Licence MIT"/></a> <a href="./LICENSE"><img src="https://img.shields.io/badge/licence-MIT-blue.svg" alt="Licence MIT"/></a>
<a href="./CODE_OF_CONDUCT.md"><img src="https://img.shields.io/badge/Contributor%20Covenant-v2.0%20adopted-ff69b4.svg" alt="Contributor Covenant" /></a> <a href="./CODE_OF_CONDUCT.md"><img src="https://img.shields.io/badge/Contributor%20Covenant-v2.0%20adopted-ff69b4.svg" alt="Contributor Covenant" /></a>
<br /> <br />
<a href="https://github.com/Divlo/html-w3c-validator/actions/workflows/build.yml"><img src="https://github.com/Divlo/html-w3c-validator/actions/workflows/build.yml/badge.svg?branch=develop" /></a> <a href="https://github.com/theoludwig/html-w3c-validator/actions/workflows/build.yml"><img src="https://github.com/theoludwig/html-w3c-validator/actions/workflows/build.yml/badge.svg?branch=develop" /></a>
<a href="https://github.com/Divlo/html-w3c-validator/actions/workflows/lint.yml"><img src="https://github.com/Divlo/html-w3c-validator/actions/workflows/lint.yml/badge.svg?branch=develop" /></a> <a href="https://github.com/theoludwig/html-w3c-validator/actions/workflows/lint.yml"><img src="https://github.com/theoludwig/html-w3c-validator/actions/workflows/lint.yml/badge.svg?branch=develop" /></a>
<a href="https://github.com/Divlo/html-w3c-validator/actions/workflows/test.yml"><img src="https://github.com/Divlo/html-w3c-validator/actions/workflows/test.yml/badge.svg?branch=develop" /></a> <a href="https://github.com/theoludwig/html-w3c-validator/actions/workflows/test.yml"><img src="https://github.com/theoludwig/html-w3c-validator/actions/workflows/test.yml/badge.svg?branch=develop" /></a>
<br /> <br />
<a href="https://conventionalcommits.org"><img src="https://img.shields.io/badge/Conventional%20Commits-1.0.0-yellow.svg" alt="Conventional Commits" /></a> <a href="https://conventionalcommits.org"><img src="https://img.shields.io/badge/Conventional%20Commits-1.0.0-yellow.svg" alt="Conventional Commits" /></a>
<a href="https://github.com/semantic-release/semantic-release"><img src="https://img.shields.io/badge/%20%20%F0%9F%93%A6%F0%9F%9A%80-semantic--release-e10079.svg" alt="semantic-release" /></a> <a href="https://github.com/semantic-release/semantic-release"><img src="https://img.shields.io/badge/%20%20%F0%9F%93%A6%F0%9F%9A%80-semantic--release-e10079.svg" alt="semantic-release" /></a>
@ -59,7 +59,7 @@ npm install --save-dev html-w3c-validator start-server-and-test
"start": "serve \"./build\"", "start": "serve \"./build\"",
// Command to validate your HTML pages // Command to validate your HTML pages
"test:html-w3c-validator": "start-server-and-test \"start\" \"http://localhost:3000\" \"html-w3c-validator\"" "test:html-w3c-validator": "start-server-and-test \"start\" \"http://127.0.0.1:3000\" \"html-w3c-validator\""
} }
} }
``` ```
@ -68,10 +68,13 @@ npm install --save-dev html-w3c-validator start-server-and-test
```jsonc ```jsonc
{ {
"urls": ["http://localhost:3000/", "http://localhost:3000/about"], "urls": ["http://127.0.0.1:3000/", "http://127.0.0.1:3000/about"],
// You can also specify HTML files instead of URLs // You can also specify HTML files instead of URLs
"files": ["./index.html", "./about.html"] "files": ["./index.html", "./about.html"],
// Specify the severities of the validator (default: ["warning", "error"])
"severities": ["info", "warning", "error"]
} }
``` ```
@ -84,8 +87,8 @@ npm run test:html-w3c-validator
Example of output (in case of success): Example of output (in case of success):
```txt ```txt
✔ Validating http://localhost:3000/ ✔ Validating http://127.0.0.1:3000/
✔ Validating http://localhost:3000/about ✔ Validating http://127.0.0.1:3000/about
Success: HTML validation (W3C) passed! 🎉 Success: HTML validation (W3C) passed! 🎉
``` ```

View File

@ -1,4 +1,4 @@
{ {
"urls": ["http://localhost:3000/", "http://localhost:3000/about"], "urls": ["http://127.0.0.1:3000/", "http://127.0.0.1:3000/about"],
"files": ["./build/index.html", "./build/about.html"] "files": ["./build/index.html", "./build/about.html"]
} }

2241
example/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -2,12 +2,12 @@
"name": "example", "name": "example",
"private": true, "private": true,
"scripts": { "scripts": {
"start": "serve \"./build\"", "start": "serve ./build",
"test:html-w3c-validator": "start-server-and-test \"start\" \"http://localhost:3000\" \"html-w3c-validator\"" "test:html-w3c-validator": "start-server-and-test \"start\" \"http://127.0.0.1:3000\" \"html-w3c-validator\""
}, },
"devDependencies": { "devDependencies": {
"serve": "13.0.2", "serve": "14.2.0",
"start-server-and-test": "1.14.0", "start-server-and-test": "2.0.0",
"html-w3c-validator": "file:.." "html-w3c-validator": "file:.."
} }
} }

23182
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -4,16 +4,16 @@
"public": true, "public": true,
"description": "CLI for validating multiple html pages using validator.w3.org.", "description": "CLI for validating multiple html pages using validator.w3.org.",
"type": "module", "type": "module",
"author": "Divlo <contact@divlo.fr>", "author": "Théo LUDWIG <contact@theoludwig.fr>",
"license": "MIT", "license": "MIT",
"repository": { "repository": {
"type": "git", "type": "git",
"url": "https://github.com/Divlo/html-w3c-validator.git" "url": "https://github.com/theoludwig/html-w3c-validator.git"
}, },
"bugs": { "bugs": {
"url": "https://github.com/Divlo/html-w3c-validator/issues" "url": "https://github.com/theoludwig/html-w3c-validator/issues"
}, },
"homepage": "https://github.com/Divlo/html-w3c-validator#readme", "homepage": "https://github.com/theoludwig/html-w3c-validator#readme",
"keywords": [ "keywords": [
"cli", "cli",
"html-validator", "html-validator",
@ -22,64 +22,69 @@
], ],
"main": "build/index.js", "main": "build/index.js",
"bin": "build/index.js", "bin": "build/index.js",
"files": [
"build",
"!**/*.test.js",
"!**/*.map"
],
"publishConfig": {
"access": "public",
"provenance": true
},
"engines": { "engines": {
"node": ">=16.0.0", "node": ">=16.0.0",
"npm": ">=8.0.0" "npm": ">=9.0.0"
}, },
"files": [
"build"
],
"scripts": { "scripts": {
"build": "rimraf ./build && swc ./src --out-dir ./build && tsc", "build": "rimraf ./build && swc ./src --out-dir ./build",
"build:typescript": "tsc",
"start": "node --enable-source-maps build/index.js",
"lint:commit": "commitlint", "lint:commit": "commitlint",
"lint:editorconfig": "editorconfig-checker", "lint:editorconfig": "editorconfig-checker",
"lint:markdown": "markdownlint \"**/*.md\" --dot --ignore-path \".gitignore\"", "lint:markdown": "markdownlint-cli2",
"lint:typescript": "eslint \"**/*.{js,jsx,ts,tsx}\" --ignore-path \".gitignore\"", "lint:eslint": "eslint . --ignore-path .gitignore",
"lint:prettier": "prettier \".\" --check --ignore-path \".gitignore\"", "lint:prettier": "prettier . --check --ignore-path .gitignore",
"lint:staged": "lint-staged", "lint:staged": "lint-staged",
"test": "tap", "test": "node --enable-source-maps --test build/",
"release": "semantic-release", "release": "semantic-release"
"postinstall": "husky install",
"prepublishOnly": "pinst --disable",
"postpublish": "pinst --enable"
}, },
"dependencies": { "dependencies": {
"chalk": "5.0.1", "chalk": "5.3.0",
"clipanion": "3.1.0", "clipanion": "3.2.1",
"html-validator": "6.0.0", "html-validator": "6.0.1",
"ora": "6.1.0", "log-symbols": "5.1.0",
"read-pkg": "7.1.0", "ora": "6.3.1",
"table": "6.8.0" "read-pkg": "8.0.0",
"table": "6.8.1"
}, },
"devDependencies": { "devDependencies": {
"@commitlint/cli": "16.2.3", "@commitlint/cli": "17.6.6",
"@commitlint/config-conventional": "16.2.1", "@commitlint/config-conventional": "17.6.6",
"@swc/cli": "0.1.57", "@swc/cli": "0.1.62",
"@swc/core": "1.2.163", "@swc/core": "1.3.67",
"@types/html-validator": "5.0.2", "@tsconfig/strictest": "2.0.1",
"@types/html-validator": "5.0.3",
"@types/mock-fs": "4.13.1", "@types/mock-fs": "4.13.1",
"@types/node": "17.0.23", "@types/node": "20.3.3",
"@types/tap": "15.0.6", "@types/tap": "15.0.8",
"@typescript-eslint/eslint-plugin": "5.18.0", "@typescript-eslint/eslint-plugin": "5.60.1",
"editorconfig-checker": "4.0.2", "editorconfig-checker": "5.1.1",
"eslint": "8.12.0", "eslint": "8.44.0",
"eslint-config-conventions": "2.0.0", "eslint-config-conventions": "9.0.0",
"eslint-config-prettier": "8.5.0", "eslint-config-prettier": "8.8.0",
"eslint-plugin-import": "2.26.0", "eslint-plugin-import": "2.27.5",
"eslint-plugin-prettier": "4.0.0", "eslint-plugin-prettier": "4.2.1",
"eslint-plugin-promise": "6.0.0", "eslint-plugin-promise": "6.1.1",
"eslint-plugin-unicorn": "42.0.0", "eslint-plugin-unicorn": "47.0.0",
"execa": "6.1.0", "execa": "7.1.1",
"husky": "7.0.4", "lint-staged": "13.2.3",
"lint-staged": "12.3.7", "markdownlint-cli2": "0.8.1",
"markdownlint-cli": "0.31.1", "markdownlint-rule-relative-links": "2.1.0",
"mock-fs": "5.1.2", "mock-fs": "5.2.0",
"pinst": "3.0.0", "prettier": "2.8.8",
"prettier": "2.6.2", "rimraf": "5.0.1",
"rimraf": "3.0.2", "semantic-release": "21.0.6",
"semantic-release": "19.0.2", "serve": "14.2.0",
"serve": "13.0.2", "typescript": "5.0.4"
"tap": "16.0.1",
"typescript": "4.6.3"
} }
} }

View File

@ -4,10 +4,12 @@ import fs from 'node:fs'
import { Command } from 'clipanion' import { Command } from 'clipanion'
import chalk from 'chalk' import chalk from 'chalk'
import ora from 'ora' import ora from 'ora'
import validateHTML, { import logSymbols from 'log-symbols'
import type {
ValidationMessageLocationObject, ValidationMessageLocationObject,
ParsedJsonAsValidationResults ParsedJsonAsValidationResults
} from 'html-validator' } from 'html-validator'
import validateHTML from 'html-validator'
import { table } from 'table' import { table } from 'table'
import { isExistingPath } from './utils/isExistingPath.js' import { isExistingPath } from './utils/isExistingPath.js'
@ -15,9 +17,14 @@ import { isExistingPath } from './utils/isExistingPath.js'
const CURRENT_DIRECTORY = process.cwd() const CURRENT_DIRECTORY = process.cwd()
const CONFIG_FILE_NAME = '.html-w3c-validatorrc.json' const CONFIG_FILE_NAME = '.html-w3c-validatorrc.json'
const severities = ['error', 'warning', 'info'] as const
export type Severity = (typeof severities)[number]
interface Config { interface Config {
urls?: string[] urls?: string[]
files?: string[] files?: string[]
severities?: Severity[]
} }
interface Error { interface Error {
@ -25,21 +32,35 @@ interface Error {
messagesTable: string[][] messagesTable: string[][]
} }
interface Result {
data: string
isSuccess: boolean
}
const printResults = (results: Result[]): void => {
for (const result of results) {
if (result.isSuccess) {
console.log(logSymbols.success, result.data)
} else {
console.log(logSymbols.error, result.data)
}
}
}
export class HTMLValidatorCommand extends Command { export class HTMLValidatorCommand extends Command {
static usage = { static override usage = {
description: description:
'CLI for validating multiple html pages using <https://validator.w3.org/>.' 'CLI for validating multiple html pages using <https://validator.w3.org/>.'
} }
async execute(): Promise<number> { public async execute(): Promise<number> {
const configPath = path.join(CURRENT_DIRECTORY, CONFIG_FILE_NAME) const configPath = path.join(CURRENT_DIRECTORY, CONFIG_FILE_NAME)
try { try {
if (!(await isExistingPath(configPath))) { if (!(await isExistingPath(configPath))) {
throw new Error( throw new Error(
`No config file found at ${configPath}. Please create ${CONFIG_FILE_NAME}.` `No config file found at ${configPath}. Please create "${CONFIG_FILE_NAME}".`
) )
} }
const configData = await fs.promises.readFile(configPath, { const configData = await fs.promises.readFile(configPath, {
encoding: 'utf-8' encoding: 'utf-8'
}) })
@ -50,12 +71,19 @@ export class HTMLValidatorCommand extends Command {
} catch { } catch {
isValidConfig = false isValidConfig = false
} }
isValidConfig =
isValidConfig &&
(Array.isArray(config.urls) || Array.isArray(config.urls))
if (!isValidConfig) { if (!isValidConfig) {
throw new Error( throw new Error(
`Invalid config file at ${configPath}. Please check the syntax.` `Invalid config file at "${configPath}". Please check the JSON syntax.`
)
}
if (config.urls != null && !Array.isArray(config.urls)) {
throw new Error(
`Invalid config file at "${configPath}". Please include an array of URLs.`
)
}
if (config.files != null && !Array.isArray(config.files)) {
throw new Error(
`Invalid config file at "${configPath}". Please include an array of files.`
) )
} }
const urls = const urls =
@ -71,10 +99,37 @@ export class HTMLValidatorCommand extends Command {
return { type: 'file', data: file } return { type: 'file', data: file }
}) })
const dataToValidate = [...urls, ...files] const dataToValidate = [...urls, ...files]
if (dataToValidate.length === 0) {
throw new Error(
`Invalid config file at "${configPath}". Please add URLs or files.`
)
}
const configSeverities: Severity[] = config.severities ?? [
'warning',
'error'
]
for (const severity of configSeverities) {
if (!severities.includes(severity)) {
throw new Error(
`Invalid config file at "${configPath}". Please add valid severities (${severities.join(
', '
)}).`
)
}
}
if (configSeverities.length === 0) {
throw new Error(
`Invalid config file at "${configPath}". Please add valid severities (${severities.join(
', '
)}).`
)
}
const errors: Error[] = [] const errors: Error[] = []
let isValid = true let isValid = true
for (const { data, type } of dataToValidate) { const loader = ora(`Validating HTML (W3C)...`).start()
const loader = ora(`Validating ${data}`).start() const results: Result[] = []
await Promise.all(
dataToValidate.map(async ({ data, type }) => {
try { try {
const options = { const options = {
format: 'json' as 'json' | undefined format: 'json' as 'json' | undefined
@ -90,7 +145,7 @@ export class HTMLValidatorCommand extends Command {
const htmlPath = path.resolve(CURRENT_DIRECTORY, data) const htmlPath = path.resolve(CURRENT_DIRECTORY, data)
if (!(await isExistingPath(htmlPath))) { if (!(await isExistingPath(htmlPath))) {
throw new Error( throw new Error(
`No file found at ${htmlPath}. Please check the path.` `No file found at "${htmlPath}". Please check the path.`
) )
} }
const html = await fs.promises.readFile(htmlPath, { const html = await fs.promises.readFile(htmlPath, {
@ -103,18 +158,27 @@ export class HTMLValidatorCommand extends Command {
} else { } else {
throw new Error('Invalid type') throw new Error('Invalid type')
} }
const isValidHTML = result.messages.length === 0 const hasErrors = result.messages.some((message) => {
if (isValidHTML) { return (
loader.succeed() configSeverities.includes(message.type as Severity) ||
configSeverities.includes(message.subType as Severity)
)
})
if (!hasErrors) {
results.push({ data, isSuccess: true })
} else { } else {
loader.fail() results.push({ data, isSuccess: false })
const messagesTable: string[][] = [] const messagesTable: string[][] = []
for (const message of result.messages) { for (const message of result.messages) {
const row: string[] = [] const row: string[] = []
if (message.type === 'error') { if (message.type === 'info') {
row.push(chalk.red(message.type)) if (message.subType === 'warning') {
row.push(chalk.yellow(message.subType))
} else { } else {
row.push(chalk.yellow(message.type)) row.push(chalk.blue(message.type))
}
} else {
row.push(chalk.red(message.type))
} }
row.push(message.message) row.push(message.message)
const violation = message as ValidationMessageLocationObject const violation = message as ValidationMessageLocationObject
@ -129,16 +193,17 @@ export class HTMLValidatorCommand extends Command {
isValid = false isValid = false
} }
} catch (error) { } catch (error) {
loader.fail()
isValid = false isValid = false
if (error instanceof Error) { if (error instanceof Error) {
const messagesTable: string[][] = [[error.message]] const messagesTable: string[][] = [[error.message]]
errors.push({ data, messagesTable }) errors.push({ data, messagesTable })
} }
} }
} })
)
if (!isValid) { if (!isValid) {
loader.fail()
printResults(results)
for (const error of errors) { for (const error of errors) {
console.error(`\n${error.data}`) console.error(`\n${error.data}`)
console.error(table(error.messagesTable)) console.error(table(error.messagesTable))
@ -147,10 +212,10 @@ export class HTMLValidatorCommand extends Command {
console.error() console.error()
throw new Error('HTML validation (W3C) failed!') throw new Error('HTML validation (W3C) failed!')
} }
console.log() loader.succeed(
console.log(
`${chalk.bold.green('Success:')} HTML validation (W3C) passed! 🎉` `${chalk.bold.green('Success:')} HTML validation (W3C) passed! 🎉`
) )
printResults(results)
return 0 return 0
} catch (error) { } catch (error) {
if (error instanceof Error) { if (error instanceof Error) {

View File

@ -1,21 +1,23 @@
import tap from 'tap' import test from 'node:test'
import assert from 'node:assert/strict'
import { execa } from 'execa' import { execa } from 'execa'
import { cli } from '../cli.js' import { cli } from '../cli.js'
import { HTMLValidatorCommand } from '../HTMLValidatorCommand.js' import { HTMLValidatorCommand } from '../HTMLValidatorCommand.js'
await tap.test('html-w3c-validator', async (t) => { await test('html-w3c-validator', async (t) => {
await t.test('should be instance of the command', async (t) => { await t.test('should be instance of the command', async () => {
const command = cli.process([]) const command = cli.process([])
t.equal(command instanceof HTMLValidatorCommand, true) assert(command instanceof HTMLValidatorCommand)
}) })
await t.test('succeeds and validate the html correctly', async (t) => { await t.test('succeeds and validate the html correctly', async () => {
const exampleURL = new URL('../../example', import.meta.url) const exampleURL = new URL('../../example', import.meta.url)
process.chdir(exampleURL.pathname) process.chdir(exampleURL.pathname)
await execa('rimraf', ['node_modules']) await execa('rimraf', ['node_modules'])
await execa('npm', ['install']) await execa('npm', ['install'])
const { exitCode } = await execa('npm', ['run', 'test:html-w3c-validator']) const { exitCode } = await execa('npm', ['run', 'test:html-w3c-validator'])
t.equal(exitCode, 0) assert.strictEqual(exitCode, 0)
}) })
}) })

View File

@ -1,9 +1,11 @@
import test from 'node:test'
import assert from 'node:assert/strict'
import fsMock from 'mock-fs' import fsMock from 'mock-fs'
import tap from 'tap'
import { isExistingPath } from '../isExistingPath.js' import { isExistingPath } from '../isExistingPath.js'
await tap.test('utils/isExistingPath', async (t) => { await test('utils/isExistingPath', async (t) => {
t.afterEach(() => { t.afterEach(() => {
fsMock.restore() fsMock.restore()
}) })
@ -12,13 +14,13 @@ await tap.test('utils/isExistingPath', async (t) => {
fsMock({ fsMock({
'/file.txt': '' '/file.txt': ''
}) })
t.equal(await isExistingPath('/file.txt'), true) assert.strictEqual(await isExistingPath('/file.txt'), true)
}) })
await t.test("should return false if the file doesn't exists", async () => { await t.test("should return false if the file doesn't exists", async () => {
fsMock({ fsMock({
'/file.txt': '' '/file.txt': ''
}) })
t.equal(await isExistingPath('/randomfile.txt'), false) assert.strictEqual(await isExistingPath('/randomfile.txt'), false)
}) })
}) })

View File

@ -1,4 +1,5 @@
{ {
"extends": "@tsconfig/strictest/tsconfig.json",
"compilerOptions": { "compilerOptions": {
"target": "ESNext", "target": "ESNext",
"module": "ESNext", "module": "ESNext",
@ -7,8 +8,6 @@
"outDir": "./build", "outDir": "./build",
"rootDir": "./src", "rootDir": "./src",
"noEmit": true, "noEmit": true,
"strict": true, "exactOptionalPropertyTypes": false
"skipLibCheck": true,
"esModuleInterop": true
} }
} }