@pnpm/global.commands 1100.0.29 → 1100.0.31
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/binOwnership.d.ts +10 -0
- package/lib/binOwnership.js +21 -0
- package/lib/checkGlobalBinConflicts.js +11 -1
- package/lib/globalAdd.js +7 -1
- package/lib/globalRemove.js +7 -1
- package/lib/globalUpdate.js +7 -2
- package/lib/readInstalledPackages.js +2 -1
- package/package.json +17 -17
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The set of bin names provided by global package groups *other* than those
|
|
3
|
+
* in `excludeHashes`.
|
|
4
|
+
*
|
|
5
|
+
* Used before unlinking a group's bins (on remove / update / replace) so
|
|
6
|
+
* that a bin name shared with — and owned by — another still-installed
|
|
7
|
+
* group is never removed. Without this, removing one group could delete a
|
|
8
|
+
* bin that actually belongs to a different global package.
|
|
9
|
+
*/
|
|
10
|
+
export declare function getBinNamesOfOtherGroups(globalDir: string, excludeHashes: Set<string>): Promise<Set<string>>;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { getInstalledBinNames, scanGlobalPackages } from '@pnpm/global.packages';
|
|
2
|
+
/**
|
|
3
|
+
* The set of bin names provided by global package groups *other* than those
|
|
4
|
+
* in `excludeHashes`.
|
|
5
|
+
*
|
|
6
|
+
* Used before unlinking a group's bins (on remove / update / replace) so
|
|
7
|
+
* that a bin name shared with — and owned by — another still-installed
|
|
8
|
+
* group is never removed. Without this, removing one group could delete a
|
|
9
|
+
* bin that actually belongs to a different global package.
|
|
10
|
+
*/
|
|
11
|
+
export async function getBinNamesOfOtherGroups(globalDir, excludeHashes) {
|
|
12
|
+
const others = scanGlobalPackages(globalDir).filter((pkg) => !excludeHashes.has(pkg.hash));
|
|
13
|
+
const names = new Set();
|
|
14
|
+
await Promise.all(others.map(async (pkg) => {
|
|
15
|
+
for (const name of await getInstalledBinNames(pkg)) {
|
|
16
|
+
names.add(name);
|
|
17
|
+
}
|
|
18
|
+
}));
|
|
19
|
+
return names;
|
|
20
|
+
}
|
|
21
|
+
//# sourceMappingURL=binOwnership.js.map
|
|
@@ -28,7 +28,7 @@ export async function checkGlobalBinConflicts(opts) {
|
|
|
28
28
|
if (newBinOwners.size === 0)
|
|
29
29
|
return binsToSkip;
|
|
30
30
|
// Quick check: only investigate if a bin with the same name already exists
|
|
31
|
-
const conflicting = new Set([...newBinOwners.keys()].filter((name) =>
|
|
31
|
+
const conflicting = new Set([...newBinOwners.keys()].filter((name) => binSlotExists(opts.globalBinDir, name)));
|
|
32
32
|
if (conflicting.size === 0)
|
|
33
33
|
return binsToSkip;
|
|
34
34
|
// Some bins already exist — find out if they belong to packages being replaced
|
|
@@ -72,4 +72,14 @@ export async function checkGlobalBinConflicts(opts) {
|
|
|
72
72
|
}
|
|
73
73
|
return binsToSkip;
|
|
74
74
|
}
|
|
75
|
+
// Whether a bin named `name` already occupies a slot in `globalBinDir`. On
|
|
76
|
+
// Windows the `node` runtime bin is linked as `<name>.exe` (with no bare
|
|
77
|
+
// `<name>` file), so that flavor is checked too — otherwise an existing
|
|
78
|
+
// `node.exe` would not be detected as a conflict and could be silently
|
|
79
|
+
// overwritten.
|
|
80
|
+
function binSlotExists(globalBinDir, name) {
|
|
81
|
+
if (fs.existsSync(path.join(globalBinDir, name)))
|
|
82
|
+
return true;
|
|
83
|
+
return process.platform === 'win32' && fs.existsSync(path.join(globalBinDir, `${name}.exe`));
|
|
84
|
+
}
|
|
75
85
|
//# sourceMappingURL=checkGlobalBinConflicts.js.map
|
package/lib/globalAdd.js
CHANGED
|
@@ -7,6 +7,7 @@ import { cleanOrphanedInstallDirs, createGlobalCacheKey, createInstallDir, findG
|
|
|
7
7
|
import { readPackageJsonFromDirRawSync } from '@pnpm/pkg-manifest.reader';
|
|
8
8
|
import { isSubdir } from 'is-subdir';
|
|
9
9
|
import { symlinkDir } from 'symlink-dir';
|
|
10
|
+
import { getBinNamesOfOtherGroups } from './binOwnership.js';
|
|
10
11
|
import { checkGlobalBinConflicts } from './checkGlobalBinConflicts.js';
|
|
11
12
|
import { installGlobalPackages } from './installGlobalPackages.js';
|
|
12
13
|
import { promptApproveGlobalBuilds } from './promptApproveGlobalBuilds.js';
|
|
@@ -181,10 +182,15 @@ async function removeExistingGlobalInstalls(globalDir, globalBinDir, aliases) {
|
|
|
181
182
|
groupsToRemove.set(existing.hash, getInstalledBinNames(existing));
|
|
182
183
|
}
|
|
183
184
|
}
|
|
185
|
+
// Bins owned by groups that survive this replacement must not be
|
|
186
|
+
// unlinked, or we'd delete a different global package's bin.
|
|
187
|
+
const protectedBins = await getBinNamesOfOtherGroups(globalDir, new Set(groupsToRemove.keys()));
|
|
184
188
|
// Remove all groups in parallel
|
|
185
189
|
await Promise.all([...groupsToRemove.entries()].map(async ([hash, binNamesPromise]) => {
|
|
186
190
|
const binNames = await binNamesPromise;
|
|
187
|
-
await Promise.all(binNames
|
|
191
|
+
await Promise.all(binNames
|
|
192
|
+
.filter((binName) => !protectedBins.has(binName))
|
|
193
|
+
.map((binName) => removeBin(path.join(globalBinDir, binName))));
|
|
188
194
|
// Remove both the hash symlink and the install dir it points to
|
|
189
195
|
const hashLink = getHashLink(globalDir, hash);
|
|
190
196
|
let installDir = null;
|
package/lib/globalRemove.js
CHANGED
|
@@ -4,6 +4,7 @@ import { removeBin } from '@pnpm/bins.remover';
|
|
|
4
4
|
import { PnpmError } from '@pnpm/error';
|
|
5
5
|
import { findGlobalPackage, getHashLink, getInstalledBinNames, } from '@pnpm/global.packages';
|
|
6
6
|
import { isSubdir } from 'is-subdir';
|
|
7
|
+
import { getBinNamesOfOtherGroups } from './binOwnership.js';
|
|
7
8
|
export async function handleGlobalRemove(opts, params) {
|
|
8
9
|
const globalDir = opts.globalPkgDir;
|
|
9
10
|
const globalBinDir = opts.bin;
|
|
@@ -16,10 +17,15 @@ export async function handleGlobalRemove(opts, params) {
|
|
|
16
17
|
}
|
|
17
18
|
groupsToRemove.set(pkg.hash, pkg);
|
|
18
19
|
}
|
|
20
|
+
// Bins shared with (and owned by) groups that survive this removal must
|
|
21
|
+
// not be unlinked, or we'd delete another global package's bin.
|
|
22
|
+
const protectedBins = await getBinNamesOfOtherGroups(globalDir, new Set(groupsToRemove.keys()));
|
|
19
23
|
// Remove bins, hash symlinks, and install dirs for all affected groups in parallel
|
|
20
24
|
await Promise.all([...groupsToRemove.entries()].map(async ([hash, pkg]) => {
|
|
21
25
|
const binNames = await getInstalledBinNames(pkg);
|
|
22
|
-
await Promise.all(binNames
|
|
26
|
+
await Promise.all(binNames
|
|
27
|
+
.filter((binName) => !protectedBins.has(binName))
|
|
28
|
+
.map((binName) => removeBin(path.join(globalBinDir, binName))));
|
|
23
29
|
await fs.promises.rm(getHashLink(globalDir, hash), { force: true });
|
|
24
30
|
if (isSubdir(globalDir, pkg.installDir)) {
|
|
25
31
|
await fs.promises.rm(pkg.installDir, { recursive: true, force: true });
|
package/lib/globalUpdate.js
CHANGED
|
@@ -6,6 +6,7 @@ import { summaryLogger } from '@pnpm/core-loggers';
|
|
|
6
6
|
import { cleanOrphanedInstallDirs, createInstallDir, getHashLink, getInstalledBinNames, scanGlobalPackages, } from '@pnpm/global.packages';
|
|
7
7
|
import { isSubdir } from 'is-subdir';
|
|
8
8
|
import { symlinkDir } from 'symlink-dir';
|
|
9
|
+
import { getBinNamesOfOtherGroups } from './binOwnership.js';
|
|
9
10
|
import { checkGlobalBinConflicts } from './checkGlobalBinConflicts.js';
|
|
10
11
|
import { installGlobalPackages } from './installGlobalPackages.js';
|
|
11
12
|
import { promptApproveGlobalBuilds } from './promptApproveGlobalBuilds.js';
|
|
@@ -91,9 +92,13 @@ async function updateGlobalPackageGroup(opts, globalDir, globalBinDir, pkg, comm
|
|
|
91
92
|
await fs.promises.rm(installDir, { recursive: true, force: true });
|
|
92
93
|
throw err;
|
|
93
94
|
}
|
|
94
|
-
// Remove stale bins from old installation before swapping
|
|
95
|
+
// Remove stale bins from old installation before swapping, but keep any
|
|
96
|
+
// bin owned by a different global group (don't delete another package's bin).
|
|
97
|
+
const protectedBins = await getBinNamesOfOtherGroups(globalDir, new Set([pkg.hash]));
|
|
95
98
|
const oldBinNames = await getInstalledBinNames(pkg);
|
|
96
|
-
await Promise.all(oldBinNames
|
|
99
|
+
await Promise.all(oldBinNames
|
|
100
|
+
.filter((binName) => !protectedBins.has(binName))
|
|
101
|
+
.map((binName) => removeBin(path.join(globalBinDir, binName))));
|
|
97
102
|
// Swap hash symlink to new install dir, then clean up old one
|
|
98
103
|
const hashLink = getHashLink(globalDir, pkg.hash);
|
|
99
104
|
const oldInstallDir = pkg.installDir;
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import path from 'node:path';
|
|
2
|
+
import { isValidGlobalDependencyAlias } from '@pnpm/global.packages';
|
|
2
3
|
import { readPackageJsonFromDir, readPackageJsonFromDirRawSync } from '@pnpm/pkg-manifest.reader';
|
|
3
4
|
export async function readInstalledPackages(installDir) {
|
|
4
5
|
const pkgJson = readPackageJsonFromDirRawSync(installDir);
|
|
5
|
-
const depNames = Object.keys(pkgJson.dependencies ?? {});
|
|
6
|
+
const depNames = Object.keys(pkgJson.dependencies ?? {}).filter(isValidGlobalDependencyAlias);
|
|
6
7
|
const manifests = await Promise.all(depNames.map((depName) => readPackageJsonFromDir(path.join(installDir, 'node_modules', depName))));
|
|
7
8
|
return depNames.map((depName, i) => ({
|
|
8
9
|
manifest: manifests[i],
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pnpm/global.commands",
|
|
3
|
-
"version": "1100.0.
|
|
3
|
+
"version": "1100.0.31",
|
|
4
4
|
"description": "Global package command handlers for pnpm",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"pnpm",
|
|
@@ -11,9 +11,9 @@
|
|
|
11
11
|
"funding": "https://opencollective.com/pnpm",
|
|
12
12
|
"repository": {
|
|
13
13
|
"type": "git",
|
|
14
|
-
"url": "https://github.com/pnpm/pnpm/tree/main/global/commands"
|
|
14
|
+
"url": "https://github.com/pnpm/pnpm/tree/main/pnpm11/global/commands"
|
|
15
15
|
},
|
|
16
|
-
"homepage": "https://github.com/pnpm/pnpm/tree/main/global/commands#readme",
|
|
16
|
+
"homepage": "https://github.com/pnpm/pnpm/tree/main/pnpm11/global/commands#readme",
|
|
17
17
|
"bugs": {
|
|
18
18
|
"url": "https://github.com/pnpm/pnpm/issues"
|
|
19
19
|
},
|
|
@@ -31,25 +31,25 @@
|
|
|
31
31
|
"@pnpm/util.lex-comparator": "^4.0.1",
|
|
32
32
|
"is-subdir": "^2.0.0",
|
|
33
33
|
"symlink-dir": "^10.0.1",
|
|
34
|
-
"@pnpm/bins.remover": "1100.0.
|
|
35
|
-
"@pnpm/
|
|
36
|
-
"@pnpm/bins.linker": "1100.0.15",
|
|
37
|
-
"@pnpm/cli.utils": "1101.0.12",
|
|
34
|
+
"@pnpm/bins.remover": "1100.0.12",
|
|
35
|
+
"@pnpm/bins.linker": "1100.0.16",
|
|
38
36
|
"@pnpm/bins.resolver": "1100.0.8",
|
|
39
|
-
"@pnpm/
|
|
40
|
-
"@pnpm/
|
|
41
|
-
"@pnpm/types": "1101.3.2",
|
|
42
|
-
"@pnpm/store.connection-manager": "1100.3.1",
|
|
43
|
-
"@pnpm/pkg-manifest.reader": "1100.0.8",
|
|
37
|
+
"@pnpm/cli.command": "1100.0.1",
|
|
38
|
+
"@pnpm/cli.utils": "1101.0.13",
|
|
44
39
|
"@pnpm/config.matcher": "1100.0.1",
|
|
45
|
-
"@pnpm/
|
|
46
|
-
"@pnpm/
|
|
47
|
-
"@pnpm/
|
|
48
|
-
"@pnpm/
|
|
40
|
+
"@pnpm/config.reader": "1101.11.0",
|
|
41
|
+
"@pnpm/core-loggers": "1100.2.1",
|
|
42
|
+
"@pnpm/error": "1100.0.1",
|
|
43
|
+
"@pnpm/global.packages": "1100.0.10",
|
|
44
|
+
"@pnpm/deps.inspection.list": "1100.0.21",
|
|
45
|
+
"@pnpm/installing.deps-installer": "1102.2.0",
|
|
46
|
+
"@pnpm/store.connection-manager": "1100.3.3",
|
|
47
|
+
"@pnpm/pkg-manifest.reader": "1100.0.9",
|
|
48
|
+
"@pnpm/types": "1101.3.2"
|
|
49
49
|
},
|
|
50
50
|
"devDependencies": {
|
|
51
51
|
"@jest/globals": "30.4.1",
|
|
52
|
-
"@pnpm/global.commands": "1100.0.
|
|
52
|
+
"@pnpm/global.commands": "1100.0.31"
|
|
53
53
|
},
|
|
54
54
|
"engines": {
|
|
55
55
|
"node": ">=22.13"
|