First commit

This commit is contained in:
Simon 2021-04-08 00:27:21 +02:00
commit d57ad3573d
17 changed files with 597 additions and 0 deletions

12
.editorconfig Normal file
View File

@ -0,0 +1,12 @@
root = true
[*]
charset = utf-8
end_of_line = lf
trim_trailing_whitespace = true
insert_final_newline = true
indent_style = space
indent_size = 2
[js]
indent_size = 2

1
.gitignore vendored Normal file
View File

@ -0,0 +1 @@
node_modules

21
LICENSE Normal file
View File

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2021 Simon Constans
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

15
README.md Normal file
View File

@ -0,0 +1,15 @@
# Website Checker
weck permet de vérifier des bonnes pratiques pour un site internet.
## Feature
- [x] Scanner un dossier local
- [ ] Scanner un site distant
- [~] Permettre facilement l'extension de règle
## Rules
Les règles que weck vérifie :
- [x] Que la balises <title> comporte entre 30 et 70 caractères
- [x] Vérifier qu'il n'existe qu'un seul et même titre sur un site internet

3
bin/weck.js Normal file
View File

@ -0,0 +1,3 @@
#!/usr/bin/env node
import * as cli from '../src/index.js'

303
package-lock.json generated Normal file
View File

@ -0,0 +1,303 @@
{
"name": "weck",
"version": "1.0.0",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "weck",
"version": "1.0.0",
"license": "MIT",
"dependencies": {
"fs-extra": "^9.1.0",
"node-html-parser": "^3.1.3"
},
"bin": {
"weck": "bin/weck.js"
}
},
"node_modules/at-least-node": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/at-least-node/-/at-least-node-1.0.0.tgz",
"integrity": "sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==",
"engines": {
"node": ">= 4.0.0"
}
},
"node_modules/boolbase": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz",
"integrity": "sha1-aN/1++YMUes3cl6p4+0xDcwed24="
},
"node_modules/css-select": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/css-select/-/css-select-3.1.2.tgz",
"integrity": "sha512-qmss1EihSuBNWNNhHjxzxSfJoFBM/lERB/Q4EnsJQQC62R2evJDW481091oAdOr9uh46/0n4nrg0It5cAnj1RA==",
"dependencies": {
"boolbase": "^1.0.0",
"css-what": "^4.0.0",
"domhandler": "^4.0.0",
"domutils": "^2.4.3",
"nth-check": "^2.0.0"
},
"funding": {
"url": "https://github.com/sponsors/fb55"
}
},
"node_modules/css-what": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/css-what/-/css-what-4.0.0.tgz",
"integrity": "sha512-teijzG7kwYfNVsUh2H/YN62xW3KK9YhXEgSlbxMlcyjPNvdKJqFx5lrwlJgoFP1ZHlB89iGDlo/JyshKeRhv5A==",
"engines": {
"node": ">= 6"
},
"funding": {
"url": "https://github.com/sponsors/fb55"
}
},
"node_modules/dom-serializer": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-1.2.0.tgz",
"integrity": "sha512-n6kZFH/KlCrqs/1GHMOd5i2fd/beQHuehKdWvNNffbGHTr/almdhuVvTVFb3V7fglz+nC50fFusu3lY33h12pA==",
"dependencies": {
"domelementtype": "^2.0.1",
"domhandler": "^4.0.0",
"entities": "^2.0.0"
},
"funding": {
"url": "https://github.com/cheeriojs/dom-serializer?sponsor=1"
}
},
"node_modules/domelementtype": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.2.0.tgz",
"integrity": "sha512-DtBMo82pv1dFtUmHyr48beiuq792Sxohr+8Hm9zoxklYPfa6n0Z3Byjj2IV7bmr2IyqClnqEQhfgHJJ5QF0R5A==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/fb55"
}
]
},
"node_modules/domhandler": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/domhandler/-/domhandler-4.1.0.tgz",
"integrity": "sha512-/6/kmsGlMY4Tup/nGVutdrK9yQi4YjWVcVeoQmixpzjOUK1U7pQkvAPHBJeUxOgxF0J8f8lwCJSlCfD0V4CMGQ==",
"dependencies": {
"domelementtype": "^2.2.0"
},
"engines": {
"node": ">= 4"
},
"funding": {
"url": "https://github.com/fb55/domhandler?sponsor=1"
}
},
"node_modules/domutils": {
"version": "2.5.1",
"resolved": "https://registry.npmjs.org/domutils/-/domutils-2.5.1.tgz",
"integrity": "sha512-hO1XwHMGAthA/1KL7c83oip/6UWo3FlUNIuWiWKltoiQ5oCOiqths8KknvY2jpOohUoUgnwa/+Rm7UpwpSbY/Q==",
"dependencies": {
"dom-serializer": "^1.0.1",
"domelementtype": "^2.2.0",
"domhandler": "^4.1.0"
},
"funding": {
"url": "https://github.com/fb55/domutils?sponsor=1"
}
},
"node_modules/entities": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/entities/-/entities-2.2.0.tgz",
"integrity": "sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==",
"funding": {
"url": "https://github.com/fb55/entities?sponsor=1"
}
},
"node_modules/fs-extra": {
"version": "9.1.0",
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz",
"integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==",
"dependencies": {
"at-least-node": "^1.0.0",
"graceful-fs": "^4.2.0",
"jsonfile": "^6.0.1",
"universalify": "^2.0.0"
},
"engines": {
"node": ">=10"
}
},
"node_modules/graceful-fs": {
"version": "4.2.6",
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.6.tgz",
"integrity": "sha512-nTnJ528pbqxYanhpDYsi4Rd8MAeaBA67+RZ10CM1m3bTAVFEDcd5AuA4a6W5YkGZ1iNXHzZz8T6TBKLeBuNriQ=="
},
"node_modules/he": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz",
"integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==",
"bin": {
"he": "bin/he"
}
},
"node_modules/jsonfile": {
"version": "6.1.0",
"resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz",
"integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==",
"dependencies": {
"universalify": "^2.0.0"
},
"optionalDependencies": {
"graceful-fs": "^4.1.6"
}
},
"node_modules/node-html-parser": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/node-html-parser/-/node-html-parser-3.1.3.tgz",
"integrity": "sha512-pCE2I5UY5iOBnWdJQkbYZSk+fyq2zepw0nsELpHQjVFyCzOeZhkMhnvKqGceKgzWsWx7EG4KtMqsy9Eklf5Thw==",
"dependencies": {
"css-select": "^3.1.2",
"he": "1.2.0"
}
},
"node_modules/nth-check": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.0.0.tgz",
"integrity": "sha512-i4sc/Kj8htBrAiH1viZ0TgU8Y5XqCaV/FziYK6TBczxmeKm3AEFWqqF3195yKudrarqy7Zu80Ra5dobFjn9X/Q==",
"dependencies": {
"boolbase": "^1.0.0"
},
"funding": {
"url": "https://github.com/fb55/nth-check?sponsor=1"
}
},
"node_modules/universalify": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz",
"integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==",
"engines": {
"node": ">= 10.0.0"
}
}
},
"dependencies": {
"at-least-node": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/at-least-node/-/at-least-node-1.0.0.tgz",
"integrity": "sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg=="
},
"boolbase": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz",
"integrity": "sha1-aN/1++YMUes3cl6p4+0xDcwed24="
},
"css-select": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/css-select/-/css-select-3.1.2.tgz",
"integrity": "sha512-qmss1EihSuBNWNNhHjxzxSfJoFBM/lERB/Q4EnsJQQC62R2evJDW481091oAdOr9uh46/0n4nrg0It5cAnj1RA==",
"requires": {
"boolbase": "^1.0.0",
"css-what": "^4.0.0",
"domhandler": "^4.0.0",
"domutils": "^2.4.3",
"nth-check": "^2.0.0"
}
},
"css-what": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/css-what/-/css-what-4.0.0.tgz",
"integrity": "sha512-teijzG7kwYfNVsUh2H/YN62xW3KK9YhXEgSlbxMlcyjPNvdKJqFx5lrwlJgoFP1ZHlB89iGDlo/JyshKeRhv5A=="
},
"dom-serializer": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-1.2.0.tgz",
"integrity": "sha512-n6kZFH/KlCrqs/1GHMOd5i2fd/beQHuehKdWvNNffbGHTr/almdhuVvTVFb3V7fglz+nC50fFusu3lY33h12pA==",
"requires": {
"domelementtype": "^2.0.1",
"domhandler": "^4.0.0",
"entities": "^2.0.0"
}
},
"domelementtype": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.2.0.tgz",
"integrity": "sha512-DtBMo82pv1dFtUmHyr48beiuq792Sxohr+8Hm9zoxklYPfa6n0Z3Byjj2IV7bmr2IyqClnqEQhfgHJJ5QF0R5A=="
},
"domhandler": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/domhandler/-/domhandler-4.1.0.tgz",
"integrity": "sha512-/6/kmsGlMY4Tup/nGVutdrK9yQi4YjWVcVeoQmixpzjOUK1U7pQkvAPHBJeUxOgxF0J8f8lwCJSlCfD0V4CMGQ==",
"requires": {
"domelementtype": "^2.2.0"
}
},
"domutils": {
"version": "2.5.1",
"resolved": "https://registry.npmjs.org/domutils/-/domutils-2.5.1.tgz",
"integrity": "sha512-hO1XwHMGAthA/1KL7c83oip/6UWo3FlUNIuWiWKltoiQ5oCOiqths8KknvY2jpOohUoUgnwa/+Rm7UpwpSbY/Q==",
"requires": {
"dom-serializer": "^1.0.1",
"domelementtype": "^2.2.0",
"domhandler": "^4.1.0"
}
},
"entities": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/entities/-/entities-2.2.0.tgz",
"integrity": "sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A=="
},
"fs-extra": {
"version": "9.1.0",
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz",
"integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==",
"requires": {
"at-least-node": "^1.0.0",
"graceful-fs": "^4.2.0",
"jsonfile": "^6.0.1",
"universalify": "^2.0.0"
}
},
"graceful-fs": {
"version": "4.2.6",
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.6.tgz",
"integrity": "sha512-nTnJ528pbqxYanhpDYsi4Rd8MAeaBA67+RZ10CM1m3bTAVFEDcd5AuA4a6W5YkGZ1iNXHzZz8T6TBKLeBuNriQ=="
},
"he": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz",
"integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw=="
},
"jsonfile": {
"version": "6.1.0",
"resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz",
"integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==",
"requires": {
"graceful-fs": "^4.1.6",
"universalify": "^2.0.0"
}
},
"node-html-parser": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/node-html-parser/-/node-html-parser-3.1.3.tgz",
"integrity": "sha512-pCE2I5UY5iOBnWdJQkbYZSk+fyq2zepw0nsELpHQjVFyCzOeZhkMhnvKqGceKgzWsWx7EG4KtMqsy9Eklf5Thw==",
"requires": {
"css-select": "^3.1.2",
"he": "1.2.0"
}
},
"nth-check": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.0.0.tgz",
"integrity": "sha512-i4sc/Kj8htBrAiH1viZ0TgU8Y5XqCaV/FziYK6TBczxmeKm3AEFWqqF3195yKudrarqy7Zu80Ra5dobFjn9X/Q==",
"requires": {
"boolbase": "^1.0.0"
}
},
"universalify": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz",
"integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ=="
}
}
}

