@pnpm/patching.commands 1100.1.4 → 1100.1.5

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.
@@ -0,0 +1,7 @@
1
+ interface PathUtils {
2
+ isAbsolute: (path: string) => boolean;
3
+ relative: (from: string, to: string) => string;
4
+ sep: string;
5
+ }
6
+ export declare function isSubdirectory(parentDir: string, childPath: string, pathUtils?: PathUtils): boolean;
7
+ export {};
@@ -0,0 +1,8 @@
1
+ import path from 'node:path';
2
+ export function isSubdirectory(parentDir, childPath, pathUtils = path) {
3
+ const relativePath = pathUtils.relative(parentDir, childPath);
4
+ return relativePath === '' || (relativePath !== '..' &&
5
+ !relativePath.startsWith(`..${pathUtils.sep}`) &&
6
+ !pathUtils.isAbsolute(relativePath));
7
+ }
8
+ //# sourceMappingURL=isSubdirectory.js.map
@@ -7,6 +7,7 @@ import { PnpmError } from '@pnpm/error';
7
7
  import { install } from '@pnpm/installing.commands';
8
8
  import { pick } from 'ramda';
9
9
  import { renderHelp } from 'render-help';
10
+ import { isSubdirectory } from './isSubdirectory.js';
10
11
  import { updatePatchedDependencies } from './updatePatchedDependencies.js';
