mirror of
https://github.com/github/awesome-copilot.git
synced 2026-09-23 14:50:26 +00:00
Add Windows app storage inspector canvas 🤖🤖🤖 (#2620)
* feat: add Windows app storage inspector canvas * Apply suggestions from code review Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> * fix: address storage inspector review findings * Correction to package and plugin * fix: harden storage inspector cleanup * Harden cleanup operation outcomes * Add select a file or folder path in the result tabs to navigate the treemap to its deepest visible parent folder. * Fixes to ensure selftesst pass --------- Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
This commit is contained in:
co-authored by
Copilot Autofix powered by AI
parent
5755234c4f
commit
df9116a689
@@ -0,0 +1,119 @@
|
||||
import { execFile } from "node:child_process";
|
||||
import { stat } from "node:fs/promises";
|
||||
import path from "node:path";
|
||||
import { promisify } from "node:util";
|
||||
import { getAnalyzerCommands } from "../core/analyzer-commands.mjs";
|
||||
|
||||
const execFileAsync = promisify(execFile);
|
||||
const UV_CLI_TIMEOUT_MS = 10_000;
|
||||
|
||||
function normalize(filePath) {
|
||||
return path.resolve(filePath).replaceAll("/", "\\").toLowerCase();
|
||||
}
|
||||
|
||||
function isWithin(candidatePath, parentPath) {
|
||||
const relative = path.relative(path.resolve(parentPath), path.resolve(candidatePath));
|
||||
return relative === "" || (relative !== ".." && !relative.startsWith(`..${path.sep}`) && !path.isAbsolute(relative));
|
||||
}
|
||||
|
||||
function getDirectoryAggregate(result, directoryPath) {
|
||||
const normalizedPath = normalize(directoryPath);
|
||||
return result.directories.find((directory) => normalize(directory.path) === normalizedPath);
|
||||
}
|
||||
|
||||
function getDefaultUvRoot() {
|
||||
const localAppData = process.env.LOCALAPPDATA ?? path.join(process.env.USERPROFILE ?? "", "AppData", "Local");
|
||||
return path.resolve(localAppData, "uv");
|
||||
}
|
||||
|
||||
async function getConfiguredCachePath(defaultCachePath) {
|
||||
try {
|
||||
const { stdout } = await execFileAsync("uv.exe", ["cache", "dir"], {
|
||||
windowsHide: true,
|
||||
timeout: UV_CLI_TIMEOUT_MS,
|
||||
maxBuffer: 1024 * 1024,
|
||||
});
|
||||
const configuredPath = stdout.trim();
|
||||
if (!configuredPath) {
|
||||
throw new Error("uv did not return a cache path");
|
||||
}
|
||||
return { path: path.resolve(configuredPath), source: "uv cache dir" };
|
||||
} catch (error) {
|
||||
return {
|
||||
path: defaultCachePath,
|
||||
source: "Windows default",
|
||||
error: error.code === "ENOENT"
|
||||
? "uv was not found, so the standard Windows cache location was checked instead."
|
||||
: `uv cache configuration could not be read: ${error.message}`,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export async function discoverUvCachePaths() {
|
||||
const rootPath = getDefaultUvRoot();
|
||||
const configuredCache = await getConfiguredCachePath(path.join(rootPath, "cache"));
|
||||
return [...new Set([rootPath, configuredCache.path])];
|
||||
}
|
||||
|
||||
async function describeDirectory(result, id, name, directoryPath) {
|
||||
try {
|
||||
const directoryStats = await stat(directoryPath);
|
||||
if (!directoryStats.isDirectory()) {
|
||||
return undefined;
|
||||
}
|
||||
const aggregate = getDirectoryAggregate(result, directoryPath);
|
||||
return {
|
||||
id,
|
||||
name,
|
||||
path: directoryPath,
|
||||
bytes: aggregate?.bytes ?? 0,
|
||||
files: aggregate?.files ?? 0,
|
||||
modifiedAt: directoryStats.mtime.toISOString(),
|
||||
};
|
||||
} catch (error) {
|
||||
if (error?.code === "ENOENT") {
|
||||
return undefined;
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
export async function analyzeUvCache(result) {
|
||||
const rootPath = getDefaultUvRoot();
|
||||
const configuredCache = await getConfiguredCachePath(path.join(rootPath, "cache"));
|
||||
const [root, cache] = await Promise.all([
|
||||
describeDirectory(result, "uv-root", "uv data", rootPath),
|
||||
describeDirectory(result, "uv-cache", "uv cache", configuredCache.path),
|
||||
]);
|
||||
const locations = [root, cache].filter((location, index, all) => (
|
||||
location && all.findIndex((candidate) => candidate?.path === location.path) === index
|
||||
));
|
||||
const analysisRoots = locations.map((location) => location.path);
|
||||
const topFiles = result.largestFiles
|
||||
.filter((file) => analysisRoots.some((rootPath) => isWithin(file.path, rootPath)))
|
||||
.sort((left, right) => right.bytes - left.bytes)
|
||||
.slice(0, 20);
|
||||
const totalBytes = root
|
||||
? root.bytes + (cache && !isWithin(cache.path, root.path) ? cache.bytes : 0)
|
||||
: (cache?.bytes ?? 0);
|
||||
|
||||
return {
|
||||
id: "uv-cache",
|
||||
status: locations.length ? "available" : "not-found",
|
||||
root,
|
||||
cache,
|
||||
locations,
|
||||
configuredCachePath: configuredCache.path,
|
||||
configurationSource: configuredCache.source,
|
||||
configurationError: configuredCache.error,
|
||||
topFiles,
|
||||
totalBytes,
|
||||
cacheBytes: cache?.bytes ?? 0,
|
||||
cleanupItems: [],
|
||||
cleanupCommands: getAnalyzerCommands("uv-cache"),
|
||||
message: locations.length
|
||||
? "uv manages this cache as append-only storage. Use uv cache commands instead of modifying its files directly."
|
||||
: "uv storage was not found in its standard Windows location. uv creates cache storage as packages and Python versions are used.",
|
||||
warning: "Do not delete files or directories inside uv's cache directly. uv coordinates concurrent access and locks cache-modifying operations.",
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user