mirror of
https://github.com/pnpm/pnpm.git
synced 2026-01-11 00:18:32 -05:00
feat: the global store location should be inside the pnpm home directory (#4522)
This commit is contained in:
12
.changeset/eleven-kids-chew.md
Normal file
12
.changeset/eleven-kids-chew.md
Normal file
@@ -0,0 +1,12 @@
|
||||
---
|
||||
"@pnpm/store-path": major
|
||||
"pnpm": major
|
||||
---
|
||||
|
||||
Changed the location of the global store from `~/.pnpm-store` to `<pnpm home directory>/store`
|
||||
|
||||
On Linux, by default it will be `~/.local/share/pnpm/store`
|
||||
On Windows: `%LOCALAPPDATA%/pnpm/store`
|
||||
On macOS: `~/Library/pnpm/store`
|
||||
|
||||
Related issue: [#2574](https://github.com/pnpm/pnpm/issues/2574)
|
||||
@@ -45,7 +45,11 @@ export default async function testDefaults<T> (
|
||||
...fetchOpts,
|
||||
})
|
||||
let storeDir = opts?.storeDir ?? path.resolve('.store')
|
||||
storeDir = await storePath(opts?.prefix ?? process.cwd(), storeDir)
|
||||
storeDir = await storePath({
|
||||
pkgRoot: opts?.prefix ?? process.cwd(),
|
||||
storePath: storeDir,
|
||||
pnpmHomeDir: '',
|
||||
})
|
||||
const storeController = await createStore(
|
||||
resolve,
|
||||
fetchers,
|
||||
|
||||
@@ -35,7 +35,11 @@ export default async function testDefaults (
|
||||
],
|
||||
{ lockfileDir }
|
||||
)
|
||||
storeDir = await storePath(lockfileDir, storeDir)
|
||||
storeDir = await storePath({
|
||||
pkgRoot: lockfileDir,
|
||||
storePath: storeDir,
|
||||
pnpmHomeDir: '',
|
||||
})
|
||||
const authConfig = { registry }
|
||||
const { resolve, fetchers } = createClient({
|
||||
authConfig,
|
||||
|
||||
@@ -44,6 +44,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@pnpm/cafs": "workspace:4.0.0",
|
||||
"@pnpm/config": "workspace:14.0.0",
|
||||
"@pnpm/lockfile-file": "workspace:5.0.0",
|
||||
"@pnpm/lockfile-utils": "workspace:4.0.0",
|
||||
"@pnpm/store-path": "workspace:5.0.0",
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import getConfig from '@pnpm/config'
|
||||
import { promises as fs } from 'fs'
|
||||
import path from 'path'
|
||||
|
||||
@@ -7,7 +8,15 @@ import createFuseHandlers from './createFuseHandlers'
|
||||
(async () => { /* eslint-disable-line */
|
||||
const mnt = path.join(process.cwd(), 'node_modules')
|
||||
await fs.mkdir(mnt, { recursive: true })
|
||||
const cafsDir = path.join(await getStorePath(process.cwd()), 'files')
|
||||
const { config } = await getConfig({
|
||||
cliOptions: {},
|
||||
packageManager: { name: '', version: '' },
|
||||
})
|
||||
const cafsDir = path.join(await getStorePath({
|
||||
pkgRoot: process.cwd(),
|
||||
storePath: config.storeDir,
|
||||
pnpmHomeDir: config.pnpmHomeDir,
|
||||
}), 'files')
|
||||
const fuse = new Fuse(mnt, await createFuseHandlers(process.cwd(), cafsDir), { debug: true })
|
||||
fuse.mount(function (err?: Error) {
|
||||
if (err != null) console.error(err)
|
||||
|
||||
@@ -12,6 +12,9 @@
|
||||
{
|
||||
"path": "../cafs"
|
||||
},
|
||||
{
|
||||
"path": "../config"
|
||||
},
|
||||
{
|
||||
"path": "../dependency-path"
|
||||
},
|
||||
|
||||
@@ -83,7 +83,11 @@ async function installNode (fetch: FetchFromRegistry, wantedNodeVersion: string,
|
||||
},
|
||||
timeout: opts.fetchTimeout,
|
||||
})
|
||||
const storeDir = await storePath(process.cwd(), opts.storeDir)
|
||||
const storeDir = await storePath({
|
||||
pkgRoot: process.cwd(),
|
||||
storePath: opts.storeDir,
|
||||
pnpmHomeDir: opts.pnpmHomeDir,
|
||||
})
|
||||
const cafsDir = path.join(storeDir, 'files')
|
||||
const cafs = createCafsStore(cafsDir)
|
||||
const { filesIndex } = await fetchTarball(cafs, { tarball }, {
|
||||
|
||||
@@ -33,6 +33,7 @@ export const DEFAULT_OPTS = {
|
||||
offline: false,
|
||||
pending: false,
|
||||
pnpmfile: './.pnpmfile.cjs',
|
||||
pnpmHomeDir: '',
|
||||
proxy: undefined,
|
||||
rawConfig: { registry: REGISTRY },
|
||||
rawLocalConfig: {},
|
||||
|
||||
@@ -32,6 +32,7 @@ export const DEFAULT_OPTS = {
|
||||
offline: false,
|
||||
pending: false,
|
||||
pnpmfile: './.pnpmfile.cjs',
|
||||
pnpmHomeDir: '',
|
||||
proxy: undefined,
|
||||
rawConfig: { registry: REGISTRY },
|
||||
rawLocalConfig: {},
|
||||
|
||||
@@ -34,6 +34,7 @@ export const DEFAULT_OPTS = {
|
||||
offline: false,
|
||||
pending: false,
|
||||
pnpmfile: './.pnpmfile.cjs',
|
||||
pnpmHomeDir: '',
|
||||
proxy: undefined,
|
||||
rawConfig: { registry: REGISTRY },
|
||||
rawLocalConfig: {},
|
||||
|
||||
@@ -32,6 +32,7 @@ export const DEFAULT_OPTS = {
|
||||
offline: false,
|
||||
pending: false,
|
||||
pnpmfile: './.pnpmfile.cjs',
|
||||
pnpmHomeDir: '',
|
||||
proxy: undefined,
|
||||
rawConfig: { registry: REGISTRY },
|
||||
rawLocalConfig: {},
|
||||
|
||||
@@ -22,6 +22,7 @@ export interface StrictRebuildOptions {
|
||||
useLockfile: boolean
|
||||
registries: Registries
|
||||
dir: string
|
||||
pnpmHomeDir: string
|
||||
|
||||
reporter: (logObj: LogBase) => void
|
||||
production: boolean
|
||||
|
||||
@@ -32,6 +32,7 @@ export const DEFAULT_OPTS = {
|
||||
offline: false,
|
||||
pending: false,
|
||||
pnpmfile: './.pnpmfile.cjs',
|
||||
pnpmHomeDir: '',
|
||||
proxy: undefined,
|
||||
rawConfig: { registry: REGISTRY },
|
||||
rawLocalConfig: {},
|
||||
|
||||
@@ -31,6 +31,7 @@ export const DEFAULT_OPTS = {
|
||||
offline: false,
|
||||
pending: false,
|
||||
pnpmfile: './.pnpmfile.cjs',
|
||||
pnpmHomeDir: '',
|
||||
proxy: undefined,
|
||||
rawConfig: { registry: REGISTRY },
|
||||
rawLocalConfig: {},
|
||||
|
||||
@@ -43,7 +43,11 @@ export default async (
|
||||
if (opts.background && !Diable.isDaemon()) {
|
||||
Diable()
|
||||
}
|
||||
const storeDir = await storePath(opts.dir, opts.storeDir)
|
||||
const storeDir = await storePath({
|
||||
pkgRoot: opts.dir,
|
||||
storePath: opts.storeDir,
|
||||
pnpmHomeDir: opts.pnpmHomeDir,
|
||||
})
|
||||
const connectionInfoDir = serverConnectionInfoDir(storeDir)
|
||||
const serverJsonPath = path.join(connectionInfoDir, 'server.json')
|
||||
await fs.mkdir(connectionInfoDir, { recursive: true })
|
||||
|
||||
@@ -5,9 +5,13 @@ import { serverConnectionInfoDir, tryLoadServerJson } from '@pnpm/store-connecti
|
||||
import storePath from '@pnpm/store-path'
|
||||
|
||||
export default async (
|
||||
opts: Pick<Config, 'dir' | 'storeDir'>
|
||||
opts: Pick<Config, 'dir' | 'pnpmHomeDir' | 'storeDir'>
|
||||
) => {
|
||||
const storeDir = await storePath(opts.dir, opts.storeDir)
|
||||
const storeDir = await storePath({
|
||||
pkgRoot: opts.dir,
|
||||
storePath: opts.storeDir,
|
||||
pnpmHomeDir: opts.pnpmHomeDir,
|
||||
})
|
||||
const connectionInfoDir = serverConnectionInfoDir(storeDir)
|
||||
const serverJson = await tryLoadServerJson({
|
||||
serverJsonPath: path.join(connectionInfoDir, 'server.json'),
|
||||
|
||||
@@ -14,9 +14,14 @@ export default async (
|
||||
opts: {
|
||||
storeDir?: string
|
||||
dir: string
|
||||
pnpmHomeDir: string
|
||||
}
|
||||
) => {
|
||||
const storeDir = await storePath(opts.dir, opts.storeDir)
|
||||
const storeDir = await storePath({
|
||||
pkgRoot: opts.dir,
|
||||
storePath: opts.storeDir,
|
||||
pnpmHomeDir: opts.pnpmHomeDir,
|
||||
})
|
||||
const connectionInfoDir = serverConnectionInfoDir(storeDir)
|
||||
const serverJson = await tryLoadServerJson({
|
||||
serverJsonPath: path.join(connectionInfoDir, 'server.json'),
|
||||
|
||||
@@ -77,7 +77,11 @@ export async function handler (opts: StoreCommandOptions, params: string[]) {
|
||||
case 'status':
|
||||
return statusCmd(opts)
|
||||
case 'path':
|
||||
return storePath(opts.dir, opts.storeDir)
|
||||
return storePath({
|
||||
pkgRoot: opts.dir,
|
||||
storePath: opts.storeDir,
|
||||
pnpmHomeDir: opts.pnpmHomeDir,
|
||||
})
|
||||
case 'prune': {
|
||||
store = await createOrConnectStoreController(opts)
|
||||
const storePruneOptions = Object.assign(opts, {
|
||||
@@ -102,7 +106,11 @@ export async function handler (opts: StoreCommandOptions, params: string[]) {
|
||||
|
||||
async function statusCmd (opts: StoreCommandOptions) {
|
||||
const modifiedPkgs = await storeStatus(Object.assign(opts, {
|
||||
storeDir: await storePath(opts.dir, opts.storeDir),
|
||||
storeDir: await storePath({
|
||||
pkgRoot: opts.dir,
|
||||
storePath: opts.storeDir,
|
||||
pnpmHomeDir: opts.pnpmHomeDir,
|
||||
}),
|
||||
}))
|
||||
if (!modifiedPkgs || (modifiedPkgs.length === 0)) {
|
||||
logger.info({
|
||||
|
||||
@@ -17,6 +17,7 @@ export { createNewStoreController, serverConnectionInfoDir }
|
||||
export type CreateStoreControllerOptions = Omit<CreateNewStoreControllerOptions, 'storeDir'> & Pick<Config,
|
||||
| 'storeDir'
|
||||
| 'dir'
|
||||
| 'pnpmHomeDir'
|
||||
| 'useRunningStoreServer'
|
||||
| 'useStoreServer'
|
||||
| 'workspaceDir'
|
||||
@@ -26,7 +27,11 @@ export async function createOrConnectStoreControllerCached (
|
||||
storeControllerCache: Map<string, Promise<{ctrl: StoreController, dir: string}>>,
|
||||
opts: CreateStoreControllerOptions
|
||||
) {
|
||||
const storeDir = await storePath(opts.dir, opts.storeDir)
|
||||
const storeDir = await storePath({
|
||||
pkgRoot: opts.dir,
|
||||
storePath: opts.storeDir,
|
||||
pnpmHomeDir: opts.pnpmHomeDir,
|
||||
})
|
||||
if (!storeControllerCache.has(storeDir)) {
|
||||
storeControllerCache.set(storeDir, createOrConnectStoreController(opts))
|
||||
}
|
||||
@@ -39,7 +44,11 @@ export async function createOrConnectStoreController (
|
||||
ctrl: StoreController
|
||||
dir: string
|
||||
}> {
|
||||
const storeDir = await storePath(opts.workspaceDir ?? opts.dir, opts.storeDir)
|
||||
const storeDir = await storePath({
|
||||
pkgRoot: opts.workspaceDir ?? opts.dir,
|
||||
storePath: opts.storeDir,
|
||||
pnpmHomeDir: opts.pnpmHomeDir,
|
||||
})
|
||||
const connectionInfoDir = serverConnectionInfoDir(storeDir)
|
||||
const serverJsonPath = path.join(connectionInfoDir, 'server.json')
|
||||
let serverJson = await tryLoadServerJson({ serverJsonPath, shouldRetryOnNoent: false })
|
||||
|
||||
@@ -11,12 +11,23 @@ import touch from 'touch'
|
||||
const STORE_VERSION = 'v3'
|
||||
|
||||
export default function (
|
||||
pkgRoot: string,
|
||||
storePath?: string
|
||||
{
|
||||
pkgRoot,
|
||||
storePath,
|
||||
pnpmHomeDir,
|
||||
}: {
|
||||
pkgRoot: string
|
||||
storePath?: string
|
||||
pnpmHomeDir: string
|
||||
}
|
||||
) {
|
||||
if (!storePath || isHomepath(storePath)) {
|
||||
const relStorePath = storePath ? storePath.substring(2) : '.pnpm-store'
|
||||
return storePathRelativeToHome(pkgRoot, relStorePath)
|
||||
if (!storePath) {
|
||||
return storePathRelativeToHome(pkgRoot, 'store', pnpmHomeDir)
|
||||
}
|
||||
|
||||
if (isHomepath(storePath)) {
|
||||
const homedir = getHomedir()
|
||||
return storePathRelativeToHome(pkgRoot, storePath.substring(2), homedir)
|
||||
}
|
||||
|
||||
const storeBasePath = pathAbsolute(storePath, pkgRoot)
|
||||
@@ -27,16 +38,16 @@ export default function (
|
||||
return path.join(storeBasePath, STORE_VERSION)
|
||||
}
|
||||
|
||||
async function storePathRelativeToHome (pkgRoot: string, relStore: string) {
|
||||
async function storePathRelativeToHome (pkgRoot: string, relStore: string, homedir: string) {
|
||||
const tempFile = pathTemp(pkgRoot)
|
||||
await fs.mkdir(path.dirname(tempFile), { recursive: true })
|
||||
await touch(tempFile)
|
||||
const homedir = getHomedir()
|
||||
const storeInHomeDir = path.join(homedir, relStore, STORE_VERSION)
|
||||
if (await canLinkToSubdir(tempFile, homedir)) {
|
||||
await fs.unlink(tempFile)
|
||||
// If the project is on the drive on which the OS home directory
|
||||
// then the store is placed in the home directory
|
||||
return path.join(homedir, relStore, STORE_VERSION)
|
||||
return storeInHomeDir
|
||||
}
|
||||
try {
|
||||
let mountpoint = await rootLinkTarget(tempFile)
|
||||
@@ -50,13 +61,13 @@ async function storePathRelativeToHome (pkgRoot: string, relStore: string) {
|
||||
// If linking works only in the project folder
|
||||
// then prefer to place the store inside the homedir
|
||||
if (dirsAreEqual(pkgRoot, mountpoint)) {
|
||||
return path.join(homedir, relStore, STORE_VERSION)
|
||||
return storeInHomeDir
|
||||
}
|
||||
return path.join(mountpoint, relStore, STORE_VERSION)
|
||||
return path.join(mountpoint, '.pnpm-store', STORE_VERSION)
|
||||
} catch (err) {
|
||||
// this is an unlikely situation but if there is no way to find
|
||||
// a linkable place on the disk, create the store in homedir
|
||||
return path.join(homedir, relStore, STORE_VERSION)
|
||||
return storeInHomeDir
|
||||
} finally {
|
||||
await fs.unlink(tempFile)
|
||||
}
|
||||
|
||||
@@ -7,13 +7,22 @@ jest.mock('fs')
|
||||
const skipOnWindows = isWindows() ? test.skip : test
|
||||
|
||||
skipOnWindows('when a link can be created to the homedir', async () => {
|
||||
expect(await storePath('/can-link-to-homedir')).toBe('/home/user/.pnpm-store/v3')
|
||||
expect(await storePath({
|
||||
pkgRoot: '/can-link-to-homedir',
|
||||
pnpmHomeDir: '/local/share/pnpm',
|
||||
})).toBe('/local/share/pnpm/store/v3')
|
||||
})
|
||||
|
||||
skipOnWindows('a link can be created to the root of the drive', async () => {
|
||||
expect(await storePath('/src/workspace/project')).toBe('/.pnpm-store/v3')
|
||||
expect(await storePath({
|
||||
pkgRoot: '/src/workspace/project',
|
||||
pnpmHomeDir: '/local/share/pnpm',
|
||||
})).toBe('/.pnpm-store/v3')
|
||||
})
|
||||
|
||||
skipOnWindows('a link can be created to the a subdir in the root of the drive', async () => {
|
||||
expect(await storePath('/mnt/project')).toBe('/mnt/.pnpm-store/v3')
|
||||
expect(await storePath({
|
||||
pkgRoot: '/mnt/project',
|
||||
pnpmHomeDir: '/local/share/pnpm',
|
||||
})).toBe('/mnt/.pnpm-store/v3')
|
||||
})
|
||||
|
||||
2
pnpm-lock.yaml
generated
2
pnpm-lock.yaml
generated
@@ -1600,6 +1600,7 @@ importers:
|
||||
packages/mount-modules:
|
||||
specifiers:
|
||||
'@pnpm/cafs': workspace:4.0.0
|
||||
'@pnpm/config': workspace:14.0.0
|
||||
'@pnpm/lockfile-file': workspace:5.0.0
|
||||
'@pnpm/lockfile-utils': workspace:4.0.0
|
||||
'@pnpm/logger': ^4.0.0
|
||||
@@ -1615,6 +1616,7 @@ importers:
|
||||
rimraf: ^3.0.2
|
||||
dependencies:
|
||||
'@pnpm/cafs': link:../cafs
|
||||
'@pnpm/config': link:../config
|
||||
'@pnpm/lockfile-file': link:../lockfile-file
|
||||
'@pnpm/lockfile-utils': link:../lockfile-utils
|
||||
'@pnpm/store-path': link:../store-path
|
||||
|
||||
Reference in New Issue
Block a user