23
package.json Normal file
View File

@ -0,0 +1,23 @@
{
"name": "weck",
"version": "1.0.0",
"description": "Website Checker",
"main": "src/index.js",
"bin": {
"weck": "bin/weck.js"
},
"type": "module",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"repository": {
"type": "git",
"url": "https://git.weko.io/weko/WebsiteChecker.git"
},
"author": "Simon CONSTANS",
"license": "MIT",
"dependencies": {
"fs-extra": "^9.1.0",
"node-html-parser": "^3.1.3"
}
}

1
requirements.txt Normal file
View File

@ -0,0 +1 @@
bs4==0.0.1

28
src/helpers.js Normal file
View File

@ -0,0 +1,28 @@
import { fileURLToPath } from 'url';
import fs from 'fs';
import path, { dirname } from 'path';
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
export const getFilenames = async (directory, files = []) => {
const dirs = fs.opendirSync(directory);
for await (const dirent of dirs) {
const dirOrFilePath = path.join(directory, dirent.name);
if (dirent.isDirectory()) {
files = await getFilenames(dirOrFilePath, files)
} else if (dirent.isFile()) {
files = [...files, ...[dirOrFilePath]]
}
}
return files
}
export const extensionFilter = (filenames, extension) => {
return filenames.filter(filename => filename.match(new RegExp(`.*\.(${extension})`, 'ig')));
}
export const getContent = (filename) => {
return fs.readFileSync(filename).toString()
}

