mirror of
https://github.com/pnpm/pnpm.git
synced 2026-04-28 02:53:15 -04:00
feat: store prune displays the total size of removed files (#11047)
* feat: store prune displays the total size of removed files * test: update
This commit is contained in:
6
.changeset/store-prune-size.md
Normal file
6
.changeset/store-prune-size.md
Normal file
@@ -0,0 +1,6 @@
|
||||
---
|
||||
"@pnpm/store.controller": patch
|
||||
"pnpm": patch
|
||||
---
|
||||
|
||||
`pnpm store prune` now displays the total size of removed files.
|
||||
3
pnpm-lock.yaml
generated
3
pnpm-lock.yaml
generated
@@ -8373,6 +8373,9 @@ importers:
|
||||
is-subdir:
|
||||
specifier: 'catalog:'
|
||||
version: 2.0.0
|
||||
pretty-bytes:
|
||||
specifier: 'catalog:'
|
||||
version: 7.1.0
|
||||
ramda:
|
||||
specifier: 'catalog:'
|
||||
version: '@pnpm/ramda@0.28.1'
|
||||
|
||||
@@ -92,6 +92,55 @@ test('remove unreferenced packages', async () => {
|
||||
expect(fs.readdirSync(cacheDir)).toStrictEqual([])
|
||||
})
|
||||
|
||||
test('prune outputs total size of removed files', async () => {
|
||||
const project = prepare()
|
||||
const cacheDir = path.resolve('cache')
|
||||
const storeDir = path.resolve('store')
|
||||
|
||||
await execa('node', [
|
||||
pnpmBin,
|
||||
'add',
|
||||
'is-negative@^2.1.0',
|
||||
`--store-dir=${storeDir}`,
|
||||
`--cache-dir=${cacheDir}`,
|
||||
`--registry=${REGISTRY}`])
|
||||
await execa('node', [
|
||||
pnpmBin,
|
||||
'remove',
|
||||
'is-negative',
|
||||
`--store-dir=${storeDir}`,
|
||||
`--cache-dir=${cacheDir}`,
|
||||
'--config.modules-cache-max-age=0',
|
||||
], { env: { npm_config_registry: REGISTRY } })
|
||||
|
||||
project.storeHas('is-negative', '2.1.0')
|
||||
|
||||
const reporter = jest.fn()
|
||||
await store.handler({
|
||||
cacheDir,
|
||||
dir: process.cwd(),
|
||||
pnpmHomeDir: '',
|
||||
rawConfig: {
|
||||
registry: REGISTRY,
|
||||
},
|
||||
registries: { default: REGISTRY },
|
||||
reporter,
|
||||
storeDir,
|
||||
userConfig: {},
|
||||
dlxCacheMaxAge: Infinity,
|
||||
virtualStoreDirMaxLength: process.platform === 'win32' ? 60 : 120,
|
||||
}, ['prune'])
|
||||
|
||||
// Check that the message includes file count and size information
|
||||
// The message should match pattern like "Removed X files (Y B)" or "Removed 1 file (Y B)"
|
||||
expect(reporter).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
level: 'info',
|
||||
message: expect.stringMatching(/Removed \d+ files? \(\d+(\.\d+)?\s+\S+\)/),
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
test('remove packages that are used by project that no longer exist', async () => {
|
||||
prepare()
|
||||
const cacheDir = path.resolve('cache')
|
||||
@@ -130,7 +179,7 @@ test('remove packages that are used by project that no longer exist', async () =
|
||||
expect(reporter).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
level: 'info',
|
||||
message: 'Removed 4 files',
|
||||
message: expect.stringMatching(/Removed 4 files \(\d+(\.\d+)?\s+\S+\)/),
|
||||
})
|
||||
)
|
||||
|
||||
@@ -318,7 +367,7 @@ describe('prune when store directory is not properly configured', () => {
|
||||
expect(reporter).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
level: 'info',
|
||||
message: 'Removed 0 files',
|
||||
message: expect.stringMatching(/Removed 0 files \(\d+(\.\d+)?\s+\S+\)/),
|
||||
})
|
||||
)
|
||||
|
||||
|
||||
@@ -57,6 +57,7 @@
|
||||
"@pnpm/types": "workspace:*",
|
||||
"@zkochan/rimraf": "catalog:",
|
||||
"is-subdir": "catalog:",
|
||||
"pretty-bytes": "catalog:",
|
||||
"ramda": "catalog:",
|
||||
"ssri": "catalog:",
|
||||
"symlink-dir": "catalog:"
|
||||
|
||||
@@ -6,6 +6,7 @@ import { globalInfo, globalWarn } from '@pnpm/logger'
|
||||
import type { PackageFilesIndex } from '@pnpm/store.cafs'
|
||||
import type { StoreIndex } from '@pnpm/store.index'
|
||||
import { rimraf } from '@zkochan/rimraf'
|
||||
import prettyBytes from 'pretty-bytes'
|
||||
|
||||
import { pruneGlobalVirtualStore } from './pruneGlobalVirtualStore.js'
|
||||
|
||||
@@ -43,6 +44,7 @@ export async function prune ({ cacheDir, storeDir, storeIndex }: PruneOptions, r
|
||||
const removedHashes = new Set<string>()
|
||||
const dirs = await getSubdirsSafely(cafsDir)
|
||||
let fileCounter = 0
|
||||
let totalSize = 0
|
||||
await Promise.all(dirs.map(async (dir) => {
|
||||
const subdir = path.join(cafsDir, dir)
|
||||
await Promise.all((await fs.readdir(subdir)).map(async (fileName) => {
|
||||
@@ -60,6 +62,7 @@ export async function prune ({ cacheDir, storeDir, storeIndex }: PruneOptions, r
|
||||
}
|
||||
}
|
||||
if (stat.nlink === 1 || stat.nlink === BIG_ONE) {
|
||||
totalSize += stat.size
|
||||
await fs.unlink(filePath)
|
||||
fileCounter++
|
||||
// Store the hex digest, which matches the format stored in PackageFileInfo.digest
|
||||
@@ -68,7 +71,7 @@ export async function prune ({ cacheDir, storeDir, storeIndex }: PruneOptions, r
|
||||
}
|
||||
}))
|
||||
}))
|
||||
globalInfo(`Removed ${fileCounter} file${fileCounter === 1 ? '' : 's'}`)
|
||||
globalInfo(`Removed ${fileCounter} file${fileCounter === 1 ? '' : 's'} (${prettyBytes(totalSize)})`)
|
||||
|
||||
// 4. Clean up orphaned package index entries
|
||||
let pkgCounter = 0
|
||||
|
||||
Reference in New Issue
Block a user