@webnumseoagent/next 0.2.0 → 0.2.2

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/bin/cli.mjs CHANGED
@@ -370,13 +370,14 @@ async function init() {
370
370
  log(`\n И смонтируй JSON-LD в layout (внутри <body>):\n \x1b[36mimport { SeoJsonLd } from "${importJsonld}";\n <SeoJsonLd route="/" />\x1b[0m`);
371
371
  }
372
372
 
373
- // 7. Блог (пакет-рендер, Фаза 3.1c) — ОПТ-ИН по флагу --blog: ставит роуты /blog и /blog/[slug].
374
- // ПОСЛЕ авто-обёртки generateMetadata: у блог-роута свой generateMetadata (seoBlogPost), его НЕ
375
- // оборачиваем seoMeta. Компоненты сами тянут конфиг+статьи; тело статьи уже санитайзено (доставка).
376
- if (args.includes("--blog")) {
373
+ // 7. Блог (пакет-рендер, Фаза 3.1c) — ПО УМОЛЧАНИЮ: ставит роуты /blog и /blog/[slug] прямо при
374
+ // подключении сайта (opt-out флагом --no-blog). ensureFile идемпотентен и НЕ перезаписывает файлы,
375
+ // поэтому собственный блог сайта (свой app/blog/page.*) остаётся нетронутым. ПОСЛЕ авто-обёртки
376
+ // generateMetadata: у блог-роута свой generateMetadata (seoBlogPost), его НЕ оборачиваем seoMeta.
377
+ if (!args.includes("--no-blog")) {
377
378
  const jsxExt = ext === "ts" ? "tsx" : "jsx";
378
379
  const mdParam = ext === "ts" ? "{ params }: { params: { slug: string } }" : "{ params }";
379
- await ensureFile(
380
+ const madeIndex = await ensureFile(
380
381
  path.join(root, appDir, `blog/page.${jsxExt}`),
381
382
  `import { SeoBlogIndex } from "${blogFor("blog/page.tsx")}";\nexport const revalidate = 300;\nexport default function BlogPage() {\n return <SeoBlogIndex title="Блог" />;\n}\n`,
382
383
  );
@@ -388,16 +389,21 @@ async function init() {
388
389
  const names = await fs.readdir(blogDir).catch(() => []);
389
390
  blogDynConflict = names.some((n) => /^\[.*\]$/.test(n) && n !== "[slug]");
390
391
  }
392
+ let madeArticle = false;
391
393
  if (blogDynConflict) {
392
- log(`\n \x1b[1mУ сайта уже есть app/${appDir === "src/app" ? "" : ""}blog/[иной-сегмент]\x1b[0m — роут статьи /blog/[slug] не создаю (Next запрещает два динамических сегмента).`);
394
+ log(`\n \x1b[1mУ сайта уже есть app/blog/[иной-сегмент]\x1b[0m — роут статьи /blog/[slug] не создаю (Next запрещает два динамических сегмента).`);
393
395
  log(` Подключи статьи к своему сегменту вручную: \x1b[36mimport { seoBlogPost, SeoBlogArticle } from "${blogFor("blog/[slug]/page.tsx")}";\x1b[0m`);
394
396
  } else {
395
- await ensureFile(
397
+ madeArticle = await ensureFile(
396
398
  path.join(root, appDir, `blog/[slug]/page.${jsxExt}`),
397
399
  `import { notFound } from "next/navigation";\nimport { seoBlogPost, SeoBlogArticle } from "${blogFor("blog/[slug]/page.tsx")}";\nexport const revalidate = 300;\n\nexport async function generateMetadata(${mdParam}) {\n const post = await seoBlogPost(params.slug);\n if (!post) return {};\n return { title: post.title, description: post.excerpt, openGraph: { images: post.cover ? [post.cover] : [] } };\n}\n\nexport default async function BlogPostPage(${mdParam}) {\n const post = await seoBlogPost(params.slug);\n if (!post) notFound();\n return <SeoBlogArticle post={post} />;\n}\n`,
398
400
  );
399
401
  }
400
- log(`\n \x1b[32m✓\x1b[0m Блог: роут /blog (пакет-рендер). Настрой доставку+anon-ключ на платформе.`);
402
+ if (madeIndex || madeArticle) {
403
+ log(`\n \x1b[32m✓\x1b[0m Блог: роуты /blog (пакет-рендер) созданы. Настрой доставку + anon-ключ на платформе. (Отключить блог: \x1b[1m--no-blog\x1b[0m)`);
404
+ } else {
405
+ log(`\n \x1b[33mℹ\x1b[0m Блог: у сайта уже есть роуты /blog — не трогаю.`);
406
+ }
401
407
  }
402
408
 
403
409
  log("\n\x1b[1mЗатем:\x1b[0m задай эти же env-переменные в Vercel (Settings → Environment Variables) и сделай git push.\n");
@@ -466,5 +472,7 @@ async function findMetadataPages(dir, acc = []) {
466
472
  if (cmd === "init") {
467
473
  init().catch((e) => { console.error(e); process.exit(1); });
468
474
  } else {
469
- log("Использование: npx @webnumseoagent/next init [--site <id>] [--token <t>] [--api <url>] [--revalidate-secret <s>]");
475
+ log("Использование: npx @webnumseoagent/next init [--wrap] [--no-blog] [--site <id>] [--token <t>] [--api <url>] [--revalidate-secret <s>]");
476
+ log(" --wrap — авто-обернуть generateMetadata страниц (иначе печатает сниппеты)");
477
+ log(" --no-blog — не создавать роуты блога /blog (по умолчанию блог создаётся)");
470
478
  }
package/client.ts CHANGED
@@ -260,7 +260,7 @@ export async function seoSitemapXml(path: string): Promise<string> {
260
260
  // export async function GET() { return new Response(await seoMerchantFeedXml("/merchant-feed.xml"), {headers:{"content-type":"application/xml"}}) }
261
261
  export async function seoMerchantFeedXml(path: string): Promise<string> {
262
262
  // Валидный RSS-канал (title/link/description обязательны в RSS 2.0) с пустым списком товаров.
263
- const FALLBACK = `<?xml version="1.0" encoding="UTF-8"?>\n<rss version="2.0" xmlns:g="http://base.google.com/ns/1.0">\n<channel>\n <title>Merchant feed</title>\n <link>https://seonum.uz</link>\n <description>Merchant feed temporarily unavailable</description>\n</channel>\n</rss>\n`;
263
+ const FALLBACK = `<?xml version="1.0" encoding="UTF-8"?>\n<rss version="2.0" xmlns:g="http://base.google.com/ns/1.0">\n<channel>\n <title>Merchant feed</title>\n <link></link>\n <description>Merchant feed temporarily unavailable</description>\n</channel>\n</rss>\n`;
264
264
  if (!API || !SITE || !TOKEN) return FALLBACK;
265
265
  try {
266
266
  const r = await fetch(`${API}/api/seo/${SITE}${path}`, {
package/dist/client.js CHANGED
@@ -262,7 +262,7 @@ async function seoSitemapXml(path) {
262
262
  // export async function GET() { return new Response(await seoMerchantFeedXml("/merchant-feed.xml"), {headers:{"content-type":"application/xml"}}) }
263
263
  async function seoMerchantFeedXml(path) {
264
264
  // Валидный RSS-канал (title/link/description обязательны в RSS 2.0) с пустым списком товаров.
265
- const FALLBACK = `<?xml version="1.0" encoding="UTF-8"?>\n<rss version="2.0" xmlns:g="http://base.google.com/ns/1.0">\n<channel>\n <title>Merchant feed</title>\n <link>https://seonum.uz</link>\n <description>Merchant feed temporarily unavailable</description>\n</channel>\n</rss>\n`;
265
+ const FALLBACK = `<?xml version="1.0" encoding="UTF-8"?>\n<rss version="2.0" xmlns:g="http://base.google.com/ns/1.0">\n<channel>\n <title>Merchant feed</title>\n <link></link>\n <description>Merchant feed temporarily unavailable</description>\n</channel>\n</rss>\n`;
266
266
  if (!API || !SITE || !TOKEN)
267
267
  return FALLBACK;
268
268
  try {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@webnumseoagent/next",
3
- "version": "0.2.0",
3
+ "version": "0.2.2",
4
4
  "description": "Runtime SEO adapter + installer for Next.js App Router sites (SEO Agent platform)",
5
5
  "main": "dist/client.js",
6
6
  "types": "dist/client.d.ts",