recker 1.0.78 → 1.0.79
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/browser/index.iife.min.js +24 -24
- package/dist/browser/index.min.js +33 -33
- package/dist/browser/index.umd.min.js +24 -24
- package/dist/browser/seo/analyzer.d.ts +1 -0
- package/dist/browser/seo/analyzer.js +46 -4
- package/dist/browser/seo/formatter.js +20 -2
- package/dist/browser/seo/types.d.ts +7 -0
- package/dist/cli/commands/seo-runner.d.ts +2 -0
- package/dist/cli/commands/seo-runner.js +4 -0
- package/dist/cli/handlers/seo.js +6 -0
- package/dist/cli/tui/components/rich-response.d.ts +2 -0
- package/dist/cli/tui/components/rich-response.js +1 -1
- package/dist/seo/analyzer.d.ts +1 -0
- package/dist/seo/analyzer.js +46 -4
- package/dist/seo/formatter.js +20 -2
- package/dist/seo/types.d.ts +7 -0
- package/dist/version.js +1 -1
- package/package.json +1 -1
|
@@ -71,7 +71,7 @@ export class SeoAnalyzer {
|
|
|
71
71
|
const ruleResults = this.rulesEngine.evaluate(context);
|
|
72
72
|
const checks = this.convertToCheckResults(ruleResults);
|
|
73
73
|
const { score, grade } = this.calculateScore(ruleResults);
|
|
74
|
-
const summary = this.buildSummary(
|
|
74
|
+
const summary = this.buildSummary(checks, {
|
|
75
75
|
content,
|
|
76
76
|
imageAnalysis,
|
|
77
77
|
linkAnalysis,
|
|
@@ -951,6 +951,27 @@ export class SeoAnalyzer {
|
|
|
951
951
|
}
|
|
952
952
|
return undefined;
|
|
953
953
|
}
|
|
954
|
+
classifyInfoCheck(result) {
|
|
955
|
+
if (result.status !== 'info')
|
|
956
|
+
return undefined;
|
|
957
|
+
const message = (result.message || '').toLowerCase();
|
|
958
|
+
const evidenceIssue = (result.evidence?.issue || '').toLowerCase();
|
|
959
|
+
const recommendation = (result.recommendation || '').toLowerCase();
|
|
960
|
+
const context = `${message} ${evidenceIssue} ${recommendation}`;
|
|
961
|
+
if (/not\s+applicable/.test(context) ||
|
|
962
|
+
/not\s+available/.test(context) ||
|
|
963
|
+
/data\s+unavailable/.test(context) ||
|
|
964
|
+
/context\s+unavailable/.test(context) ||
|
|
965
|
+
/unable\s+to\s+check/.test(context) ||
|
|
966
|
+
/cannot\s+verify/.test(context) ||
|
|
967
|
+
/cannot\s+be\s+determined/.test(context) ||
|
|
968
|
+
/\bunavailable\b/.test(context) ||
|
|
969
|
+
/not\s+set/.test(context) ||
|
|
970
|
+
/not\s+present/.test(context)) {
|
|
971
|
+
return 'not_applicable';
|
|
972
|
+
}
|
|
973
|
+
return 'suggestion';
|
|
974
|
+
}
|
|
954
975
|
convertToCheckResults(results) {
|
|
955
976
|
return results.map((r) => ({
|
|
956
977
|
id: r.id,
|
|
@@ -959,26 +980,36 @@ export class SeoAnalyzer {
|
|
|
959
980
|
severity: r.severity,
|
|
960
981
|
status: r.status,
|
|
961
982
|
message: r.message,
|
|
983
|
+
infoType: this.classifyInfoCheck(r),
|
|
962
984
|
value: r.value,
|
|
963
985
|
recommendation: r.recommendation,
|
|
964
986
|
evidence: r.evidence,
|
|
965
987
|
}));
|
|
966
988
|
}
|
|
967
|
-
buildSummary(
|
|
989
|
+
buildSummary(checks, data) {
|
|
968
990
|
const pageType = data.pageType;
|
|
969
991
|
const timings = data.timings;
|
|
970
992
|
const passed = checks.filter((c) => c.status === 'pass').length;
|
|
971
993
|
const warnings = checks.filter((c) => c.status === 'warn').length;
|
|
972
994
|
const errors = checks.filter((c) => c.status === 'fail').length;
|
|
973
995
|
const infos = checks.filter((c) => c.status === 'info').length;
|
|
996
|
+
const notApplicable = checks.filter((c) => c.status === 'info' && c.infoType === 'not_applicable').length;
|
|
997
|
+
const suggestions = checks.filter((c) => c.status === 'info' && c.infoType !== 'not_applicable').length;
|
|
974
998
|
const totalChecks = checks.length;
|
|
975
999
|
const scoringChecks = totalChecks - infos;
|
|
976
1000
|
const passRate = scoringChecks > 0 ? Math.round((passed / scoringChecks) * 100) : 100;
|
|
977
1001
|
const issuesByCategory = {};
|
|
978
|
-
for (const result of
|
|
1002
|
+
for (const result of checks) {
|
|
979
1003
|
const cat = result.category;
|
|
980
1004
|
if (!issuesByCategory[cat]) {
|
|
981
|
-
issuesByCategory[cat] = {
|
|
1005
|
+
issuesByCategory[cat] = {
|
|
1006
|
+
passed: 0,
|
|
1007
|
+
warnings: 0,
|
|
1008
|
+
errors: 0,
|
|
1009
|
+
infos: 0,
|
|
1010
|
+
notApplicable: 0,
|
|
1011
|
+
suggestions: 0,
|
|
1012
|
+
};
|
|
982
1013
|
}
|
|
983
1014
|
if (result.status === 'pass')
|
|
984
1015
|
issuesByCategory[cat].passed++;
|
|
@@ -986,6 +1017,15 @@ export class SeoAnalyzer {
|
|
|
986
1017
|
issuesByCategory[cat].warnings++;
|
|
987
1018
|
else if (result.status === 'fail')
|
|
988
1019
|
issuesByCategory[cat].errors++;
|
|
1020
|
+
else if (result.status === 'info') {
|
|
1021
|
+
issuesByCategory[cat].infos++;
|
|
1022
|
+
if (result.infoType === 'not_applicable') {
|
|
1023
|
+
issuesByCategory[cat].notApplicable++;
|
|
1024
|
+
}
|
|
1025
|
+
else {
|
|
1026
|
+
issuesByCategory[cat].suggestions++;
|
|
1027
|
+
}
|
|
1028
|
+
}
|
|
989
1029
|
}
|
|
990
1030
|
const topIssues = checks
|
|
991
1031
|
.filter((c) => c.status === 'fail' || c.status === 'warn')
|
|
@@ -1052,6 +1092,8 @@ export class SeoAnalyzer {
|
|
|
1052
1092
|
warnings,
|
|
1053
1093
|
errors,
|
|
1054
1094
|
infos,
|
|
1095
|
+
notApplicable,
|
|
1096
|
+
suggestions,
|
|
1055
1097
|
passRate,
|
|
1056
1098
|
issuesByCategory,
|
|
1057
1099
|
pageType: pageType,
|
|
@@ -29,7 +29,16 @@ export function statusIcon(status) {
|
|
|
29
29
|
export function formatCheck(check, showEvidence = false) {
|
|
30
30
|
const lines = [];
|
|
31
31
|
const icon = statusIcon(check.status);
|
|
32
|
-
|
|
32
|
+
let infoLabel = '';
|
|
33
|
+
if (check.status === 'info') {
|
|
34
|
+
if (check.infoType === 'not_applicable') {
|
|
35
|
+
infoLabel = colors.magenta(' [N/A]');
|
|
36
|
+
}
|
|
37
|
+
else if (check.infoType === 'suggestion') {
|
|
38
|
+
infoLabel = colors.cyan(' [SUGGESTION]');
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
lines.push(` ${icon} ${check.message}${infoLabel}`);
|
|
33
42
|
if (check.value !== undefined) {
|
|
34
43
|
lines.push(` ${colors.gray('Value:')} ${check.value}`);
|
|
35
44
|
}
|
|
@@ -239,7 +248,10 @@ export function formatSeoReport(report, url, options = {}) {
|
|
|
239
248
|
lines.push(colors.gray(' ──────────────────────────────────────────────────'));
|
|
240
249
|
lines.push('');
|
|
241
250
|
lines.push(` ${colors.bold('Summary')}`);
|
|
242
|
-
|
|
251
|
+
const infoSummary = (s.notApplicable !== undefined || s.suggestions !== undefined)
|
|
252
|
+
? ` (${s.notApplicable ?? 0} not applicable, ${s.suggestions ?? 0} suggestions)`
|
|
253
|
+
: '';
|
|
254
|
+
lines.push(` ${colors.green('✔ Passed:')} ${s.passed} ${colors.yellow('⚠ Warnings:')} ${s.warnings} ${colors.red('✖ Errors:')} ${s.errors} ${colors.blue('ℹ Info:')} ${s.infos}${infoSummary}`);
|
|
243
255
|
if (s.vitals) {
|
|
244
256
|
lines.push(` ${colors.gray('Words:')} ${s.vitals.wordCount} | ${colors.gray('Images:')} ${s.vitals.imageCount} | ${colors.gray('Links:')} ${s.vitals.linkCount}`);
|
|
245
257
|
}
|
|
@@ -381,12 +393,16 @@ function groupChecksByCategory(checks) {
|
|
|
381
393
|
const warnings = catChecks.filter(c => c.status === 'warn').length;
|
|
382
394
|
const errors = catChecks.filter(c => c.status === 'fail').length;
|
|
383
395
|
const infos = catChecks.filter(c => c.status === 'info').length;
|
|
396
|
+
const notApplicable = catChecks.filter(c => c.status === 'info' && c.infoType === 'not_applicable').length;
|
|
397
|
+
const suggestions = catChecks.filter(c => c.status === 'info' && c.infoType !== 'not_applicable').length;
|
|
384
398
|
const total = catChecks.length - infos;
|
|
385
399
|
result[cat] = {
|
|
386
400
|
passed,
|
|
387
401
|
warnings,
|
|
388
402
|
errors,
|
|
389
403
|
infos,
|
|
404
|
+
notApplicable,
|
|
405
|
+
suggestions,
|
|
390
406
|
passRate: total > 0 ? Math.round((passed / total) * 100) : 100,
|
|
391
407
|
checks: catChecks,
|
|
392
408
|
};
|
|
@@ -410,6 +426,8 @@ export function formatSeoReportJson(report, url, options) {
|
|
|
410
426
|
warnings: report.checks.filter(c => c.status === 'warn').length,
|
|
411
427
|
errors: report.checks.filter(c => c.status === 'fail').length,
|
|
412
428
|
infos: report.checks.filter(c => c.status === 'info').length,
|
|
429
|
+
notApplicable: report.checks.filter(c => c.status === 'info' && c.infoType === 'not_applicable').length,
|
|
430
|
+
suggestions: report.checks.filter(c => c.status === 'info' && c.infoType !== 'not_applicable').length,
|
|
413
431
|
passRate: report.summary.passRate,
|
|
414
432
|
completeness: report.summary.completeness,
|
|
415
433
|
topIssues: report.summary.topIssues,
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { KeywordCloud } from './keywords.js';
|
|
2
2
|
export type { KeywordCloud, KeywordItem } from './keywords.js';
|
|
3
3
|
export type SeoStatus = 'pass' | 'warn' | 'fail' | 'info';
|
|
4
|
+
export type SeoInfoType = 'not_applicable' | 'suggestion';
|
|
4
5
|
export type SeoPageType = 'homepage' | 'product' | 'article' | 'category' | 'search' | 'other';
|
|
5
6
|
export interface SeoCheckEvidence {
|
|
6
7
|
found?: string | number | string[];
|
|
@@ -18,6 +19,7 @@ export interface SeoCheckResult {
|
|
|
18
19
|
status: SeoStatus;
|
|
19
20
|
severity?: 'error' | 'warning' | 'info';
|
|
20
21
|
message: string;
|
|
22
|
+
infoType?: SeoInfoType;
|
|
21
23
|
value?: string | number;
|
|
22
24
|
recommendation?: string;
|
|
23
25
|
evidence?: SeoCheckEvidence;
|
|
@@ -115,11 +117,16 @@ export interface SeoSummary {
|
|
|
115
117
|
warnings: number;
|
|
116
118
|
errors: number;
|
|
117
119
|
infos: number;
|
|
120
|
+
notApplicable: number;
|
|
121
|
+
suggestions: number;
|
|
118
122
|
passRate: number;
|
|
119
123
|
issuesByCategory: Record<string, {
|
|
120
124
|
passed: number;
|
|
121
125
|
warnings: number;
|
|
122
126
|
errors: number;
|
|
127
|
+
infos: number;
|
|
128
|
+
notApplicable: number;
|
|
129
|
+
suggestions: number;
|
|
123
130
|
}>;
|
|
124
131
|
topIssues: Array<{
|
|
125
132
|
name: string;
|
|
@@ -74,6 +74,8 @@ export class SEORunner extends CommandEmitter {
|
|
|
74
74
|
errors: report.summary.errors,
|
|
75
75
|
warnings: report.summary.warnings,
|
|
76
76
|
infos: report.summary.infos,
|
|
77
|
+
notApplicable: report.summary.notApplicable,
|
|
78
|
+
suggestions: report.summary.suggestions,
|
|
77
79
|
passed: report.summary.passed,
|
|
78
80
|
totalChecks: report.summary.totalChecks,
|
|
79
81
|
},
|
|
@@ -145,6 +147,8 @@ export class SEORunner extends CommandEmitter {
|
|
|
145
147
|
errors: report.summary.errors,
|
|
146
148
|
warnings: report.summary.warnings,
|
|
147
149
|
passed: report.summary.passed,
|
|
150
|
+
notApplicable: report.summary.notApplicable,
|
|
151
|
+
suggestions: report.summary.suggestions,
|
|
148
152
|
});
|
|
149
153
|
return result;
|
|
150
154
|
}
|
package/dist/cli/handlers/seo.js
CHANGED
|
@@ -105,6 +105,8 @@ export const seoAnalyzeHandler = withHandler({ loading: true }, async (ctx, out,
|
|
|
105
105
|
warnings: report.summary.warnings,
|
|
106
106
|
errors: report.summary.errors,
|
|
107
107
|
infos: report.summary.infos,
|
|
108
|
+
notApplicable: report.summary.notApplicable,
|
|
109
|
+
suggestions: report.summary.suggestions,
|
|
108
110
|
vitals: report.summary.vitals,
|
|
109
111
|
topIssues: report.summary.topIssues,
|
|
110
112
|
quickWins: report.summary.quickWins,
|
|
@@ -131,6 +133,10 @@ export const seoAnalyzeHandler = withHandler({ loading: true }, async (ctx, out,
|
|
|
131
133
|
{ text: `Passed: ${report.summary.passed}`, checked: true },
|
|
132
134
|
{ text: `Warnings: ${report.summary.warnings}`, checked: report.summary.warnings === 0 },
|
|
133
135
|
{ text: `Errors: ${report.summary.errors}`, checked: report.summary.errors === 0 },
|
|
136
|
+
{
|
|
137
|
+
text: `Info: ${report.summary.infos} (N/A: ${report.summary.notApplicable || 0}, Sugestões: ${report.summary.suggestions || 0})`,
|
|
138
|
+
checked: report.summary.infos === 0,
|
|
139
|
+
},
|
|
134
140
|
]);
|
|
135
141
|
out.blank();
|
|
136
142
|
if (report.summary.topIssues?.length > 0) {
|
|
@@ -343,7 +343,7 @@ function renderSeoResponse(data, width) {
|
|
|
343
343
|
if (data.summary) {
|
|
344
344
|
const s = data.summary;
|
|
345
345
|
children.push(Box({ marginTop: 1 }, Text({ color: themeColor('foreground'), bold: true }, 'Summary')));
|
|
346
|
-
children.push(Box({ flexDirection: 'row' }, Text({ color: themeColor('success') }, `✔ Passed: ${s.passed || 0} `), Text({ color: themeColor('warning') }, `⚠ Warnings: ${s.warnings || 0} `), Text({ color: themeColor('error') }, `✖ Errors: ${s.errors || 0} `), Text({ color: themeColor('accent') }, `ℹ Info: ${s.infos || 0}`)));
|
|
346
|
+
children.push(Box({ flexDirection: 'row' }, Text({ color: themeColor('success') }, `✔ Passed: ${s.passed || 0} `), Text({ color: themeColor('warning') }, `⚠ Warnings: ${s.warnings || 0} `), Text({ color: themeColor('error') }, `✖ Errors: ${s.errors || 0} `), Text({ color: themeColor('accent') }, `ℹ Info: ${s.infos || 0}`), Text({ color: themeColor('mutedForeground'), dim: true }, ` (N/A: ${s.notApplicable || 0}, Sugestões: ${s.suggestions || 0})`)));
|
|
347
347
|
if (s.vitals) {
|
|
348
348
|
children.push(Text({ color: themeColor('mutedForeground') }, `Words: ${s.vitals.wordCount || 0} | Images: ${s.vitals.imageCount || 0} | Links: ${s.vitals.linkCount || 0}${s.vitals.htmlSize ? ` | HTML: ${Math.round(s.vitals.htmlSize / 1024)}KB` : ''}`));
|
|
349
349
|
}
|
package/dist/seo/analyzer.d.ts
CHANGED
package/dist/seo/analyzer.js
CHANGED
|
@@ -71,7 +71,7 @@ export class SeoAnalyzer {
|
|
|
71
71
|
const ruleResults = this.rulesEngine.evaluate(context);
|
|
72
72
|
const checks = this.convertToCheckResults(ruleResults);
|
|
73
73
|
const { score, grade } = this.calculateScore(ruleResults);
|
|
74
|
-
const summary = this.buildSummary(
|
|
74
|
+
const summary = this.buildSummary(checks, {
|
|
75
75
|
content,
|
|
76
76
|
imageAnalysis,
|
|
77
77
|
linkAnalysis,
|
|
@@ -951,6 +951,27 @@ export class SeoAnalyzer {
|
|
|
951
951
|
}
|
|
952
952
|
return undefined;
|
|
953
953
|
}
|
|
954
|
+
classifyInfoCheck(result) {
|
|
955
|
+
if (result.status !== 'info')
|
|
956
|
+
return undefined;
|
|
957
|
+
const message = (result.message || '').toLowerCase();
|
|
958
|
+
const evidenceIssue = (result.evidence?.issue || '').toLowerCase();
|
|
959
|
+
const recommendation = (result.recommendation || '').toLowerCase();
|
|
960
|
+
const context = `${message} ${evidenceIssue} ${recommendation}`;
|
|
961
|
+
if (/not\s+applicable/.test(context) ||
|
|
962
|
+
/not\s+available/.test(context) ||
|
|
963
|
+
/data\s+unavailable/.test(context) ||
|
|
964
|
+
/context\s+unavailable/.test(context) ||
|
|
965
|
+
/unable\s+to\s+check/.test(context) ||
|
|
966
|
+
/cannot\s+verify/.test(context) ||
|
|
967
|
+
/cannot\s+be\s+determined/.test(context) ||
|
|
968
|
+
/\bunavailable\b/.test(context) ||
|
|
969
|
+
/not\s+set/.test(context) ||
|
|
970
|
+
/not\s+present/.test(context)) {
|
|
971
|
+
return 'not_applicable';
|
|
972
|
+
}
|
|
973
|
+
return 'suggestion';
|
|
974
|
+
}
|
|
954
975
|
convertToCheckResults(results) {
|
|
955
976
|
return results.map((r) => ({
|
|
956
977
|
id: r.id,
|
|
@@ -959,26 +980,36 @@ export class SeoAnalyzer {
|
|
|
959
980
|
severity: r.severity,
|
|
960
981
|
status: r.status,
|
|
961
982
|
message: r.message,
|
|
983
|
+
infoType: this.classifyInfoCheck(r),
|
|
962
984
|
value: r.value,
|
|
963
985
|
recommendation: r.recommendation,
|
|
964
986
|
evidence: r.evidence,
|
|
965
987
|
}));
|
|
966
988
|
}
|
|
967
|
-
buildSummary(
|
|
989
|
+
buildSummary(checks, data) {
|
|
968
990
|
const pageType = data.pageType;
|
|
969
991
|
const timings = data.timings;
|
|
970
992
|
const passed = checks.filter((c) => c.status === 'pass').length;
|
|
971
993
|
const warnings = checks.filter((c) => c.status === 'warn').length;
|
|
972
994
|
const errors = checks.filter((c) => c.status === 'fail').length;
|
|
973
995
|
const infos = checks.filter((c) => c.status === 'info').length;
|
|
996
|
+
const notApplicable = checks.filter((c) => c.status === 'info' && c.infoType === 'not_applicable').length;
|
|
997
|
+
const suggestions = checks.filter((c) => c.status === 'info' && c.infoType !== 'not_applicable').length;
|
|
974
998
|
const totalChecks = checks.length;
|
|
975
999
|
const scoringChecks = totalChecks - infos;
|
|
976
1000
|
const passRate = scoringChecks > 0 ? Math.round((passed / scoringChecks) * 100) : 100;
|
|
977
1001
|
const issuesByCategory = {};
|
|
978
|
-
for (const result of
|
|
1002
|
+
for (const result of checks) {
|
|
979
1003
|
const cat = result.category;
|
|
980
1004
|
if (!issuesByCategory[cat]) {
|
|
981
|
-
issuesByCategory[cat] = {
|
|
1005
|
+
issuesByCategory[cat] = {
|
|
1006
|
+
passed: 0,
|
|
1007
|
+
warnings: 0,
|
|
1008
|
+
errors: 0,
|
|
1009
|
+
infos: 0,
|
|
1010
|
+
notApplicable: 0,
|
|
1011
|
+
suggestions: 0,
|
|
1012
|
+
};
|
|
982
1013
|
}
|
|
983
1014
|
if (result.status === 'pass')
|
|
984
1015
|
issuesByCategory[cat].passed++;
|
|
@@ -986,6 +1017,15 @@ export class SeoAnalyzer {
|
|
|
986
1017
|
issuesByCategory[cat].warnings++;
|
|
987
1018
|
else if (result.status === 'fail')
|
|
988
1019
|
issuesByCategory[cat].errors++;
|
|
1020
|
+
else if (result.status === 'info') {
|
|
1021
|
+
issuesByCategory[cat].infos++;
|
|
1022
|
+
if (result.infoType === 'not_applicable') {
|
|
1023
|
+
issuesByCategory[cat].notApplicable++;
|
|
1024
|
+
}
|
|
1025
|
+
else {
|
|
1026
|
+
issuesByCategory[cat].suggestions++;
|
|
1027
|
+
}
|
|
1028
|
+
}
|
|
989
1029
|
}
|
|
990
1030
|
const topIssues = checks
|
|
991
1031
|
.filter((c) => c.status === 'fail' || c.status === 'warn')
|
|
@@ -1052,6 +1092,8 @@ export class SeoAnalyzer {
|
|
|
1052
1092
|
warnings,
|
|
1053
1093
|
errors,
|
|
1054
1094
|
infos,
|
|
1095
|
+
notApplicable,
|
|
1096
|
+
suggestions,
|
|
1055
1097
|
passRate,
|
|
1056
1098
|
issuesByCategory,
|
|
1057
1099
|
pageType: pageType,
|
package/dist/seo/formatter.js
CHANGED
|
@@ -29,7 +29,16 @@ export function statusIcon(status) {
|
|
|
29
29
|
export function formatCheck(check, showEvidence = false) {
|
|
30
30
|
const lines = [];
|
|
31
31
|
const icon = statusIcon(check.status);
|
|
32
|
-
|
|
32
|
+
let infoLabel = '';
|
|
33
|
+
if (check.status === 'info') {
|
|
34
|
+
if (check.infoType === 'not_applicable') {
|
|
35
|
+
infoLabel = colors.magenta(' [N/A]');
|
|
36
|
+
}
|
|
37
|
+
else if (check.infoType === 'suggestion') {
|
|
38
|
+
infoLabel = colors.cyan(' [SUGGESTION]');
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
lines.push(` ${icon} ${check.message}${infoLabel}`);
|
|
33
42
|
if (check.value !== undefined) {
|
|
34
43
|
lines.push(` ${colors.gray('Value:')} ${check.value}`);
|
|
35
44
|
}
|
|
@@ -239,7 +248,10 @@ export function formatSeoReport(report, url, options = {}) {
|
|
|
239
248
|
lines.push(colors.gray(' ──────────────────────────────────────────────────'));
|
|
240
249
|
lines.push('');
|
|
241
250
|
lines.push(` ${colors.bold('Summary')}`);
|
|
242
|
-
|
|
251
|
+
const infoSummary = (s.notApplicable !== undefined || s.suggestions !== undefined)
|
|
252
|
+
? ` (${s.notApplicable ?? 0} not applicable, ${s.suggestions ?? 0} suggestions)`
|
|
253
|
+
: '';
|
|
254
|
+
lines.push(` ${colors.green('✔ Passed:')} ${s.passed} ${colors.yellow('⚠ Warnings:')} ${s.warnings} ${colors.red('✖ Errors:')} ${s.errors} ${colors.blue('ℹ Info:')} ${s.infos}${infoSummary}`);
|
|
243
255
|
if (s.vitals) {
|
|
244
256
|
lines.push(` ${colors.gray('Words:')} ${s.vitals.wordCount} | ${colors.gray('Images:')} ${s.vitals.imageCount} | ${colors.gray('Links:')} ${s.vitals.linkCount}`);
|
|
245
257
|
}
|
|
@@ -381,12 +393,16 @@ function groupChecksByCategory(checks) {
|
|
|
381
393
|
const warnings = catChecks.filter(c => c.status === 'warn').length;
|
|
382
394
|
const errors = catChecks.filter(c => c.status === 'fail').length;
|
|
383
395
|
const infos = catChecks.filter(c => c.status === 'info').length;
|
|
396
|
+
const notApplicable = catChecks.filter(c => c.status === 'info' && c.infoType === 'not_applicable').length;
|
|
397
|
+
const suggestions = catChecks.filter(c => c.status === 'info' && c.infoType !== 'not_applicable').length;
|
|
384
398
|
const total = catChecks.length - infos;
|
|
385
399
|
result[cat] = {
|
|
386
400
|
passed,
|
|
387
401
|
warnings,
|
|
388
402
|
errors,
|
|
389
403
|
infos,
|
|
404
|
+
notApplicable,
|
|
405
|
+
suggestions,
|
|
390
406
|
passRate: total > 0 ? Math.round((passed / total) * 100) : 100,
|
|
391
407
|
checks: catChecks,
|
|
392
408
|
};
|
|
@@ -410,6 +426,8 @@ export function formatSeoReportJson(report, url, options) {
|
|
|
410
426
|
warnings: report.checks.filter(c => c.status === 'warn').length,
|
|
411
427
|
errors: report.checks.filter(c => c.status === 'fail').length,
|
|
412
428
|
infos: report.checks.filter(c => c.status === 'info').length,
|
|
429
|
+
notApplicable: report.checks.filter(c => c.status === 'info' && c.infoType === 'not_applicable').length,
|
|
430
|
+
suggestions: report.checks.filter(c => c.status === 'info' && c.infoType !== 'not_applicable').length,
|
|
413
431
|
passRate: report.summary.passRate,
|
|
414
432
|
completeness: report.summary.completeness,
|
|
415
433
|
topIssues: report.summary.topIssues,
|
package/dist/seo/types.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { KeywordCloud } from './keywords.js';
|
|
2
2
|
export type { KeywordCloud, KeywordItem } from './keywords.js';
|
|
3
3
|
export type SeoStatus = 'pass' | 'warn' | 'fail' | 'info';
|
|
4
|
+
export type SeoInfoType = 'not_applicable' | 'suggestion';
|
|
4
5
|
export type SeoPageType = 'homepage' | 'product' | 'article' | 'category' | 'search' | 'other';
|
|
5
6
|
export interface SeoCheckEvidence {
|
|
6
7
|
found?: string | number | string[];
|
|
@@ -18,6 +19,7 @@ export interface SeoCheckResult {
|
|
|
18
19
|
status: SeoStatus;
|
|
19
20
|
severity?: 'error' | 'warning' | 'info';
|
|
20
21
|
message: string;
|
|
22
|
+
infoType?: SeoInfoType;
|
|
21
23
|
value?: string | number;
|
|
22
24
|
recommendation?: string;
|
|
23
25
|
evidence?: SeoCheckEvidence;
|
|
@@ -115,11 +117,16 @@ export interface SeoSummary {
|
|
|
115
117
|
warnings: number;
|
|
116
118
|
errors: number;
|
|
117
119
|
infos: number;
|
|
120
|
+
notApplicable: number;
|
|
121
|
+
suggestions: number;
|
|
118
122
|
passRate: number;
|
|
119
123
|
issuesByCategory: Record<string, {
|
|
120
124
|
passed: number;
|
|
121
125
|
warnings: number;
|
|
122
126
|
errors: number;
|
|
127
|
+
infos: number;
|
|
128
|
+
notApplicable: number;
|
|
129
|
+
suggestions: number;
|
|
123
130
|
}>;
|
|
124
131
|
topIssues: Array<{
|
|
125
132
|
name: string;
|
package/dist/version.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "recker",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.79",
|
|
4
4
|
"description": "Multi-Protocol SDK for the AI Era - HTTP, WebSocket, DNS, FTP, SFTP, Telnet, HLS unified with AI providers and MCP tools",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"types": "./dist/index.d.ts",
|