45
src/index.js Executable file
View File

@ -0,0 +1,45 @@
import getRules from './rules/index.js'
import { getFilenames } from './helpers.js'
const checkDirectory = async (directory) => {
const filenames = await getFilenames(directory);
const rules = getRules(filenames, { directory: directory });
let issues = []
for (const rule of rules) {
const issue = rule.check()
if (issue) {
issues = [...issues, ...[issue]]
}
}
createReport(issues)
}
const createReport = (issues) => {
if (!!process.argv.find(p => p == '--md')) {
for (const rule of issues) {
if (rule.issueCount > 0) {
console.log()
console.log(`### ${rule.issueTitle}`)
console.log()
console.log(`${rule.issueDescription}`)
console.log()
for (const issue of rule.issues) {
console.log(`- element \`${issue.title}\` on \`${issue.url}\` file.`)
}
console.log()
let referenceLinks = ''
for (const reference of rule.issueReferences) {
referenceLinks += `[${reference}](${reference}), `
}
console.log(`References : ${referenceLinks}`)
}
}
} else {
console.log(JSON.stringify(issues))
}
}
const directory = process.argv[2]
checkDirectory(directory)

5
src/parser.js Normal file
View File

@ -0,0 +1,5 @@
import nhp from 'node-html-parser';
export const getTitle = (html) => {
return nhp.parse(html).querySelector('title').toString();
}

