UNPKG

run402-mcp

Version:

MCP server for Run402 — full-stack backend infrastructure for AI agents: Postgres, auth, storage, serverless functions and atomic deploys. Paid with x402/MPP. Includes $0.03 image generation.

112 lines • 4.23 kB
/** * Client-side breakdown of the paths a `DESTRUCTIVE_SITE_BULK_REMOVAL` * warning lists in `affected`. * * The gateway counts every removed static path toward its bulk-removal * threshold. A framework rebuild renames every content-hashed bundle * (`_astro/Foo.Ds5BwtTY.js` → `_astro/Foo.<newhash>.js`), so a routine * redeploy can trip the warning without removing a single page. This * summary lets a reviewer tell the two cases apart without diffing * releases by hand. It is a heuristic and never changes whether the * warning blocks; the gateway stays authoritative. * * Current gateways already leave out removed fingerprinted bundles that * the build replaces in the same directory (`details.replaced_fingerprinted`), * so `affected` holds only the removals still counted; this summary then * splits those into pages and other files. */ export const SITE_BULK_REMOVAL_WARNING_CODE = "DESTRUCTIVE_SITE_BULK_REMOVAL"; const PAGE_PATH_SAMPLE_LIMIT = 20; const OTHER_PATH_SAMPLE_LIMIT = 20; /** * A content hash is a `.`- or `-`-separated basename segment of at least * eight `[A-Za-z0-9_]` characters that contains a digit or mixes upper * and lower case (`Ds5BwtTY`, `CzEebLVq`, `a3f9c2e1`). Plain words such as * `component` or `profile` do not qualify. */ export function isContentHashedPath(path) { const base = path.slice(path.lastIndexOf("/") + 1); const dot = base.lastIndexOf("."); if (dot <= 0) return false; const stem = base.slice(0, dot); if (/\.html?$/i.test(base)) return false; for (const segment of stem.split(/[.-]/).slice(1)) { if (!/^[A-Za-z0-9_]{8,}$/.test(segment)) continue; if (/\d/.test(segment) || (/[a-z]/.test(segment) && /[A-Z]/.test(segment))) return true; } return false; } function isPagePath(path) { return /\.html?$/i.test(path); } function topLevelDir(path) { const trimmed = path.replace(/^\/+/, ""); const slash = trimmed.indexOf("/"); return slash === -1 ? "." : trimmed.slice(0, slash); } export function summarizeSiteRemoval(paths, opts = {}) { const byDir = {}; const pagePaths = []; const otherPaths = []; let pages = 0; let hashed = 0; let other = 0; for (const path of paths) { const dir = topLevelDir(path); byDir[dir] = (byDir[dir] ?? 0) + 1; if (isPagePath(path)) { pages += 1; if (pagePaths.length < PAGE_PATH_SAMPLE_LIMIT) pagePaths.push(path); } else if (isContentHashedPath(path)) { hashed += 1; } else { other += 1; if (otherPaths.length < OTHER_PATH_SAMPLE_LIMIT) otherPaths.push(path); } } return { total: paths.length, pages, content_hashed_build_assets: hashed, other, only_content_hashed_build_assets: paths.length > 0 && hashed === paths.length, ...(opts.includeByTopLevelDir === false ? {} : { by_top_level_dir: byDir }), page_paths: pagePaths, other_paths: otherPaths, confidence: "heuristic", }; } /** * Adds `details.removed_paths_summary` to each bulk-removal warning that * lists its paths and does not already carry a summary. Other warnings * pass through untouched. */ export function withSiteRemovalSummaries(warnings) { let changed = false; const next = warnings.map((warning) => { if (warning?.code !== SITE_BULK_REMOVAL_WARNING_CODE) return warning; if (!Array.isArray(warning.affected) || warning.affected.length === 0) return warning; if (warning.details && "removed_paths_summary" in warning.details) return warning; changed = true; return { ...warning, details: { ...(warning.details ?? {}), removed_paths_summary: summarizeSiteRemoval(warning.affected.filter((p) => typeof p === "string"), { includeByTopLevelDir: !(warning.details && "counted_removed_by_dir" in warning.details) }), }, }; }); return changed ? next : warnings; } //# sourceMappingURL=site-removal-summary.js.map