@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 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/route.ts`, `${appDir}/sitemap.xml/route.js`,
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/route.ts`, `${appDir}/robots.txt/route.js`,
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 { seoRobots } from "${importClient}";\nexport const revalidate = 300;\nexport default async function robots() {\n return seoRobots();\n}\n`,
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
- for (const m of routeExpr.matchAll(/\$\{(\w+)\}/g)) {
107
- if (!new RegExp("\\b" + m[1] + "\\b").test(code)) return { ok: false, reason: "no-param:" + m[1] };
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: ${routeExpr}, ${localePart}fallback: null })`, { plugins });
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@webnumseoagent/next",
3
- "version": "0.1.4",
3
+ "version": "0.1.6",
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",