Files
striker ffb3f94a57
Some checks failed
deploy / deploy (push) Failing after 13s
feat(seo): discoverability + Schema.org + IndexNow + Trivy (#1)
2026-05-21 14:25:14 +03:00

121 lines
4.2 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// IndexNow: уведомить Yandex/Bing о новых/обновлённых URL.
// Запускается после деплоя из CI или вручную:
// node scripts/indexnow.js
//
// IndexNow ключ хранится в файле public/<key>.txt — он должен быть доступен по
// тому же URL что и сайт, чтобы поисковики могли подтвердить ownership.
//
// Источник URL (по приоритету):
// 1) dist/sitemap-index.xml (Astro @astrojs/sitemap) + входящие sitemap-*.xml
// 2) dist/sitemap.txt / dist/sitemap.xml
// 3) fetch с прод-сайта: https://pushkinohistory.ru/sitemap-index.xml
// (для запуска из CI после удалённого деплоя — без локального build)
import fs from 'node:fs';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const root = path.resolve(__dirname, '..');
const BASE = 'https://pushkinohistory.ru';
const HOST = 'pushkinohistory.ru';
const KEY = '9018cf11050b4f379b8cec01ae3239bb';
// На случай если кто-то удалит файл — пересоздадим автоматически.
const keyFile = path.join(root, 'public', `${KEY}.txt`);
if (!fs.existsSync(keyFile)) {
fs.writeFileSync(keyFile, KEY, 'utf-8');
console.log(`created key file: public/${KEY}.txt`);
}
function extractLocs(xml) {
return [...xml.matchAll(/<loc>([^<]+)<\/loc>/g)].map((m) => m[1].trim());
}
let urls = [];
const distDir = path.join(root, 'dist');
const sitemapIndex = path.join(distDir, 'sitemap-index.xml');
const sitemapTxt = path.join(distDir, 'sitemap.txt');
const fallbackTxt = path.join(distDir, 'sitemap.xml');
if (fs.existsSync(sitemapIndex)) {
const idx = fs.readFileSync(sitemapIndex, 'utf-8');
const subs = extractLocs(idx);
for (const sub of subs) {
// sub — абсолютный URL вида https://pushkinohistory.ru/sitemap-0.xml
const fname = sub.split('/').pop();
const localPath = path.join(distDir, fname);
if (fs.existsSync(localPath)) {
const subXml = fs.readFileSync(localPath, 'utf-8');
urls.push(...extractLocs(subXml));
} else {
console.warn(` sub-sitemap not found locally: ${localPath}`);
}
}
} else if (fs.existsSync(sitemapTxt)) {
urls = fs
.readFileSync(sitemapTxt, 'utf-8')
.split(/\r?\n/)
.map((l) => l.trim())
.filter((l) => l.startsWith('http'));
} else if (fs.existsSync(fallbackTxt)) {
const xml = fs.readFileSync(fallbackTxt, 'utf-8');
urls = extractLocs(xml);
} else {
console.log('no local sitemap found, fetching from production…');
try {
const idxResp = await fetch(`${BASE}/sitemap-index.xml`);
if (!idxResp.ok) throw new Error(`HTTP ${idxResp.status}`);
const idxXml = await idxResp.text();
const subs = extractLocs(idxXml);
for (const sub of subs) {
const subResp = await fetch(sub);
if (!subResp.ok) {
console.warn(` ${sub}: HTTP ${subResp.status}`);
continue;
}
const subXml = await subResp.text();
urls.push(...extractLocs(subXml));
}
} catch (e) {
console.error(`failed to fetch sitemap from ${BASE}: ${e.message}`);
process.exit(1);
}
}
// Дедупликация и фильтрация на всякий случай.
urls = [...new Set(urls)].filter((u) => u.startsWith(BASE));
if (urls.length === 0) {
console.error('no URLs collected — nothing to submit');
process.exit(1);
}
console.log(`Submitting ${urls.length} URLs to IndexNow…`);
const payload = {
host: HOST,
key: KEY,
keyLocation: `${BASE}/${KEY}.txt`,
urlList: urls,
};
async function submit(endpoint) {
try {
const r = await fetch(endpoint, {
method: 'POST',
headers: { 'Content-Type': 'application/json; charset=utf-8' },
body: JSON.stringify(payload),
});
console.log(` ${endpoint}: HTTP ${r.status}`);
} catch (e) {
console.error(` ${endpoint}: ${e.message}`);
}
}
await Promise.all([
submit('https://yandex.com/indexnow'),
submit('https://api.indexnow.org/indexnow'),
]);