seo-intel 1.4.4 → 1.4.5
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/CHANGELOG.md +7 -0
- package/package.json +1 -1
- package/server.js +37 -12
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 1.4.5 (2026-04-09)
|
|
4
|
+
|
|
5
|
+
### Export: actionable summaries only
|
|
6
|
+
- Technical export: per-page issue summary (own site only) — lists specific problems per URL
|
|
7
|
+
- Links export: per-page link issue summary (own site only) — orphan pages, missing anchors, excessive external links
|
|
8
|
+
- No more raw data dumps in profile exports — every row is an action item
|
|
9
|
+
|
|
3
10
|
## 1.4.4 (2026-04-08)
|
|
4
11
|
|
|
5
12
|
### Export Profiles
|
package/package.json
CHANGED
package/server.js
CHANGED
|
@@ -759,12 +759,24 @@ async function handleRequest(req, res) {
|
|
|
759
759
|
}
|
|
760
760
|
case 'technical': {
|
|
761
761
|
if (!Array.isArray(data) || prof === 'ai-pipeline') return data;
|
|
762
|
-
//
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
762
|
+
// Own site only, per-page issue summary
|
|
763
|
+
const own = data.filter(r => r.role === 'target' || r.role === 'owned');
|
|
764
|
+
const issues = [];
|
|
765
|
+
for (const r of own) {
|
|
766
|
+
const problems = [];
|
|
767
|
+
if (r.status_code >= 400) problems.push(`HTTP ${r.status_code}`);
|
|
768
|
+
if (!r.has_canonical) problems.push('no canonical');
|
|
769
|
+
if (!r.has_og_tags) problems.push('no OG tags');
|
|
770
|
+
if (!r.has_schema) problems.push('no schema');
|
|
771
|
+
if (!r.has_robots) problems.push('no robots meta');
|
|
772
|
+
if (!r.is_mobile_ok) problems.push('not mobile-friendly');
|
|
773
|
+
if (r.load_ms && r.load_ms > 3000) problems.push(`slow (${r.load_ms}ms)`);
|
|
774
|
+
if (r.word_count != null && r.word_count < 100) problems.push(`thin content (${r.word_count} words)`);
|
|
775
|
+
if (problems.length) {
|
|
776
|
+
issues.push({ url: r.url, domain: r.domain, issues: problems.join(', '), status: r.status_code, load_ms: r.load_ms, word_count: r.word_count });
|
|
777
|
+
}
|
|
778
|
+
}
|
|
779
|
+
return issues;
|
|
768
780
|
}
|
|
769
781
|
case 'headings': {
|
|
770
782
|
if (!Array.isArray(data)) return data;
|
|
@@ -795,12 +807,25 @@ async function handleRequest(req, res) {
|
|
|
795
807
|
}
|
|
796
808
|
case 'links': {
|
|
797
809
|
if (!Array.isArray(data)) return data;
|
|
798
|
-
//
|
|
799
|
-
const
|
|
800
|
-
const
|
|
801
|
-
|
|
802
|
-
const
|
|
803
|
-
|
|
810
|
+
// Own site only, summarize to per-page link issues
|
|
811
|
+
const ownLinks = data.filter(r => r.role === 'target' || r.role === 'owned');
|
|
812
|
+
const internalTargets = new Set(ownLinks.filter(l => l.is_internal).map(l => l.target_url));
|
|
813
|
+
const byPage = {};
|
|
814
|
+
for (const r of ownLinks) (byPage[r.source_url] ||= []).push(r);
|
|
815
|
+
const issues = [];
|
|
816
|
+
for (const [url, links] of Object.entries(byPage)) {
|
|
817
|
+
const problems = [];
|
|
818
|
+
const noAnchor = links.filter(l => !l.anchor_text);
|
|
819
|
+
if (noAnchor.length) problems.push(`${noAnchor.length} links missing anchor text`);
|
|
820
|
+
if (!internalTargets.has(url)) problems.push('orphan page (no internal links point here)');
|
|
821
|
+
const extLinks = links.filter(l => !l.is_internal);
|
|
822
|
+
// flag if page has excessive external links
|
|
823
|
+
if (extLinks.length > 20) problems.push(`${extLinks.length} external links`);
|
|
824
|
+
if (problems.length) {
|
|
825
|
+
issues.push({ url, domain: links[0].domain, issues: problems.join(', '), total_links: links.length, internal: links.filter(l => l.is_internal).length, external: extLinks.length });
|
|
826
|
+
}
|
|
827
|
+
}
|
|
828
|
+
return issues;
|
|
804
829
|
}
|
|
805
830
|
case 'schemas': {
|
|
806
831
|
if (!Array.isArray(data) || prof !== 'dev') return data;
|