This commit is contained in:
Joscha Götzer 2025-12-23 15:42:27 +08:00 committed by GitHub
commit 06cc7b5b57
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 163 additions and 38 deletions

View file

@ -0,0 +1,119 @@
import type { Argv } from "yargs"
import { cmd } from "./cmd"
import * as prompts from "@clack/prompts"
import { Global } from "../../global"
import { getDirectorySize, formatSize, shortenPath } from "../util"
import fs from "fs/promises"
import path from "path"
const CacheCleanCommand = cmd({
command: "clean",
describe: "remove cached plugins and packages",
builder: (yargs: Argv) =>
yargs
.option("force", {
alias: "f",
type: "boolean",
describe: "skip confirmation prompt",
default: false,
})
.option("dry-run", {
type: "boolean",
describe: "show what would be removed without removing",
default: false,
}),
async handler(args) {
const exists = await fs
.access(Global.Path.cache)
.then(() => true)
.catch(() => false)
if (!exists) {
prompts.log.info("Cache directory does not exist")
return
}
const size = await getDirectorySize(Global.Path.cache)
prompts.log.info(`Cache: ${shortenPath(Global.Path.cache)} (${formatSize(size)})`)
if (args.dryRun) {
prompts.log.warn("Dry run - no changes made")
return
}
if (!args.force) {
const confirm = await prompts.confirm({
message: "Remove cache directory?",
initialValue: true,
})
if (!confirm || prompts.isCancel(confirm)) {
prompts.log.warn("Cancelled")
return
}
}
const spinner = prompts.spinner()
spinner.start("Removing cache...")
const err = await fs.rm(Global.Path.cache, { recursive: true, force: true }).catch((e) => e)
if (err) {
spinner.stop("Failed to remove cache", 1)
prompts.log.error(err.message)
return
}
spinner.stop("Cache removed")
},
})
const CacheInfoCommand = cmd({
command: "info",
describe: "show cache directory information",
async handler() {
const exists = await fs
.access(Global.Path.cache)
.then(() => true)
.catch(() => false)
prompts.log.info(`Path: ${shortenPath(Global.Path.cache)}`)
if (!exists) {
prompts.log.info("Status: not created")
return
}
const size = await getDirectorySize(Global.Path.cache)
prompts.log.info(`Size: ${formatSize(size)}`)
const pkgjson = Bun.file(path.join(Global.Path.cache, "package.json"))
const parsed = await pkgjson.json().catch(() => null)
if (parsed?.dependencies) {
const deps = Object.entries(parsed.dependencies)
if (deps.length > 0) {
prompts.log.info(`Packages:`)
for (const [pkg, version] of deps) {
prompts.log.info(` ${pkg}@${version}`)
}
}
}
const versionFile = Bun.file(path.join(Global.Path.cache, "version"))
const version = await versionFile.text().catch(() => null)
if (version) {
prompts.log.info(`Cache version: ${version.trim()}`)
}
},
})
export const CacheCommand = cmd({
command: "cache",
describe: "manage plugin and package cache",
builder: (yargs) =>
yargs
.command(CacheCleanCommand)
.command(CacheInfoCommand)
.demandCommand(1, "Please specify a subcommand: clean or info"),
async handler() {},
})

View file

@ -3,6 +3,7 @@ import { UI } from "../ui"
import * as prompts from "@clack/prompts"
import { Installation } from "../../installation"
import { Global } from "../../global"
import { getDirectorySize, formatSize, shortenPath } from "../util"
import { $ } from "bun"
import fs from "fs/promises"
import path from "path"
@ -304,41 +305,3 @@ async function cleanShellConfig(file: string) {
const output = filtered.join("\n") + "\n"
await Bun.write(file, output)
}
async function getDirectorySize(dir: string): Promise<number> {
let total = 0
const walk = async (current: string) => {
const entries = await fs.readdir(current, { withFileTypes: true }).catch(() => [])
for (const entry of entries) {
const full = path.join(current, entry.name)
if (entry.isDirectory()) {
await walk(full)
continue
}
if (entry.isFile()) {
const stat = await fs.stat(full).catch(() => null)
if (stat) total += stat.size
}
}
}
await walk(dir)
return total
}
function formatSize(bytes: number): string {
if (bytes < 1024) return `${bytes} B`
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`
if (bytes < 1024 * 1024 * 1024) return `${(bytes / (1024 * 1024)).toFixed(1)} MB`
return `${(bytes / (1024 * 1024 * 1024)).toFixed(1)} GB`
}
function shortenPath(p: string): string {
const home = os.homedir()
if (p.startsWith(home)) {
return p.replace(home, "~")
}
return p
}

View file

@ -0,0 +1,41 @@
import fs from "fs/promises"
import path from "path"
import os from "os"
export async function getDirectorySize(dir: string): Promise<number> {
let total = 0
const walk = async (current: string) => {
const entries = await fs.readdir(current, { withFileTypes: true }).catch(() => [])
for (const entry of entries) {
const full = path.join(current, entry.name)
if (entry.isDirectory()) {
await walk(full)
continue
}
if (entry.isFile()) {
const stat = await fs.stat(full).catch(() => null)
if (stat) total += stat.size
}
}
}
await walk(dir)
return total
}
export function formatSize(bytes: number): string {
if (bytes < 1024) return `${bytes} B`
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`
if (bytes < 1024 * 1024 * 1024) return `${(bytes / (1024 * 1024)).toFixed(1)} MB`
return `${(bytes / (1024 * 1024 * 1024)).toFixed(1)} GB`
}
export function shortenPath(p: string): string {
const home = os.homedir()
if (p.startsWith(home)) {
return p.replace(home, "~")
}
return p
}

View file

@ -27,6 +27,7 @@ import { EOL } from "os"
import { WebCommand } from "./cli/cmd/web"
import { PrCommand } from "./cli/cmd/pr"
import { SessionCommand } from "./cli/cmd/session"
import { CacheCommand } from "./cli/cmd/cache"
process.on("unhandledRejection", (e) => {
Log.Default.error("rejection", {
@ -98,6 +99,7 @@ const cli = yargs(hideBin(process.argv))
.command(GithubCommand)
.command(PrCommand)
.command(SessionCommand)
.command(CacheCommand)
.fail((msg) => {
if (
msg.startsWith("Unknown argument") ||