@pnpm/global.packages 1101.1.3 → 1101.1.4

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/CHANGELOG.md CHANGED
@@ -1,5 +1,17 @@
1
1
  # @pnpm/global.packages
2
2
 
3
+ ## 1101.1.4
4
+
5
+ ### Patch Changes
6
+
7
+ - `pnpm add -g`, `pnpm update -g`, and `pnpm remove -g` no longer fail with `ERR_PNPM_PACKAGE_MANIFEST_IO_ERROR` when another global package's link into the store dangles, for example after the store was pruned. The pnpm install script failed the same way on such a machine.
8
+
9
+ - Updated dependencies:
10
+ - @pnpm/bins.resolver@1100.0.17
11
+ - @pnpm/crypto.hash@1100.0.6
12
+ - @pnpm/pkg-manifest.reader@1100.0.20
13
+ - @pnpm/types@1102.1.1
14
+
3
15
  ## 1101.1.3
4
16
 
5
17
  ### Patch Changes
@@ -18,4 +18,15 @@ export declare function scanGlobalPackages(globalDir: string): GlobalPackageInfo
18
18
  export declare function findGlobalPackage(globalDir: string, alias: string): GlobalPackageInfo | null;
19
19
  export declare function getGlobalPackageDetails(info: GlobalPackageInfo): Promise<InstalledGlobalPackage[]>;
20
20
  export declare function cleanOrphanedInstallDirs(globalDir: string): void;
21
+ /**
22
+ * The bin names installed by a group (deduplicated).
23
+ *
24
+ * A group whose `node_modules` is wholly absent owns no bins, and neither
25
+ * does a declared dependency whose directory under `node_modules` is absent,
26
+ * a link left dangling by a pruned store included: no bin can resolve
27
+ * through a directory that is not there. Every dependency directory that
28
+ * does exist must hold a readable, valid manifest: returning a partial set
29
+ * would make destructive callers mistake unknown ownership for an unowned
30
+ * bin.
31
+ */
21
32
  export declare function getInstalledBinNames(info: GlobalPackageInfo): Promise<string[]>;
@@ -46,9 +46,8 @@ export function scanGlobalPackages(globalDir) {
46
46
  entries = fs.readdirSync(globalDir, { withFileTypes: true });
47
47
  }
48
48
  catch (err) {
49
- if (util.types.isNativeError(err) && 'code' in err && err.code === 'ENOENT') {
49
+ if (isNotFound(err))
50
50
  return [];
51
- }
52
51
  throw err;
53
52
  }
54
53
  const result = [];
@@ -139,13 +138,36 @@ export function cleanOrphanedInstallDirs(globalDir) {
139
138
  fs.rmSync(dirPath, { recursive: true, force: true });
140
139
  }
141
140
  }
141
+ /**
142
+ * The bin names installed by a group (deduplicated).
143
+ *
144
+ * A group whose `node_modules` is wholly absent owns no bins, and neither
145
+ * does a declared dependency whose directory under `node_modules` is absent,
146
+ * a link left dangling by a pruned store included: no bin can resolve
147
+ * through a directory that is not there. Every dependency directory that
148
+ * does exist must hold a readable, valid manifest: returning a partial set
149
+ * would make destructive callers mistake unknown ownership for an unowned
150
+ * bin.
151
+ */
142
152
  export async function getInstalledBinNames(info) {
143
153
  const bins = new Set();
144
154
  const aliases = Object.keys(info.dependencies);
145
155
  const modulesDir = path.join(info.installDir, 'node_modules');
156
+ if (!await dirExists(modulesDir))
157
+ return [];
146
158
  await Promise.all(aliases.map(async (alias) => {
147
159
  const depDir = path.join(modulesDir, alias);
148
- const manifest = await readPackageJsonFromDir(depDir);
160
+ let manifest;
161
+ try {
162
+ manifest = await readPackageJsonFromDir(depDir);
163
+ }
164
+ catch (err) {
165
+ // Probing after the read rather than before also covers a link
166
+ // pruned while the scan runs.
167
+ if (isNotFound(err) && !await dirExists(depDir))
168
+ return;
169
+ throw err;
170
+ }
149
171
  const binsOfPkg = await getBinsFromPackageManifest(manifest, depDir);
150
172
  for (const bin of binsOfPkg) {
151
173
  bins.add(bin.name);
@@ -153,4 +175,22 @@ export async function getInstalledBinNames(info) {
153
175
  }));
154
176
  return [...bins];
155
177
  }
178
+ /**
179
+ * Only ENOENT reads as absent; every other error surfaces, so unreadable
180
+ * ownership is never mistaken for unowned.
181
+ */
182
+ async function dirExists(dir) {
183
+ try {
184
+ await fs.promises.stat(dir);
185
+ return true;
186
+ }
187
+ catch (err) {
188
+ if (isNotFound(err))
189
+ return false;
190
+ throw err;
191
+ }
192
+ }
193
+ function isNotFound(err) {
194
+ return util.types.isNativeError(err) && 'code' in err && err.code === 'ENOENT';
195
+ }
156
196
  //# sourceMappingURL=scanGlobalPackages.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pnpm/global.packages",
3
- "version": "1101.1.3",
3
+ "version": "1101.1.4",
4
4
  "description": "Utilities for managing isolated global packages",
5
5
  "keywords": [
6
6
  "pnpm",
@@ -28,14 +28,14 @@
28
28
  "!*.map"
29
29
  ],
30
30
  "dependencies": {
31
- "@pnpm/bins.resolver": "1100.0.16",
32
- "@pnpm/crypto.hash": "1100.0.5",
33
- "@pnpm/pkg-manifest.reader": "1100.0.19",
31
+ "@pnpm/bins.resolver": "1100.0.17",
32
+ "@pnpm/crypto.hash": "1100.0.6",
33
+ "@pnpm/pkg-manifest.reader": "1100.0.20",
34
34
  "@pnpm/text.ordinal-comparator": "1100.0.0",
35
- "@pnpm/types": "1102.1.0"
35
+ "@pnpm/types": "1102.1.1"
36
36
  },
37
37
  "devDependencies": {
38
- "@pnpm/global.packages": "1101.1.3"
38
+ "@pnpm/global.packages": "1101.1.4"
39
39
  },
40
40
  "engines": {
41
41
  "node": ">=22.13"