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:
		
							
								
								
									
										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"] | ||||
| } | ||||
|   | ||||
							
								
								
									
										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", | ||||
|     "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') | ||||
|     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