Skip to content

Commit da7a3c7

Browse files
committed
Add pnpm support
Fixes #16
1 parent 84bf1cc commit da7a3c7

6 files changed

Lines changed: 203 additions & 13 deletions

File tree

index.d.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,22 @@ declare const globalDirectory: {
5353
```
5454
*/
5555
readonly yarn: GlobalDirectory;
56+
57+
/**
58+
Get the directory of globally installed packages and binaries.
59+
60+
@example
61+
```
62+
import globalDirectory from 'global-directory';
63+
64+
console.log(globalDirectory.pnpm.prefix);
65+
//=> '/Users/sindresorhus/Library/pnpm'
66+
67+
console.log(globalDirectory.pnpm.packages);
68+
//=> '/Users/sindresorhus/Library/pnpm/global/5/node_modules'
69+
```
70+
*/
71+
readonly pnpm: GlobalDirectory;
5672
};
5773

5874
export default globalDirectory;

index.js

Lines changed: 90 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -8,15 +8,20 @@ const isWindows = process.platform === 'win32';
88

99
const untildify = pathWithTilde => pathWithTilde && pathWithTilde.startsWith('~') ? path.join(os.homedir(), pathWithTilde.slice(1)) : pathWithTilde;
1010

11-
const readRc = filePath => {
11+
const readConfigValue = (filePath, key) => {
12+
if (!filePath) {
13+
return;
14+
}
15+
1216
try {
13-
return ini.parse(fs.readFileSync(filePath, 'utf8')).prefix;
17+
return ini.parse(fs.readFileSync(filePath, 'utf8'))[key];
1418
} catch {}
1519
};
1620

17-
const getEnvNpmPrefix = () => {
18-
const key = Object.keys(process.env).find(name => name.toLowerCase() === 'npm_config_prefix');
19-
return key ? process.env[key] : undefined;
21+
const getEnvironmentNpmConfigValue = key => {
22+
const normalizedKey = `npm_config_${key.replaceAll('-', '_')}`.toLowerCase();
23+
const environmentKey = Object.keys(process.env).find(name => name.toLowerCase() === normalizedKey);
24+
return environmentKey ? process.env[environmentKey] : undefined;
2025
};
2126

2227
const getGlobalNpmrc = () => {
@@ -54,22 +59,22 @@ const getDefaultNpmPrefix = () => {
5459
};
5560

5661
const getNpmPrefix = () => {
57-
const envPrefix = getEnvNpmPrefix();
58-
if (envPrefix) {
59-
return envPrefix;
62+
const environmentPrefix = getEnvironmentNpmConfigValue('prefix');
63+
if (environmentPrefix !== undefined) {
64+
return environmentPrefix;
6065
}
6166

62-
const homePrefix = readRc(path.join(os.homedir(), '.npmrc'));
63-
if (homePrefix) {
67+
const homePrefix = readConfigValue(path.join(os.homedir(), '.npmrc'), 'prefix');
68+
if (homePrefix !== undefined) {
6469
return homePrefix;
6570
}
6671

6772
if (process.env.PREFIX) {
6873
return process.env.PREFIX;
6974
}
7075

71-
const globalPrefix = readRc(getGlobalNpmrc());
72-
if (globalPrefix) {
76+
const globalPrefix = readConfigValue(getGlobalNpmrc(), 'prefix');
77+
if (globalPrefix !== undefined) {
7378
return globalPrefix;
7479
}
7580

@@ -127,4 +132,77 @@ globalDirectory.yarn.prefix = yarnDataDir;
127132
globalDirectory.yarn.packages = path.join(yarnDataDir, 'global/node_modules');
128133
globalDirectory.yarn.binaries = path.join(path.resolve(getYarnBinPrefix()), 'bin');
129134

135+
const getPnpmDataDirectory = () => {
136+
if (process.env.PNPM_HOME) {
137+
return process.env.PNPM_HOME;
138+
}
139+
140+
if (process.env.XDG_DATA_HOME) {
141+
return path.join(process.env.XDG_DATA_HOME, 'pnpm');
142+
}
143+
144+
if (process.platform === 'darwin') {
145+
return path.join(os.homedir(), 'Library/pnpm');
146+
}
147+
148+
if (!isWindows) {
149+
return path.join(os.homedir(), '.local/share/pnpm');
150+
}
151+
152+
if (process.env.LOCALAPPDATA) {
153+
return path.join(process.env.LOCALAPPDATA, 'pnpm');
154+
}
155+
156+
return path.join(os.homedir(), '.pnpm');
157+
};
158+
159+
const getPnpmConfigFilePath = () => {
160+
if (process.env.XDG_CONFIG_HOME) {
161+
return path.join(process.env.XDG_CONFIG_HOME, 'pnpm', 'rc');
162+
}
163+
164+
if (isWindows) {
165+
const localConfigHome = process.env.LOCALAPPDATA ?? path.join(os.homedir(), 'AppData', 'Local');
166+
return path.join(localConfigHome, 'pnpm', 'config', 'rc');
167+
}
168+
169+
if (process.platform === 'darwin') {
170+
return path.join(os.homedir(), 'Library', 'Preferences', 'pnpm', 'rc');
171+
}
172+
173+
return path.join(os.homedir(), '.config', 'pnpm', 'rc');
174+
};
175+
176+
const getPnpmConfigValue = key => {
177+
const environmentValue = getEnvironmentNpmConfigValue(key);
178+
if (environmentValue !== undefined) {
179+
return environmentValue;
180+
}
181+
182+
const pnpmGlobalValue = readConfigValue(getPnpmConfigFilePath(), key);
183+
if (pnpmGlobalValue !== undefined) {
184+
return pnpmGlobalValue;
185+
}
186+
187+
const homeValue = readConfigValue(path.join(os.homedir(), '.npmrc'), key);
188+
if (homeValue !== undefined) {
189+
return homeValue;
190+
}
191+
192+
const globalValue = readConfigValue(getGlobalNpmrc(), key);
193+
if (globalValue !== undefined) {
194+
return globalValue;
195+
}
196+
};
197+
198+
const pnpmDataDir = path.resolve(getPnpmDataDirectory());
199+
const pnpmGlobalDir = getPnpmConfigValue('global-dir');
200+
const pnpmGlobalBinDir = getPnpmConfigValue('global-bin-dir');
201+
const resolvedPnpmGlobalDir = path.resolve(untildify(pnpmGlobalDir ?? path.join(pnpmDataDir, 'global')));
202+
const resolvedPnpmGlobalBinDir = path.resolve(untildify(pnpmGlobalBinDir ?? pnpmDataDir));
203+
globalDirectory.pnpm = {};
204+
globalDirectory.pnpm.prefix = pnpmDataDir;
205+
globalDirectory.pnpm.packages = path.join(resolvedPnpmGlobalDir, '5/node_modules');
206+
globalDirectory.pnpm.binaries = resolvedPnpmGlobalBinDir;
207+
130208
export default globalDirectory;

index.test-d.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,3 +7,6 @@ expectType<string>(globalDirectory.npm.binaries);
77
expectType<string>(globalDirectory.yarn.prefix);
88
expectType<string>(globalDirectory.yarn.packages);
99
expectType<string>(globalDirectory.yarn.binaries);
10+
expectType<string>(globalDirectory.pnpm.prefix);
11+
expectType<string>(globalDirectory.pnpm.packages);
12+
expectType<string>(globalDirectory.pnpm.binaries);

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
"paths",
3434
"npm",
3535
"yarn",
36+
"pnpm",
3637
"node",
3738
"modules",
3839
"node-modules",

readme.md

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
> Get the directory of globally installed packages and binaries
44
5-
Uses the same resolution logic as `npm` and `yarn`.
5+
Uses the same resolution logic as `npm`, `yarn`, and `pnpm`.
66

77
## Install
88

@@ -26,6 +26,12 @@ console.log(globalDirectory.npm.binaries);
2626

2727
console.log(globalDirectory.yarn.packages);
2828
//=> '/Users/sindresorhus/.config/yarn/global/node_modules'
29+
30+
console.log(globalDirectory.pnpm.prefix);
31+
//=> '/Users/sindresorhus/Library/pnpm'
32+
33+
console.log(globalDirectory.pnpm.packages);
34+
//=> '/Users/sindresorhus/Library/pnpm/global/5/node_modules'
2935
```
3036

3137
## API
@@ -34,6 +40,7 @@ console.log(globalDirectory.yarn.packages);
3440

3541
#### npm
3642
#### yarn
43+
#### pnpm
3744

3845
##### packages
3946

test.js

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import process from 'node:process';
22
import os from 'node:os';
33
import path from 'node:path';
4+
import fs from 'node:fs';
45
import test from 'ava';
56
import {execa} from 'execa';
67

@@ -62,6 +63,90 @@ test.serial('yarn with PREFIX', async t => {
6263
delete process.env.PREFIX;
6364
});
6465

66+
test('pnpm', t => {
67+
t.truthy(globalDirectory.pnpm);
68+
t.truthy(globalDirectory.pnpm.prefix);
69+
t.truthy(globalDirectory.pnpm.packages);
70+
t.truthy(globalDirectory.pnpm.binaries);
71+
});
72+
73+
test('pnpm.packages is inside prefix', t => {
74+
t.true(globalDirectory.pnpm.packages.startsWith(globalDirectory.pnpm.prefix));
75+
t.true(globalDirectory.pnpm.packages.endsWith('/global/5/node_modules') || globalDirectory.pnpm.packages.endsWith('\\global\\5\\node_modules'));
76+
});
77+
78+
test.serial('pnpm with PNPM_HOME', async t => {
79+
process.env.PNPM_HOME = '/custom/pnpm-home';
80+
const {default: globalDirectory} = await importFresh('./index.js');
81+
t.is(globalDirectory.pnpm.prefix, '/custom/pnpm-home');
82+
t.is(globalDirectory.pnpm.packages, '/custom/pnpm-home/global/5/node_modules');
83+
t.is(globalDirectory.pnpm.binaries, '/custom/pnpm-home');
84+
delete process.env.PNPM_HOME;
85+
});
86+
87+
test.serial('pnpm with NPM_CONFIG_GLOBAL_DIR', async t => {
88+
const savedGlobalDirectory = process.env.NPM_CONFIG_GLOBAL_DIR;
89+
process.env.NPM_CONFIG_GLOBAL_DIR = '/custom/pnpm-global';
90+
const {default: globalDirectory} = await importFresh('./index.js');
91+
t.is(globalDirectory.pnpm.packages, path.join('/custom/pnpm-global', '5/node_modules'));
92+
93+
if (savedGlobalDirectory === undefined) {
94+
delete process.env.NPM_CONFIG_GLOBAL_DIR;
95+
} else {
96+
process.env.NPM_CONFIG_GLOBAL_DIR = savedGlobalDirectory;
97+
}
98+
});
99+
100+
test.serial('pnpm with NPM_CONFIG_GLOBAL_BIN_DIR', async t => {
101+
const savedGlobalBinDirectory = process.env.NPM_CONFIG_GLOBAL_BIN_DIR;
102+
process.env.NPM_CONFIG_GLOBAL_BIN_DIR = '/custom/pnpm-bin';
103+
const {default: globalDirectory} = await importFresh('./index.js');
104+
t.is(globalDirectory.pnpm.binaries, '/custom/pnpm-bin');
105+
106+
if (savedGlobalBinDirectory === undefined) {
107+
delete process.env.NPM_CONFIG_GLOBAL_BIN_DIR;
108+
} else {
109+
process.env.NPM_CONFIG_GLOBAL_BIN_DIR = savedGlobalBinDirectory;
110+
}
111+
});
112+
113+
test.serial('pnpm with pnpm rc config', async t => {
114+
const savedXdgConfigHome = process.env.XDG_CONFIG_HOME;
115+
const temporaryDirectory = path.join(process.cwd(), 'tmp');
116+
fs.mkdirSync(temporaryDirectory, {recursive: true});
117+
const configHome = fs.mkdtempSync(path.join(temporaryDirectory, 'pnpm-config-'));
118+
const pnpmConfigFile = path.join(configHome, 'pnpm', 'rc');
119+
fs.mkdirSync(path.dirname(pnpmConfigFile), {recursive: true});
120+
fs.writeFileSync(pnpmConfigFile, 'global-dir=/custom/pnpm-global\nglobal-bin-dir=/custom/pnpm-bin\n');
121+
process.env.XDG_CONFIG_HOME = configHome;
122+
const {default: globalDirectory} = await importFresh('./index.js');
123+
t.is(globalDirectory.pnpm.packages, path.join('/custom/pnpm-global', '5/node_modules'));
124+
t.is(globalDirectory.pnpm.binaries, '/custom/pnpm-bin');
125+
fs.rmSync(configHome, {recursive: true, force: true});
126+
127+
if (savedXdgConfigHome === undefined) {
128+
delete process.env.XDG_CONFIG_HOME;
129+
} else {
130+
process.env.XDG_CONFIG_HOME = savedXdgConfigHome;
131+
}
132+
});
133+
134+
test.serial('pnpm with XDG_DATA_HOME', async t => {
135+
delete process.env.PNPM_HOME;
136+
process.env.XDG_DATA_HOME = '/tmp/xdg-test';
137+
const {default: globalDirectory} = await importFresh('./index.js');
138+
t.is(globalDirectory.pnpm.prefix, '/tmp/xdg-test/pnpm');
139+
t.is(globalDirectory.pnpm.packages, '/tmp/xdg-test/pnpm/global/5/node_modules');
140+
delete process.env.XDG_DATA_HOME;
141+
});
142+
143+
test.serial('pnpm without PNPM_HOME uses data dir for binaries', async t => {
144+
delete process.env.PNPM_HOME;
145+
delete process.env.XDG_DATA_HOME;
146+
const {default: globalDirectory} = await importFresh('./index.js');
147+
t.is(globalDirectory.pnpm.binaries, globalDirectory.pnpm.prefix);
148+
});
149+
65150
test.serial('npm.prefix expands tilde in prefix', async t => {
66151
const savedKeys = Object.keys(process.env).filter(name => name.toLowerCase() === 'npm_config_prefix');
67152
const savedValues = Object.fromEntries(savedKeys.map(key => [key, process.env[key]]));

0 commit comments

Comments
 (0)