mirror of
https://github.com/getpaseo/paseo.git
synced 2026-10-23 09:42:53 +00:00
* feat(plugins): install reviewed artifacts from open registries * fix(website): build protocol before deployment checks * fix(website): make plugin pages discoverable to search engines Resolve missing registry entries in route loaders to return HTTP 404. Serve a registry-backed sitemap so newly listed plugins and authors are discoverable without rebuilding the website. * feat(website): keep plugin directory unlinked for coordinated launch Preserve direct routes and SEO while retaining the partner directory link. Cover nested Git artifacts through registry installation and pinned website commands. * fix(website): link plugin cards by registry owner and stub the clipboard in e2e Plugin and author URLs come from the owner segment of the plugin ID, not the author's GitHub login, so a listed plugin always resolves. The Playwright spec records clipboard writes instead of granting a permission WebKit does not have.
136 lines
3.6 KiB
TypeScript
136 lines
3.6 KiB
TypeScript
import fs from "node:fs";
|
|
import path from "node:path";
|
|
import { defineConfig, type UserConfig } from "vite";
|
|
import tsConfigPaths from "vite-tsconfig-paths";
|
|
import { tanstackStart } from "@tanstack/react-start/plugin/vite";
|
|
import { cloudflare } from "@cloudflare/vite-plugin";
|
|
import react from "@vitejs/plugin-react";
|
|
import tailwindcss from "@tailwindcss/vite";
|
|
|
|
const repoRoot = path.resolve(__dirname, "../..");
|
|
const siteHost = "https://paseo.sh";
|
|
|
|
function discoverDocsRoutes(): string[] {
|
|
const docsDir = path.join(repoRoot, "public-docs");
|
|
if (!fs.existsSync(docsDir)) return ["/docs"];
|
|
const routes = new Set<string>(["/docs"]);
|
|
const walk = (dir: string) => {
|
|
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
const full = path.join(dir, entry.name);
|
|
if (entry.isDirectory()) {
|
|
walk(full);
|
|
continue;
|
|
}
|
|
if (!entry.name.endsWith(".md")) continue;
|
|
const rel = path
|
|
.relative(docsDir, full)
|
|
.replace(/\.md$/, "")
|
|
.replace(/\/index$/, "");
|
|
if (rel === "index" || rel === "") continue;
|
|
routes.add(`/docs/${rel.split(path.sep).join("/")}`);
|
|
}
|
|
};
|
|
walk(docsDir);
|
|
return [...routes].sort();
|
|
}
|
|
|
|
function discoverAgentRoutes(): string[] {
|
|
const routesDir = path.join(__dirname, "src/routes");
|
|
const reserved = new Set([
|
|
"__root",
|
|
"agents",
|
|
"blog",
|
|
"changelog",
|
|
"docs",
|
|
"download",
|
|
"hub",
|
|
"index",
|
|
"sponsor",
|
|
"privacy",
|
|
"terms",
|
|
]);
|
|
return fs
|
|
.readdirSync(routesDir, { withFileTypes: true })
|
|
.filter((entry) => entry.isFile() && entry.name.endsWith(".tsx"))
|
|
.map((entry) => entry.name.replace(/\.tsx$/, ""))
|
|
.filter((name) => !reserved.has(name))
|
|
.sort()
|
|
.map((slug) => `/${slug}`);
|
|
}
|
|
|
|
function discoverAlternativeRoutes(): string[] {
|
|
const alternativesDir = path.join(__dirname, "src/routes/alternatives");
|
|
if (!fs.existsSync(alternativesDir)) return [];
|
|
const slugs = fs
|
|
.readdirSync(alternativesDir, { withFileTypes: true })
|
|
.filter((entry) => entry.isFile() && entry.name.endsWith(".tsx"))
|
|
.map((entry) => entry.name.replace(/\.tsx$/, ""))
|
|
.sort();
|
|
return ["/alternatives", ...slugs.map((slug) => `/alternatives/${slug}`)];
|
|
}
|
|
|
|
function discoverBlogRoutes(): string[] {
|
|
const postsDir = path.join(__dirname, "posts");
|
|
if (!fs.existsSync(postsDir)) return ["/blog"];
|
|
const slugs = fs
|
|
.readdirSync(postsDir, { withFileTypes: true })
|
|
.filter((entry) => entry.isFile() && entry.name.endsWith(".md"))
|
|
.map((entry) => entry.name.replace(/\.md$/, ""))
|
|
.sort();
|
|
return ["/blog", ...slugs.map((slug) => `/blog/${slug}`)];
|
|
}
|
|
|
|
// Author and detail URLs are served by /sitemap-plugins.xml as the registry changes.
|
|
function discoverPluginRoutes(): string[] {
|
|
return ["/plugins"];
|
|
}
|
|
|
|
const sitemapPages = [
|
|
"/",
|
|
"/agents",
|
|
"/changelog",
|
|
"/download",
|
|
"/hub",
|
|
"/privacy",
|
|
"/terms",
|
|
...discoverAgentRoutes(),
|
|
...discoverAlternativeRoutes(),
|
|
...discoverPluginRoutes(),
|
|
...discoverDocsRoutes(),
|
|
...discoverBlogRoutes(),
|
|
].map((routePath) => ({
|
|
path: routePath,
|
|
}));
|
|
|
|
export default defineConfig((): UserConfig => {
|
|
return {
|
|
server: {
|
|
host: "0.0.0.0",
|
|
port: 8082,
|
|
strictPort: false,
|
|
fs: {
|
|
allow: [repoRoot],
|
|
},
|
|
watch: {
|
|
ignored: ["**/.tanstack/**"],
|
|
},
|
|
},
|
|
plugins: [
|
|
cloudflare({ viteEnvironment: { name: "ssr" } }),
|
|
tsConfigPaths(),
|
|
tanstackStart({
|
|
router: {
|
|
quoteStyle: "double",
|
|
semicolons: true,
|
|
},
|
|
pages: sitemapPages,
|
|
sitemap: {
|
|
host: siteHost,
|
|
},
|
|
}),
|
|
react(),
|
|
tailwindcss(),
|
|
],
|
|
};
|
|
});
|