rankforge 0.3.0
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/LICENSE +21 -0
- package/README.md +30 -0
- package/package.json +49 -0
- package/src/audit-output-schema.mjs +88 -0
- package/src/audit.mjs +202 -0
- package/src/cli.mjs +508 -0
- package/src/config-schema.mjs +292 -0
- package/src/crawl.mjs +188 -0
- package/src/finding-task.mjs +9 -0
- package/src/html-extract.mjs +226 -0
- package/src/index.mjs +9 -0
- package/src/integrations.mjs +78 -0
- package/src/io-guards.mjs +196 -0
- package/src/performance.mjs +112 -0
- package/src/regex-guards.mjs +52 -0
- package/src/render-parity.mjs +149 -0
- package/src/render.mjs +45 -0
- package/src/repo-audit.mjs +429 -0
- package/src/repo-detect.mjs +87 -0
- package/src/repo-findings.mjs +9 -0
- package/src/repo-manifests.mjs +169 -0
- package/src/repo-process.mjs +298 -0
- package/src/repo-routes.mjs +46 -0
- package/src/report.mjs +898 -0
- package/src/robots.mjs +60 -0
- package/src/rule-depth.mjs +190 -0
- package/src/rule-engine.mjs +360 -0
- package/src/rules.mjs +350 -0
- package/src/site-rule-engine.mjs +177 -0
- package/src/sitemap.mjs +30 -0
- package/src/snapshot.mjs +119 -0
- package/src/source-map.json +28 -0
- package/src/structured-data.mjs +59 -0
- package/src/url-utils.mjs +25 -0
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
{
|
|
2
|
+
"search_essentials": "https://developers.google.com/search/docs/essentials",
|
|
3
|
+
"technical_requirements": "https://developers.google.com/search/docs/essentials/technical",
|
|
4
|
+
"how_search_works": "https://developers.google.com/search/docs/fundamentals/how-search-works",
|
|
5
|
+
"crawlable_links": "https://developers.google.com/search/docs/crawling-indexing/links-crawlable",
|
|
6
|
+
"robots_meta": "https://developers.google.com/search/docs/crawling-indexing/robots-meta-tag",
|
|
7
|
+
"robots_txt": "https://developers.google.com/search/docs/crawling-indexing/robots/intro",
|
|
8
|
+
"block_indexing": "https://developers.google.com/search/docs/crawling-indexing/block-indexing",
|
|
9
|
+
"canonicalization": "https://developers.google.com/search/docs/crawling-indexing/canonicalization",
|
|
10
|
+
"consolidate_duplicate_urls": "https://developers.google.com/search/docs/crawling-indexing/consolidate-duplicate-urls",
|
|
11
|
+
"sitemaps": "https://developers.google.com/search/docs/crawling-indexing/sitemaps/overview",
|
|
12
|
+
"javascript_seo": "https://developers.google.com/search/docs/crawling-indexing/javascript/javascript-seo-basics",
|
|
13
|
+
"fix_javascript_seo": "https://developers.google.com/search/docs/crawling-indexing/javascript/fix-search-javascript",
|
|
14
|
+
"valid_metadata": "https://developers.google.com/search/docs/crawling-indexing/valid-page-metadata",
|
|
15
|
+
"helpful_content": "https://developers.google.com/search/docs/fundamentals/creating-helpful-content",
|
|
16
|
+
"ai_optimization": "https://developers.google.com/search/docs/fundamentals/ai-optimization-guide",
|
|
17
|
+
"ai_features": "https://developers.google.com/search/docs/appearance/ai-features",
|
|
18
|
+
"structured_data_intro": "https://developers.google.com/search/docs/appearance/structured-data/intro-structured-data",
|
|
19
|
+
"structured_data_policies": "https://developers.google.com/search/docs/appearance/structured-data/sd-policies",
|
|
20
|
+
"structured_data_gallery": "https://developers.google.com/search/docs/appearance/structured-data/search-gallery",
|
|
21
|
+
"organization_schema": "https://developers.google.com/search/docs/appearance/structured-data/organization",
|
|
22
|
+
"title_links": "https://developers.google.com/search/docs/appearance/title-link",
|
|
23
|
+
"snippets": "https://developers.google.com/search/docs/appearance/snippet",
|
|
24
|
+
"google_images": "https://developers.google.com/search/docs/appearance/google-images",
|
|
25
|
+
"favicon": "https://developers.google.com/search/docs/appearance/favicon-in-search",
|
|
26
|
+
"site_names": "https://developers.google.com/search/docs/appearance/site-names",
|
|
27
|
+
"spam_policies": "https://developers.google.com/search/docs/essentials/spam-policies"
|
|
28
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
const requiredProperties = {
|
|
2
|
+
Organization: ["name", "url"],
|
|
3
|
+
Product: ["name", "offers"],
|
|
4
|
+
FAQPage: ["mainEntity"],
|
|
5
|
+
Article: ["headline", "datePublished", "author"],
|
|
6
|
+
BreadcrumbList: ["itemListElement"],
|
|
7
|
+
Event: ["name", "startDate", "location"],
|
|
8
|
+
VideoObject: ["name", "thumbnailUrl", "uploadDate"],
|
|
9
|
+
SoftwareApplication: ["name", "applicationCategory", "operatingSystem"],
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
const hasValue = (value) => {
|
|
13
|
+
if (value === null || value === undefined) return false;
|
|
14
|
+
if (typeof value === "string") return value.trim().length > 0;
|
|
15
|
+
if (Array.isArray(value)) return value.length > 0;
|
|
16
|
+
return true;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
export const structuredDataTypeNames = (value) => {
|
|
20
|
+
if (!value) return [];
|
|
21
|
+
return Array.isArray(value) ? value.map(String) : [String(value)];
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
export const structuredDataNodes = (value) => {
|
|
25
|
+
if (!value) return [];
|
|
26
|
+
if (Array.isArray(value)) return value.flatMap(structuredDataNodes);
|
|
27
|
+
if (typeof value !== "object") return [];
|
|
28
|
+
|
|
29
|
+
const nodes = [value];
|
|
30
|
+
if (value["@graph"]) nodes.push(...structuredDataNodes(value["@graph"]));
|
|
31
|
+
return nodes;
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
export const validateStructuredData = (structuredData = []) => {
|
|
35
|
+
const issues = [];
|
|
36
|
+
|
|
37
|
+
for (const [blockIndex, block] of structuredData.entries()) {
|
|
38
|
+
if (!block?.data) continue;
|
|
39
|
+
|
|
40
|
+
for (const [nodeIndex, node] of structuredDataNodes(block.data).entries()) {
|
|
41
|
+
for (const type of structuredDataTypeNames(node["@type"])) {
|
|
42
|
+
const required = requiredProperties[type];
|
|
43
|
+
if (!required) continue;
|
|
44
|
+
|
|
45
|
+
const missing = required.filter((property) => !hasValue(node[property]));
|
|
46
|
+
if (missing.length) {
|
|
47
|
+
issues.push({
|
|
48
|
+
type,
|
|
49
|
+
missing,
|
|
50
|
+
blockIndex,
|
|
51
|
+
nodeIndex,
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
return issues;
|
|
59
|
+
};
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
export const isHttpUrl = (value) => /^https?:\/\//i.test(String(value || ""));
|
|
2
|
+
|
|
3
|
+
export const normalizeUrl = (value) => {
|
|
4
|
+
const parsed = new URL(value);
|
|
5
|
+
parsed.hash = "";
|
|
6
|
+
if (parsed.pathname.length > 1) parsed.pathname = parsed.pathname.replace(/\/+$/, "");
|
|
7
|
+
return parsed.href;
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
export const resolveUrl = (href, base) => {
|
|
11
|
+
if (!href) return null;
|
|
12
|
+
try {
|
|
13
|
+
return normalizeUrl(new URL(href, base).href);
|
|
14
|
+
} catch {
|
|
15
|
+
return null;
|
|
16
|
+
}
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
export const sameOrigin = (a, b) => {
|
|
20
|
+
try {
|
|
21
|
+
return new URL(a).origin === new URL(b).origin;
|
|
22
|
+
} catch {
|
|
23
|
+
return false;
|
|
24
|
+
}
|
|
25
|
+
};
|