import { Link, Outlet, useLocation, useMatchRoute } from "@tanstack/react-router"; import { useCallback, useEffect, useState } from "react"; import { useSources, type Source } from "@executor/react"; import { Button, sourcePluginsIndexPath } from "@executor/react/plugins"; import { cn } from "../lib/utils"; import { IconPlus, IconCopy, IconCheck, IconClose, IconMenu } from "./icons"; import { LoadableBlock } from "./loadable"; import { SourceFavicon } from "./source-favicon"; import { getSourceFrontendPaths, registeredFrontendPluginNavRoutes, } from "../plugins"; // ── Status dot color ───────────────────────────────────────────────────── const statusColor: Record = { connected: "bg-primary", probing: "bg-amber-400", draft: "bg-muted-foreground/30", auth_required: "bg-amber-500", error: "bg-destructive", }; type AppMetaEnv = { readonly VITE_APP_VERSION: string; readonly VITE_GITHUB_URL: string; }; const { VITE_APP_VERSION, VITE_GITHUB_URL } = (import.meta as ImportMeta & { readonly env: AppMetaEnv; }).env; type UpdateChannel = "latest" | "beta"; const EXECUTOR_DIST_TAGS_PATH = "/v1/app/npm/dist-tags"; type ParsedVersion = { readonly major: number; readonly minor: number; readonly patch: number; readonly prerelease: ReadonlyArray | null; }; const semverPattern = /^(?\d+)\.(?\d+)\.(?\d+)(?:-(?[0-9A-Za-z.-]+))?(?:\+[0-9A-Za-z.-]+)?$/; const resolveUpdateChannel = (version: string): UpdateChannel => version.includes("-beta.") ? "beta" : "latest"; const parseVersion = (version: string): ParsedVersion | null => { const match = version.trim().match(semverPattern); if (!match?.groups) { return null; } return { major: Number(match.groups.major), minor: Number(match.groups.minor), patch: Number(match.groups.patch), prerelease: match.groups.prerelease ? match.groups.prerelease.split(".").map((identifier) => /^\d+$/.test(identifier) ? Number(identifier) : identifier, ) : null, }; }; const comparePrereleaseIdentifiers = ( left: ReadonlyArray | null, right: ReadonlyArray | null, ): number => { if (left === null && right === null) { return 0; } if (left === null) { return 1; } if (right === null) { return -1; } const length = Math.max(left.length, right.length); for (let index = 0; index < length; index += 1) { const leftIdentifier = left[index]; const rightIdentifier = right[index]; if (leftIdentifier === rightIdentifier) { continue; } if (leftIdentifier === undefined) { return -1; } if (rightIdentifier === undefined) { return 1; } if (typeof leftIdentifier === "number" && typeof rightIdentifier === "number") { return leftIdentifier < rightIdentifier ? -1 : 1; } if (typeof leftIdentifier === "number") { return -1; } if (typeof rightIdentifier === "number") { return 1; } return leftIdentifier < rightIdentifier ? -1 : 1; } return 0; }; const compareVersions = (left: string, right: string): number | null => { const leftVersion = parseVersion(left); const rightVersion = parseVersion(right); if (!leftVersion || !rightVersion) { return null; } if (leftVersion.major !== rightVersion.major) { return leftVersion.major < rightVersion.major ? -1 : 1; } if (leftVersion.minor !== rightVersion.minor) { return leftVersion.minor < rightVersion.minor ? -1 : 1; } if (leftVersion.patch !== rightVersion.patch) { return leftVersion.patch < rightVersion.patch ? -1 : 1; } return comparePrereleaseIdentifiers(leftVersion.prerelease, rightVersion.prerelease); }; // ── useLatestVersion ───────────────────────────────────────────────────── function useLatestVersion(currentVersion: string) { const channel = resolveUpdateChannel(currentVersion); const [latestVersion, setLatestVersion] = useState(null); useEffect(() => { let cancelled = false; fetch(EXECUTOR_DIST_TAGS_PATH) .then(async (res) => { if (!res.ok) { throw new Error(`Failed to load dist tags: ${res.status}`); } return res.json() as Promise>>; }) .then((data: Partial>) => { if (!cancelled) { setLatestVersion(data[channel] ?? null); } }) .catch(() => {}); return () => { cancelled = true; }; }, [channel]); const updateAvailable = latestVersion !== null && compareVersions(currentVersion, latestVersion) === -1; return { latestVersion, updateAvailable, channel }; } // ── UpdateCard ─────────────────────────────────────────────────────────── function UpdateCard(props: { latestVersion: string; channel: UpdateChannel }) { const command = `npm i -g executor@${props.channel}`; const [copied, setCopied] = useState(false); const handleCopy = useCallback(() => { void navigator.clipboard.writeText(command).then(() => { setCopied(true); setTimeout(() => setCopied(false), 1500); }); }, [command]); return (

Update available

v{props.latestVersion}

); } // ── AppShell ───────────────────────────────────────────────────────────── export function AppShell() { const sources = useSources(); const location = useLocation(); const matchRoute = useMatchRoute(); const [mobileSidebarOpen, setMobileSidebarOpen] = useState(false); const isHome = matchRoute({ to: "/" }); const isSecrets = matchRoute({ to: "/secrets" }); const isPolicies = matchRoute({ to: "/policies" }); const { latestVersion, updateAvailable, channel } = useLatestVersion(VITE_APP_VERSION); const mainPluginNavItems = registeredFrontendPluginNavRoutes.filter( ({ route }) => (route.nav?.section ?? "main") === "main", ); const sourcePluginNavItems = registeredFrontendPluginNavRoutes.filter( ({ route }) => route.nav?.section === "sources", ); useEffect(() => { setMobileSidebarOpen(false); }, [location.pathname]); useEffect(() => { if (!mobileSidebarOpen) { return; } const previousOverflow = document.body.style.overflow; document.body.style.overflow = "hidden"; return () => { document.body.style.overflow = previousOverflow; }; }, [mobileSidebarOpen]); return (
{mobileSidebarOpen && (
setMobileSidebarOpen(false)} showBrand={false} />
)} {/* Main content */}
executor v3
); } function SidebarContent(props: { isHome: boolean; isSecrets: boolean; isPolicies: boolean; latestVersion: string | null; updateAvailable: boolean; channel: UpdateChannel; locationPathname: string; mainPluginNavItems: typeof registeredFrontendPluginNavRoutes; sourcePluginNavItems: typeof registeredFrontendPluginNavRoutes; sources: ReturnType; onNavigate?: () => void; showBrand?: boolean; }) { return ( <> {props.showBrand !== false && (
executor v3
)} {props.updateAvailable && props.latestVersion && ( )}
v{VITE_APP_VERSION} GitHub
); } // ── SourceItem ─────────────────────────────────────────────────────────── function SourceItem(props: { pathname: string; source: Source; onNavigate?: () => void; }) { const paths = getSourceFrontendPaths(props.source.kind); if (!paths) { return (
{props.source.name}
); } const detailPath = paths.detail(props.source.id); const active = props.pathname === detailPath || props.pathname.startsWith(`${detailPath}/`); return (
{props.source.name} ); } // ── NavItem ────────────────────────────────────────────────────────────── function NavItem(props: { to: string; label: string; active: boolean; onNavigate?: () => void; }) { return ( {props.label} ); }