Files
awesome-copilot/extensions/pr-artifact-explorer/detector.mjs
T
github-actions[bot] e321e4a9c7 chore: publish from main
2026-08-06 12:30:14 +00:00

219 lines
7.0 KiB
JavaScript

import { posix } from "node:path";
const MIME_TYPES = Object.freeze({
".avif": "image/avif",
".bmp": "image/bmp",
".cast": "application/x-asciicast",
".config": "application/xml; charset=utf-8",
".css": "text/css; charset=utf-8",
".csproj": "application/xml; charset=utf-8",
".csv": "text/csv; charset=utf-8",
".gif": "image/gif",
".htm": "text/html; charset=utf-8",
".html": "text/html; charset=utf-8",
".ico": "image/x-icon",
".jpeg": "image/jpeg",
".jpg": "image/jpeg",
".js": "text/javascript; charset=utf-8",
".json": "application/json; charset=utf-8",
".log": "text/plain; charset=utf-8",
".map": "application/json; charset=utf-8",
".md": "text/markdown; charset=utf-8",
".mjs": "text/javascript; charset=utf-8",
".nuspec": "application/xml; charset=utf-8",
".pdf": "application/pdf",
".png": "image/png",
".props": "application/xml; charset=utf-8",
".resx": "application/xml; charset=utf-8",
".svg": "image/svg+xml",
".text": "text/plain; charset=utf-8",
".toml": "text/plain; charset=utf-8",
".ts": "text/plain; charset=utf-8",
".tsx": "text/plain; charset=utf-8",
".targets": "application/xml; charset=utf-8",
".txt": "text/plain; charset=utf-8",
".trx": "application/xml; charset=utf-8",
".webp": "image/webp",
".xaml": "application/xml; charset=utf-8",
".xml": "application/xml; charset=utf-8",
".yaml": "text/yaml; charset=utf-8",
".yml": "text/yaml; charset=utf-8",
".zip": "application/zip",
});
const TEXT_EXTENSIONS = new Set([
".config", ".css", ".csproj", ".csv", ".htm", ".html", ".ini", ".js",
".json", ".log", ".map", ".md", ".mjs", ".nuspec", ".props", ".ps1",
".resx", ".sh", ".targets", ".text", ".toml", ".ts", ".tsx", ".txt",
".trx", ".xaml", ".xml", ".yaml", ".yml",
]);
const XML_EXTENSIONS = new Set([
".config", ".csproj", ".nuspec", ".props", ".resx", ".targets", ".xaml",
".trx", ".xml",
]);
export function extensionOf(path) {
const base = posix.basename(path);
const index = base.lastIndexOf(".");
return index > 0 ? base.slice(index).toLowerCase() : "";
}
export function mimeForPath(path) {
return MIME_TYPES[extensionOf(path)] ?? "application/octet-stream";
}
export function kindForPath(path) {
const extension = extensionOf(path);
if (extension === ".cast") return "asciinema";
if (extension === ".trx") return "trx";
if (extension === ".html" || extension === ".htm") return "html";
if ([".png", ".jpg", ".jpeg", ".gif", ".webp", ".avif", ".bmp", ".svg", ".ico"].includes(extension)) {
return "image";
}
if (extension === ".pdf") return "pdf";
if (extension === ".md") return "markdown";
if (extension === ".json" || extension === ".map") return "json";
if (XML_EXTENSIONS.has(extension)) return "xml";
if (TEXT_EXTENSIONS.has(extension)) return "text";
if ([".zip", ".gz", ".tgz", ".7z", ".tar", ".rar"].includes(extension)) return "archive";
return "binary";
}
export function hasRootIndexHtml(entries) {
return entries.some((entry) => {
if (!entry?.supported) return false;
const path = String(entry?.name ?? entry?.path ?? "")
.replaceAll("\\", "/")
.replace(/^\.\/+/, "");
return path.toLocaleLowerCase() === "index.html";
});
}
function shallowest(entries) {
return [...entries].sort((left, right) => {
const depth = left.name.split("/").length - right.name.split("/").length;
return depth || left.name.localeCompare(right.name);
})[0] ?? null;
}
async function looksLikeAsciinema(entry, readHead) {
if (kindForPath(entry.name) === "asciinema") return true;
if (
entry.uncompressedSize > 8 * 1024 * 1024 ||
!/(recording|asciinema|terminal|session)/i.test(posix.basename(entry.name))
) {
return false;
}
let head;
try {
head = await readHead(entry);
} catch {
return false;
}
if (!head) return false;
const firstLine = head.toString("utf8").split(/\r?\n/, 1)[0].trim();
if (!firstLine.startsWith("{")) return false;
try {
const header = JSON.parse(firstLine);
return (
(header.version === 2 || header.version === 3) &&
Number.isFinite(header.width) &&
Number.isFinite(header.height)
);
} catch {
return false;
}
}
export async function analyzeArtifact(index, readHead) {
const files = index.entries.filter((entry) => !entry.directory);
const previewableFiles = files.filter((entry) => entry.supported);
const castEntries = [];
for (const entry of previewableFiles.slice(0, 2_000)) {
if (await looksLikeAsciinema(entry, readHead)) castEntries.push(entry);
}
const htmlEntries = previewableFiles.filter(
(entry) => kindForPath(entry.name) === "html",
);
const indexEntries = htmlEntries.filter(
(entry) => posix.basename(entry.name).toLowerCase() === "index.html",
);
const rootIndexEntry = indexEntries.find(
(entry) => hasRootIndexHtml([entry]),
);
const firstTrx = shallowest(
previewableFiles.filter((entry) => kindForPath(entry.name) === "trx"),
);
const firstImage = shallowest(
previewableFiles.filter((entry) => kindForPath(entry.name) === "image"),
);
const firstMarkdown = shallowest(
previewableFiles.filter((entry) => kindForPath(entry.name) === "markdown"),
);
const firstText = shallowest(
previewableFiles.filter((entry) =>
["json", "text", "xml"].includes(kindForPath(entry.name)),
),
);
let primary = null;
if (rootIndexEntry) {
primary = {
kind: "static-site",
path: rootIndexEntry.name,
root: "",
label: "Static HTML site",
};
} else if (castEntries.length) {
primary = {
kind: "asciinema",
path: castEntries[0].name,
label: castEntries.length === 1 ? "Asciinema recording" : `${castEntries.length} Asciinema recordings`,
};
} else if (firstTrx) {
primary = { kind: "trx", path: firstTrx.name, label: "Test results" };
} else if (firstImage) {
primary = { kind: "image", path: firstImage.name, label: "Image preview" };
} else if (firstMarkdown) {
primary = { kind: "markdown", path: firstMarkdown.name, label: "Markdown document" };
} else if (firstText) {
const kind = kindForPath(firstText.name);
primary = {
kind,
path: firstText.name,
label: kind === "xml" ? "XML code" : kind === "json" ? "JSON code" : "Text preview",
};
}
const counts = {};
const entries = files.map((entry) => {
const detectedKind = castEntries.includes(entry) ? "asciinema" : kindForPath(entry.name);
counts[detectedKind] = (counts[detectedKind] ?? 0) + 1;
return {
path: entry.name,
name: posix.basename(entry.name),
size: entry.uncompressedSize,
compressedSize: entry.compressedSize,
kind: detectedKind,
mime: detectedKind === "asciinema" ? "application/x-asciicast" : mimeForPath(entry.name),
supported: entry.supported,
modifiedAt: entry.modifiedAt,
};
});
return {
primary,
entries,
counts,
fileCount: files.length,
totalUncompressedBytes: index.totalUncompressedBytes,
zipBacked: true,
};
}
export function isInlineTextKind(kind) {
return ["json", "markdown", "text", "trx", "xml"].includes(kind);
}