mirror of
https://github.com/pnpm/pnpm.git
synced 2025-12-23 23:29:17 -05:00
feat: prune --force removes alien directories (#7272)
add the ability to use --force to remove alien directory from the store
This commit is contained in:
8
.changeset/rich-radios-share.md
Normal file
8
.changeset/rich-radios-share.md
Normal file
@@ -0,0 +1,8 @@
|
||||
---
|
||||
"@pnpm/store-controller-types": patch
|
||||
"@pnpm/plugin-commands-store": patch
|
||||
"@pnpm/package-store": patch
|
||||
"pnpm": patch
|
||||
---
|
||||
|
||||
When using `pnpm store prune --force` alien directories are removed from the store [#7272](https://github.com/pnpm/pnpm/pull/7272).
|
||||
@@ -13,7 +13,7 @@ export interface PruneOptions {
|
||||
storeDir: string
|
||||
}
|
||||
|
||||
export async function prune ({ cacheDir, storeDir }: PruneOptions) {
|
||||
export async function prune ({ cacheDir, storeDir }: PruneOptions, removeAlienFiles?: boolean) {
|
||||
const cafsDir = path.join(storeDir, 'files')
|
||||
await Promise.all([
|
||||
rimraf(path.join(cacheDir, 'metadata')),
|
||||
@@ -38,8 +38,15 @@ export async function prune ({ cacheDir, storeDir }: PruneOptions) {
|
||||
}
|
||||
const stat = await fs.stat(filePath)
|
||||
if (stat.isDirectory()) {
|
||||
globalWarn(`An alien directory is present in the store: ${filePath}`)
|
||||
return
|
||||
if (removeAlienFiles) {
|
||||
await rimraf(filePath)
|
||||
globalWarn(`An alien directory has been removed from the store: ${filePath}`)
|
||||
fileCounter++
|
||||
return
|
||||
} else {
|
||||
globalWarn(`An alien directory is present in the store: ${filePath}`)
|
||||
return
|
||||
}
|
||||
}
|
||||
if (stat.nlink === 1 || stat.nlink === BIG_ONE) {
|
||||
await fs.unlink(filePath)
|
||||
|
||||
@@ -17,6 +17,7 @@ export function cliOptionsTypes () {
|
||||
'registry',
|
||||
'store',
|
||||
'store-dir',
|
||||
'force',
|
||||
], allTypes)
|
||||
}
|
||||
|
||||
@@ -47,6 +48,11 @@ Pruning the store is not harmful, but might slow down future installations. \
|
||||
Visit the documentation for more information on unreferenced packages and why they occur',
|
||||
name: 'prune',
|
||||
},
|
||||
{
|
||||
description: 'If there are alien directories in the store, this command removes them. \
|
||||
Alien directories are directories/files that were not created by the package manager.',
|
||||
name: 'prune --force',
|
||||
},
|
||||
{
|
||||
description: 'Returns the path to the active store directory.',
|
||||
name: 'path',
|
||||
@@ -67,7 +73,7 @@ class StoreStatusError extends PnpmError {
|
||||
}
|
||||
}
|
||||
|
||||
export type StoreCommandOptions = Pick<Config, 'dir' | 'registries' | 'tag' | 'storeDir'> & CreateStoreControllerOptions & {
|
||||
export type StoreCommandOptions = Pick<Config, 'dir' | 'registries' | 'tag' | 'storeDir' | 'force'> & CreateStoreControllerOptions & {
|
||||
reporter?: (logObj: LogBase) => void
|
||||
}
|
||||
|
||||
@@ -87,6 +93,7 @@ export async function handler (opts: StoreCommandOptions, params: string[]) {
|
||||
const storePruneOptions = Object.assign(opts, {
|
||||
storeController: store.ctrl,
|
||||
storeDir: store.dir,
|
||||
removeAlienFiles: opts.force,
|
||||
})
|
||||
return storePrune(storePruneOptions)
|
||||
}
|
||||
|
||||
@@ -6,13 +6,14 @@ export async function storePrune (
|
||||
opts: {
|
||||
reporter?: ReporterFunction
|
||||
storeController: StoreController
|
||||
removeAlienFiles?: boolean
|
||||
}
|
||||
) {
|
||||
const reporter = opts?.reporter
|
||||
if ((reporter != null) && typeof reporter === 'function') {
|
||||
streamParser.on('data', reporter)
|
||||
}
|
||||
await opts.storeController.prune()
|
||||
await opts.storeController.prune(opts.removeAlienFiles)
|
||||
await opts.storeController.close()
|
||||
|
||||
if ((reporter != null) && typeof reporter === 'function') {
|
||||
|
||||
@@ -233,4 +233,41 @@ test('prune does not fail if the store contains an unexpected directory', async
|
||||
message: `An alien directory is present in the store: ${alienDir}`,
|
||||
})
|
||||
)
|
||||
|
||||
// as force is not used, the alien directory is not removed
|
||||
expect(fs.existsSync(alienDir)).toBeTruthy()
|
||||
})
|
||||
|
||||
test('prune removes alien files from the store if the --force flag is used', 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, '--registry', REGISTRY])
|
||||
|
||||
await project.storeHas('is-negative', '2.1.0')
|
||||
const alienDir = path.join(storeDir, 'v3/files/44/directory')
|
||||
fs.mkdirSync(alienDir)
|
||||
|
||||
const reporter = jest.fn()
|
||||
await store.handler({
|
||||
cacheDir,
|
||||
dir: process.cwd(),
|
||||
pnpmHomeDir: '',
|
||||
rawConfig: {
|
||||
registry: REGISTRY,
|
||||
},
|
||||
registries: { default: REGISTRY },
|
||||
reporter,
|
||||
storeDir,
|
||||
userConfig: {},
|
||||
force: true,
|
||||
}, ['prune'])
|
||||
expect(reporter).toBeCalledWith(
|
||||
expect.objectContaining({
|
||||
level: 'warn',
|
||||
message: `An alien directory has been removed from the store: ${alienDir}`,
|
||||
})
|
||||
)
|
||||
expect(fs.existsSync(alienDir)).toBeFalsy()
|
||||
})
|
||||
|
||||
@@ -51,7 +51,7 @@ export interface StoreController {
|
||||
getFilesIndexFilePath: GetFilesIndexFilePath
|
||||
importPackage: ImportPackageFunctionAsync
|
||||
close: () => Promise<void>
|
||||
prune: () => Promise<void>
|
||||
prune: (removeAlienFiles?: boolean) => Promise<void>
|
||||
upload: UploadPkgToStore
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user