mirror of
https://github.com/github/awesome-copilot.git
synced 2026-08-07 09:38:45 +00:00
219 lines
7.0 KiB
JavaScript
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);
|
|
}
|