diff --git a/packages/landing-friend-core/src/analyzer.ts b/packages/landing-friend-core/src/analyzer.ts index 5d1861b..2014e47 100644 --- a/packages/landing-friend-core/src/analyzer.ts +++ b/packages/landing-friend-core/src/analyzer.ts @@ -10,8 +10,10 @@ import { message, forbiddenCharacters as _forbiddenCharacters, TagsPatterns, + AdvancedTagsPatterns, checkFiles, prepareHTMLWithTables, + AllTagsName, } from "./index.js"; export const websiteAnalyzer = (config: ConfigFile) => { @@ -28,23 +30,7 @@ export const websiteAnalyzer = (config: ConfigFile) => { const allFiles = getFilesToAnalyze(input); let tagsPatterns: TagsPatterns = {}; - let countKeywords: boolean = true; - let countWordsInLast: boolean = true; - if ( - Object.entries(analyzer).some( - ([tag, value]) => tag === "keywords" && value.countKeywords !== true - ) - ) { - countKeywords = false; - } - if ( - Object.entries(analyzer).some( - ([tag, value]) => - tag === "lastSentence" && value.countWordsInLast !== true - ) - ) { - countWordsInLast = false; - } + let advancedTagsPatterns: AdvancedTagsPatterns = {}; allFiles.forEach((file) => { if (!matchedSetting(file, excludedPage, input)) { @@ -53,38 +39,41 @@ export const websiteAnalyzer = (config: ConfigFile) => { tags, advancedTags, tagsPatterns, - countKeywords, - countWordsInLast, + advancedTagsPatterns, + countKeywords: tags.keywords.count, + countWordsInLast: tags.lastSentence.count, }); } }); - const htmlWithTablesAndCharts = prepareHTMLWithTables({ tagsPatterns, }); if (analyzer) { - const cleanedTagsPatterns: TagsPatterns = {}; + let cleanedTagsPatterns: TagsPatterns = {}; Object.entries(tagsPatterns).forEach(([file, tagData]) => { - cleanedTagsPatterns[file] = {}; - Object.entries(tagData).forEach(([tag, value]) => { - !(tag === "keywords" && !value.countKeywords) && - !(tag === "lastSentence" && !value.countWordsInLast) - ? (cleanedTagsPatterns[file][tag] = { - requirement: - value.requirement && - value.requirement.replace(/<\/?strong>/gs, ""), - count: value.count, - content: value.content, - forbiddenCharacters: value.forbiddenCharacters, - keywordsIncluded: - tag !== "keywords" ? value.keywordsIncluded : undefined, - countKeywords: - tag === "keywords" ? value.countKeywords : undefined, - countWordsInLast: - tag === "lastSentence" ? value.countWordsInLast : undefined, - }) - : undefined; + cleanedTagsPatterns[file] = { ...cleanedTagsPatterns[file] }; + Object.entries(tagData).forEach(([_tag, value]) => { + const tag = _tag as AllTagsName; + if ( + !(tag === "keywords" && !value.countKeywords) && + !(tag === "lastSentence" && !value.countWordsInLast) + ) { + cleanedTagsPatterns[file][tag] = { + requirement: + value.requirement && + value.requirement.replace(/<\/?strong>/gs, ""), + quantity: value.quantity, + content: value.content, + forbiddenCharacters: value.forbiddenCharacters, + keywordsIncluded: + tag !== "keywords" ? value.keywordsIncluded : undefined, + countKeywords: + tag === "keywords" ? value.countKeywords : undefined, + countWordsInLast: + tag === "lastSentence" ? value.countWordsInLast : undefined, + }; + } }); }); try { diff --git a/packages/landing-friend-core/src/config.ts b/packages/landing-friend-core/src/config.ts index 0ceb4e0..b4c4ae2 100644 --- a/packages/landing-friend-core/src/config.ts +++ b/packages/landing-friend-core/src/config.ts @@ -1,61 +1,8 @@ import fs from "fs"; import { message } from "./console.js"; -import { LanguageCode } from "iso-639-1"; import ts from "typescript"; +import { ConfigFile } from "./index.js"; -type SitemapSettings = { - locale: { - defaultLocale: LanguageCode; - localeWildcard: string; - }; - trailingSlash: boolean; - sortBy: "priority" | "alphabetically-asc" | "alphabetically-desc"; -}; - -export type AdvancedTagsProps = { - og?: boolean; -}; - -export type TagsProps = - | { - h1: { - minLength: number; - maxLength: number; - }; - } - | { - title: { - minLength: number; - maxLength: number; - }; - } - | { - description: { - minLength: number; - maxLength: number; - }; - } - | { - lastSentence: { - countWordsInLast: boolean; - }; - } - | { - keywords: { - countKeywords: boolean; - }; - }; - -export type ConfigFile = { - domain: string; - input: string; - output: string; - robots: boolean; - excludedPage: string[]; - sitemap?: SitemapSettings; - analyzer?: TagsProps; - advancedAnalyzer?: AdvancedTagsProps; -}; export const GLOBAL_CONFIG_FILE: ConfigFile = { domain: "https://www.example.com", input: "./out", @@ -81,18 +28,18 @@ export const EXTENDED_ANALYZER_GLOBAL_CONFIG_FILE: Pick< analyzer: { h1: { minLength: 10, - maxLength: 70, + maxLength: 100, }, title: { minLength: 10, - maxLength: 70, + maxLength: 60, }, description: { - maxLength: 200, - minLength: 50, + maxLength: 160, + minLength: 120, }, - lastSentence: { countWordsInLast: true }, - keywords: { countKeywords: true }, + lastSentence: { count: true }, + keywords: { count: true }, }, }; export const EXTENDED_ADVANCED_ANALYZER_GLOBAL_CONFIG_FILE: Pick< @@ -101,6 +48,7 @@ export const EXTENDED_ADVANCED_ANALYZER_GLOBAL_CONFIG_FILE: Pick< > = { advancedAnalyzer: { og: true, + twitter: true, }, }; diff --git a/packages/landing-friend-core/src/functions/analyzer/advancedAnalyzer.ts b/packages/landing-friend-core/src/functions/analyzer/advancedAnalyzer.ts new file mode 100644 index 0000000..5106a42 --- /dev/null +++ b/packages/landing-friend-core/src/functions/analyzer/advancedAnalyzer.ts @@ -0,0 +1,102 @@ +import { + AdvancedTagsProps, + staticTags, + unicode, + forbiddenCharacters as _forbiddenCharacters, + AdvancedTagsName, + AdvancedTagsPatterns, +} from "../../index.js"; + +interface MatchedArrayProps { + content: string; +} + +const matchedTags = (advancedTags: AdvancedTagsName, fileContent: string) => { + let regex: RegExp | undefined; + const matchedArray: { [tagName: string]: MatchedArrayProps }[] = []; + + if (advancedTags === "og") { + regex = new RegExp(` { + const captureGroups = regex!.exec(match); + if (captureGroups) { + const tagName = captureGroups[1]; + const content = captureGroups[2]; + const tagObject = { [tagName]: { content } }; + matchedArray.push(tagObject); + } + }); + } + } + + return matchedArray; +}; + +export const checkFileToAdvanceAnalyzer = ({ + file, + fileContent, + advancedTags, + advancedTagsPatterns, +}: { + file: string; + fileContent: string; + advancedTags?: AdvancedTagsProps; + advancedTagsPatterns: AdvancedTagsPatterns; +}) => { + if (!advancedTags) { + return; + } + Object.entries(advancedTags).forEach(([_tag, value]) => { + if (!value) return; + const tag = _tag as AdvancedTagsName; + const matches = matchedTags(tag, fileContent); + + if (matches) { + matches.forEach((match) => { + Object.entries(match).map(([metaName, value]) => { + let content: string; + staticTags.forEach((staticTag) => { + const _content = value.content; + const staticTagRegex = new RegExp( + `<${staticTag}.*?>|<\/${staticTag}>`, + "g" + ); + Object.entries(unicode).forEach(([unicode, replacement]) => { + const unicodeRegex = new RegExp(`${unicode}`, "g"); + content = _content.replace(unicodeRegex, replacement); + }); + + content = _content.replace(staticTagRegex, ""); + }); + + const forbiddenCharacters = _forbiddenCharacters.filter((char) => + content.includes(char) + ); + return (advancedTagsPatterns[file] = { + ...advancedTagsPatterns[file], + [tag]: { + tagAmount: matches.length, + metaName, + content: value.content, + forbiddenCharacters, + }, + }); + }); + }); + } else { + return (advancedTagsPatterns[file] = { + ...advancedTagsPatterns[file], + [tag]: { + tagAmount: NaN, + }, + }); + } + }); +}; diff --git a/packages/landing-friend-core/src/functions/basicAnalyzer.ts b/packages/landing-friend-core/src/functions/analyzer/basicAnalyzer.ts similarity index 52% rename from packages/landing-friend-core/src/functions/basicAnalyzer.ts rename to packages/landing-friend-core/src/functions/analyzer/basicAnalyzer.ts index 4a198d7..927bc20 100644 --- a/packages/landing-friend-core/src/functions/basicAnalyzer.ts +++ b/packages/landing-friend-core/src/functions/analyzer/basicAnalyzer.ts @@ -1,77 +1,59 @@ import { - AdvancedTagsProps, TagsProps, staticTags, unicode, forbiddenCharacters as _forbiddenCharacters, - readFile, -} from "../index.js"; + TagsPatterns, + AllTagsName, + TagsWithReason, +} from "../../index.js"; -export type TagsPatterns = Record>; +const checkContent = (tagsName: AllTagsName, fileContent: string) => { + let regex: RegExp | undefined; + const matchedArray: string[] = []; + if (tagsName === "description") { + regex = new RegExp(`(.*?)<\/div>`, "g"); + } else { + regex = new RegExp(`<${tagsName}.*?>(.*?)`, "g"); + } -export type TagsWithReason = { - minLength?: number; - maxLength?: number; - countKeywords?: boolean; - countWordsInLast?: boolean; - content?: string; - requirement?: string; - count: number; - multipleTags?: boolean; - keywordsIncluded?: string[]; - forbiddenCharacters?: string[]; -}; + if (regex) { + const matches = fileContent.match(regex); + if (matches) { + matches.forEach((match) => { + const captureGroups = regex!.exec(match); + if (captureGroups) { + let content = captureGroups[1]; -const matchTag = (tag: string, value: TagsWithReason) => { - if (tag === "description") { - return new RegExp(`(.*?)<\/div>`, "gs"); - } else { - return new RegExp(`<${tag}.*?>(.*?)`, "gs"); + staticTags.forEach((staticTag) => { + const staticTagRegex = new RegExp( + `<${staticTag}.*?>|<\/${staticTag}>`, + "g" + ); + Object.entries(unicode).forEach(([unicode, replacement]) => { + const unicodeRegex = new RegExp(`${unicode}`, "g"); + content = content.replace(unicodeRegex, replacement); + }); + + content = content.replace(staticTagRegex, ""); + }); + + matchedArray.push(content); + } + }); + } } -}; -export const checkFiles = ({ - file, - tags, - advancedTags, - tagsPatterns, - countKeywords, - countWordsInLast, -}: { - file: string; - tags: TagsProps; - advancedTags?: AdvancedTagsProps; - tagsPatterns: TagsPatterns; - countKeywords: boolean; - countWordsInLast: boolean; -}) => { - const fileContent = readFile(file); - checkFileByPatterns({ - file, - fileContent, - tags, - advancedTags, - tagsPatterns, - countKeywords, - countWordsInLast, - }); + return matchedArray; }; -const checkFileByPatterns = ({ +export const checkFileToBasicAnalyzer = ({ file, - fileContent: _fileContent, + fileContent, tags, tagsPatterns, countKeywords, @@ -80,19 +62,17 @@ const checkFileByPatterns = ({ file: string; fileContent: string; tags: TagsProps; - advancedTags?: AdvancedTagsProps; tagsPatterns: TagsPatterns; countKeywords: boolean; countWordsInLast: boolean; }) => { - Object.entries(tags).forEach(([tag, value]) => { - let fileContent = _fileContent.replace(/\n\s*/g, " "); - - const regex = matchTag(tag, value); + Object.entries(tags).forEach(([_tag, _value]) => { + const tag = _tag as AllTagsName; + let value = _value as TagsWithReason; let keywordsArray: string[] | undefined = []; if (countKeywords) { const keywordsMatch = fileContent.match( - new RegExp(` 0) { @@ -105,7 +85,7 @@ const checkFileByPatterns = ({ } } - let matches = fileContent.match(regex); + let matches = checkContent(tag, fileContent); if (tag === "lastSentence" && matches) { matches = [matches[matches.length - 1]]; } @@ -117,7 +97,7 @@ const checkFileByPatterns = ({ [tag]: { ...value, requirement: `Tag length should be between ${value.minLength} and ${value.maxLength}`, - count: matches.length, + quantity: matches.length, multipleTags: true, countKeywords, countWordsInLast, @@ -125,49 +105,7 @@ const checkFileByPatterns = ({ }); } else { matches.forEach((match) => { - let text: string; - - if (tag === "description") { - text = match.replace( - /|$`, "gs"), ""); - } else { - text = match.replace( - new RegExp(`^<${tag}.*?>|$`, "gs"), - "" - ); - } - - staticTags.forEach((staticTag) => { - const staticTagRegex = new RegExp( - `<${staticTag}.*?>|<\/${staticTag}>`, - "gs" - ); - Object.entries(unicode).forEach(([unicode, replacement]) => { - const unicodeRegex = new RegExp(`${unicode}`, "gs"); - text = text.replace(unicodeRegex, replacement); - }); - - text = text.replace(staticTagRegex, ""); - }); + let text = match; const forbiddenCharacters = _forbiddenCharacters.filter((char) => text.includes(char) @@ -183,7 +121,7 @@ const checkFileByPatterns = ({ tag === "keywords" ? undefined : `Tag length should be between ${value.minLength} and ${value.maxLength}`, - count: text.length, + quantity: text.length, content: text, multipleTags: undefined, keywordsIncluded: @@ -211,7 +149,7 @@ const checkFileByPatterns = ({ tag === "keywords" ? `At least one keyword required` : `Tag length should be between ${value.minLength} and ${value.maxLength}`, - count: NaN, + quantity: NaN, countKeywords, countWordsInLast, }, diff --git a/packages/landing-friend-core/src/functions/analyzer/checkFile.ts b/packages/landing-friend-core/src/functions/analyzer/checkFile.ts new file mode 100644 index 0000000..fdfad55 --- /dev/null +++ b/packages/landing-friend-core/src/functions/analyzer/checkFile.ts @@ -0,0 +1,46 @@ +import { + AdvancedTagsPatterns, + AdvancedTagsProps, + TagsPatterns, + TagsProps, + checkFileToAdvanceAnalyzer, + checkFileToBasicAnalyzer, + readFile, +} from "../../index.js"; + +export const checkFiles = ({ + file, + tags, + advancedTags, + tagsPatterns, + advancedTagsPatterns, + countKeywords, + countWordsInLast, +}: { + file: string; + tags: TagsProps; + advancedTags?: AdvancedTagsProps; + tagsPatterns: TagsPatterns; + advancedTagsPatterns: AdvancedTagsPatterns; + countKeywords: boolean; + countWordsInLast: boolean; +}) => { + const _fileContent = readFile(file); + const fileContent = _fileContent.replace(/\n\s*/g, " "); + const basicAnalyze = checkFileToBasicAnalyzer({ + file, + fileContent, + tags, + tagsPatterns, + countKeywords, + countWordsInLast, + }); + const advancedAnalyze = checkFileToAdvanceAnalyzer({ + file, + fileContent, + advancedTags, + advancedTagsPatterns, + }); + console.log("basic", basicAnalyze); + console.log("advanced", advancedAnalyze); +}; diff --git a/packages/landing-friend-core/src/functions/analyzer/index.ts b/packages/landing-friend-core/src/functions/analyzer/index.ts new file mode 100644 index 0000000..b2fa0d1 --- /dev/null +++ b/packages/landing-friend-core/src/functions/analyzer/index.ts @@ -0,0 +1,5 @@ +export * from "../config/index.js"; +export * from "./basicAnalyzer.js"; +export * from "./prepareHTML.js"; +export * from "./advancedAnalyzer.js"; +export * from "./checkFile.js"; diff --git a/packages/landing-friend-core/src/functions/prepareHTML.ts b/packages/landing-friend-core/src/functions/analyzer/prepareHTML.ts similarity index 94% rename from packages/landing-friend-core/src/functions/prepareHTML.ts rename to packages/landing-friend-core/src/functions/analyzer/prepareHTML.ts index 14c5dda..5a3f09b 100644 --- a/packages/landing-friend-core/src/functions/prepareHTML.ts +++ b/packages/landing-friend-core/src/functions/analyzer/prepareHTML.ts @@ -1,4 +1,4 @@ -import { TagsPatterns } from "./index.js"; +import { TagsPatterns } from "../../index.js"; type KeywordsTagsProps = Record; @@ -57,16 +57,16 @@ export const generateTableRows = (tagsPatterns: TagsPatterns) => { ${ !(tag === "keywords" && !value.countKeywords) ? !(tag === "lastSentence" && !value.countWordsInLast) - ? !isNaN(value.count) + ? !isNaN(value.quantity) ? value.maxLength && value.minLength ? value.multipleTags - ? `Warning! Number of ${tag} on the page: ${value.count}Check the code` + ? `Warning! Number of ${tag} on the page: ${value.quantity}Check the code` : `Length of ${tag}: ${value.count}${ + }">${value.quantity}${ value.forbiddenCharacters && value.forbiddenCharacters.length > 0 ? ` (Contains forbidden words: ${value.forbiddenCharacters})` @@ -78,8 +78,8 @@ export const generateTableRows = (tagsPatterns: TagsPatterns) => { : ` | Does not contain keywords` : `` }${value.requirement}` diff --git a/packages/landing-friend-core/src/functions/config.ts b/packages/landing-friend-core/src/functions/config/index.ts similarity index 98% rename from packages/landing-friend-core/src/functions/config.ts rename to packages/landing-friend-core/src/functions/config/index.ts index cb00323..45962b2 100644 --- a/packages/landing-friend-core/src/functions/config.ts +++ b/packages/landing-friend-core/src/functions/config/index.ts @@ -1,4 +1,4 @@ -import { message } from "../index.js"; +import { message } from "../../index.js"; import fs from "fs"; import path from "path"; diff --git a/packages/landing-friend-core/src/functions/index.ts b/packages/landing-friend-core/src/functions/index.ts index ff90915..e4fa104 100644 --- a/packages/landing-friend-core/src/functions/index.ts +++ b/packages/landing-friend-core/src/functions/index.ts @@ -1,3 +1,2 @@ -export * from "./config.js"; -export * from "./basicAnalyzer.js"; -export * from "./prepareHTML.js"; +export * from "./analyzer/index.js"; +export * from "./config/index.js"; diff --git a/packages/landing-friend-core/src/index.ts b/packages/landing-friend-core/src/index.ts index d632ad0..d88aae2 100644 --- a/packages/landing-friend-core/src/index.ts +++ b/packages/landing-friend-core/src/index.ts @@ -4,3 +4,4 @@ export * from "./sitemap.js"; export * from "./analyzer.js"; export * from "./data/index.js"; export * from "./functions/index.js"; +export * from "./types/index.js"; diff --git a/packages/landing-friend-core/src/types/analyzerTypes.ts b/packages/landing-friend-core/src/types/analyzerTypes.ts new file mode 100644 index 0000000..c420753 --- /dev/null +++ b/packages/landing-friend-core/src/types/analyzerTypes.ts @@ -0,0 +1,45 @@ +export type AdvancedTagsName = "og" | "twitter"; + +export type TagsName = "h1" | "title" | "description"; + +export type AdditionalTagsName = "lastSentence" | "keywords"; + +export type AllTagsName = TagsName | AdditionalTagsName; + +export type TagsProps = Record< + TagsName, + { + minLength: number; + maxLength: number; + } +> & + Record; + +export type AdvancedTagsProps = Record; + +export type TagsWithReason = { + quantity: number; + minLength?: number; + maxLength?: number; + countKeywords?: boolean; + countWordsInLast?: boolean; + content?: string; + requirement?: string; + multipleTags?: boolean; + keywordsIncluded?: string[]; + forbiddenCharacters?: string[]; +}; + +export type AdvancedTagsWithReason = { + tagAmount: number; + content?: string; + metaName?: string; + forbiddenCharacters?: string[]; +}; + +export type TagsPatterns = Record>; + +export type AdvancedTagsPatterns = Record< + string, + Record +>; diff --git a/packages/landing-friend-core/src/types/configTypes.ts b/packages/landing-friend-core/src/types/configTypes.ts new file mode 100644 index 0000000..8a940eb --- /dev/null +++ b/packages/landing-friend-core/src/types/configTypes.ts @@ -0,0 +1,22 @@ +import { LanguageCode } from "iso-639-1"; +import { AdvancedTagsProps, TagsProps } from "./index.js"; + +export type SitemapSettings = { + locale: { + defaultLocale: LanguageCode; + localeWildcard: string; + }; + trailingSlash: boolean; + sortBy: "priority" | "alphabetically-asc" | "alphabetically-desc"; +}; + +export type ConfigFile = { + domain: string; + input: string; + output: string; + robots: boolean; + excludedPage: string[]; + sitemap?: SitemapSettings; + analyzer?: TagsProps; + advancedAnalyzer?: AdvancedTagsProps; +}; diff --git a/packages/landing-friend-core/src/types/index.ts b/packages/landing-friend-core/src/types/index.ts new file mode 100644 index 0000000..80bcb93 --- /dev/null +++ b/packages/landing-friend-core/src/types/index.ts @@ -0,0 +1,2 @@ +export * from "./analyzerTypes.js"; +export * from "./configTypes.js"; diff --git a/packages/landing-friend/src/functions/configInit.ts b/packages/landing-friend/src/functions/configInit.ts index 68ec4fe..00ee439 100644 --- a/packages/landing-friend/src/functions/configInit.ts +++ b/packages/landing-friend/src/functions/configInit.ts @@ -162,22 +162,21 @@ export const configInit = async () => { }, { type: "confirm", - name: "analyzer.keywords.countKeywords", + name: "analyzer.keywords.count", message: "Do you want to count keywords?", default: EXTENDED_ANALYZER_GLOBAL_CONFIG_FILE.analyzer instanceof Object && "keywords" in EXTENDED_ANALYZER_GLOBAL_CONFIG_FILE.analyzer && - EXTENDED_ANALYZER_GLOBAL_CONFIG_FILE.analyzer.keywords.countKeywords, + EXTENDED_ANALYZER_GLOBAL_CONFIG_FILE.analyzer.keywords.count, }, { type: "confirm", - name: "analyzer.lastSentence.countWordsInLast", + name: "analyzer.lastSentence.count", message: "Do you want to check for matching keywords in last the div?", default: EXTENDED_ANALYZER_GLOBAL_CONFIG_FILE.analyzer instanceof Object && "lastSentence" in EXTENDED_ANALYZER_GLOBAL_CONFIG_FILE.analyzer && - EXTENDED_ANALYZER_GLOBAL_CONFIG_FILE.analyzer.lastSentence - .countWordsInLast, + EXTENDED_ANALYZER_GLOBAL_CONFIG_FILE.analyzer.lastSentence.count, }, ]); const { extendAdvanceAnalyzer } = await inquirer.prompt<{ @@ -194,10 +193,18 @@ export const configInit = async () => { { type: "confirm", name: "advancedAnalyzer.og", - message: "Do you want to check all og protocols ?", + message: "Do you want to check all og protocols?", default: EXTENDED_ADVANCED_ANALYZER_GLOBAL_CONFIG_FILE.advancedAnalyzer?.og, }, + { + type: "confirm", + name: "advancedAnalyzer.twitter", + message: "Do you want to check all twitter metadata?", + default: + EXTENDED_ADVANCED_ANALYZER_GLOBAL_CONFIG_FILE.advancedAnalyzer + ?.twitter, + }, ]); } }