@uptrademedia/site-kit 1.0.28 → 1.0.29
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/dist/{chunk-FRSN2JKU.js → chunk-6NTMCSHP.js} +15 -18
- package/dist/chunk-6NTMCSHP.js.map +1 -0
- package/dist/{chunk-WMOA3332.js → chunk-FOBATMSH.js} +70 -55
- package/dist/chunk-FOBATMSH.js.map +1 -0
- package/dist/{chunk-K2PERQLP.mjs → chunk-MLY7AWHG.mjs} +70 -55
- package/dist/chunk-MLY7AWHG.mjs.map +1 -0
- package/dist/{chunk-HHAJAANV.mjs → chunk-QY7CDW6P.mjs} +189 -34
- package/dist/chunk-QY7CDW6P.mjs.map +1 -0
- package/dist/{chunk-JOAULVQB.mjs → chunk-WG2SI2UN.mjs} +15 -18
- package/dist/chunk-WG2SI2UN.mjs.map +1 -0
- package/dist/{chunk-RM4XFDE6.js → chunk-ZGT5ZYQ5.js} +189 -34
- package/dist/chunk-ZGT5ZYQ5.js.map +1 -0
- package/dist/commerce/index.d.mts +1 -1
- package/dist/commerce/index.d.ts +1 -1
- package/dist/commerce/index.js +39 -39
- package/dist/commerce/index.mjs +1 -1
- package/dist/index.d.mts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +39 -35
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +15 -11
- package/dist/index.mjs.map +1 -1
- package/dist/{routing-D6bSzuw-.d.ts → routing-CHmSC8p0.d.ts} +1 -1
- package/dist/{routing-nObgWX16.d.mts → routing-Cy9vtQq8.d.mts} +1 -1
- package/dist/seo/index.d.mts +28 -28
- package/dist/seo/index.d.ts +28 -28
- package/dist/seo/index.js +96 -245
- package/dist/seo/index.js.map +1 -1
- package/dist/seo/index.mjs +25 -224
- package/dist/seo/index.mjs.map +1 -1
- package/dist/seo/register-sitemap-cli.js +3 -3
- package/dist/seo/register-sitemap-cli.mjs +2 -2
- package/dist/seo/server.d.mts +2 -2
- package/dist/seo/server.d.ts +2 -2
- package/dist/seo/server.js +185 -44
- package/dist/seo/server.js.map +1 -1
- package/dist/seo/server.mjs +183 -2
- package/dist/seo/server.mjs.map +1 -1
- package/dist/{api-EXKDAYGB.mjs → server-api-3HJLLZB7.mjs} +3 -3
- package/dist/server-api-3HJLLZB7.mjs.map +1 -0
- package/dist/{api-HBENRDUH.js → server-api-RM25RPFH.js} +20 -20
- package/dist/server-api-RM25RPFH.js.map +1 -0
- package/dist/{types-Cb9d3lkc.d.mts → types-CwyWiHtq.d.mts} +22 -11
- package/dist/{types-Cb9d3lkc.d.ts → types-CwyWiHtq.d.ts} +22 -11
- package/package.json +1 -1
- package/dist/api-EXKDAYGB.mjs.map +0 -1
- package/dist/api-HBENRDUH.js.map +0 -1
- package/dist/chunk-FRSN2JKU.js.map +0 -1
- package/dist/chunk-HHAJAANV.mjs.map +0 -1
- package/dist/chunk-JOAULVQB.mjs.map +0 -1
- package/dist/chunk-K2PERQLP.mjs.map +0 -1
- package/dist/chunk-RM4XFDE6.js.map +0 -1
- package/dist/chunk-WMOA3332.js.map +0 -1
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
var
|
|
3
|
+
var chunkFOBATMSH_js = require('./chunk-FOBATMSH.js');
|
|
4
4
|
|
|
5
5
|
// src/seo/routing.ts
|
|
6
6
|
async function getRedirect(options) {
|
|
7
|
-
const {
|
|
8
|
-
const redirect = await
|
|
7
|
+
const { path } = options;
|
|
8
|
+
const redirect = await chunkFOBATMSH_js.getRedirectData(path);
|
|
9
9
|
if (!redirect) {
|
|
10
10
|
return null;
|
|
11
11
|
}
|
|
@@ -46,27 +46,27 @@ function parseRobotsString(robots) {
|
|
|
46
46
|
return directive;
|
|
47
47
|
}
|
|
48
48
|
async function getRobotsDirective(options) {
|
|
49
|
-
const {
|
|
50
|
-
const robotsString = await
|
|
49
|
+
const { path } = options;
|
|
50
|
+
const robotsString = await chunkFOBATMSH_js.getRobotsData(path);
|
|
51
51
|
if (!robotsString) {
|
|
52
52
|
return { index: true, follow: true };
|
|
53
53
|
}
|
|
54
54
|
return parseRobotsString(robotsString);
|
|
55
55
|
}
|
|
56
56
|
async function generateSitemap(options) {
|
|
57
|
-
const {
|
|
58
|
-
const pages = await
|
|
57
|
+
const { baseUrl, publishedOnly = true } = options;
|
|
58
|
+
const pages = await chunkFOBATMSH_js.getSitemapEntries({ publishedOnly });
|
|
59
59
|
const normalizedBase = baseUrl.endsWith("/") ? baseUrl.slice(0, -1) : baseUrl;
|
|
60
60
|
return pages.map((page) => ({
|
|
61
61
|
path: page.path,
|
|
62
62
|
url: `${normalizedBase}${page.path}`,
|
|
63
|
-
lastmod: page.
|
|
64
|
-
changefreq: page.
|
|
65
|
-
priority: page.
|
|
63
|
+
lastmod: page.lastmod,
|
|
64
|
+
changefreq: page.changefreq || "weekly",
|
|
65
|
+
priority: page.priority ?? 0.5
|
|
66
66
|
}));
|
|
67
67
|
}
|
|
68
68
|
async function registerLocalSitemap(options) {
|
|
69
|
-
const { registerSitemap } = await import('./api-
|
|
69
|
+
const { registerSitemap } = await import('./server-api-RM25RPFH.js');
|
|
70
70
|
let entries = options.entries || [];
|
|
71
71
|
if (options.autoDiscover && entries.length === 0) {
|
|
72
72
|
try {
|
|
@@ -86,10 +86,7 @@ async function registerLocalSitemap(options) {
|
|
|
86
86
|
return { success: true, created: 0, updated: 0 };
|
|
87
87
|
}
|
|
88
88
|
console.log(`[Uptrade] Registering ${entries.length} sitemap entries...`);
|
|
89
|
-
const result = await registerSitemap(entries
|
|
90
|
-
optimize_meta: options.optimize_meta !== false
|
|
91
|
-
// Default to true
|
|
92
|
-
});
|
|
89
|
+
const result = await registerSitemap(entries);
|
|
93
90
|
if (result.success) {
|
|
94
91
|
console.log(`[Uptrade] Sitemap registered: ${result.created} new, ${result.updated} updated`);
|
|
95
92
|
}
|
|
@@ -130,7 +127,7 @@ function discoverNextJsRoutes(appDir, fs, path, basePath = "") {
|
|
|
130
127
|
return entries;
|
|
131
128
|
}
|
|
132
129
|
async function isIndexable(projectId, path) {
|
|
133
|
-
const directive = await getRobotsDirective({
|
|
130
|
+
const directive = await getRobotsDirective({ path });
|
|
134
131
|
return directive.index;
|
|
135
132
|
}
|
|
136
133
|
|
|
@@ -139,5 +136,5 @@ exports.getRedirect = getRedirect;
|
|
|
139
136
|
exports.getRobotsDirective = getRobotsDirective;
|
|
140
137
|
exports.isIndexable = isIndexable;
|
|
141
138
|
exports.registerLocalSitemap = registerLocalSitemap;
|
|
142
|
-
//# sourceMappingURL=chunk-
|
|
143
|
-
//# sourceMappingURL=chunk-
|
|
139
|
+
//# sourceMappingURL=chunk-6NTMCSHP.js.map
|
|
140
|
+
//# sourceMappingURL=chunk-6NTMCSHP.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/seo/routing.ts"],"names":["getRedirectData","getRobotsData","getSitemapEntries"],"mappings":"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","file":"chunk-6NTMCSHP.js","sourcesContent":["import { getRedirectData, getRobotsData, getSitemapEntries } from './server-api'\nimport type { \n GetRedirectOptions, \n RedirectResult, \n GetRobotsOptions, \n RobotsDirective,\n GetSitemapEntriesOptions,\n SitemapEntry \n} from './types'\n\n/**\n * Get redirect for a path if one exists\n * \n * Use in Next.js middleware to handle managed redirects\n * \n * @example\n * ```tsx\n * // middleware.ts\n * import { getRedirect } from '@uptrade/seo'\n * \n * export async function middleware(request) {\n * const redirect = await getRedirect({\n * projectId: process.env.UPTRADE_PROJECT_ID!,\n * path: request.nextUrl.pathname\n * })\n * \n * if (redirect) {\n * return NextResponse.redirect(redirect.destination, redirect.statusCode)\n * }\n * }\n * ```\n */\nexport async function getRedirect(\n options: GetRedirectOptions\n): Promise<RedirectResult | null> {\n const { path } = options\n\n const redirect = await getRedirectData(path)\n\n if (!redirect) {\n return null\n }\n\n // Check if expired\n if (redirect.expires_at && new Date(redirect.expires_at) < new Date()) {\n return null\n }\n\n // Determine destination\n const destination = redirect.destination_url || redirect.destination_path\n const isExternal = destination.startsWith('http://') || destination.startsWith('https://')\n\n return {\n destination,\n statusCode: redirect.status_code,\n isExternal,\n }\n}\n\n/**\n * Parse robots directive string into structured object\n */\nfunction parseRobotsString(robots: string): RobotsDirective {\n const directive: RobotsDirective = {\n index: true,\n follow: true,\n }\n\n const parts = robots.toLowerCase().split(',').map(p => p.trim())\n\n for (const part of parts) {\n if (part === 'noindex') directive.index = false\n if (part === 'nofollow') directive.follow = false\n if (part === 'noarchive') directive.noarchive = true\n if (part === 'nosnippet') directive.nosnippet = true\n if (part === 'noimageindex') directive.noimageindex = true\n if (part.startsWith('max-snippet:')) {\n directive.max_snippet = parseInt(part.split(':')[1], 10)\n }\n if (part.startsWith('max-image-preview:')) {\n const value = part.split(':')[1] as 'none' | 'standard' | 'large'\n directive.max_image_preview = value\n }\n if (part.startsWith('max-video-preview:')) {\n directive.max_video_preview = parseInt(part.split(':')[1], 10)\n }\n }\n\n return directive\n}\n\n/**\n * Get robots directive for a page\n * \n * @example\n * ```tsx\n * const robots = await getRobotsDirective({\n * projectId: process.env.UPTRADE_PROJECT_ID!,\n * path: '/private-page'\n * })\n * \n * if (!robots.index) {\n * // Page should not be indexed\n * }\n * ```\n */\nexport async function getRobotsDirective(\n options: GetRobotsOptions\n): Promise<RobotsDirective> {\n const { path } = options\n\n const robotsString = await getRobotsData(path)\n\n if (!robotsString) {\n // Default: index and follow\n return { index: true, follow: true }\n }\n\n return parseRobotsString(robotsString)\n}\n\n/**\n * Get sitemap entries for a project\n * \n * Use in sitemap.ts to generate dynamic sitemap\n * \n * @example\n * ```tsx\n * // app/sitemap.ts\n * import { generateSitemap } from '@uptrade/seo'\n * \n * export default async function sitemap() {\n * return generateSitemap({\n * projectId: process.env.UPTRADE_PROJECT_ID!,\n * baseUrl: 'https://example.com',\n * publishedOnly: true\n * })\n * }\n * ```\n */\nexport async function generateSitemap(\n options: GetSitemapEntriesOptions\n): Promise<SitemapEntry[]> {\n const { baseUrl, publishedOnly = true } = options\n\n const pages = await getSitemapEntries({ publishedOnly })\n\n const normalizedBase = baseUrl.endsWith('/') ? baseUrl.slice(0, -1) : baseUrl\n\n return pages.map((page: { path: string; lastmod?: string; changefreq?: string; priority?: number }) => ({\n path: page.path,\n url: `${normalizedBase}${page.path}`,\n lastmod: page.lastmod,\n changefreq: (page.changefreq || 'weekly') as SitemapEntry['changefreq'],\n priority: page.priority ?? 0.5,\n }))\n}\n\n/**\n * Register local sitemap entries with Uptrade Portal\n * \n * Call this at build time to sync your local routes to seo_pages.\n * This ensures analytics only tracks real pages.\n * \n * After registration, Signal AI will generate optimized meta titles\n * and descriptions for pages that don't have managed meta yet.\n * \n * @example\n * ```ts\n * // scripts/register-sitemap.ts\n * import { registerLocalSitemap } from '@uptrade/seo'\n * \n * // Option 1: Provide entries directly\n * await registerLocalSitemap({\n * entries: [\n * { path: '/', title: 'Home', priority: 1.0 },\n * { path: '/about', title: 'About Us', priority: 0.8 },\n * ]\n * })\n * \n * // Option 2: Auto-discover from Next.js app directory\n * await registerLocalSitemap({ autoDiscover: true })\n * \n * // Option 3: Skip Signal AI meta optimization\n * await registerLocalSitemap({ autoDiscover: true, optimize_meta: false })\n * ```\n */\nexport async function registerLocalSitemap(options: {\n entries?: Array<{\n path: string\n title?: string\n priority?: number\n changefreq?: 'always' | 'hourly' | 'daily' | 'weekly' | 'monthly' | 'yearly' | 'never'\n }>\n autoDiscover?: boolean\n /** Trigger Signal AI to generate optimized meta titles/descriptions (default: true) */\n optimize_meta?: boolean\n}): Promise<{ \n success: boolean\n created: number\n updated: number\n removed?: number\n meta_optimization?: {\n triggered: boolean\n pages_queued: number\n } | null\n}> {\n const { registerSitemap } = await import('./server-api')\n \n let entries = options.entries || []\n \n // Auto-discover from Next.js app directory if requested\n if (options.autoDiscover && entries.length === 0) {\n try {\n const fs = await import('fs')\n const path = await import('path')\n \n const appDir = path.join(process.cwd(), 'app')\n if (fs.existsSync(appDir)) {\n entries = discoverNextJsRoutes(appDir, fs, path)\n console.log(`[Uptrade] Auto-discovered ${entries.length} routes from app directory`)\n }\n } catch (error) {\n console.error('[Uptrade] Auto-discovery failed:', error)\n }\n }\n \n if (entries.length === 0) {\n console.warn('[Uptrade] No sitemap entries to register')\n return { success: true, created: 0, updated: 0 }\n }\n \n console.log(`[Uptrade] Registering ${entries.length} sitemap entries...`)\n const result = await registerSitemap(entries)\n \n if (result.success) {\n console.log(`[Uptrade] Sitemap registered: ${result.created} new, ${result.updated} updated`)\n }\n \n return result\n}\n\n/**\n * Discover routes from Next.js app directory\n */\nfunction discoverNextJsRoutes(\n appDir: string,\n fs: typeof import('fs'),\n path: typeof import('path'),\n basePath: string = ''\n): Array<{ path: string; priority: number }> {\n const entries: Array<{ path: string; priority: number }> = []\n \n const items = fs.readdirSync(appDir, { withFileTypes: true })\n \n for (const item of items) {\n // Skip private folders, api routes, and special files\n if (item.name.startsWith('_') || item.name.startsWith('.')) continue\n if (item.name === 'api') continue\n if (item.name === 'node_modules') continue\n \n const itemPath = path.join(appDir, item.name)\n \n if (item.isDirectory()) {\n // Check for page.tsx/page.js in this directory\n const hasPage = fs.existsSync(path.join(itemPath, 'page.tsx')) ||\n fs.existsSync(path.join(itemPath, 'page.js')) ||\n fs.existsSync(path.join(itemPath, 'page.jsx'))\n \n // Handle route groups (parentheses)\n const isRouteGroup = item.name.startsWith('(') && item.name.endsWith(')')\n \n // Handle dynamic segments [slug]\n const isDynamic = item.name.startsWith('[') && item.name.endsWith(']')\n \n let routePath = basePath\n if (!isRouteGroup && !isDynamic) {\n routePath = `${basePath}/${item.name}`\n }\n \n if (hasPage && !isDynamic) {\n const priority = routePath === '' ? 1.0 : 0.8\n entries.push({ path: routePath || '/', priority })\n }\n \n // Recurse into subdirectories (but not dynamic ones)\n if (!isDynamic) {\n const subEntries = discoverNextJsRoutes(itemPath, fs, path, isRouteGroup ? basePath : routePath)\n entries.push(...subEntries)\n }\n }\n }\n \n // Add root if app/page.tsx exists and we're at root\n if (basePath === '') {\n const hasRootPage = fs.existsSync(path.join(appDir, 'page.tsx')) ||\n fs.existsSync(path.join(appDir, 'page.js')) ||\n fs.existsSync(path.join(appDir, 'page.jsx'))\n if (hasRootPage) {\n entries.unshift({ path: '/', priority: 1.0 })\n }\n }\n \n return entries\n}\n\n/**\n * Check if a path should be indexed\n * \n * Quick helper to check indexability without full directive parsing\n */\nexport async function isIndexable(\n projectId: string,\n path: string\n): Promise<boolean> {\n const directive = await getRobotsDirective({ projectId, path })\n return directive.index\n}\n"]}
|
|
@@ -1,24 +1,23 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
var react = require('react');
|
|
4
|
+
require('server-only');
|
|
4
5
|
|
|
5
|
-
// src/seo/api.ts
|
|
6
|
-
|
|
7
|
-
console.warn(
|
|
8
|
-
"@uptrade/seo: WARNING - You are using the deprecated api.ts which exposes API keys. Please migrate to server-api.ts for better security. See: packages/site-kit/src/seo/README.md#migration"
|
|
9
|
-
);
|
|
10
|
-
}
|
|
11
|
-
function getApiConfig() {
|
|
6
|
+
// src/seo/server-api.ts
|
|
7
|
+
function getSecureApiConfig() {
|
|
12
8
|
const apiUrl = process.env.UPTRADE_API_URL || process.env.NEXT_PUBLIC_UPTRADE_API_URL || "https://api.uptrademedia.com";
|
|
13
9
|
const apiKey = process.env.UPTRADE_API_KEY || process.env.NEXT_PUBLIC_UPTRADE_API_KEY || "";
|
|
14
|
-
return { apiUrl, apiKey };
|
|
15
|
-
}
|
|
16
|
-
async function apiPost(endpoint, body = {}) {
|
|
17
|
-
const { apiUrl, apiKey } = getApiConfig();
|
|
18
10
|
if (!apiKey) {
|
|
19
|
-
|
|
20
|
-
return null;
|
|
11
|
+
throw new Error("@uptrade/seo: UPTRADE_API_KEY or NEXT_PUBLIC_UPTRADE_API_KEY environment variable is required for server-side SEO functions");
|
|
21
12
|
}
|
|
13
|
+
return { apiUrl, apiKey };
|
|
14
|
+
}
|
|
15
|
+
function getProjectIdFromKey(apiKey) {
|
|
16
|
+
const match = apiKey.match(/^uptrade_([0-9a-f-]{36})_/);
|
|
17
|
+
return match ? match[1] : null;
|
|
18
|
+
}
|
|
19
|
+
async function secureApiPost(endpoint, body = {}) {
|
|
20
|
+
const { apiUrl, apiKey } = getSecureApiConfig();
|
|
22
21
|
try {
|
|
23
22
|
const response = await fetch(`${apiUrl}${endpoint}`, {
|
|
24
23
|
method: "POST",
|
|
@@ -31,7 +30,13 @@ async function apiPost(endpoint, body = {}) {
|
|
|
31
30
|
// Cache for 60 seconds
|
|
32
31
|
});
|
|
33
32
|
if (!response.ok) {
|
|
34
|
-
|
|
33
|
+
let detail = response.statusText;
|
|
34
|
+
try {
|
|
35
|
+
const err = await response.json().catch(() => ({}));
|
|
36
|
+
if (err?.message) detail = err.message;
|
|
37
|
+
} catch {
|
|
38
|
+
}
|
|
39
|
+
console.error(`@uptrade/seo: API error ${response.status} ${endpoint}: ${detail}`);
|
|
35
40
|
return null;
|
|
36
41
|
}
|
|
37
42
|
return await response.json();
|
|
@@ -40,68 +45,67 @@ async function apiPost(endpoint, body = {}) {
|
|
|
40
45
|
return null;
|
|
41
46
|
}
|
|
42
47
|
}
|
|
43
|
-
var getSEOPageData = react.cache(async (
|
|
44
|
-
const result = await
|
|
45
|
-
return
|
|
48
|
+
var getSEOPageData = react.cache(async (path) => {
|
|
49
|
+
const result = await secureApiPost("/api/public/seo/page", { path });
|
|
50
|
+
return {
|
|
51
|
+
page: result?.page || null,
|
|
52
|
+
project: result?.project || null
|
|
53
|
+
};
|
|
46
54
|
});
|
|
47
|
-
var getSchemaMarkups = react.cache(async (
|
|
48
|
-
const result = await
|
|
55
|
+
var getSchemaMarkups = react.cache(async (path, options) => {
|
|
56
|
+
const result = await secureApiPost("/api/public/seo/schemas", {
|
|
49
57
|
path,
|
|
50
58
|
includeTypes: options?.includeTypes,
|
|
51
59
|
excludeTypes: options?.excludeTypes
|
|
52
60
|
});
|
|
53
61
|
return result?.schemas || [];
|
|
54
62
|
});
|
|
55
|
-
var getFAQData = react.cache(async (
|
|
56
|
-
const result = await
|
|
63
|
+
var getFAQData = react.cache(async (path) => {
|
|
64
|
+
const result = await secureApiPost("/api/public/seo/faq", { path });
|
|
57
65
|
return result?.faq || null;
|
|
58
66
|
});
|
|
59
|
-
var getInternalLinks = react.cache(async (
|
|
60
|
-
const result = await
|
|
67
|
+
var getInternalLinks = react.cache(async (sourcePath, options) => {
|
|
68
|
+
const result = await secureApiPost("/api/public/seo/internal-links", {
|
|
61
69
|
sourcePath,
|
|
62
70
|
position: options?.position,
|
|
63
71
|
limit: options?.limit
|
|
64
72
|
});
|
|
65
73
|
return result?.links || [];
|
|
66
74
|
});
|
|
67
|
-
var getContentBlock = react.cache(async (
|
|
68
|
-
const result = await
|
|
75
|
+
var getContentBlock = react.cache(async (path, section) => {
|
|
76
|
+
const result = await secureApiPost("/api/public/seo/content", { path, section });
|
|
69
77
|
return result?.content || null;
|
|
70
78
|
});
|
|
71
|
-
var getABTest = react.cache(async (
|
|
72
|
-
const result = await
|
|
79
|
+
var getABTest = react.cache(async (path, field) => {
|
|
80
|
+
const result = await secureApiPost("/api/public/seo/ab-test", { path, field });
|
|
73
81
|
return result?.test || null;
|
|
74
82
|
});
|
|
75
83
|
async function recordABImpression(testId, variant, sessionId) {
|
|
76
|
-
await
|
|
84
|
+
await secureApiPost("/api/public/seo/ab-impression", { testId, variant, sessionId });
|
|
77
85
|
}
|
|
78
|
-
var getRedirectData = react.cache(async (
|
|
79
|
-
const result = await
|
|
86
|
+
var getRedirectData = react.cache(async (path) => {
|
|
87
|
+
const result = await secureApiPost("/api/public/seo/redirect", { path });
|
|
80
88
|
return result?.redirect || null;
|
|
81
89
|
});
|
|
82
|
-
var getManagedScripts = react.cache(async (
|
|
83
|
-
const result = await
|
|
90
|
+
var getManagedScripts = react.cache(async (position, currentPath) => {
|
|
91
|
+
const result = await secureApiPost("/api/public/seo/scripts", {
|
|
84
92
|
position,
|
|
85
93
|
currentPath
|
|
86
94
|
});
|
|
87
95
|
return result?.scripts || [];
|
|
88
96
|
});
|
|
89
|
-
var getRobotsData = react.cache(async (
|
|
90
|
-
const result = await
|
|
97
|
+
var getRobotsData = react.cache(async (path) => {
|
|
98
|
+
const result = await secureApiPost("/api/public/seo/page", { path });
|
|
91
99
|
return result?.page?.managed_robots || null;
|
|
92
100
|
});
|
|
93
|
-
var getSitemapEntries = react.cache(async (
|
|
94
|
-
const result = await
|
|
101
|
+
var getSitemapEntries = react.cache(async (options) => {
|
|
102
|
+
const result = await secureApiPost("/api/public/seo/sitemap", {
|
|
95
103
|
publishedOnly: options?.publishedOnly
|
|
96
104
|
});
|
|
97
105
|
return result?.entries || [];
|
|
98
106
|
});
|
|
99
|
-
async function registerSitemap(entries
|
|
100
|
-
const { apiUrl, apiKey } =
|
|
101
|
-
if (!apiKey) {
|
|
102
|
-
console.error("@uptrade/seo: No API key configured. Set UPTRADE_API_KEY.");
|
|
103
|
-
return { success: false, created: 0, updated: 0 };
|
|
104
|
-
}
|
|
107
|
+
async function registerSitemap(entries) {
|
|
108
|
+
const { apiUrl, apiKey } = getSecureApiConfig();
|
|
105
109
|
try {
|
|
106
110
|
const response = await fetch(`${apiUrl}/api/public/seo/register-sitemap`, {
|
|
107
111
|
method: "POST",
|
|
@@ -109,11 +113,7 @@ async function registerSitemap(entries, options) {
|
|
|
109
113
|
"Content-Type": "application/json",
|
|
110
114
|
"x-api-key": apiKey
|
|
111
115
|
},
|
|
112
|
-
body: JSON.stringify({
|
|
113
|
-
entries,
|
|
114
|
-
optimize_meta: options?.optimize_meta !== false
|
|
115
|
-
// Default to true
|
|
116
|
-
})
|
|
116
|
+
body: JSON.stringify({ entries })
|
|
117
117
|
});
|
|
118
118
|
if (!response.ok) {
|
|
119
119
|
console.error(`@uptrade/seo: Sitemap registration failed: ${response.statusText}`);
|
|
@@ -127,7 +127,7 @@ async function registerSitemap(entries, options) {
|
|
|
127
127
|
}
|
|
128
128
|
function getSignalApiConfig() {
|
|
129
129
|
const apiUrl = process.env.SIGNAL_API_URL || process.env.NEXT_PUBLIC_SIGNAL_API_URL || "https://signal.uptrademedia.com";
|
|
130
|
-
const apiKey = process.env.UPTRADE_API_KEY ||
|
|
130
|
+
const apiKey = process.env.UPTRADE_API_KEY || "";
|
|
131
131
|
return { apiUrl, apiKey };
|
|
132
132
|
}
|
|
133
133
|
async function signalApiGet(endpoint) {
|
|
@@ -155,7 +155,10 @@ async function signalApiGet(endpoint) {
|
|
|
155
155
|
return null;
|
|
156
156
|
}
|
|
157
157
|
}
|
|
158
|
-
var getEntities = react.cache(async (
|
|
158
|
+
var getEntities = react.cache(async (options) => {
|
|
159
|
+
const { apiKey } = getSecureApiConfig();
|
|
160
|
+
const projectId = getProjectIdFromKey(apiKey);
|
|
161
|
+
if (!projectId) return [];
|
|
159
162
|
let endpoint = `/skills/seo/entities/${projectId}`;
|
|
160
163
|
if (options?.type) {
|
|
161
164
|
endpoint += `?type=${options.type}`;
|
|
@@ -163,21 +166,33 @@ var getEntities = react.cache(async (projectId, options) => {
|
|
|
163
166
|
const result = await signalApiGet(endpoint);
|
|
164
167
|
return result || [];
|
|
165
168
|
});
|
|
166
|
-
var getPrimaryEntity = react.cache(async (
|
|
169
|
+
var getPrimaryEntity = react.cache(async () => {
|
|
170
|
+
const { apiKey } = getSecureApiConfig();
|
|
171
|
+
const projectId = getProjectIdFromKey(apiKey);
|
|
172
|
+
if (!projectId) return null;
|
|
167
173
|
return signalApiGet(`/skills/seo/entities/${projectId}/primary`);
|
|
168
174
|
});
|
|
169
|
-
var getEntityEnhancedSchema = react.cache(async (
|
|
175
|
+
var getEntityEnhancedSchema = react.cache(async (pagePath) => {
|
|
176
|
+
const { apiKey } = getSecureApiConfig();
|
|
177
|
+
const projectId = getProjectIdFromKey(apiKey);
|
|
178
|
+
if (!projectId) return [];
|
|
170
179
|
const result = await signalApiGet(
|
|
171
180
|
`/skills/seo/schema/${projectId}/entity-enhanced?pagePath=${encodeURIComponent(pagePath)}`
|
|
172
181
|
);
|
|
173
182
|
return result?.schemas || [];
|
|
174
183
|
});
|
|
175
|
-
var getVisibilityScore = react.cache(async (
|
|
184
|
+
var getVisibilityScore = react.cache(async (pagePath) => {
|
|
185
|
+
const { apiKey } = getSecureApiConfig();
|
|
186
|
+
const projectId = getProjectIdFromKey(apiKey);
|
|
187
|
+
if (!projectId) return null;
|
|
176
188
|
const result = await signalApiGet(`/skills/seo/visibility/${projectId}`);
|
|
177
189
|
if (!result) return null;
|
|
178
190
|
return result.find((s) => s.page_path === pagePath) || null;
|
|
179
191
|
});
|
|
180
|
-
var getVisibilitySummary = react.cache(async (
|
|
192
|
+
var getVisibilitySummary = react.cache(async () => {
|
|
193
|
+
const { apiKey } = getSecureApiConfig();
|
|
194
|
+
const projectId = getProjectIdFromKey(apiKey);
|
|
195
|
+
if (!projectId) return null;
|
|
181
196
|
return signalApiGet(`/skills/seo/visibility/${projectId}/summary`);
|
|
182
197
|
});
|
|
183
198
|
|
|
@@ -198,5 +213,5 @@ exports.getVisibilityScore = getVisibilityScore;
|
|
|
198
213
|
exports.getVisibilitySummary = getVisibilitySummary;
|
|
199
214
|
exports.recordABImpression = recordABImpression;
|
|
200
215
|
exports.registerSitemap = registerSitemap;
|
|
201
|
-
//# sourceMappingURL=chunk-
|
|
202
|
-
//# sourceMappingURL=chunk-
|
|
216
|
+
//# sourceMappingURL=chunk-FOBATMSH.js.map
|
|
217
|
+
//# sourceMappingURL=chunk-FOBATMSH.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/seo/server-api.ts"],"names":["cache"],"mappings":"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","file":"chunk-FOBATMSH.js","sourcesContent":["/**\n * @uptrade/site-kit/seo - Server-Only API Functions\n * \n * SECURITY: These functions use private environment variables\n * and should ONLY be imported in server-side code (RSC, API routes, server actions).\n * \n * DO NOT import this file in client components or it will expose API keys.\n */\n\nimport { cache } from 'react'\nimport 'server-only' // This ensures the module can only be imported server-side\n\n// ============================================\n// Server-Only API Config\n// ============================================\n\n/**\n * Config for portal API calls. Project is resolved by the portal from the API key;\n * sites never send or read project ID.\n */\nfunction getSecureApiConfig() {\n const apiUrl = process.env.UPTRADE_API_URL || process.env.NEXT_PUBLIC_UPTRADE_API_URL || 'https://api.uptrademedia.com'\n const apiKey = process.env.UPTRADE_API_KEY || process.env.NEXT_PUBLIC_UPTRADE_API_KEY || ''\n\n if (!apiKey) {\n throw new Error('@uptrade/seo: UPTRADE_API_KEY or NEXT_PUBLIC_UPTRADE_API_KEY environment variable is required for server-side SEO functions')\n }\n\n return { apiUrl, apiKey }\n}\n\n/** Derive project ID from API key for Signal API only (format: uptrade_{uuid}_{secret}). Not used for portal. */\nfunction getProjectIdFromKey(apiKey: string): string | null {\n const match = apiKey.match(/^uptrade_([0-9a-f-]{36})_/)\n return match ? match[1] : null\n}\n\nasync function secureApiPost<T>(endpoint: string, body: Record<string, any> = {}): Promise<T | null> {\n const { apiUrl, apiKey } = getSecureApiConfig()\n\n try {\n const response = await fetch(`${apiUrl}${endpoint}`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n 'x-api-key': apiKey,\n },\n body: JSON.stringify(body),\n next: { revalidate: 60 }, // Cache for 60 seconds\n })\n\n if (!response.ok) {\n let detail = response.statusText\n try {\n const err = await response.json().catch(() => ({}))\n if (err?.message) detail = err.message\n } catch {\n /* ignore */\n }\n console.error(`@uptrade/seo: API error ${response.status} ${endpoint}: ${detail}`)\n return null\n }\n\n return await response.json()\n } catch (error) {\n console.error('@uptrade/seo: Network error:', error)\n return null\n }\n}\n\nasync function secureApiGet<T>(endpoint: string): Promise<T | null> {\n const { apiUrl, apiKey } = getSecureApiConfig()\n \n try {\n const response = await fetch(`${apiUrl}${endpoint}`, {\n method: 'GET',\n headers: {\n 'Content-Type': 'application/json',\n 'x-api-key': apiKey,\n },\n next: { revalidate: 60 },\n })\n \n if (!response.ok) {\n console.error(`@uptrade/seo: API error ${response.status}: ${response.statusText}`)\n return null\n }\n \n return await response.json()\n } catch (error) {\n console.error('@uptrade/seo: Network error:', error)\n return null\n }\n}\n\n// ============================================\n// Secure Cached Data Fetchers\n// ============================================\n\n/**\n * Fetch SEO page data - cached per request\n * @server-only\n */\nexport const getSEOPageData = cache(async (path: string) => {\n const result = await secureApiPost<{ page: any; project: any }>('/api/public/seo/page', { path })\n return {\n page: result?.page || null,\n project: result?.project || null,\n }\n})\n\n/**\n * Fetch schema markups for a page - cached per request\n * @server-only\n */\nexport const getSchemaMarkups = cache(async (\n path: string,\n options?: { includeTypes?: string[]; excludeTypes?: string[] }\n) => {\n const result = await secureApiPost<{ schemas: any[] }>('/api/public/seo/schemas', {\n path,\n includeTypes: options?.includeTypes,\n excludeTypes: options?.excludeTypes,\n })\n return result?.schemas || []\n})\n\n/**\n * Fetch FAQ data for a page - cached per request\n * @server-only\n */\nexport const getFAQData = cache(async (path: string) => {\n const result = await secureApiPost<{ faq: any }>('/api/public/seo/faq', { path })\n return result?.faq || null\n})\n\n/**\n * Fetch internal links for a page - cached per request\n * @server-only\n */\nexport const getInternalLinks = cache(async (\n sourcePath: string,\n options?: { position?: string; limit?: number }\n) => {\n const result = await secureApiPost<{ links: any[] }>('/api/public/seo/internal-links', {\n sourcePath,\n position: options?.position,\n limit: options?.limit,\n })\n return result?.links || []\n})\n\n/**\n * Fetch content block - cached per request\n * @server-only\n */\nexport const getContentBlock = cache(async (path: string, section: string) => {\n const result = await secureApiPost<{ content: any }>('/api/public/seo/content', { path, section })\n return result?.content || null\n})\n\n/**\n * Fetch A/B test and determine variant - cached per request\n * @server-only\n */\nexport const getABTest = cache(async (path: string, field: string) => {\n const result = await secureApiPost<{ test: any }>('/api/public/seo/ab-test', { path, field })\n return result?.test || null\n})\n\n/**\n * Record A/B test impression\n * @server-only\n */\nexport async function recordABImpression(\n testId: string,\n variant: 'a' | 'b',\n sessionId?: string\n): Promise<void> {\n await secureApiPost('/api/public/seo/ab-impression', { testId, variant, sessionId })\n}\n\n/**\n * Fetch redirect for a path - cached per request\n * @server-only\n */\nexport const getRedirectData = cache(async (path: string) => {\n const result = await secureApiPost<{ redirect: any }>('/api/public/seo/redirect', { path })\n return result?.redirect || null\n})\n\n/**\n * Fetch managed scripts - cached per request\n * @server-only\n */\nexport const getManagedScripts = cache(async (position: string, currentPath?: string) => {\n const result = await secureApiPost<{ scripts: any[] }>('/api/public/seo/scripts', {\n position,\n currentPath,\n })\n return result?.scripts || []\n})\n\n/**\n * Fetch robots directive for a page - cached per request\n * @server-only\n */\nexport const getRobotsData = cache(async (path: string) => {\n const result = await secureApiPost<{ page: any }>('/api/public/seo/page', { path })\n return result?.page?.managed_robots || null\n})\n\n/**\n * Fetch sitemap entries - cached per request\n * @server-only\n */\nexport const getSitemapEntries = cache(async (options?: { publishedOnly?: boolean }) => {\n const result = await secureApiPost<{ entries: any[] }>('/api/public/seo/sitemap', {\n publishedOnly: options?.publishedOnly,\n })\n return result?.entries || []\n})\n\n/**\n * Register/sync sitemap entries from the client site\n * Call this at build time to populate seo_pages from your sitemap.xml\n * @server-only\n * \n * @example\n * ```ts\n * // scripts/register-sitemap.ts (run at build time)\n * import { registerSitemap } from '@uptrade/seo/server'\n * \n * await registerSitemap([\n * { path: '/', priority: 1.0, changefreq: 'daily' },\n * { path: '/about', priority: 0.8, changefreq: 'weekly' },\n * ])\n * ```\n */\nexport async function registerSitemap(\n entries: Array<{\n path: string\n title?: string\n priority?: number\n changefreq?: 'always' | 'hourly' | 'daily' | 'weekly' | 'monthly' | 'yearly' | 'never'\n }>\n): Promise<{ success: boolean; created: number; updated: number }> {\n const { apiUrl, apiKey } = getSecureApiConfig()\n\n try {\n const response = await fetch(`${apiUrl}/api/public/seo/register-sitemap`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n 'x-api-key': apiKey,\n },\n body: JSON.stringify({ entries }),\n })\n \n if (!response.ok) {\n console.error(`@uptrade/seo: Sitemap registration failed: ${response.statusText}`)\n return { success: false, created: 0, updated: 0 }\n }\n \n return await response.json()\n } catch (error) {\n console.error('@uptrade/seo: Sitemap registration error:', error)\n return { success: false, created: 0, updated: 0 }\n }\n}\n\n// ============================================\n// AI Visibility & Entity Graph API (Signal)\n// ============================================\n\nfunction getSignalApiConfig() {\n const apiUrl = process.env.SIGNAL_API_URL || process.env.NEXT_PUBLIC_SIGNAL_API_URL || 'https://signal.uptrademedia.com'\n const apiKey = process.env.UPTRADE_API_KEY || ''\n return { apiUrl, apiKey }\n}\n\nasync function signalApiGet<T>(endpoint: string): Promise<T | null> {\n const { apiUrl, apiKey } = getSignalApiConfig()\n \n if (!apiKey) {\n return null\n }\n \n try {\n const response = await fetch(`${apiUrl}${endpoint}`, {\n method: 'GET',\n headers: {\n 'Content-Type': 'application/json',\n 'x-api-key': apiKey,\n },\n next: { revalidate: 300 }, // Cache for 5 minutes\n })\n \n if (!response.ok) {\n return null\n }\n \n const result = await response.json()\n return result?.data || result\n } catch (error) {\n console.error('@uptrade/seo: Signal API error:', error)\n return null\n }\n}\n\n/**\n * Entity types for the knowledge graph\n */\nexport type EntityType = \n | 'organization'\n | 'person'\n | 'service'\n | 'product'\n | 'location'\n | 'concept'\n | 'credential'\n\n/**\n * Entity from the knowledge graph\n */\nexport interface SEOEntity {\n id: string\n project_id: string\n entity_type: EntityType\n name: string\n slug: string\n properties: Record<string, unknown>\n knows_about: string[]\n same_as: string[]\n schema_type?: string\n is_primary: boolean\n}\n\n/**\n * Fetch entities for a project - cached per request\n * Returns the entity graph for enhanced schema markup\n * @server-only\n */\nexport const getEntities = cache(async (\n options?: { type?: EntityType }\n): Promise<SEOEntity[]> => {\n const { apiKey } = getSecureApiConfig()\n const projectId = getProjectIdFromKey(apiKey)\n if (!projectId) return []\n let endpoint = `/skills/seo/entities/${projectId}`\n if (options?.type) {\n endpoint += `?type=${options.type}`\n }\n const result = await signalApiGet<SEOEntity[]>(endpoint)\n return result || []\n})\n\n/**\n * Fetch primary entity (the business) - cached per request\n * @server-only\n */\nexport const getPrimaryEntity = cache(async (): Promise<SEOEntity | null> => {\n const { apiKey } = getSecureApiConfig()\n const projectId = getProjectIdFromKey(apiKey)\n if (!projectId) return null\n return signalApiGet<SEOEntity>(`/skills/seo/entities/${projectId}/primary`)\n})\n\n/**\n * Fetch entity-enhanced schema for a page\n * Returns Organization schema with knowsAbout, areaServed, employee, etc.\n * @server-only\n */\nexport const getEntityEnhancedSchema = cache(async (pagePath: string): Promise<object[]> => {\n const { apiKey } = getSecureApiConfig()\n const projectId = getProjectIdFromKey(apiKey)\n if (!projectId) return []\n const result = await signalApiGet<{ schemas: object[] }>(\n `/skills/seo/schema/${projectId}/entity-enhanced?pagePath=${encodeURIComponent(pagePath)}`\n )\n return result?.schemas || []\n})\n\n/**\n * Get AI visibility score for a page\n * @server-only\n */\nexport const getVisibilityScore = cache(async (\n pagePath: string\n): Promise<{\n overall_score: number\n entity_coverage: number\n answer_density: number\n chunk_readability: number\n authority_signals: number\n schema_completeness: number\n} | null> => {\n const { apiKey } = getSecureApiConfig()\n const projectId = getProjectIdFromKey(apiKey)\n if (!projectId) return null\n const result = await signalApiGet<any[]>(`/skills/seo/visibility/${projectId}`)\n if (!result) return null\n return result.find(s => s.page_path === pagePath) || null\n})\n\n/**\n * Get AI visibility summary for project\n * @server-only\n */\nexport const getVisibilitySummary = cache(async (): Promise<{\n overall_score: number\n total_entities: number\n pages_analyzed: number\n top_recommendations: Array<{ priority: string; type: string; message: string }>\n} | null> => {\n const { apiKey } = getSecureApiConfig()\n const projectId = getProjectIdFromKey(apiKey)\n if (!projectId) return null\n return signalApiGet(`/skills/seo/visibility/${projectId}/summary`)\n})\n"]}
|
|
@@ -1,22 +1,21 @@
|
|
|
1
1
|
import { cache } from 'react';
|
|
2
|
+
import 'server-only';
|
|
2
3
|
|
|
3
|
-
// src/seo/api.ts
|
|
4
|
-
|
|
5
|
-
console.warn(
|
|
6
|
-
"@uptrade/seo: WARNING - You are using the deprecated api.ts which exposes API keys. Please migrate to server-api.ts for better security. See: packages/site-kit/src/seo/README.md#migration"
|
|
7
|
-
);
|
|
8
|
-
}
|
|
9
|
-
function getApiConfig() {
|
|
4
|
+
// src/seo/server-api.ts
|
|
5
|
+
function getSecureApiConfig() {
|
|
10
6
|
const apiUrl = process.env.UPTRADE_API_URL || process.env.NEXT_PUBLIC_UPTRADE_API_URL || "https://api.uptrademedia.com";
|
|
11
7
|
const apiKey = process.env.UPTRADE_API_KEY || process.env.NEXT_PUBLIC_UPTRADE_API_KEY || "";
|
|
12
|
-
return { apiUrl, apiKey };
|
|
13
|
-
}
|
|
14
|
-
async function apiPost(endpoint, body = {}) {
|
|
15
|
-
const { apiUrl, apiKey } = getApiConfig();
|
|
16
8
|
if (!apiKey) {
|
|
17
|
-
|
|
18
|
-
return null;
|
|
9
|
+
throw new Error("@uptrade/seo: UPTRADE_API_KEY or NEXT_PUBLIC_UPTRADE_API_KEY environment variable is required for server-side SEO functions");
|
|
19
10
|
}
|
|
11
|
+
return { apiUrl, apiKey };
|
|
12
|
+
}
|
|
13
|
+
function getProjectIdFromKey(apiKey) {
|
|
14
|
+
const match = apiKey.match(/^uptrade_([0-9a-f-]{36})_/);
|
|
15
|
+
return match ? match[1] : null;
|
|
16
|
+
}
|
|
17
|
+
async function secureApiPost(endpoint, body = {}) {
|
|
18
|
+
const { apiUrl, apiKey } = getSecureApiConfig();
|
|
20
19
|
try {
|
|
21
20
|
const response = await fetch(`${apiUrl}${endpoint}`, {
|
|
22
21
|
method: "POST",
|
|
@@ -29,7 +28,13 @@ async function apiPost(endpoint, body = {}) {
|
|
|
29
28
|
// Cache for 60 seconds
|
|
30
29
|
});
|
|
31
30
|
if (!response.ok) {
|
|
32
|
-
|
|
31
|
+
let detail = response.statusText;
|
|
32
|
+
try {
|
|
33
|
+
const err = await response.json().catch(() => ({}));
|
|
34
|
+
if (err?.message) detail = err.message;
|
|
35
|
+
} catch {
|
|
36
|
+
}
|
|
37
|
+
console.error(`@uptrade/seo: API error ${response.status} ${endpoint}: ${detail}`);
|
|
33
38
|
return null;
|
|
34
39
|
}
|
|
35
40
|
return await response.json();
|
|
@@ -38,68 +43,67 @@ async function apiPost(endpoint, body = {}) {
|
|
|
38
43
|
return null;
|
|
39
44
|
}
|
|
40
45
|
}
|
|
41
|
-
var getSEOPageData = cache(async (
|
|
42
|
-
const result = await
|
|
43
|
-
return
|
|
46
|
+
var getSEOPageData = cache(async (path) => {
|
|
47
|
+
const result = await secureApiPost("/api/public/seo/page", { path });
|
|
48
|
+
return {
|
|
49
|
+
page: result?.page || null,
|
|
50
|
+
project: result?.project || null
|
|
51
|
+
};
|
|
44
52
|
});
|
|
45
|
-
var getSchemaMarkups = cache(async (
|
|
46
|
-
const result = await
|
|
53
|
+
var getSchemaMarkups = cache(async (path, options) => {
|
|
54
|
+
const result = await secureApiPost("/api/public/seo/schemas", {
|
|
47
55
|
path,
|
|
48
56
|
includeTypes: options?.includeTypes,
|
|
49
57
|
excludeTypes: options?.excludeTypes
|
|
50
58
|
});
|
|
51
59
|
return result?.schemas || [];
|
|
52
60
|
});
|
|
53
|
-
var getFAQData = cache(async (
|
|
54
|
-
const result = await
|
|
61
|
+
var getFAQData = cache(async (path) => {
|
|
62
|
+
const result = await secureApiPost("/api/public/seo/faq", { path });
|
|
55
63
|
return result?.faq || null;
|
|
56
64
|
});
|
|
57
|
-
var getInternalLinks = cache(async (
|
|
58
|
-
const result = await
|
|
65
|
+
var getInternalLinks = cache(async (sourcePath, options) => {
|
|
66
|
+
const result = await secureApiPost("/api/public/seo/internal-links", {
|
|
59
67
|
sourcePath,
|
|
60
68
|
position: options?.position,
|
|
61
69
|
limit: options?.limit
|
|
62
70
|
});
|
|
63
71
|
return result?.links || [];
|
|
64
72
|
});
|
|
65
|
-
var getContentBlock = cache(async (
|
|
66
|
-
const result = await
|
|
73
|
+
var getContentBlock = cache(async (path, section) => {
|
|
74
|
+
const result = await secureApiPost("/api/public/seo/content", { path, section });
|
|
67
75
|
return result?.content || null;
|
|
68
76
|
});
|
|
69
|
-
var getABTest = cache(async (
|
|
70
|
-
const result = await
|
|
77
|
+
var getABTest = cache(async (path, field) => {
|
|
78
|
+
const result = await secureApiPost("/api/public/seo/ab-test", { path, field });
|
|
71
79
|
return result?.test || null;
|
|
72
80
|
});
|
|
73
81
|
async function recordABImpression(testId, variant, sessionId) {
|
|
74
|
-
await
|
|
82
|
+
await secureApiPost("/api/public/seo/ab-impression", { testId, variant, sessionId });
|
|
75
83
|
}
|
|
76
|
-
var getRedirectData = cache(async (
|
|
77
|
-
const result = await
|
|
84
|
+
var getRedirectData = cache(async (path) => {
|
|
85
|
+
const result = await secureApiPost("/api/public/seo/redirect", { path });
|
|
78
86
|
return result?.redirect || null;
|
|
79
87
|
});
|
|
80
|
-
var getManagedScripts = cache(async (
|
|
81
|
-
const result = await
|
|
88
|
+
var getManagedScripts = cache(async (position, currentPath) => {
|
|
89
|
+
const result = await secureApiPost("/api/public/seo/scripts", {
|
|
82
90
|
position,
|
|
83
91
|
currentPath
|
|
84
92
|
});
|
|
85
93
|
return result?.scripts || [];
|
|
86
94
|
});
|
|
87
|
-
var getRobotsData = cache(async (
|
|
88
|
-
const result = await
|
|
95
|
+
var getRobotsData = cache(async (path) => {
|
|
96
|
+
const result = await secureApiPost("/api/public/seo/page", { path });
|
|
89
97
|
return result?.page?.managed_robots || null;
|
|
90
98
|
});
|
|
91
|
-
var getSitemapEntries = cache(async (
|
|
92
|
-
const result = await
|
|
99
|
+
var getSitemapEntries = cache(async (options) => {
|
|
100
|
+
const result = await secureApiPost("/api/public/seo/sitemap", {
|
|
93
101
|
publishedOnly: options?.publishedOnly
|
|
94
102
|
});
|
|
95
103
|
return result?.entries || [];
|
|
96
104
|
});
|
|
97
|
-
async function registerSitemap(entries
|
|
98
|
-
const { apiUrl, apiKey } =
|
|
99
|
-
if (!apiKey) {
|
|
100
|
-
console.error("@uptrade/seo: No API key configured. Set UPTRADE_API_KEY.");
|
|
101
|
-
return { success: false, created: 0, updated: 0 };
|
|
102
|
-
}
|
|
105
|
+
async function registerSitemap(entries) {
|
|
106
|
+
const { apiUrl, apiKey } = getSecureApiConfig();
|
|
103
107
|
try {
|
|
104
108
|
const response = await fetch(`${apiUrl}/api/public/seo/register-sitemap`, {
|
|
105
109
|
method: "POST",
|
|
@@ -107,11 +111,7 @@ async function registerSitemap(entries, options) {
|
|
|
107
111
|
"Content-Type": "application/json",
|
|
108
112
|
"x-api-key": apiKey
|
|
109
113
|
},
|
|
110
|
-
body: JSON.stringify({
|
|
111
|
-
entries,
|
|
112
|
-
optimize_meta: options?.optimize_meta !== false
|
|
113
|
-
// Default to true
|
|
114
|
-
})
|
|
114
|
+
body: JSON.stringify({ entries })
|
|
115
115
|
});
|
|
116
116
|
if (!response.ok) {
|
|
117
117
|
console.error(`@uptrade/seo: Sitemap registration failed: ${response.statusText}`);
|
|
@@ -125,7 +125,7 @@ async function registerSitemap(entries, options) {
|
|
|
125
125
|
}
|
|
126
126
|
function getSignalApiConfig() {
|
|
127
127
|
const apiUrl = process.env.SIGNAL_API_URL || process.env.NEXT_PUBLIC_SIGNAL_API_URL || "https://signal.uptrademedia.com";
|
|
128
|
-
const apiKey = process.env.UPTRADE_API_KEY ||
|
|
128
|
+
const apiKey = process.env.UPTRADE_API_KEY || "";
|
|
129
129
|
return { apiUrl, apiKey };
|
|
130
130
|
}
|
|
131
131
|
async function signalApiGet(endpoint) {
|
|
@@ -153,7 +153,10 @@ async function signalApiGet(endpoint) {
|
|
|
153
153
|
return null;
|
|
154
154
|
}
|
|
155
155
|
}
|
|
156
|
-
var getEntities = cache(async (
|
|
156
|
+
var getEntities = cache(async (options) => {
|
|
157
|
+
const { apiKey } = getSecureApiConfig();
|
|
158
|
+
const projectId = getProjectIdFromKey(apiKey);
|
|
159
|
+
if (!projectId) return [];
|
|
157
160
|
let endpoint = `/skills/seo/entities/${projectId}`;
|
|
158
161
|
if (options?.type) {
|
|
159
162
|
endpoint += `?type=${options.type}`;
|
|
@@ -161,24 +164,36 @@ var getEntities = cache(async (projectId, options) => {
|
|
|
161
164
|
const result = await signalApiGet(endpoint);
|
|
162
165
|
return result || [];
|
|
163
166
|
});
|
|
164
|
-
var getPrimaryEntity = cache(async (
|
|
167
|
+
var getPrimaryEntity = cache(async () => {
|
|
168
|
+
const { apiKey } = getSecureApiConfig();
|
|
169
|
+
const projectId = getProjectIdFromKey(apiKey);
|
|
170
|
+
if (!projectId) return null;
|
|
165
171
|
return signalApiGet(`/skills/seo/entities/${projectId}/primary`);
|
|
166
172
|
});
|
|
167
|
-
var getEntityEnhancedSchema = cache(async (
|
|
173
|
+
var getEntityEnhancedSchema = cache(async (pagePath) => {
|
|
174
|
+
const { apiKey } = getSecureApiConfig();
|
|
175
|
+
const projectId = getProjectIdFromKey(apiKey);
|
|
176
|
+
if (!projectId) return [];
|
|
168
177
|
const result = await signalApiGet(
|
|
169
178
|
`/skills/seo/schema/${projectId}/entity-enhanced?pagePath=${encodeURIComponent(pagePath)}`
|
|
170
179
|
);
|
|
171
180
|
return result?.schemas || [];
|
|
172
181
|
});
|
|
173
|
-
var getVisibilityScore = cache(async (
|
|
182
|
+
var getVisibilityScore = cache(async (pagePath) => {
|
|
183
|
+
const { apiKey } = getSecureApiConfig();
|
|
184
|
+
const projectId = getProjectIdFromKey(apiKey);
|
|
185
|
+
if (!projectId) return null;
|
|
174
186
|
const result = await signalApiGet(`/skills/seo/visibility/${projectId}`);
|
|
175
187
|
if (!result) return null;
|
|
176
188
|
return result.find((s) => s.page_path === pagePath) || null;
|
|
177
189
|
});
|
|
178
|
-
var getVisibilitySummary = cache(async (
|
|
190
|
+
var getVisibilitySummary = cache(async () => {
|
|
191
|
+
const { apiKey } = getSecureApiConfig();
|
|
192
|
+
const projectId = getProjectIdFromKey(apiKey);
|
|
193
|
+
if (!projectId) return null;
|
|
179
194
|
return signalApiGet(`/skills/seo/visibility/${projectId}/summary`);
|
|
180
195
|
});
|
|
181
196
|
|
|
182
197
|
export { getABTest, getContentBlock, getEntities, getEntityEnhancedSchema, getFAQData, getInternalLinks, getManagedScripts, getPrimaryEntity, getRedirectData, getRobotsData, getSEOPageData, getSchemaMarkups, getSitemapEntries, getVisibilityScore, getVisibilitySummary, recordABImpression, registerSitemap };
|
|
183
|
-
//# sourceMappingURL=chunk-
|
|
184
|
-
//# sourceMappingURL=chunk-
|
|
198
|
+
//# sourceMappingURL=chunk-MLY7AWHG.mjs.map
|
|
199
|
+
//# sourceMappingURL=chunk-MLY7AWHG.mjs.map
|