@webnumseoagent/next 0.1.24 → 0.1.25
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 +17 -5
- package/client.ts +21 -0
- package/dist/client.d.ts +1 -0
- package/dist/client.js +24 -0
- package/package.json +1 -1
package/bin/cli.mjs
CHANGED
|
@@ -97,13 +97,17 @@ async function init() {
|
|
|
97
97
|
ok("Пакет @webnumseoagent/next установлен — импорт из пакета.");
|
|
98
98
|
} else {
|
|
99
99
|
const vendor = path.join(root, "seoagent");
|
|
100
|
+
await fs.mkdir(vendor, { recursive: true });
|
|
101
|
+
// Вендор-файлы адаптера — генерируемые (их не правят руками): при повторном init ОБНОВЛЯЕМ,
|
|
102
|
+
// если содержимое изменилось. Иначе апгрейд оставит старый client.ts без новых экспортов
|
|
103
|
+
// (напр. seoMerchantFeedXml), а свежесозданный роут импортнёт несуществующий символ → build падёт.
|
|
100
104
|
for (const f of ["client.ts", "jsonld.tsx", "sitemap-xsl.ts", "sitemap-build.ts", "redirects.ts"]) {
|
|
101
105
|
const dest = path.join(vendor, f);
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
106
|
+
const src = await fs.readFile(path.join(pkgRoot, f));
|
|
107
|
+
const cur = await fs.readFile(dest).catch(() => null);
|
|
108
|
+
if (cur && cur.equals(src)) { skip(`seoagent/${f}`); continue; }
|
|
109
|
+
await fs.writeFile(dest, src);
|
|
110
|
+
ok(`seoagent/${f}${cur ? " (обновлён)" : ""}`);
|
|
107
111
|
}
|
|
108
112
|
const imp = importPath(appDir, "sitemap.ts");
|
|
109
113
|
importClient = `${imp}/client`;
|
|
@@ -231,6 +235,14 @@ async function init() {
|
|
|
231
235
|
`import { seoIndexNowKey } from "${clientFor("seoagent-indexnow.txt/route.ts")}";\nexport const dynamic = "force-dynamic";\nexport async function GET() {\n return new Response(await seoIndexNowKey(), { headers: { "content-type": "text/plain; charset=utf-8" } });\n}\n`,
|
|
232
236
|
);
|
|
233
237
|
|
|
238
|
+
// 4.3 Google Merchant Center — товарный фид /seoagent-merchant-feed.xml (RSS 2.0 + g:).
|
|
239
|
+
// Адаптер лишь проксирует XML с платформы (та собирает его из индекса товаров сайта).
|
|
240
|
+
// Пустой валидный RSS, пока фид не включён в настройках — роут никогда не отдаёт 404.
|
|
241
|
+
await ensureFile(
|
|
242
|
+
path.join(root, appDir, `seoagent-merchant-feed.xml/route.${ext}`),
|
|
243
|
+
`import { seoMerchantFeedXml } from "${clientFor("seoagent-merchant-feed.xml/route.ts")}";\nexport const revalidate = 300;\nexport async function GET() {\n return new Response(await seoMerchantFeedXml("/merchant-feed.xml"), { headers: { "content-type": "application/xml; charset=utf-8", "x-seoagent-merchant": "1" } });\n}\n`,
|
|
244
|
+
);
|
|
245
|
+
|
|
234
246
|
// 4.5 middleware (переадресации). Next разрешает ОДИН middleware, а авто-слияние чужого
|
|
235
247
|
// небезопасно → создаём свой, только если своего нет; иначе печатаем ручную инструкцию.
|
|
236
248
|
{
|
package/client.ts
CHANGED
|
@@ -254,6 +254,27 @@ export async function seoSitemapXml(path: string): Promise<string> {
|
|
|
254
254
|
}
|
|
255
255
|
}
|
|
256
256
|
|
|
257
|
+
// Товарный фид Google Merchant Center (RSS 2.0 + g:-namespace): сырой XML с платформы.
|
|
258
|
+
// Платформа собирает его из закэшированного индекса товаров (быстро, без живого краула).
|
|
259
|
+
// app/seoagent-merchant-feed.xml/route.ts:
|
|
260
|
+
// export async function GET() { return new Response(await seoMerchantFeedXml("/merchant-feed.xml"), {headers:{"content-type":"application/xml"}}) }
|
|
261
|
+
export async function seoMerchantFeedXml(path: string): Promise<string> {
|
|
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`;
|
|
264
|
+
if (!API || !SITE || !TOKEN) return FALLBACK;
|
|
265
|
+
try {
|
|
266
|
+
const r = await fetch(`${API}/api/seo/${SITE}${path}`, {
|
|
267
|
+
headers: { "x-seo-agent-key": TOKEN },
|
|
268
|
+
next: { revalidate: 300, tags: [`seo-${SITE}`] },
|
|
269
|
+
signal: AbortSignal.timeout(8000),
|
|
270
|
+
});
|
|
271
|
+
if (!r.ok) return FALLBACK;
|
|
272
|
+
return await r.text();
|
|
273
|
+
} catch {
|
|
274
|
+
return FALLBACK;
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
|
|
257
278
|
export { SITEMAP_XSL } from "./sitemap-xsl";
|
|
258
279
|
export { seoWrapSitemap } from "./sitemap-build";
|
|
259
280
|
|
package/dist/client.d.ts
CHANGED
|
@@ -5,6 +5,7 @@ export declare function seoMeta(opts: {
|
|
|
5
5
|
fallback?: Metadata;
|
|
6
6
|
}): Promise<Metadata>;
|
|
7
7
|
export declare function seoSitemapXml(path: string): Promise<string>;
|
|
8
|
+
export declare function seoMerchantFeedXml(path: string): Promise<string>;
|
|
8
9
|
export { SITEMAP_XSL } from "./sitemap-xsl";
|
|
9
10
|
export { seoWrapSitemap } from "./sitemap-build";
|
|
10
11
|
export declare function seoSitemap(): Promise<MetadataRoute.Sitemap>;
|
package/dist/client.js
CHANGED
|
@@ -14,6 +14,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
14
14
|
exports.seoWrapSitemap = exports.SITEMAP_XSL = void 0;
|
|
15
15
|
exports.seoMeta = seoMeta;
|
|
16
16
|
exports.seoSitemapXml = seoSitemapXml;
|
|
17
|
+
exports.seoMerchantFeedXml = seoMerchantFeedXml;
|
|
17
18
|
exports.seoSitemap = seoSitemap;
|
|
18
19
|
exports.seoRobots = seoRobots;
|
|
19
20
|
exports.seoRobotsTxt = seoRobotsTxt;
|
|
@@ -255,6 +256,29 @@ async function seoSitemapXml(path) {
|
|
|
255
256
|
return FALLBACK;
|
|
256
257
|
}
|
|
257
258
|
}
|
|
259
|
+
// Товарный фид Google Merchant Center (RSS 2.0 + g:-namespace): сырой XML с платформы.
|
|
260
|
+
// Платформа собирает его из закэшированного индекса товаров (быстро, без живого краула).
|
|
261
|
+
// app/seoagent-merchant-feed.xml/route.ts:
|
|
262
|
+
// export async function GET() { return new Response(await seoMerchantFeedXml("/merchant-feed.xml"), {headers:{"content-type":"application/xml"}}) }
|
|
263
|
+
async function seoMerchantFeedXml(path) {
|
|
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`;
|
|
266
|
+
if (!API || !SITE || !TOKEN)
|
|
267
|
+
return FALLBACK;
|
|
268
|
+
try {
|
|
269
|
+
const r = await fetch(`${API}/api/seo/${SITE}${path}`, {
|
|
270
|
+
headers: { "x-seo-agent-key": TOKEN },
|
|
271
|
+
next: { revalidate: 300, tags: [`seo-${SITE}`] },
|
|
272
|
+
signal: AbortSignal.timeout(8000),
|
|
273
|
+
});
|
|
274
|
+
if (!r.ok)
|
|
275
|
+
return FALLBACK;
|
|
276
|
+
return await r.text();
|
|
277
|
+
}
|
|
278
|
+
catch {
|
|
279
|
+
return FALLBACK;
|
|
280
|
+
}
|
|
281
|
+
}
|
|
258
282
|
var sitemap_xsl_1 = require("./sitemap-xsl");
|
|
259
283
|
Object.defineProperty(exports, "SITEMAP_XSL", { enumerable: true, get: function () { return sitemap_xsl_1.SITEMAP_XSL; } });
|
|
260
284
|
var sitemap_build_1 = require("./sitemap-build");
|
package/package.json
CHANGED