Skip to content

Commit b26bdaf

Browse files
docs: add sitemap (#54)
* docs: add sitemap * check redirects
1 parent b21c699 commit b26bdaf

5 files changed

Lines changed: 294 additions & 1 deletion

File tree

.vitepress/config.mts

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import path from "node:path";
44
import { generateNav } from "./config/nav.js";
55
import { generateAllSidebars } from "./config/sidebar.js";
66
import { configureMarkdown } from "./config/markdown.js";
7+
import { generateSitemap } from "./config/sitemap.js";
78

89
const docsDir = path.join(process.cwd(), "docs");
910

@@ -18,7 +19,19 @@ export default withMermaid(
1819
/^http:\/\/localhost/,
1920
],
2021

21-
head: [["link", { rel: "icon", href: "/favicon.png" }]],
22+
head: [
23+
["link", { rel: "icon", href: "/favicon.png" }],
24+
["meta", { name: "theme-color", content: "#6366f1" }],
25+
["meta", { property: "og:type", content: "website" }],
26+
["meta", { property: "og:locale", content: "en" }],
27+
["meta", { property: "og:site_name", content: "Tailor Platform Documentation" }],
28+
["meta", { name: "robots", content: "index,follow" }],
29+
],
30+
31+
// Generate sitemap on build
32+
buildEnd: async (config) => {
33+
generateSitemap(config);
34+
},
2235

2336
vite: {
2437
optimizeDeps: {

.vitepress/config/redirects.ts

Lines changed: 171 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,171 @@
1+
// Redirect mappings from Next.js (tailor-web/apps/docs) to VitePress
2+
// Generated based on comparison of old and new structures
3+
4+
export const redirectMap: Record<string, string> = {
5+
// ============================================
6+
// tailor-sdk → sdk (MAIN MIGRATION PATTERN)
7+
// ============================================
8+
"/tailor-sdk": "/sdk/",
9+
"/tailor-sdk/": "/sdk/",
10+
"/tailor-sdk/quickstart": "/sdk/quickstart",
11+
"/tailor-sdk/configuration": "/sdk/configuration",
12+
"/tailor-sdk/testing": "/sdk/testing",
13+
"/tailor-sdk/cli-reference": "/sdk/cli-reference",
14+
15+
// CLI pages
16+
"/tailor-sdk/cli/application": "/sdk/cli/application",
17+
"/tailor-sdk/cli/auth": "/sdk/cli/auth",
18+
"/tailor-sdk/cli/completion": "/sdk/cli/completion",
19+
"/tailor-sdk/cli/executor": "/sdk/cli/executor",
20+
"/tailor-sdk/cli/function": "/sdk/cli/function",
21+
"/tailor-sdk/cli/secret": "/sdk/cli/secret",
22+
"/tailor-sdk/cli/staticwebsite": "/sdk/cli/staticwebsite",
23+
"/tailor-sdk/cli/tailordb": "/sdk/cli/tailordb",
24+
"/tailor-sdk/cli/user": "/sdk/cli/user",
25+
"/tailor-sdk/cli/workflow": "/sdk/cli/workflow",
26+
"/tailor-sdk/cli/workspace": "/sdk/cli/workspace",
27+
28+
// Services
29+
"/tailor-sdk/services": "/sdk/services/",
30+
"/tailor-sdk/services/auth": "/sdk/services/auth",
31+
"/tailor-sdk/services/executor": "/sdk/services/executor",
32+
"/tailor-sdk/services/idp": "/sdk/services/idp",
33+
"/tailor-sdk/services/resolver": "/sdk/services/resolver",
34+
"/tailor-sdk/services/secret": "/sdk/services/secret",
35+
"/tailor-sdk/services/staticwebsite": "/sdk/services/staticwebsite",
36+
"/tailor-sdk/services/tailordb": "/sdk/services/tailordb",
37+
"/tailor-sdk/services/workflow": "/sdk/services/workflow",
38+
39+
// Generator
40+
"/tailor-sdk/generator": "/sdk/generator/",
41+
"/tailor-sdk/generator/builtin": "/sdk/generator/builtin",
42+
"/tailor-sdk/generator/custom": "/sdk/generator/custom",
43+
"/tailor-sdk/generator/index-file": "/sdk/generator/index",
44+
45+
// Plugin
46+
"/tailor-sdk/plugin": "/sdk/plugin/",
47+
"/tailor-sdk/plugin/custom": "/sdk/plugin/custom",
48+
"/tailor-sdk/plugin/index-file": "/sdk/plugin/index",
49+
50+
// ============================================
51+
// tailorctl → DEPRECATED (redirect to SDK)
52+
// ============================================
53+
"/tailorctl": "/sdk/",
54+
"/tailorctl/": "/sdk/",
55+
"/tailorctl/auth": "/sdk/cli/auth",
56+
"/tailorctl/auth/login": "/sdk/cli/auth",
57+
"/tailorctl/auth/logout": "/sdk/cli/auth",
58+
"/tailorctl/auth/get": "/sdk/cli/auth",
59+
"/tailorctl/auth/machineuser": "/sdk/cli/auth",
60+
"/tailorctl/auth/machineuser_get": "/sdk/cli/auth",
61+
"/tailorctl/auth/machineuser_list": "/sdk/cli/auth",
62+
"/tailorctl/auth/pat": "/sdk/cli/auth",
63+
"/tailorctl/auth/pat_create": "/sdk/cli/auth",
64+
"/tailorctl/auth/pat_delete": "/sdk/cli/auth",
65+
"/tailorctl/auth/pat_list": "/sdk/cli/auth",
66+
"/tailorctl/config": "/sdk/configuration",
67+
"/tailorctl/config/delete": "/sdk/configuration",
68+
"/tailorctl/config/describe": "/sdk/configuration",
69+
"/tailorctl/config/list": "/sdk/configuration",
70+
"/tailorctl/config/set": "/sdk/configuration",
71+
"/tailorctl/config/switch": "/sdk/configuration",
72+
"/tailorctl/workspace": "/sdk/cli/workspace",
73+
"/tailorctl/workspace/create": "/sdk/cli/workspace",
74+
"/tailorctl/workspace/list": "/sdk/cli/workspace",
75+
"/tailorctl/workspace/machineuser_list": "/sdk/cli/workspace",
76+
77+
// ============================================
78+
// Getting Started (minor changes)
79+
// ============================================
80+
"/getting-started/quickstart/cue": "/getting-started/quickstart",
81+
"/getting-started/quickstart/terraform": "/getting-started/quickstart",
82+
"/getting-started/cue": "/getting-started/",
83+
"/getting-started/overview": "/getting-started/",
84+
85+
// ============================================
86+
// Tutorials base-template → develop-from-scratch
87+
// ============================================
88+
"/tutorials/base-template": "/tutorials/develop-from-scratch/",
89+
"/tutorials/base-template/cue": "/tutorials/develop-from-scratch/",
90+
"/tutorials/base-template/sdk": "/tutorials/develop-from-scratch/",
91+
"/tutorials/base-template/terraform": "/tutorials/develop-from-scratch/",
92+
93+
// ============================================
94+
// Root pages that moved to /reference
95+
// ============================================
96+
"/security": "/reference/security",
97+
98+
// ============================================
99+
// Common variations with /docs prefix
100+
// ============================================
101+
"/docs/tailor-sdk": "/sdk/",
102+
"/docs/tailor-sdk/": "/sdk/",
103+
"/docs/getting-started": "/getting-started/",
104+
"/docs/guides": "/guides/",
105+
"/docs/tutorials": "/tutorials/",
106+
"/docs/reference": "/reference/",
107+
"/docs/sdk": "/sdk/",
108+
"/docs/administration": "/administration/",
109+
"/docs/security": "/reference/security",
110+
111+
// ============================================
112+
// Index pages → overview pages
113+
// ============================================
114+
"/guides/auth/index": "/guides/auth/overview",
115+
"/guides/executor/index": "/guides/executor/overview",
116+
"/guides/function/index": "/guides/function/overview",
117+
"/guides/tailordb/index": "/guides/tailordb/overview",
118+
"/guides/workflow/index": "/guides/workflow/overview",
119+
"/guides/integration/index": "/guides/integration/overview",
120+
};
121+
122+
// Fuzzy match redirects - tries to find similar pages
123+
export function findBestMatch(path: string): string | null {
124+
// Remove trailing slashes and normalize
125+
const normalizedPath = path.replace(/\/$/, "").toLowerCase();
126+
127+
// Direct match
128+
if (redirectMap[normalizedPath]) {
129+
return redirectMap[normalizedPath];
130+
}
131+
132+
// Try with /docs prefix removed (common Next.js → VitePress migration)
133+
if (normalizedPath.startsWith("/docs/")) {
134+
const withoutDocs = normalizedPath.replace("/docs/", "/");
135+
if (redirectMap[withoutDocs]) {
136+
return redirectMap[withoutDocs];
137+
}
138+
// If no redirect map, try the path without /docs directly
139+
return withoutDocs;
140+
}
141+
142+
// Try tailor-sdk → sdk conversion
143+
if (normalizedPath.includes("/tailor-sdk/")) {
144+
const converted = normalizedPath.replace("/tailor-sdk/", "/sdk/");
145+
// Check if this new path might exist
146+
return converted;
147+
}
148+
149+
// Try fuzzy matching based on path segments
150+
const segments = normalizedPath.split("/").filter(Boolean);
151+
const lastSegment = segments[segments.length - 1];
152+
153+
// Look for pages with the same last segment in redirect map
154+
for (const [oldPath, newPath] of Object.entries(redirectMap)) {
155+
if (oldPath.endsWith("/" + lastSegment) && newPath) {
156+
return newPath;
157+
}
158+
}
159+
160+
// No match found
161+
return null;
162+
}
163+
164+
// Check if a path should redirect
165+
export function shouldRedirect(path: string): string | null {
166+
const match = findBestMatch(path);
167+
if (match && match !== path) {
168+
return match;
169+
}
170+
return null;
171+
}

.vitepress/config/sitemap.ts

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
import { SiteConfig } from "vitepress";
2+
import fs from "node:fs";
3+
import path from "node:path";
4+
5+
interface SitemapEntry {
6+
url: string;
7+
lastmod?: string;
8+
changefreq?: "always" | "hourly" | "daily" | "weekly" | "monthly" | "yearly" | "never";
9+
priority?: number;
10+
}
11+
12+
// Generate sitemap.xml from all markdown files
13+
export function generateSitemap(config: SiteConfig) {
14+
const docsDir = path.join(process.cwd(), "docs");
15+
const siteUrl = "https://docs.tailor.tech"; // Update with your actual domain
16+
const entries: SitemapEntry[] = [];
17+
18+
// Recursively find all .md files
19+
function findMarkdownFiles(dir: string, baseDir: string = ""): void {
20+
const files = fs.readdirSync(dir, { withFileTypes: true });
21+
22+
for (const file of files) {
23+
const fullPath = path.join(dir, file.name);
24+
const relativePath = path.join(baseDir, file.name);
25+
26+
// Skip hidden files and directories
27+
if (file.name.startsWith(".")) continue;
28+
29+
// Skip certain directories
30+
if (file.isDirectory() && ["node_modules", "dist", "cache"].includes(file.name)) {
31+
continue;
32+
}
33+
34+
if (file.isDirectory()) {
35+
findMarkdownFiles(fullPath, relativePath);
36+
} else if (file.name.endsWith(".md")) {
37+
// Convert file path to URL
38+
let url = relativePath
39+
.replace(/\.md$/, "")
40+
.replace(/\\/g, "/")
41+
.replace(/index$/, "");
42+
43+
// Clean up the URL
44+
if (url && !url.startsWith("/")) url = "/" + url;
45+
if (url.endsWith("/index")) url = url.replace("/index", "/");
46+
47+
// Get file modification time
48+
const stats = fs.statSync(fullPath);
49+
const lastmod = stats.mtime.toISOString().split("T")[0];
50+
51+
// Determine priority based on path depth
52+
const depth = url.split("/").filter((p) => p).length;
53+
const priority = Math.max(0.3, 1.0 - depth * 0.1);
54+
55+
entries.push({
56+
url: siteUrl + url,
57+
lastmod,
58+
changefreq: depth <= 2 ? "weekly" : "monthly",
59+
priority: Number(priority.toFixed(1)),
60+
});
61+
}
62+
}
63+
}
64+
65+
findMarkdownFiles(docsDir);
66+
67+
// Generate XML
68+
const xml = `<?xml version="1.0" encoding="UTF-8"?>
69+
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
70+
${entries
71+
.map(
72+
(entry) => ` <url>
73+
<loc>${entry.url}</loc>
74+
<lastmod>${entry.lastmod}</lastmod>
75+
<changefreq>${entry.changefreq}</changefreq>
76+
<priority>${entry.priority}</priority>
77+
</url>`
78+
)
79+
.join("\n")}
80+
</urlset>`;
81+
82+
// Write sitemap to public directory
83+
const publicDir = path.join(process.cwd(), "docs", ".vitepress", "dist");
84+
if (!fs.existsSync(publicDir)) {
85+
fs.mkdirSync(publicDir, { recursive: true });
86+
}
87+
88+
const sitemapPath = path.join(publicDir, "sitemap.xml");
89+
fs.writeFileSync(sitemapPath, xml);
90+
91+
console.log(`✓ Generated sitemap with ${entries.length} URLs`);
92+
}

.vitepress/theme/index.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import "./styles/tabs.css";
1919
import "./styles/search.css";
2020

2121
import { onMounted } from "vue";
22+
import { shouldRedirect } from "../config/redirects";
2223

2324
const theme: Theme = {
2425
extends: DefaultTheme,
@@ -32,6 +33,13 @@ const theme: Theme = {
3233
},
3334
setup() {
3435
onMounted(() => {
36+
// Check for redirects on 404 pages
37+
const redirectPath = shouldRedirect(window.location.pathname);
38+
if (redirectPath) {
39+
window.location.href = redirectPath;
40+
return;
41+
}
42+
3543
document.addEventListener("click", (e) => {
3644
const btn = (e.target as HTMLElement).closest(".vp-tab-btn");
3745
if (!btn) return;

docs/public/robots.txt

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
# Allow all crawlers
2+
User-agent: *
3+
Allow: /
4+
5+
# Sitemap location
6+
Sitemap: https://docs.tailor.tech/sitemap.xml
7+
8+
# Crawl-delay for being polite
9+
Crawl-delay: 1

0 commit comments

Comments
 (0)