@webnumseoagent/next 0.1.4 → 0.1.6
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 +8 -5
- package/bin/wrap.mjs +24 -3
- 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
|
@@ -119,7 +119,8 @@ async function init() {
|
|
|
119
119
|
// 2. sitemap.ts
|
|
120
120
|
const hasSitemap = await anyExists([
|
|
121
121
|
"public/sitemap.xml", "public/sitemap_index.xml",
|
|
122
|
-
`${appDir}/sitemap.ts`, `${appDir}/sitemap.js`, `${appDir}/sitemap.xml
|
|
122
|
+
`${appDir}/sitemap.ts`, `${appDir}/sitemap.js`, `${appDir}/sitemap.xml`,
|
|
123
|
+
`${appDir}/sitemap.xml/route.ts`, `${appDir}/sitemap.xml/route.js`,
|
|
123
124
|
]);
|
|
124
125
|
if (hasSitemap) skip("sitemap (у сайта уже есть — не трогаем)");
|
|
125
126
|
else await ensureFile(
|
|
@@ -127,15 +128,17 @@ async function init() {
|
|
|
127
128
|
`import { seoSitemap } from "${importClient}";\nexport const revalidate = 300;\nexport default async function sitemap() {\n return seoSitemap();\n}\n`,
|
|
128
129
|
);
|
|
129
130
|
|
|
130
|
-
// 3. robots
|
|
131
|
+
// 3. robots.txt (вариант B — byte-exact: отдаём сырой текст с платформы как есть).
|
|
132
|
+
// Если у сайта уже есть свой robots — НЕ трогаем (его можно передать платформе позже).
|
|
131
133
|
const hasRobots = await anyExists([
|
|
132
134
|
"public/robots.txt",
|
|
133
|
-
`${appDir}/robots.ts`, `${appDir}/robots.js`, `${appDir}/robots.txt
|
|
135
|
+
`${appDir}/robots.ts`, `${appDir}/robots.js`, `${appDir}/robots.txt`,
|
|
136
|
+
`${appDir}/robots.txt/route.ts`, `${appDir}/robots.txt/route.js`,
|
|
134
137
|
]);
|
|
135
138
|
if (hasRobots) skip("robots (у сайта уже есть — не трогаем)");
|
|
136
139
|
else await ensureFile(
|
|
137
|
-
path.join(root, appDir, `robots.${ext}`),
|
|
138
|
-
`import {
|
|
140
|
+
path.join(root, appDir, `robots.txt/route.${ext}`),
|
|
141
|
+
`import { seoRobotsTxt } from "${importClient}";\nexport const revalidate = 300;\nexport async function GET() {\n return new Response(await seoRobotsTxt(), { headers: { "content-type": "text/plain; charset=utf-8", "x-seoagent-robots": "1" } });\n}\n`,
|
|
139
142
|
);
|
|
140
143
|
|
|
141
144
|
// 4. revalidate-роут (мгновенное применение правок с платформы)
|
package/bin/wrap.mjs
CHANGED
|
@@ -103,16 +103,37 @@ export async function wrapFile(code, routeExpr, importSpec) {
|
|
|
103
103
|
// ── Путь 1: функция generateMetadata — оборачиваем её последний object-return ──
|
|
104
104
|
// locale опционально (мультиязычные — есть, одноязычные — нет).
|
|
105
105
|
const hasLocale = /\blocale\b/.test(code);
|
|
106
|
-
|
|
107
|
-
|
|
106
|
+
|
|
107
|
+
// База доступа к params: `generateMetadata({ params })` → "params";
|
|
108
|
+
// `generateMetadata(props)` → "props.params".
|
|
109
|
+
let paramsBase = "params";
|
|
110
|
+
const p0 = fn.params && fn.params[0];
|
|
111
|
+
if (p0) {
|
|
112
|
+
if (p0.type === "ObjectPattern") {
|
|
113
|
+
const pp = p0.properties.find((x) => x.key && x.key.name === "params");
|
|
114
|
+
if (pp && pp.value && pp.value.name) paramsBase = pp.value.name;
|
|
115
|
+
} else if (p0.type === "Identifier") {
|
|
116
|
+
paramsBase = p0.name + ".params";
|
|
117
|
+
}
|
|
108
118
|
}
|
|
119
|
+
|
|
120
|
+
// Динамические параметры маршрута: если параметр деструктурирован (есть как
|
|
121
|
+
// отдельная переменная) — используем его напрямую (${slug}); иначе — через
|
|
122
|
+
// объект params (${params.slug}). Это чинит страницы, где обращаются как params.X.
|
|
123
|
+
let effRoute = routeExpr;
|
|
124
|
+
for (const m of [...routeExpr.matchAll(/\$\{(\w+)\}/g)]) {
|
|
125
|
+
const name = m[1];
|
|
126
|
+
const standalone = new RegExp("(?<!\\.)\\b" + name + "\\b").test(code);
|
|
127
|
+
if (!standalone) effRoute = effRoute.replace("${" + name + "}", "${" + paramsBase + "." + name + "}");
|
|
128
|
+
}
|
|
129
|
+
|
|
109
130
|
const returns = collectReturns(fn.body, fn);
|
|
110
131
|
if (!returns.length) return { ok: false, reason: "no-object-return" };
|
|
111
132
|
const target = returns[returns.length - 1];
|
|
112
133
|
let call;
|
|
113
134
|
try {
|
|
114
135
|
const localePart = hasLocale ? "locale, " : "";
|
|
115
|
-
call = babelParser.parseExpression(`seoMeta({ route: ${
|
|
136
|
+
call = babelParser.parseExpression(`seoMeta({ route: ${effRoute}, ${localePart}fallback: null })`, { plugins });
|
|
116
137
|
} catch { return { ok: false, reason: "route-expr" }; }
|
|
117
138
|
call.arguments[0].properties.find((p) => p.key && p.key.name === "fallback").value = target.argument;
|
|
118
139
|
target.argument = call;
|
package/client.ts
CHANGED
|
@@ -112,6 +112,27 @@ export async function seoRobots(): Promise<MetadataRoute.Robots> {
|
|
|
112
112
|
return data ?? { rules: [{ userAgent: "*", allow: "/" }] };
|
|
113
113
|
}
|
|
114
114
|
|
|
115
|
+
// Сырой robots.txt (byte-exact, вариант B). Используется в app/robots.txt/route.ts:
|
|
116
|
+
// export async function GET() {
|
|
117
|
+
// return new Response(await seoRobotsTxt(), { headers: { "content-type": "text/plain" } });
|
|
118
|
+
// }
|
|
119
|
+
// Fail-safe: при недоступности платформы отдаём разрешающий robots (никогда не Disallow: /).
|
|
120
|
+
export async function seoRobotsTxt(): Promise<string> {
|
|
121
|
+
const FALLBACK = "User-agent: *\nAllow: /\n";
|
|
122
|
+
if (!API || !SITE || !TOKEN) return FALLBACK;
|
|
123
|
+
try {
|
|
124
|
+
const r = await fetch(`${API}/api/seo/${SITE}/robots.txt`, {
|
|
125
|
+
headers: { "x-seo-agent-key": TOKEN },
|
|
126
|
+
next: { revalidate: 300, tags: [`seo-${SITE}`] },
|
|
127
|
+
signal: AbortSignal.timeout(5000),
|
|
128
|
+
});
|
|
129
|
+
if (!r.ok) return FALLBACK;
|
|
130
|
+
return await r.text();
|
|
131
|
+
} catch {
|
|
132
|
+
return FALLBACK;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
115
136
|
// Для JSON-LD берём массив узлов конфига данного маршрута/локали (см. jsonld.tsx).
|
|
116
137
|
export async function seoJsonLd(route: string, locale?: string): Promise<unknown[]> {
|
|
117
138
|
const data = await fetchJson<{ fields: SeoFields }>(
|
package/dist/client.d.ts
CHANGED
|
@@ -6,4 +6,5 @@ export declare function seoMeta(opts: {
|
|
|
6
6
|
}): Promise<Metadata>;
|
|
7
7
|
export declare function seoSitemap(): Promise<MetadataRoute.Sitemap>;
|
|
8
8
|
export declare function seoRobots(): Promise<MetadataRoute.Robots>;
|
|
9
|
+
export declare function seoRobotsTxt(): Promise<string>;
|
|
9
10
|
export declare function seoJsonLd(route: string, locale?: string): Promise<unknown[]>;
|
package/dist/client.js
CHANGED
|
@@ -14,6 +14,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
14
14
|
exports.seoMeta = seoMeta;
|
|
15
15
|
exports.seoSitemap = seoSitemap;
|
|
16
16
|
exports.seoRobots = seoRobots;
|
|
17
|
+
exports.seoRobotsTxt = seoRobotsTxt;
|
|
17
18
|
exports.seoJsonLd = seoJsonLd;
|
|
18
19
|
const API = process.env.SEOAGENT_API_BASE ?? "";
|
|
19
20
|
const SITE = process.env.SEOAGENT_SITE_ID ?? "";
|
|
@@ -99,6 +100,29 @@ async function seoRobots() {
|
|
|
99
100
|
// Fail-safe: при недоступности отдаём разрешающий robots (никогда не Disallow: /).
|
|
100
101
|
return data ?? { rules: [{ userAgent: "*", allow: "/" }] };
|
|
101
102
|
}
|
|
103
|
+
// Сырой robots.txt (byte-exact, вариант B). Используется в app/robots.txt/route.ts:
|
|
104
|
+
// export async function GET() {
|
|
105
|
+
// return new Response(await seoRobotsTxt(), { headers: { "content-type": "text/plain" } });
|
|
106
|
+
// }
|
|
107
|
+
// Fail-safe: при недоступности платформы отдаём разрешающий robots (никогда не Disallow: /).
|
|
108
|
+
async function seoRobotsTxt() {
|
|
109
|
+
const FALLBACK = "User-agent: *\nAllow: /\n";
|
|
110
|
+
if (!API || !SITE || !TOKEN)
|
|
111
|
+
return FALLBACK;
|
|
112
|
+
try {
|
|
113
|
+
const r = await fetch(`${API}/api/seo/${SITE}/robots.txt`, {
|
|
114
|
+
headers: { "x-seo-agent-key": TOKEN },
|
|
115
|
+
next: { revalidate: 300, tags: [`seo-${SITE}`] },
|
|
116
|
+
signal: AbortSignal.timeout(5000),
|
|
117
|
+
});
|
|
118
|
+
if (!r.ok)
|
|
119
|
+
return FALLBACK;
|
|
120
|
+
return await r.text();
|
|
121
|
+
}
|
|
122
|
+
catch {
|
|
123
|
+
return FALLBACK;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
102
126
|
// Для JSON-LD берём массив узлов конфига данного маршрута/локали (см. jsonld.tsx).
|
|
103
127
|
async function seoJsonLd(route, locale) {
|
|
104
128
|
const data = await fetchJson(`/config?route=${encodeURIComponent(route)}&locale=${encodeURIComponent(locale ?? "")}`);
|
package/package.json
CHANGED