11
12
  export function rcOptionsTypes() {
12
13
  return pick([], allTypes);
@@ -25,7 +26,7 @@ export function help() {
25
26
  }
26
27
  export async function handler(opts, params) {
27
28
  let patchesToRemove = params;
28
- const patchedDependencies = opts.patchedDependencies ?? {};
29
+ const patchedDependencies = { ...opts.patchedDependencies };
29
30
  if (!params.length) {
30
31
  const allPatches = Object.keys(patchedDependencies);
31
32
  if (allPatches.length) {
@@ -56,15 +57,19 @@ export async function handler(opts, params) {
56
57
  throw new PnpmError('PATCH_NOT_FOUND', `Patch "${patch}" not found in patched dependencies`);
57
58
  }
58
59
  }
59
- const patchesDirs = new Set();
60
- await Promise.all(patchesToRemove.map(async (patch) => {
61
- if (Object.hasOwn(patchedDependencies, patch)) {
62
- const patchFile = patchedDependencies[patch];
63
- patchesDirs.add(path.dirname(patchFile));
64
- await fs.rm(patchFile, { force: true });
65
- delete patchedDependencies[patch];
60
+ const patchRemovalContext = await getPatchRemovalContext(opts);
61
+ const patchesToRemoveTargets = await Promise.all(patchesToRemove.map(async (patch) => {
62
+ const patchFile = patchedDependencies[patch];
63
+ if (patchFile == null) {
64
+ throw new PnpmError('PATCH_NOT_FOUND', `Patch "${patch}" not found in patched dependencies`);
66
65
  }
66
+ return getPatchRemovalTarget(patch, patchFile, patchRemovalContext);
67
67
  }));
68
+ await Promise.all(patchesToRemoveTargets.map(unlinkPatchIfExists));
69
+ for (const { patch } of patchesToRemoveTargets) {
70
+ delete patchedDependencies[patch];
71
+ }
72
+ const patchesDirs = new Set(patchesToRemoveTargets.map(({ parentDir }) => parentDir));
68
73
  await Promise.all(Array.from(patchesDirs).map(async (dir) => {
69
74
  try {
70
75
  const files = await fs.readdir(dir);
@@ -78,6 +83,88 @@ export async function handler(opts, params) {
78
83
  ...opts,
79
84
  workspaceDir: opts.workspaceDir ?? opts.rootProjectManifestDir,
80
85
  });
81
- return install.handler(opts);
86
+ return install.handler({
87
+ ...opts,
88
+ patchedDependencies,
89
+ });
90
+ }
91
+ async function getPatchRemovalContext(opts) {
92
+ const lockfileDir = path.resolve(opts.lockfileDir ?? opts.dir ?? process.cwd());
93
+ const realLockfileDir = await fs.realpath(lockfileDir);
94
+ const patchesDirSetting = opts.patchesDir ?? 'patches';
95
+ const patchesDir = path.join(lockfileDir, path.normalize(patchesDirSetting));
96
+ if (!isSubdirectory(lockfileDir, patchesDir)) {
97
+ throw new PnpmError('PATCHES_DIR_OUTSIDE_PROJECT', `The configured patches directory is outside the project: ${patchesDirSetting}`);
98
+ }
99
+ const realPatchesDir = await realpathIfExists(patchesDir);
100
+ if (realPatchesDir != null && !isSubdirectory(realLockfileDir, realPatchesDir)) {
101
+ throw new PnpmError('PATCHES_DIR_OUTSIDE_PROJECT', `The configured patches directory is outside the project: ${patchesDirSetting}`);
102
+ }
103
+ return {
104
+ lockfileDir,
105
+ patchesDir,
106
+ realPatchesDir,
107
+ };
108
+ }
109
+ async function getPatchRemovalTarget(patch, patchFile, ctx) {
110
+ const targetPath = path.resolve(ctx.lockfileDir, patchFile);
111
+ if (targetPath === ctx.patchesDir ||
112
+ !isSubdirectory(ctx.patchesDir, targetPath)) {
113
+ throw new PnpmError('PATCH_FILE_OUTSIDE_PATCHES_DIR', `Patch file "${patchFile}" is outside the configured patches directory`);
114
+ }
115
+ const parentDir = path.dirname(targetPath);
116
+ const targetStats = await lstatIfExists(targetPath);
117
+ const realParentDir = await realpathIfExists(parentDir);
118
+ const realPatchesDir = ctx.realPatchesDir ?? (await realpathIfExists(ctx.patchesDir));
119
+ if (realParentDir != null &&
120
+ realPatchesDir != null &&
121
+ !isSubdirectory(realPatchesDir, realParentDir)) {
122
+ throw new PnpmError('PATCH_FILE_OUTSIDE_PATCHES_DIR', `Patch file "${patchFile}" is outside the configured patches directory`);
123
+ }
124
+ if (targetStats?.isDirectory()) {
125
+ throw new PnpmError('PATCH_FILE_IS_DIRECTORY', `Patch file "${patchFile}" is a directory`);
126
+ }
127
+ return {
128
+ patch,
129
+ patchFile,
130
+ parentDir,
131
+ targetPath,
132
+ targetExists: targetStats != null,
133
+ };
134
+ }
135
+ async function unlinkPatchIfExists({ targetExists, targetPath }) {
136
+ if (!targetExists)
137
+ return;
138
+ try {
139
+ await fs.unlink(targetPath);
140
+ }
141
+ catch (err) {
142
+ if (isErrorWithCode(err, 'ENOENT'))
143
+ return;
144
+ throw err;
145
+ }
146
+ }
147
+ async function lstatIfExists(targetPath) {
148
+ try {
149
+ return await fs.lstat(targetPath);
150
+ }
151
+ catch (err) {
152
+ if (isErrorWithCode(err, 'ENOENT'))
153
+ return undefined;
154
+ throw err;
155
+ }
156
+ }
157
+ async function realpathIfExists(targetPath) {
158
+ try {
159
+ return await fs.realpath(targetPath);
160
+ }
161
+ catch (err) {
162
+ if (isErrorWithCode(err, 'ENOENT'))
163
+ return undefined;
164
+ throw err;
165
+ }
166
+ }
167
+ function isErrorWithCode(err, code) {
168
+ return typeof err === 'object' && err !== null && 'code' in err && err.code === code;
82
169
  }
83
170
  //# sourceMappingURL=patchRemove.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pnpm/patching.commands",
3
- "version": "1100.1.4",
3
+ "version": "1100.1.5",
4
4
  "description": "Commands for creating patches",
5
5
  "keywords": [
6
6
  "pnpm",
@@ -38,35 +38,35 @@
38
38
  "realpath-missing": "^2.0.0",
39
39
  "render-help": "^2.0.0",
40
40
  "safe-execa": "^0.3.0",
41
- "semver": "^7.8.1",
41
+ "semver": "^7.8.4",
42
42
  "terminal-link": "^5.0.0",
43
43
  "tinyglobby": "^0.2.16",
44
- "@pnpm/config.writer": "1100.0.12",
45
- "@pnpm/cli.utils": "1101.0.11",
46
- "@pnpm/crypto.hash": "1100.0.1",
47
- "@pnpm/error": "1100.0.0",
44
+ "@pnpm/config.reader": "1101.9.0",
48
45
  "@pnpm/constants": "1100.0.0",
49
- "@pnpm/config.reader": "1101.8.0",
46
+ "@pnpm/config.writer": "1100.0.13",
47
+ "@pnpm/crypto.hash": "1100.0.1",
50
48
  "@pnpm/fs.packlist": "1100.0.1",
51
- "@pnpm/fetching.pick-fetcher": "1100.0.11",
52
- "@pnpm/installing.commands": "1100.8.0",
53
- "@pnpm/installing.modules-yaml": "1100.0.8",
54
- "@pnpm/lockfile.fs": "1100.1.4",
55
- "@pnpm/lockfile.utils": "1100.0.12",
56
- "@pnpm/patching.apply-patch": "1100.0.1",
49
+ "@pnpm/installing.commands": "1100.9.0",
50
+ "@pnpm/installing.modules-yaml": "1100.0.9",
51
+ "@pnpm/cli.utils": "1101.0.12",
52
+ "@pnpm/lockfile.utils": "1100.0.13",
53
+ "@pnpm/patching.apply-patch": "1100.0.2",
54
+ "@pnpm/pkg-manifest.reader": "1100.0.8",
57
55
  "@pnpm/resolving.parse-wanted-dependency": "1100.0.1",
58
- "@pnpm/store.connection-manager": "1100.2.8",
59
- "@pnpm/types": "1101.3.1",
60
- "@pnpm/workspace.project-manifest-reader": "1100.0.12",
56
+ "@pnpm/store.connection-manager": "1100.3.0",
61
57
  "@pnpm/store.path": "1100.0.1",
62
- "@pnpm/workspace.workspace-manifest-reader": "1100.0.7",
63
- "@pnpm/pkg-manifest.reader": "1100.0.7"
58
+ "@pnpm/types": "1101.3.2",
59
+ "@pnpm/workspace.project-manifest-reader": "1100.0.13",
60
+ "@pnpm/workspace.workspace-manifest-reader": "1100.0.8",
61
+ "@pnpm/error": "1100.0.0",
62
+ "@pnpm/fetching.pick-fetcher": "1100.0.12",
63
+ "@pnpm/lockfile.fs": "1100.1.5"
64
64
  },
65
65
  "peerDependencies": {
66
- "@pnpm/logger": "^1001.0.1"
66
+ "@pnpm/logger": "^1100.0.0"
67
67
  },
68
68
  "devDependencies": {
69
- "@jest/globals": "30.3.0",
69
+ "@jest/globals": "30.4.1",
70
70
  "@types/is-windows": "^1.0.2",
71
71
  "@types/normalize-path": "^3.0.2",
72
72
  "@types/ramda": "0.31.1",
@@ -74,12 +74,12 @@
74
74
  "tempy": "3.0.0",
75
75
  "write-yaml-file": "^6.0.0",
76
76
  "@pnpm/logger": "1100.0.0",
77
- "@pnpm/prepare": "1100.0.15",
78
- "@pnpm/patching.commands": "1100.1.4",
79
- "@pnpm/testing.command-defaults": "1100.0.5",
80
- "@pnpm/workspace.projects-filter": "1100.0.20",
81
- "@pnpm/testing.registry-mock": "1100.0.5",
82
- "@pnpm/test-fixtures": "1100.0.0"
77
+ "@pnpm/patching.commands": "1100.1.5",
78
+ "@pnpm/prepare": "1100.0.16",
79
+ "@pnpm/test-fixtures": "1100.0.0",
80
+ "@pnpm/testing.command-defaults": "1100.0.6",
81
+ "@pnpm/testing.registry-mock": "1100.0.6",
82
+ "@pnpm/workspace.projects-filter": "1100.0.21"
83
83
  },
84
84
  "engines": {
85
85
  "node": ">=22.13"