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
JavaScript
/**
* 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