📦 deps(thirdparty): update snapshots
This commit is contained in:
@@ -108,7 +108,7 @@ export function buildSitemap(skills, topCount = TOP_SKILL_COUNT, baseUrl = SITE_
|
||||
const topSkillPaths = selectTopSkillEntries(skills, topCount);
|
||||
return generateSitemapXml({
|
||||
baseUrl,
|
||||
paths: ['/', ...topSkillPaths],
|
||||
paths: ['/', '/plugins', ...topSkillPaths],
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -9,9 +9,31 @@ const PUBLIC_DIR = path.join(ROOT_DIR, 'public');
|
||||
const TEMPLATE_PATH = path.join(DIST_DIR, 'index.html');
|
||||
const SKILLS_PATH = path.join(PUBLIC_DIR, 'skills.json');
|
||||
|
||||
const HOME_CATALOG_COUNT_FALLBACK = 1465;
|
||||
const HOME_CATALOG_COUNT_FALLBACK = 1494;
|
||||
const PRERENDER_SOCIAL_IMAGE = 'social-card.svg';
|
||||
const SITE_NAME = 'Antigravity Awesome Skills';
|
||||
const FAQ_ITEMS = [
|
||||
{
|
||||
question: 'What is Antigravity Awesome Skills?',
|
||||
answer:
|
||||
'Antigravity Awesome Skills is an installable GitHub library of 1,494+ reusable SKILL.md playbooks for AI coding assistants. It supports Claude Code, Cursor, Codex CLI, Gemini CLI, Antigravity, and related hosts through direct skill installs, specialized plugins, bundles, workflows, and a searchable catalog.',
|
||||
},
|
||||
{
|
||||
question: 'How do I install Antigravity Awesome Skills?',
|
||||
answer:
|
||||
'Install the library with npx antigravity-awesome-skills. Use tool-specific flags such as --codex, --cursor, --gemini, --claude, or --antigravity when you want the installer to target a specific skills directory already used by your assistant runtime.',
|
||||
},
|
||||
{
|
||||
question: 'What are AAS specialized plugins?',
|
||||
answer:
|
||||
'AAS specialized plugins are focused, domain-specific distributions of the skill library. They package relevant skills for web apps, security, data analytics, documents, DevOps, QA, OSS maintenance, and agent or MCP work so users can start with the right surface instead of activating the entire catalog.',
|
||||
},
|
||||
{
|
||||
question: 'How are plugins, bundles, and workflows different?',
|
||||
answer:
|
||||
'Plugins are installable packaging surfaces, bundles are curated skill recommendations, and workflows are ordered execution playbooks. Start with a plugin when the domain is clear, use bundles to compare adjacent skills, and use workflows when sequencing planning, coding, testing, auditing, or release work matters.',
|
||||
},
|
||||
];
|
||||
|
||||
function parseCount(value, fallback) {
|
||||
const parsed = Number.parseInt(value, 10);
|
||||
@@ -156,8 +178,9 @@ function safeText(value) {
|
||||
function buildHomeMeta({ catalogCount, imageUrl, canonicalUrl }) {
|
||||
const visibleCount = Math.max(catalogCount, HOME_CATALOG_COUNT_FALLBACK);
|
||||
const formattedCount = visibleCount.toLocaleString('en-US');
|
||||
const title = `Antigravity Awesome Skills | ${formattedCount}+ installable AI skills catalog`;
|
||||
const description = `Explore ${visibleCount}+ installable agentic skills and prompt templates. Discover what fits your workflow, copy prompts fast, and launch AI-powered actions with confidence.`;
|
||||
const title = `Antigravity Awesome Skills | ${formattedCount}+ AI coding skills and plugins`;
|
||||
const description = `Explore ${formattedCount}+ installable agentic skills, specialized plugins, bundles, and workflows for Claude Code, Cursor, Codex CLI, Gemini CLI, Antigravity, and other AI coding assistants.`;
|
||||
const catalogBaseUrl = canonicalUrl.replace(/\/$/, '');
|
||||
|
||||
return {
|
||||
title,
|
||||
@@ -167,22 +190,86 @@ function buildHomeMeta({ catalogCount, imageUrl, canonicalUrl }) {
|
||||
ogDescription: description,
|
||||
ogImage: imageUrl,
|
||||
twitterCard: 'summary_large_image',
|
||||
jsonLd: {
|
||||
'@context': 'https://schema.org',
|
||||
'@type': 'CollectionPage',
|
||||
name: SITE_NAME,
|
||||
description,
|
||||
url: canonicalUrl,
|
||||
isPartOf: {
|
||||
jsonLd: [
|
||||
{
|
||||
'@context': 'https://schema.org',
|
||||
'@type': 'CollectionPage',
|
||||
name: SITE_NAME,
|
||||
description,
|
||||
url: canonicalUrl,
|
||||
isPartOf: {
|
||||
'@type': 'WebSite',
|
||||
name: SITE_NAME,
|
||||
url: catalogBaseUrl,
|
||||
},
|
||||
mainEntity: {
|
||||
'@type': 'ItemList',
|
||||
name: `${SITE_NAME} catalog`,
|
||||
},
|
||||
},
|
||||
{
|
||||
'@context': 'https://schema.org',
|
||||
'@type': 'Organization',
|
||||
name: SITE_NAME,
|
||||
url: 'https://github.com/sickn33/antigravity-awesome-skills',
|
||||
sameAs: [
|
||||
'https://x.com/AASkills_',
|
||||
'https://www.npmjs.com/package/antigravity-awesome-skills',
|
||||
'https://sickn33.github.io/antigravity-awesome-skills/',
|
||||
],
|
||||
},
|
||||
{
|
||||
'@context': 'https://schema.org',
|
||||
'@type': 'WebSite',
|
||||
name: SITE_NAME,
|
||||
url: canonicalUrl.replace(/\/$/, ''),
|
||||
url: catalogBaseUrl,
|
||||
inLanguage: 'en',
|
||||
potentialAction: {
|
||||
'@type': 'SearchAction',
|
||||
target: `${catalogBaseUrl}/?q={search_term_string}`,
|
||||
'query-input': 'required name=search_term_string',
|
||||
},
|
||||
},
|
||||
mainEntity: {
|
||||
'@type': 'ItemList',
|
||||
name: `${SITE_NAME} catalog`,
|
||||
{
|
||||
'@context': 'https://schema.org',
|
||||
'@type': 'SoftwareSourceCode',
|
||||
name: SITE_NAME,
|
||||
description: `Installable GitHub library of ${formattedCount}+ agentic skills, specialized plugins, bundles, and workflows for AI coding assistants.`,
|
||||
url: canonicalUrl,
|
||||
codeRepository: 'https://github.com/sickn33/antigravity-awesome-skills',
|
||||
applicationCategory: 'DeveloperApplication',
|
||||
keywords: [
|
||||
'AI coding assistant skills',
|
||||
'Claude Code skills',
|
||||
'Codex CLI skills',
|
||||
'Cursor skills',
|
||||
'Gemini CLI skills',
|
||||
'Antigravity skills',
|
||||
'specialized plugins',
|
||||
'SKILL.md',
|
||||
],
|
||||
isAccessibleForFree: true,
|
||||
programmingLanguage: {
|
||||
'@type': 'ComputerLanguage',
|
||||
name: 'Markdown',
|
||||
url: 'https://en.wikipedia.org/wiki/Markdown',
|
||||
},
|
||||
license: 'https://github.com/sickn33/antigravity-awesome-skills/blob/main/LICENSE',
|
||||
},
|
||||
},
|
||||
{
|
||||
'@context': 'https://schema.org',
|
||||
'@type': 'FAQPage',
|
||||
url: canonicalUrl,
|
||||
mainEntity: FAQ_ITEMS.map((item) => ({
|
||||
'@type': 'Question',
|
||||
name: item.question,
|
||||
acceptedAnswer: {
|
||||
'@type': 'Answer',
|
||||
text: item.answer,
|
||||
},
|
||||
})),
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
@@ -234,6 +321,46 @@ function buildSkillMeta({ skill, isPriority, imageUrl, canonicalUrl }) {
|
||||
};
|
||||
}
|
||||
|
||||
function buildPluginsMeta({ pluginCount, imageUrl, canonicalUrl }) {
|
||||
const countLabel = pluginCount > 0 ? `${pluginCount.toLocaleString('en-US')} ` : '';
|
||||
const description = `Compare ${countLabel}specialized plugin packs for web apps, security, data analytics, documents, DevOps, QA, OSS maintenance, mobile apps, automation, and agent or MCP systems.`;
|
||||
|
||||
return {
|
||||
title: `AAS Specialized Plugins | ${countLabel}AI coding workflow packs`,
|
||||
description,
|
||||
canonicalUrl,
|
||||
ogTitle: 'AAS Specialized Plugins | AI coding workflow packs',
|
||||
ogDescription: description,
|
||||
ogImage: imageUrl,
|
||||
twitterCard: 'summary_large_image',
|
||||
jsonLd: [
|
||||
{
|
||||
'@context': 'https://schema.org',
|
||||
'@type': 'CollectionPage',
|
||||
name: 'AAS Specialized Plugins',
|
||||
description,
|
||||
url: canonicalUrl,
|
||||
isPartOf: {
|
||||
'@type': 'WebSite',
|
||||
name: SITE_NAME,
|
||||
url: canonicalUrl.replace(/\/plugins\/?$/, ''),
|
||||
},
|
||||
mainEntity: {
|
||||
'@type': 'ItemList',
|
||||
name: 'AAS specialized plugin packs',
|
||||
numberOfItems: pluginCount,
|
||||
},
|
||||
},
|
||||
{
|
||||
'@context': 'https://schema.org',
|
||||
'@type': 'Organization',
|
||||
name: SITE_NAME,
|
||||
url: 'https://github.com/sickn33/antigravity-awesome-skills',
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
function applySeoMeta(templateHtml, meta) {
|
||||
let output = templateHtml;
|
||||
const title = safeText(meta.title);
|
||||
@@ -306,6 +433,14 @@ function main() {
|
||||
});
|
||||
writePrerenderedRoute('/', template, homeMeta);
|
||||
|
||||
const pluginsCanonical = routeToUrl('/plugins', siteBaseUrl);
|
||||
const pluginsMeta = buildPluginsMeta({
|
||||
pluginCount: 15,
|
||||
imageUrl: socialImage,
|
||||
canonicalUrl: pluginsCanonical,
|
||||
});
|
||||
writePrerenderedRoute('/plugins', template, pluginsMeta);
|
||||
|
||||
for (const skillRoute of topSkillPaths) {
|
||||
const decodedId = decodeURIComponent(skillRoute.replace(/^\/skill\//, ''));
|
||||
const skill = skillMap.get(decodedId);
|
||||
|
||||
@@ -26,6 +26,7 @@ function parseCliArgs(argv) {
|
||||
const args = {
|
||||
sitemapPath: 'dist/sitemap.xml',
|
||||
robotsPath: 'dist/robots.txt',
|
||||
llmsPath: 'dist/llms.txt',
|
||||
manifestPath: 'dist/site.webmanifest',
|
||||
indexPath: 'dist/index.html',
|
||||
distDir: 'dist',
|
||||
@@ -39,6 +40,7 @@ function parseCliArgs(argv) {
|
||||
if (value) {
|
||||
args.sitemapPath = path.join(value, 'sitemap.xml');
|
||||
args.robotsPath = path.join(value, 'robots.txt');
|
||||
args.llmsPath = path.join(value, 'llms.txt');
|
||||
args.manifestPath = path.join(value, 'site.webmanifest');
|
||||
args.indexPath = path.join(value, 'index.html');
|
||||
args.distDir = value;
|
||||
@@ -65,6 +67,12 @@ function parseCliArgs(argv) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (arg === '--llms' && argv[i + 1]) {
|
||||
args.llmsPath = argv[i + 1];
|
||||
i += 1;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (arg === '--manifest' && argv[i + 1]) {
|
||||
args.manifestPath = argv[i + 1];
|
||||
i += 1;
|
||||
@@ -95,6 +103,11 @@ function extractMetaContent(htmlText, selectorType, selectorValue) {
|
||||
return match?.[1]?.trim();
|
||||
}
|
||||
|
||||
function extractTitle(htmlText) {
|
||||
const match = String(htmlText ?? '').match(/<title[^>]*>([\s\S]*?)<\/title>/i);
|
||||
return match?.[1]?.trim() || '';
|
||||
}
|
||||
|
||||
function assertMetaContent(htmlText, selectorType, selectorValue) {
|
||||
const content = extractMetaContent(htmlText, selectorType, selectorValue);
|
||||
assert(Boolean(content), `Missing required meta tag ${selectorType}="${selectorValue}".`);
|
||||
@@ -148,9 +161,17 @@ export function analyzeSitemap(urlText, { minSkillUrls = 1 } = {}) {
|
||||
|
||||
const isRoot = ({ parsed: parsedUrl }) => rootPathVariants.has(parsedUrl.pathname);
|
||||
const extraRoutes = parsed.filter(({ parsed: parsedUrl }) => !isRoot({ parsed: parsedUrl }));
|
||||
const allowedExtraPathVariants = new Set([
|
||||
`${normalizedRoot}/plugins`,
|
||||
`${normalizedRoot}/plugins/`,
|
||||
]);
|
||||
const skillRoutes = extraRoutes.filter(({ parsed: parsedUrl }) =>
|
||||
parsedUrl.pathname.startsWith(skillPrefix),
|
||||
);
|
||||
const unsupportedRoutes = extraRoutes.filter(
|
||||
({ parsed: parsedUrl }) =>
|
||||
!parsedUrl.pathname.startsWith(skillPrefix) && !allowedExtraPathVariants.has(parsedUrl.pathname),
|
||||
);
|
||||
|
||||
assert(
|
||||
skillRoutes.length >= effectiveMinSkillUrls,
|
||||
@@ -158,7 +179,7 @@ export function analyzeSitemap(urlText, { minSkillUrls = 1 } = {}) {
|
||||
);
|
||||
|
||||
assert(
|
||||
extraRoutes.every(({ parsed: parsedUrl }) => parsedUrl.pathname.startsWith(skillPrefix)),
|
||||
unsupportedRoutes.length === 0,
|
||||
'Sitemap contains unsupported non-skill routes.',
|
||||
);
|
||||
|
||||
@@ -167,6 +188,9 @@ export function analyzeSitemap(urlText, { minSkillUrls = 1 } = {}) {
|
||||
rootPath: rootUrl.pathname,
|
||||
normalizedRootPath: normalizedRoot,
|
||||
skillUrls: skillRoutes.map(({ raw }) => raw),
|
||||
pluginUrls: extraRoutes
|
||||
.filter(({ parsed: parsedUrl }) => allowedExtraPathVariants.has(parsedUrl.pathname))
|
||||
.map(({ raw }) => raw),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -174,12 +198,84 @@ export function assertSitemap(urlText, { minSkillUrls = 1 } = {}) {
|
||||
analyzeSitemap(urlText, { minSkillUrls });
|
||||
}
|
||||
|
||||
function extractJsonLdEntries(htmlText) {
|
||||
const raw = String(htmlText ?? '');
|
||||
const matches = raw.matchAll(
|
||||
/<script\b[^>]*type=["']application\/ld\+json["'][^>]*>([\s\S]*?)<\/script>/gi,
|
||||
);
|
||||
const entries = [];
|
||||
|
||||
for (const match of matches) {
|
||||
const text = match[1]?.trim();
|
||||
if (!text) {
|
||||
continue;
|
||||
}
|
||||
|
||||
let parsed;
|
||||
try {
|
||||
parsed = JSON.parse(text);
|
||||
} catch (_err) {
|
||||
throw new Error('JSON-LD script contains invalid JSON.');
|
||||
}
|
||||
|
||||
if (Array.isArray(parsed)) {
|
||||
entries.push(...parsed);
|
||||
} else {
|
||||
entries.push(parsed);
|
||||
}
|
||||
}
|
||||
|
||||
return entries;
|
||||
}
|
||||
|
||||
function assertJsonLdTypes(htmlText, requiredTypes) {
|
||||
const entries = extractJsonLdEntries(htmlText);
|
||||
const types = new Set(entries.map((entry) => entry?.['@type']).filter(Boolean));
|
||||
|
||||
for (const requiredType of requiredTypes) {
|
||||
assert(types.has(requiredType), `JSON-LD missing required @type: ${requiredType}`);
|
||||
}
|
||||
}
|
||||
|
||||
export function assertIndexSocialMeta(htmlText) {
|
||||
assertMetaContent(htmlText, 'property', 'og:image');
|
||||
assertMetaContent(htmlText, 'name', 'twitter:image');
|
||||
assertMetaContent(htmlText, 'name', 'twitter:image:alt');
|
||||
}
|
||||
|
||||
export function assertIndexDiscoveryMeta(htmlText) {
|
||||
const title = extractTitle(htmlText);
|
||||
const description = extractMetaContent(htmlText, 'name', 'description') || '';
|
||||
const ogTitle = extractMetaContent(htmlText, 'property', 'og:title') || '';
|
||||
const ogDescription = extractMetaContent(htmlText, 'property', 'og:description') || '';
|
||||
const twitterTitle = extractMetaContent(htmlText, 'name', 'twitter:title') || '';
|
||||
const twitterDescription = extractMetaContent(htmlText, 'name', 'twitter:description') || '';
|
||||
const combined = [
|
||||
title,
|
||||
description,
|
||||
ogTitle,
|
||||
ogDescription,
|
||||
twitterTitle,
|
||||
twitterDescription,
|
||||
].join(' ');
|
||||
|
||||
assert(combined.includes('1,494+'), 'Home SEO metadata must expose the current 1,494+ skill count.');
|
||||
assert(combined.includes('specialized plugins'), 'Home SEO metadata must mention specialized plugins.');
|
||||
assert(!combined.includes('prompt templates'), 'Home SEO metadata must not use stale prompt-template positioning.');
|
||||
assertJsonLdTypes(htmlText, ['CollectionPage', 'Organization', 'WebSite', 'SoftwareSourceCode', 'FAQPage']);
|
||||
}
|
||||
|
||||
export function assertPluginsDiscoveryMeta(htmlText) {
|
||||
const title = extractTitle(htmlText);
|
||||
const description = extractMetaContent(htmlText, 'name', 'description') || '';
|
||||
const ogTitle = extractMetaContent(htmlText, 'property', 'og:title') || '';
|
||||
const combined = [title, description, ogTitle].join(' ');
|
||||
|
||||
assert(combined.includes('AAS Specialized Plugins'), 'Plugins page SEO metadata must expose the plugin landing title.');
|
||||
assert(combined.includes('specialized plugin packs'), 'Plugins page SEO metadata must mention specialized plugin packs.');
|
||||
assertJsonLdTypes(htmlText, ['CollectionPage', 'Organization']);
|
||||
}
|
||||
|
||||
function routePathToDistFile(routePath, normalizedRootPath) {
|
||||
const normalizedPath = (routePath || '/').replace(/\/+$/, '') || '/';
|
||||
const normalizedRoot = normalizedRootPath === '/' ? '' : String(normalizedRootPath || '').replace(/\/+$/, '');
|
||||
@@ -201,13 +297,45 @@ export function assertPrerenderedSkillRoutes(skillUrls, distDir = 'dist', normal
|
||||
}
|
||||
}
|
||||
|
||||
export function assertPrerenderedPluginRoutes(pluginUrls, distDir = 'dist', normalizedRootPath = '') {
|
||||
for (const pluginUrl of pluginUrls) {
|
||||
const parsed = new URL(pluginUrl);
|
||||
const filePath = path.join(distDir, routePathToDistFile(parsed.pathname, normalizedRootPath));
|
||||
assert(
|
||||
fs.existsSync(filePath),
|
||||
`Missing prerendered page for plugin route: ${parsed.pathname}. Expected ${filePath}.`,
|
||||
);
|
||||
assertPluginsDiscoveryMeta(readFile(filePath));
|
||||
}
|
||||
}
|
||||
|
||||
export function assertRobots(robotsText) {
|
||||
const lines = String(robotsText ?? '').split(/\r?\n/).map((line) => line.trim());
|
||||
const allowsRoot = lines.some((line) => line.startsWith('Allow: /'));
|
||||
const hasSitemap = lines.some((line) => /^Sitemap:\s*.+\/?sitemap\.xml$/i.test(line));
|
||||
const allowsAiSearchCrawlers = ['GPTBot', 'OAI-SearchBot', 'ClaudeBot', 'PerplexityBot'].every((crawler) =>
|
||||
lines.some((line) => line === `User-agent: ${crawler}`),
|
||||
);
|
||||
|
||||
assert(allowsRoot, 'robots.txt must allow root crawling.');
|
||||
assert(hasSitemap, 'robots.txt must expose sitemap location.');
|
||||
assert(allowsAiSearchCrawlers, 'robots.txt must explicitly expose AI search crawler directives.');
|
||||
}
|
||||
|
||||
export function assertLlms(llmsText) {
|
||||
const text = String(llmsText ?? '');
|
||||
const requiredSnippets = [
|
||||
'# Antigravity Awesome Skills',
|
||||
'1,494+',
|
||||
'specialized plugins',
|
||||
'Claude Code',
|
||||
'Codex CLI',
|
||||
'https://github.com/sickn33/antigravity-awesome-skills',
|
||||
];
|
||||
|
||||
for (const snippet of requiredSnippets) {
|
||||
assert(text.includes(snippet), `llms.txt missing required snippet: ${snippet}`);
|
||||
}
|
||||
}
|
||||
|
||||
export function assertManifest(manifestText) {
|
||||
@@ -229,15 +357,20 @@ function readFile(filePath) {
|
||||
export function runVerification({
|
||||
sitemapPath,
|
||||
robotsPath,
|
||||
llmsPath = 'dist/llms.txt',
|
||||
manifestPath,
|
||||
indexPath = 'dist/index.html',
|
||||
distDir = 'dist',
|
||||
minSkillUrls,
|
||||
}) {
|
||||
const sitemapReport = analyzeSitemap(readFile(sitemapPath), { minSkillUrls });
|
||||
const indexHtml = readFile(indexPath);
|
||||
assertPrerenderedSkillRoutes(sitemapReport.skillUrls, distDir, sitemapReport.normalizedRootPath);
|
||||
assertIndexSocialMeta(readFile(indexPath));
|
||||
assertPrerenderedPluginRoutes(sitemapReport.pluginUrls, distDir, sitemapReport.normalizedRootPath);
|
||||
assertIndexSocialMeta(indexHtml);
|
||||
assertIndexDiscoveryMeta(indexHtml);
|
||||
assertRobots(readFile(robotsPath));
|
||||
assertLlms(readFile(llmsPath));
|
||||
assertManifest(readFile(manifestPath));
|
||||
}
|
||||
|
||||
|
||||
@@ -4,9 +4,13 @@ import os from 'node:os';
|
||||
import path from 'node:path';
|
||||
import {
|
||||
assertManifest,
|
||||
assertIndexDiscoveryMeta,
|
||||
assertPluginsDiscoveryMeta,
|
||||
analyzeSitemap,
|
||||
assertPrerenderedPluginRoutes,
|
||||
assertPrerenderedSkillRoutes,
|
||||
assertIndexSocialMeta,
|
||||
assertLlms,
|
||||
assertRobots,
|
||||
assertSitemap,
|
||||
extractSitemapLocations,
|
||||
@@ -33,6 +37,7 @@ describe('seo assets verification helpers', () => {
|
||||
const xml = `
|
||||
<urlset>
|
||||
<url><loc>https://owner.github.io/repo/</loc></url>
|
||||
<url><loc>https://owner.github.io/repo/plugins</loc></url>
|
||||
<url><loc>https://owner.github.io/repo/skill/agent-a</loc></url>
|
||||
<url><loc>https://owner.github.io/repo/skill/agent-b</loc></url>
|
||||
</urlset>
|
||||
@@ -56,12 +61,30 @@ describe('seo assets verification helpers', () => {
|
||||
const robots = `
|
||||
User-agent: *
|
||||
Allow: /
|
||||
User-agent: GPTBot
|
||||
Allow: /
|
||||
User-agent: OAI-SearchBot
|
||||
Allow: /
|
||||
User-agent: ClaudeBot
|
||||
Allow: /
|
||||
User-agent: PerplexityBot
|
||||
Allow: /
|
||||
Sitemap: https://example.com/sitemap.xml
|
||||
`;
|
||||
|
||||
expect(() => assertRobots(robots)).not.toThrow();
|
||||
});
|
||||
|
||||
it('requires llms.txt discovery signals', () => {
|
||||
const llms = `
|
||||
# Antigravity Awesome Skills
|
||||
1,494+ agentic skills with specialized plugins for Claude Code and Codex CLI.
|
||||
https://github.com/sickn33/antigravity-awesome-skills
|
||||
`;
|
||||
|
||||
expect(() => assertLlms(llms)).not.toThrow();
|
||||
});
|
||||
|
||||
it('requires social image tags in rendered index html', () => {
|
||||
const html = `
|
||||
<html>
|
||||
@@ -76,6 +99,52 @@ describe('seo assets verification helpers', () => {
|
||||
expect(() => assertIndexSocialMeta(html)).not.toThrow();
|
||||
});
|
||||
|
||||
it('requires current discovery copy in rendered index html', () => {
|
||||
const html = `
|
||||
<html>
|
||||
<head>
|
||||
<title>Antigravity Awesome Skills | 1,494+ AI coding skills and plugins</title>
|
||||
<meta name="description" content="Explore 1,494+ installable agentic skills, specialized plugins, bundles, and workflows." />
|
||||
<meta property="og:title" content="Antigravity Awesome Skills | 1,494+ AI coding skills and plugins" />
|
||||
<meta property="og:description" content="Explore 1,494+ installable agentic skills, specialized plugins, bundles, and workflows." />
|
||||
<meta name="twitter:title" content="Antigravity Awesome Skills | 1,494+ AI coding skills and plugins" />
|
||||
<meta name="twitter:description" content="Explore 1,494+ installable agentic skills, specialized plugins, bundles, and workflows." />
|
||||
<script type="application/ld+json">
|
||||
[
|
||||
{"@context":"https://schema.org","@type":"CollectionPage"},
|
||||
{"@context":"https://schema.org","@type":"Organization"},
|
||||
{"@context":"https://schema.org","@type":"WebSite"},
|
||||
{"@context":"https://schema.org","@type":"SoftwareSourceCode"},
|
||||
{"@context":"https://schema.org","@type":"FAQPage"}
|
||||
]
|
||||
</script>
|
||||
</head>
|
||||
</html>
|
||||
`;
|
||||
|
||||
expect(() => assertIndexDiscoveryMeta(html)).not.toThrow();
|
||||
});
|
||||
|
||||
it('requires plugin landing discovery copy in rendered plugin html', () => {
|
||||
const html = `
|
||||
<html>
|
||||
<head>
|
||||
<title>AAS Specialized Plugins | 15 AI coding workflow packs</title>
|
||||
<meta name="description" content="Compare 15 specialized plugin packs for web apps and security." />
|
||||
<meta property="og:title" content="AAS Specialized Plugins | AI coding workflow packs" />
|
||||
<script type="application/ld+json">
|
||||
[
|
||||
{"@context":"https://schema.org","@type":"CollectionPage"},
|
||||
{"@context":"https://schema.org","@type":"Organization"}
|
||||
]
|
||||
</script>
|
||||
</head>
|
||||
</html>
|
||||
`;
|
||||
|
||||
expect(() => assertPluginsDiscoveryMeta(html)).not.toThrow();
|
||||
});
|
||||
|
||||
it('validates prerendered skill route files when present', () => {
|
||||
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'seo-assets-'));
|
||||
const distDir = path.join(tmpDir, 'dist');
|
||||
@@ -94,6 +163,27 @@ describe('seo assets verification helpers', () => {
|
||||
expect(() => assertPrerenderedSkillRoutes(report.skillUrls, distDir, report.normalizedRootPath)).not.toThrow();
|
||||
});
|
||||
|
||||
it('validates prerendered plugin route files when present', () => {
|
||||
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'seo-assets-'));
|
||||
const distDir = path.join(tmpDir, 'dist');
|
||||
const routeFile = path.join(distDir, 'plugins', 'index.html');
|
||||
fs.mkdirSync(path.dirname(routeFile), { recursive: true });
|
||||
fs.writeFileSync(
|
||||
routeFile,
|
||||
'<html><head><title>AAS Specialized Plugins | 15 AI coding workflow packs</title><meta name="description" content="Compare 15 specialized plugin packs." /><meta property="og:title" content="AAS Specialized Plugins | AI coding workflow packs" /><script type="application/ld+json">[{"@context":"https://schema.org","@type":"CollectionPage"},{"@context":"https://schema.org","@type":"Organization"}]</script></head></html>',
|
||||
);
|
||||
|
||||
const xml = `
|
||||
<urlset>
|
||||
<url><loc>https://owner.github.io/repo/</loc></url>
|
||||
<url><loc>https://owner.github.io/repo/plugins</loc></url>
|
||||
</urlset>
|
||||
`;
|
||||
|
||||
const report = analyzeSitemap(xml, { minSkillUrls: 0 });
|
||||
expect(() => assertPrerenderedPluginRoutes(report.pluginUrls, distDir, report.normalizedRootPath)).not.toThrow();
|
||||
});
|
||||
|
||||
it('throws when a prerendered skill file is missing', () => {
|
||||
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'seo-assets-'));
|
||||
const distDir = path.join(tmpDir, 'dist');
|
||||
|
||||
Reference in New Issue
Block a user