17
src/rules/Rule.js Normal file
View File

@ -0,0 +1,17 @@
export default class Rule {
constructor (filenames, options) {
this.options = options,
this.filenames = filenames
this.issueCount = 0
this.issues = []
this.initialize()
}
report() {
return {
ruleName: this.constructor.name,
issueCount: this.issueCount,
issues: this.issues
}
}
}

38
src/rules/TitleLength.js Normal file
View File

@ -0,0 +1,38 @@
import Rule from './Rule.js'
import { extensionFilter, getContent } from '../helpers.js'
import { getTitle } from '../parser.js'
export default class TitleLength extends Rule {
initialize() {
this.minLength = 30
this.maxLength = 70
this.filenames = extensionFilter(this.filenames, 'html');
}
report() {
return {
...super.report(),
...{
issueDescription: `The <title> should contain between ${this.minLength} and ${this.maxLength} characters.`,
issueReferences: ['https://moz.com/learn/seo/title-tag', 'https://www.authorityhacker.com/seo-title-tags/', 'https://www.codeur.com/blog/seo-optimiser-title/']
}
}
}
check() {
for (const filename of this.filenames) {
const content = getContent(filename);
const title = getTitle(content)
if (this.test(title)) {
this.issueCount++
const issue = {
url: filename.replace(this.options.directory, ''),
title: title
}
this.issues = [...this.issues, ...[issue]]
}
}
return this.report()
}
}

View File

@ -0,0 +1,16 @@
import TitleLength from './TitleLength.js'
export default class TitleLengthTooLong extends TitleLength {
report() {
if (this.issueCount == 0) return
return {
...super.report(),
...{ issueTitle: `Your title is to long on ${this.issueCount} page${this.issueCount == 1 ? '' : 's'}` }
}
}
test(title) {
return title.length > this.maxLength
}
}

View File

@ -0,0 +1,16 @@
import TitleLength from './TitleLength.js'
export default class TitleLengthTooShort extends TitleLength {
report() {
if (this.issueCount == 0) return
return {
...super.report(),
...{ issueTitle: `Your title is to short on ${this.issueCount} page${this.issueCount == 1 ? '' : 's'}` }
}
}
test(title) {
return title.length < this.minLength
}
}

42
src/rules/TitleUnique.js Normal file
View File

@ -0,0 +1,42 @@
import Rule from './Rule.js'
import { extensionFilter, getContent } from '../helpers.js'
import { getTitle } from '../parser.js'
export default class TitleUnique extends Rule {
initialize() {
this.filenames = extensionFilter(this.filenames, 'html');
}
report() {
return {
...super.report(),
...{
issueTitle: `The <title> should be unique for every page, ${this.issueCount} pages are concerned.`,
issueDescription: `You should change title on:`,
issueReferences: ['https://moz.com/learn/seo/title-tag']
}
}
}
check() {
let titles = {}
for (const filename of this.filenames) {
const content = getContent(filename);
const title = getTitle(content)
titles[title] = titles[title] ? [...titles[title], ...[filename]] : [filename]
}
for (const title in titles) {
if (titles[title].length > 1) {
this.issueCount += titles[title].length
const issue = {
title: title,
url: titles[title]
}
this.issues = [...this.issues, ...[issue]]
}
}
return this.report()
}
}

11
src/rules/index.js Normal file
View File

@ -0,0 +1,11 @@
import TitleUnique from './TitleUnique.js'
import TitleLengthTooLong from './TitleLengthTooLong.js'
import TitleLengthTooShort from './TitleLengthTooShort.js'
export default function getRules(filesnames, options) {
return [
new TitleUnique(filesnames, options),
new TitleLengthTooLong(filesnames, options),
new TitleLengthTooShort(filesnames, options),
]
}