isolate-package 1.33.0 → 1.35.0
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/dist/index.mjs +1 -1
- package/dist/index.mjs.map +1 -1
- package/dist/isolate-bin.mjs +5 -6
- package/dist/isolate-bin.mjs.map +1 -1
- package/dist/{isolate-DyRD5Zd_.mjs → isolate-ts-Igq7C.mjs} +888 -271
- package/dist/isolate-ts-Igq7C.mjs.map +1 -0
- package/package.json +23 -19
- package/src/get-internal-package-names.test.ts +1 -1
- package/src/get-internal-package-names.ts +2 -2
- package/src/isolate-bin.ts +5 -5
- package/src/isolate.ts +22 -17
- package/src/lib/config.test.ts +1 -1
- package/src/lib/config.ts +3 -3
- package/src/lib/lockfile/helpers/bun-lockfile.ts +153 -0
- package/src/lib/lockfile/helpers/generate-bun-lockfile.test.ts +3 -3
- package/src/lib/lockfile/helpers/generate-bun-lockfile.ts +14 -146
- package/src/lib/lockfile/helpers/generate-npm-lockfile.integration.test.ts +1 -5
- package/src/lib/lockfile/helpers/generate-npm-lockfile.test.ts +311 -16
- package/src/lib/lockfile/helpers/generate-npm-lockfile.ts +193 -22
- package/src/lib/lockfile/helpers/generate-pnpm-lockfile.test.ts +83 -2
- package/src/lib/lockfile/helpers/generate-pnpm-lockfile.ts +33 -6
- package/src/lib/lockfile/helpers/generate-yarn-lockfile.ts +5 -5
- package/src/lib/lockfile/process-lockfile.test.ts +2 -2
- package/src/lib/manifest/adapt-target-package-manifest.ts +22 -13
- package/src/lib/manifest/helpers/adapt-internal-package-manifests.test.ts +72 -3
- package/src/lib/manifest/helpers/adapt-internal-package-manifests.ts +22 -12
- package/src/lib/manifest/helpers/adapt-manifest-internal-deps.ts +1 -1
- package/src/lib/manifest/helpers/adopt-pnpm-fields-from-root.test.ts +4 -4
- package/src/lib/manifest/helpers/adopt-pnpm-fields-from-root.ts +7 -7
- package/src/lib/manifest/helpers/resolve-catalog-dependencies.test.ts +410 -0
- package/src/lib/manifest/helpers/resolve-catalog-dependencies.ts +115 -27
- package/src/lib/manifest/io.ts +6 -2
- package/src/lib/manifest/validate-manifest.ts +2 -2
- package/src/lib/output/get-build-output-dir.ts +1 -1
- package/src/lib/output/pack-dependencies.ts +1 -1
- package/src/lib/output/process-build-output-files.ts +6 -17
- package/src/lib/package-manager/helpers/infer-from-files.ts +5 -5
- package/src/lib/package-manager/helpers/infer-from-manifest.ts +7 -8
- package/src/lib/package-manager/index.ts +1 -1
- package/src/lib/package-manager/names.ts +8 -10
- package/src/lib/patches/collect-installed-names-bun.test.ts +154 -0
- package/src/lib/patches/collect-installed-names-bun.ts +87 -0
- package/src/lib/patches/collect-installed-names-pnpm.test.ts +316 -0
- package/src/lib/patches/collect-installed-names-pnpm.ts +365 -0
- package/src/lib/patches/copy-patches.test.ts +130 -13
- package/src/lib/patches/copy-patches.ts +47 -10
- package/src/lib/patches/write-isolate-pnpm-workspace.test.ts +83 -3
- package/src/lib/patches/write-isolate-pnpm-workspace.ts +4 -4
- package/src/lib/registry/collect-reachable-package-names.test.ts +1 -1
- package/src/lib/registry/create-packages-registry.ts +34 -31
- package/src/lib/registry/helpers/find-packages-globs.ts +23 -19
- package/src/lib/registry/list-internal-packages.test.ts +2 -2
- package/src/lib/types.ts +2 -2
- package/src/lib/utils/filter-patched-dependencies.test.ts +1 -1
- package/src/lib/utils/filter-patched-dependencies.ts +2 -2
- package/src/lib/utils/get-dirname.ts +1 -1
- package/src/lib/utils/index.ts +1 -1
- package/src/lib/utils/json.ts +12 -14
- package/src/lib/utils/pack.ts +32 -22
- package/src/lib/utils/reset-isolate-dir.test.ts +165 -0
- package/src/lib/utils/reset-isolate-dir.ts +147 -0
- package/src/lib/utils/unpack.test.ts +76 -0
- package/src/lib/utils/unpack.ts +16 -10
- package/src/lib/utils/wait-for-complete-file.test.ts +105 -0
- package/src/lib/utils/wait-for-complete-file.ts +44 -0
- package/src/lib/utils/yaml.ts +8 -9
- package/src/testing/setup.ts +1 -1
- package/dist/isolate-DyRD5Zd_.mjs.map +0 -1
|
@@ -0,0 +1,365 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import {
|
|
3
|
+
getLockfileImporterId as getLockfileImporterId_v8,
|
|
4
|
+
readWantedLockfile as readWantedLockfile_v8,
|
|
5
|
+
} from "pnpm_lockfile_file_v8";
|
|
6
|
+
import {
|
|
7
|
+
getLockfileImporterId as getLockfileImporterId_v9,
|
|
8
|
+
readWantedLockfile as readWantedLockfile_v9,
|
|
9
|
+
} from "pnpm_lockfile_file_v9";
|
|
10
|
+
import { useLogger } from "#/lib/logger";
|
|
11
|
+
import type { PackagesRegistry } from "#/lib/types";
|
|
12
|
+
import { getPackageName, isRushWorkspace } from "#/lib/utils";
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Walk the workspace pnpm lockfile starting from the target package and its
|
|
16
|
+
* internal workspace dependencies, returning the set of every package name
|
|
17
|
+
* that will end up installed in the isolate (including deep
|
|
18
|
+
* external-to-external transitives).
|
|
19
|
+
*
|
|
20
|
+
* Used by `copyPatches` to preserve patches for transitive deps that aren't
|
|
21
|
+
* directly listed on any internal manifest. Returns an empty set on any
|
|
22
|
+
* failure so the caller falls back to manifest-based reachability. When the
|
|
23
|
+
* lockfile is present but lacks a `packages` section, returns just the
|
|
24
|
+
* direct importer dep names.
|
|
25
|
+
*/
|
|
26
|
+
export async function collectInstalledNamesFromPnpmLockfile({
|
|
27
|
+
workspaceRootDir,
|
|
28
|
+
targetPackageDir,
|
|
29
|
+
internalDepPackageNames,
|
|
30
|
+
packagesRegistry,
|
|
31
|
+
majorVersion,
|
|
32
|
+
includeDevDependencies,
|
|
33
|
+
}: {
|
|
34
|
+
workspaceRootDir: string;
|
|
35
|
+
targetPackageDir: string;
|
|
36
|
+
internalDepPackageNames: string[];
|
|
37
|
+
packagesRegistry: PackagesRegistry;
|
|
38
|
+
majorVersion: number;
|
|
39
|
+
includeDevDependencies: boolean;
|
|
40
|
+
}): Promise<Set<string>> {
|
|
41
|
+
const log = useLogger();
|
|
42
|
+
|
|
43
|
+
try {
|
|
44
|
+
const useVersion9 = majorVersion >= 9;
|
|
45
|
+
const isRush = isRushWorkspace(workspaceRootDir);
|
|
46
|
+
const lockfileDir = isRush
|
|
47
|
+
? path.join(workspaceRootDir, "common/config/rush")
|
|
48
|
+
: workspaceRootDir;
|
|
49
|
+
|
|
50
|
+
const lockfile = useVersion9
|
|
51
|
+
? await readWantedLockfile_v9(lockfileDir, { ignoreIncompatible: false })
|
|
52
|
+
: await readWantedLockfile_v8(lockfileDir, { ignoreIncompatible: false });
|
|
53
|
+
|
|
54
|
+
if (!lockfile) {
|
|
55
|
+
log.debug("No pnpm lockfile available for installed-names walk");
|
|
56
|
+
return new Set();
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const rawTargetImporterId = useVersion9
|
|
60
|
+
? getLockfileImporterId_v9(workspaceRootDir, targetPackageDir)
|
|
61
|
+
: getLockfileImporterId_v8(workspaceRootDir, targetPackageDir);
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Normalize separators to POSIX so Windows callers match the lockfile's
|
|
65
|
+
* importer keys (mirrors generate-pnpm-lockfile.ts). Applied once here so
|
|
66
|
+
* the `isTarget` equality check below compares apples-to-apples — without
|
|
67
|
+
* this, on Windows the raw id with backslashes wouldn't match the
|
|
68
|
+
* normalized id used as the importers map key.
|
|
69
|
+
*/
|
|
70
|
+
const targetImporterId = toLockfileImporterKey(rawTargetImporterId, isRush);
|
|
71
|
+
|
|
72
|
+
const importerIds = [
|
|
73
|
+
targetImporterId,
|
|
74
|
+
...(
|
|
75
|
+
internalDepPackageNames
|
|
76
|
+
.map((name) => packagesRegistry[name]?.rootRelativeDir)
|
|
77
|
+
.filter(Boolean) as string[]
|
|
78
|
+
).map((dir) => toLockfileImporterKey(dir, isRush)),
|
|
79
|
+
];
|
|
80
|
+
|
|
81
|
+
const packages = (lockfile as { packages?: Record<string, PnpmPackage> })
|
|
82
|
+
.packages;
|
|
83
|
+
|
|
84
|
+
if (!packages) {
|
|
85
|
+
log.debug("Lockfile has no packages section to walk");
|
|
86
|
+
return collectImporterDirectNames(
|
|
87
|
+
lockfile.importers,
|
|
88
|
+
importerIds,
|
|
89
|
+
targetImporterId,
|
|
90
|
+
includeDevDependencies,
|
|
91
|
+
);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const names = new Set<string>();
|
|
95
|
+
const seen = new Set<string>();
|
|
96
|
+
const queue: string[] = [];
|
|
97
|
+
|
|
98
|
+
for (const importerId of importerIds) {
|
|
99
|
+
const importer = lockfile.importers[importerId];
|
|
100
|
+
if (!importer) continue;
|
|
101
|
+
|
|
102
|
+
const isTarget = importerId === targetImporterId;
|
|
103
|
+
|
|
104
|
+
enqueueImporterDeps({
|
|
105
|
+
importer,
|
|
106
|
+
names,
|
|
107
|
+
queue,
|
|
108
|
+
useVersion9,
|
|
109
|
+
includeDevDependencies: isTarget && includeDevDependencies,
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
let depPath: string | undefined;
|
|
114
|
+
while ((depPath = queue.pop()) !== undefined) {
|
|
115
|
+
if (seen.has(depPath)) continue;
|
|
116
|
+
seen.add(depPath);
|
|
117
|
+
|
|
118
|
+
names.add(extractPackageName(depPath));
|
|
119
|
+
|
|
120
|
+
const pkg = packages[depPath];
|
|
121
|
+
if (!pkg) continue;
|
|
122
|
+
|
|
123
|
+
enqueueResolvedDeps(pkg.dependencies, names, queue, useVersion9, seen);
|
|
124
|
+
enqueueResolvedDeps(
|
|
125
|
+
pkg.optionalDependencies,
|
|
126
|
+
names,
|
|
127
|
+
queue,
|
|
128
|
+
useVersion9,
|
|
129
|
+
seen,
|
|
130
|
+
);
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Peer requirement values are name → semver-range, not resolved depPaths.
|
|
134
|
+
* Just record the names so a patch on a peer-only external transitive
|
|
135
|
+
* survives filtering (mirrors the bun walker and the sister manifest
|
|
136
|
+
* walker, which both include peerDependencies).
|
|
137
|
+
*/
|
|
138
|
+
collectNames(pkg.peerDependencies, names);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
return names;
|
|
142
|
+
} catch (error) {
|
|
143
|
+
log.debug(
|
|
144
|
+
`Failed to walk pnpm lockfile for installed names: ${error instanceof Error ? error.message : String(error)}`,
|
|
145
|
+
);
|
|
146
|
+
return new Set();
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Convert a raw importer id (as returned by `getLockfileImporterId` or a
|
|
152
|
+
* package's rootRelativeDir) to the form actually used as a key in
|
|
153
|
+
* `lockfile.importers`: POSIX separators, with the Rush `../../` prefix when
|
|
154
|
+
* the workspace lives under `common/config/rush`. Lockfile keys are always
|
|
155
|
+
* POSIX regardless of the host OS, so backslashes are normalized
|
|
156
|
+
* unconditionally rather than relying on `path.sep`.
|
|
157
|
+
*/
|
|
158
|
+
function toLockfileImporterKey(importerId: string, isRush: boolean): string {
|
|
159
|
+
const posix = importerId
|
|
160
|
+
.split(path.sep)
|
|
161
|
+
.join(path.posix.sep)
|
|
162
|
+
.replace(/\\/g, "/");
|
|
163
|
+
return isRush ? `../../${posix}` : posix;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
type ResolvedDeps = Record<string, string>;
|
|
167
|
+
|
|
168
|
+
type PnpmImporter = {
|
|
169
|
+
dependencies?: ResolvedDeps;
|
|
170
|
+
optionalDependencies?: ResolvedDeps;
|
|
171
|
+
devDependencies?: ResolvedDeps;
|
|
172
|
+
peerDependencies?: ResolvedDeps;
|
|
173
|
+
};
|
|
174
|
+
|
|
175
|
+
type PnpmPackage = {
|
|
176
|
+
dependencies?: ResolvedDeps;
|
|
177
|
+
optionalDependencies?: ResolvedDeps;
|
|
178
|
+
peerDependencies?: ResolvedDeps;
|
|
179
|
+
};
|
|
180
|
+
|
|
181
|
+
function enqueueImporterDeps({
|
|
182
|
+
importer,
|
|
183
|
+
names,
|
|
184
|
+
queue,
|
|
185
|
+
useVersion9,
|
|
186
|
+
includeDevDependencies,
|
|
187
|
+
}: {
|
|
188
|
+
importer: PnpmImporter;
|
|
189
|
+
names: Set<string>;
|
|
190
|
+
queue: string[];
|
|
191
|
+
useVersion9: boolean;
|
|
192
|
+
includeDevDependencies: boolean;
|
|
193
|
+
}): void {
|
|
194
|
+
enqueueResolvedDeps(importer.dependencies, names, queue, useVersion9);
|
|
195
|
+
enqueueResolvedDeps(importer.optionalDependencies, names, queue, useVersion9);
|
|
196
|
+
if (includeDevDependencies) {
|
|
197
|
+
enqueueResolvedDeps(importer.devDependencies, names, queue, useVersion9);
|
|
198
|
+
}
|
|
199
|
+
/**
|
|
200
|
+
* Importer peerDependencies usually aren't a separate map in the lockfile
|
|
201
|
+
* (autoInstallPeers folds them into `dependencies`), but record names if
|
|
202
|
+
* they happen to be present.
|
|
203
|
+
*/
|
|
204
|
+
collectNames(importer.peerDependencies, names);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
function enqueueResolvedDeps(
|
|
208
|
+
deps: ResolvedDeps | undefined,
|
|
209
|
+
names: Set<string>,
|
|
210
|
+
queue: string[],
|
|
211
|
+
useVersion9: boolean,
|
|
212
|
+
seen?: Set<string>,
|
|
213
|
+
): void {
|
|
214
|
+
if (!deps) return;
|
|
215
|
+
|
|
216
|
+
for (const [alias, ref] of Object.entries(deps)) {
|
|
217
|
+
/**
|
|
218
|
+
* The alias is the name as listed in the parent's dependencies map. For
|
|
219
|
+
* non-aliased installs this is also the resolved package name. We add it
|
|
220
|
+
* to the set as a candidate name; visiting the actual depPath below
|
|
221
|
+
* refines this with the true installed name.
|
|
222
|
+
*/
|
|
223
|
+
names.add(alias);
|
|
224
|
+
|
|
225
|
+
const depPath = refToRelative(ref, alias, useVersion9);
|
|
226
|
+
if (depPath && !seen?.has(depPath)) {
|
|
227
|
+
queue.push(depPath);
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
function collectNames(
|
|
233
|
+
deps: ResolvedDeps | undefined,
|
|
234
|
+
names: Set<string>,
|
|
235
|
+
): void {
|
|
236
|
+
if (!deps) return;
|
|
237
|
+
for (const name of Object.keys(deps)) {
|
|
238
|
+
names.add(name);
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
/**
|
|
243
|
+
* Mirrors `@pnpm/dependency-path`'s `refToRelative`. The depPath shape differs
|
|
244
|
+
* between pnpm 8 (lockfile v6, normalized to v5 keys like `/foo/1.0.0`) and
|
|
245
|
+
* pnpm 9 (lockfile v9 keys like `foo@1.0.0`). Returns the depPath used as a
|
|
246
|
+
* key in `lockfile.packages`, or null if the ref points to a workspace link.
|
|
247
|
+
*/
|
|
248
|
+
function refToRelative(
|
|
249
|
+
reference: string,
|
|
250
|
+
pkgName: string,
|
|
251
|
+
useVersion9: boolean,
|
|
252
|
+
): string | null {
|
|
253
|
+
if (!reference) return null;
|
|
254
|
+
if (reference.startsWith("link:")) return null;
|
|
255
|
+
return useVersion9
|
|
256
|
+
? refToRelativeV9(reference, pkgName)
|
|
257
|
+
: refToRelativeV8(reference, pkgName);
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
function refToRelativeV9(reference: string, pkgName: string): string | null {
|
|
261
|
+
if (reference.startsWith("@")) return reference;
|
|
262
|
+
const atIndex = reference.indexOf("@");
|
|
263
|
+
if (atIndex === -1) return `${pkgName}@${reference}`;
|
|
264
|
+
const colonIndex = reference.indexOf(":");
|
|
265
|
+
const bracketIndex = reference.indexOf("(");
|
|
266
|
+
if (
|
|
267
|
+
(colonIndex === -1 || atIndex < colonIndex) &&
|
|
268
|
+
(bracketIndex === -1 || atIndex < bracketIndex)
|
|
269
|
+
) {
|
|
270
|
+
return reference;
|
|
271
|
+
}
|
|
272
|
+
return `${pkgName}@${reference}`;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
/**
|
|
276
|
+
* v8 form: pnpm 8 (lockfile v6) is normalized on read to v5-style depPaths
|
|
277
|
+
* with leading slash and `/` separator between name and version. Plain
|
|
278
|
+
* version refs build that key; refs already containing a `/` (peer-suffixed
|
|
279
|
+
* or pre-formed) are returned verbatim. Mirrors `@pnpm/dependency-path@2.x`.
|
|
280
|
+
*/
|
|
281
|
+
function refToRelativeV8(reference: string, pkgName: string): string | null {
|
|
282
|
+
if (reference.startsWith("file:")) return reference;
|
|
283
|
+
const slashIndex = reference.indexOf("/");
|
|
284
|
+
const bracketIndex = reference.indexOf("(");
|
|
285
|
+
const noSlashBeforeBracket =
|
|
286
|
+
bracketIndex !== -1 && reference.lastIndexOf("/", bracketIndex) === -1;
|
|
287
|
+
if (slashIndex === -1 || noSlashBeforeBracket) {
|
|
288
|
+
return `/${pkgName}/${reference}`;
|
|
289
|
+
}
|
|
290
|
+
return reference;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
/**
|
|
294
|
+
* Extract the bare package name from a pnpm depPath. Strips the optional
|
|
295
|
+
* peer-resolution suffix (e.g. `(react@18.0.0)`) before parsing. Handles
|
|
296
|
+
* both v9 (`@scope/foo@1.0.0`) and v8 (`/@scope/foo/1.0.0`) shapes.
|
|
297
|
+
*/
|
|
298
|
+
function extractPackageName(depPath: string): string {
|
|
299
|
+
const peerStart = indexOfPeersSuffix(depPath);
|
|
300
|
+
const trimmed = peerStart === -1 ? depPath : depPath.slice(0, peerStart);
|
|
301
|
+
|
|
302
|
+
if (trimmed.startsWith("/")) {
|
|
303
|
+
/** v8 v5-style: `/<name>/<version>` */
|
|
304
|
+
const stripped = trimmed.slice(1);
|
|
305
|
+
if (stripped.startsWith("@")) {
|
|
306
|
+
const secondSlash = stripped.indexOf("/", stripped.indexOf("/") + 1);
|
|
307
|
+
return secondSlash === -1 ? stripped : stripped.slice(0, secondSlash);
|
|
308
|
+
}
|
|
309
|
+
const firstSlash = stripped.indexOf("/");
|
|
310
|
+
return firstSlash === -1 ? stripped : stripped.slice(0, firstSlash);
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
return getPackageName(trimmed);
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
/**
|
|
317
|
+
* Mirrors `@pnpm/dependency-path`'s `indexOfPeersSuffix`. Returns the index
|
|
318
|
+
* where the peer-resolution suffix starts, or -1 if there is none.
|
|
319
|
+
*/
|
|
320
|
+
function indexOfPeersSuffix(depPath: string): number {
|
|
321
|
+
if (!depPath.endsWith(")")) return -1;
|
|
322
|
+
let open = 1;
|
|
323
|
+
for (let i = depPath.length - 2; i >= 0; i--) {
|
|
324
|
+
if (depPath[i] === "(") {
|
|
325
|
+
open--;
|
|
326
|
+
} else if (depPath[i] === ")") {
|
|
327
|
+
open++;
|
|
328
|
+
} else if (!open) {
|
|
329
|
+
return i + 1;
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
return -1;
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
/**
|
|
336
|
+
* Fallback when the lockfile is missing `packages`: just return importer
|
|
337
|
+
* direct dep names so we at least cover some of the graph.
|
|
338
|
+
*/
|
|
339
|
+
function collectImporterDirectNames(
|
|
340
|
+
importers: Record<string, PnpmImporter>,
|
|
341
|
+
importerIds: string[],
|
|
342
|
+
targetImporterId: string,
|
|
343
|
+
includeDevDependencies: boolean,
|
|
344
|
+
): Set<string> {
|
|
345
|
+
const names = new Set<string>();
|
|
346
|
+
for (const importerId of importerIds) {
|
|
347
|
+
const importer = importers[importerId];
|
|
348
|
+
if (!importer) continue;
|
|
349
|
+
const isTarget = importerId === targetImporterId;
|
|
350
|
+
for (const name of Object.keys(importer.dependencies ?? {}))
|
|
351
|
+
names.add(name);
|
|
352
|
+
for (const name of Object.keys(importer.optionalDependencies ?? {})) {
|
|
353
|
+
names.add(name);
|
|
354
|
+
}
|
|
355
|
+
for (const name of Object.keys(importer.peerDependencies ?? {})) {
|
|
356
|
+
names.add(name);
|
|
357
|
+
}
|
|
358
|
+
if (isTarget && includeDevDependencies) {
|
|
359
|
+
for (const name of Object.keys(importer.devDependencies ?? {})) {
|
|
360
|
+
names.add(name);
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
return names;
|
|
365
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { describe, it, expect, beforeEach, afterEach, vi } from "vitest";
|
|
2
|
-
import type { PackageManifest, PnpmSettings } from "
|
|
2
|
+
import type { PackageManifest, PnpmSettings } from "#/lib/types";
|
|
3
3
|
import { copyPatches } from "./copy-patches";
|
|
4
4
|
|
|
5
5
|
/** Mock fs-extra */
|
|
@@ -12,33 +12,50 @@ vi.mock("fs-extra", () => ({
|
|
|
12
12
|
}));
|
|
13
13
|
|
|
14
14
|
/** Mock the utils */
|
|
15
|
-
vi.mock("
|
|
15
|
+
vi.mock("#/lib/utils", () => ({
|
|
16
16
|
filterPatchedDependencies: vi.fn(),
|
|
17
17
|
getIsolateRelativeLogPath: vi.fn((p: string) => p),
|
|
18
|
+
getPackageName: vi.fn((spec: string) => {
|
|
19
|
+
if (spec.startsWith("@")) {
|
|
20
|
+
const parts = spec.split("@");
|
|
21
|
+
return `@${parts[1] ?? ""}`;
|
|
22
|
+
}
|
|
23
|
+
return spec.split("@")[0] ?? "";
|
|
24
|
+
}),
|
|
18
25
|
getRootRelativeLogPath: vi.fn((p: string) => p),
|
|
19
26
|
isRushWorkspace: vi.fn(() => false),
|
|
20
27
|
readTypedJson: vi.fn(),
|
|
28
|
+
readTypedJsonSync: vi.fn(),
|
|
21
29
|
readTypedYamlSync: vi.fn(),
|
|
22
30
|
}));
|
|
23
31
|
|
|
24
32
|
/** Mock the package manager */
|
|
25
|
-
vi.mock("
|
|
33
|
+
vi.mock("#/lib/package-manager", () => ({
|
|
26
34
|
usePackageManager: vi.fn(() => ({ name: "pnpm", majorVersion: 9 })),
|
|
27
35
|
}));
|
|
28
36
|
|
|
29
37
|
/** Mock the pnpm lockfile readers */
|
|
30
38
|
vi.mock("pnpm_lockfile_file_v8", () => ({
|
|
31
39
|
readWantedLockfile: vi.fn(() => Promise.resolve(null)),
|
|
40
|
+
getLockfileImporterId: vi.fn(
|
|
41
|
+
(root: string, dir: string) => dir.replace(`${root}/`, "") || ".",
|
|
42
|
+
),
|
|
32
43
|
}));
|
|
33
44
|
|
|
34
45
|
vi.mock("pnpm_lockfile_file_v9", () => ({
|
|
35
46
|
readWantedLockfile: vi.fn(() => Promise.resolve(null)),
|
|
47
|
+
getLockfileImporterId: vi.fn(
|
|
48
|
+
(root: string, dir: string) => dir.replace(`${root}/`, "") || ".",
|
|
49
|
+
),
|
|
36
50
|
}));
|
|
37
51
|
|
|
38
52
|
const fs = vi.mocked((await import("fs-extra")).default);
|
|
39
53
|
const { filterPatchedDependencies, readTypedJson, readTypedYamlSync } =
|
|
40
|
-
vi.mocked(await import("
|
|
41
|
-
const { usePackageManager } = vi.mocked(await import("
|
|
54
|
+
vi.mocked(await import("#/lib/utils"));
|
|
55
|
+
const { usePackageManager } = vi.mocked(await import("#/lib/package-manager"));
|
|
56
|
+
const { readWantedLockfile: readWantedLockfile_v9 } = vi.mocked(
|
|
57
|
+
await import("pnpm_lockfile_file_v9"),
|
|
58
|
+
);
|
|
42
59
|
|
|
43
60
|
describe("copyPatches", () => {
|
|
44
61
|
beforeEach(() => {
|
|
@@ -49,14 +66,6 @@ describe("copyPatches", () => {
|
|
|
49
66
|
vi.restoreAllMocks();
|
|
50
67
|
});
|
|
51
68
|
|
|
52
|
-
const mockJsonSettings = (settings: PnpmSettings | undefined) => {
|
|
53
|
-
readTypedJson.mockResolvedValue({
|
|
54
|
-
name: "root",
|
|
55
|
-
version: "1.0.0",
|
|
56
|
-
pnpm: settings,
|
|
57
|
-
});
|
|
58
|
-
};
|
|
59
|
-
|
|
60
69
|
it("should return empty object when workspace root package.json cannot be read", async () => {
|
|
61
70
|
readTypedYamlSync.mockImplementation(() => {
|
|
62
71
|
throw new Error("File not found");
|
|
@@ -65,6 +74,8 @@ describe("copyPatches", () => {
|
|
|
65
74
|
|
|
66
75
|
const result = await copyPatches({
|
|
67
76
|
workspaceRootDir: "/workspace",
|
|
77
|
+
targetPackageDir: "/workspace/packages/test",
|
|
78
|
+
internalDepPackageNames: [],
|
|
68
79
|
targetPackageManifest: { name: "test", version: "1.0.0" },
|
|
69
80
|
isolateDir: "/workspace/isolate",
|
|
70
81
|
packagesRegistry: {},
|
|
@@ -126,6 +137,8 @@ describe("copyPatches", () => {
|
|
|
126
137
|
|
|
127
138
|
const result = await copyPatches({
|
|
128
139
|
workspaceRootDir: "/workspace",
|
|
140
|
+
targetPackageDir: "/workspace/packages/test",
|
|
141
|
+
internalDepPackageNames: [],
|
|
129
142
|
targetPackageManifest: { name: "test", version: "1.0.0" },
|
|
130
143
|
isolateDir: "/workspace/isolate",
|
|
131
144
|
packagesRegistry: {},
|
|
@@ -146,6 +159,8 @@ describe("copyPatches", () => {
|
|
|
146
159
|
|
|
147
160
|
const result = await copyPatches({
|
|
148
161
|
workspaceRootDir: "/workspace",
|
|
162
|
+
targetPackageDir: "/workspace/packages/test",
|
|
163
|
+
internalDepPackageNames: [],
|
|
149
164
|
targetPackageManifest: { name: "test", version: "1.0.0" },
|
|
150
165
|
isolateDir: "/workspace/isolate",
|
|
151
166
|
packagesRegistry: {},
|
|
@@ -176,6 +191,8 @@ describe("copyPatches", () => {
|
|
|
176
191
|
|
|
177
192
|
const result = await copyPatches({
|
|
178
193
|
workspaceRootDir: "/workspace",
|
|
194
|
+
targetPackageDir: "/workspace/packages/test",
|
|
195
|
+
internalDepPackageNames: [],
|
|
179
196
|
targetPackageManifest: targetManifest,
|
|
180
197
|
isolateDir: "/workspace/isolate",
|
|
181
198
|
packagesRegistry: {},
|
|
@@ -214,6 +231,8 @@ describe("copyPatches", () => {
|
|
|
214
231
|
|
|
215
232
|
const result = await copyPatches({
|
|
216
233
|
workspaceRootDir: "/workspace",
|
|
234
|
+
targetPackageDir: "/workspace/packages/test",
|
|
235
|
+
internalDepPackageNames: [],
|
|
217
236
|
targetPackageManifest: targetManifest,
|
|
218
237
|
isolateDir: "/workspace/isolate",
|
|
219
238
|
packagesRegistry: {},
|
|
@@ -256,6 +275,8 @@ describe("copyPatches", () => {
|
|
|
256
275
|
|
|
257
276
|
const result = await copyPatches({
|
|
258
277
|
workspaceRootDir: "/workspace",
|
|
278
|
+
targetPackageDir: "/workspace/packages/test",
|
|
279
|
+
internalDepPackageNames: [],
|
|
259
280
|
targetPackageManifest: targetManifest,
|
|
260
281
|
isolateDir: "/workspace/isolate",
|
|
261
282
|
packagesRegistry: {},
|
|
@@ -287,6 +308,8 @@ describe("copyPatches", () => {
|
|
|
287
308
|
|
|
288
309
|
const result = await copyPatches({
|
|
289
310
|
workspaceRootDir: "/workspace",
|
|
311
|
+
targetPackageDir: "/workspace/packages/test",
|
|
312
|
+
internalDepPackageNames: [],
|
|
290
313
|
targetPackageManifest: targetManifest,
|
|
291
314
|
isolateDir: "/workspace/isolate",
|
|
292
315
|
packagesRegistry: {},
|
|
@@ -321,6 +344,8 @@ describe("copyPatches", () => {
|
|
|
321
344
|
|
|
322
345
|
const result = await copyPatches({
|
|
323
346
|
workspaceRootDir: "/workspace",
|
|
347
|
+
targetPackageDir: "/workspace/packages/test",
|
|
348
|
+
internalDepPackageNames: [],
|
|
324
349
|
targetPackageManifest: targetManifest,
|
|
325
350
|
isolateDir: "/workspace/isolate",
|
|
326
351
|
packagesRegistry: {},
|
|
@@ -364,6 +389,8 @@ describe("copyPatches", () => {
|
|
|
364
389
|
|
|
365
390
|
const result = await copyPatches({
|
|
366
391
|
workspaceRootDir: "/workspace",
|
|
392
|
+
targetPackageDir: "/workspace/packages/test",
|
|
393
|
+
internalDepPackageNames: [],
|
|
367
394
|
targetPackageManifest: targetManifest,
|
|
368
395
|
isolateDir: "/workspace/isolate",
|
|
369
396
|
packagesRegistry: {},
|
|
@@ -409,6 +436,8 @@ describe("copyPatches", () => {
|
|
|
409
436
|
|
|
410
437
|
const result = await copyPatches({
|
|
411
438
|
workspaceRootDir: "/workspace",
|
|
439
|
+
targetPackageDir: "/workspace/packages/test",
|
|
440
|
+
internalDepPackageNames: [],
|
|
412
441
|
targetPackageManifest: targetManifest,
|
|
413
442
|
isolateDir: "/workspace/isolate",
|
|
414
443
|
packagesRegistry: {},
|
|
@@ -456,6 +485,8 @@ describe("copyPatches", () => {
|
|
|
456
485
|
|
|
457
486
|
const result = await copyPatches({
|
|
458
487
|
workspaceRootDir: "/workspace",
|
|
488
|
+
targetPackageDir: "/workspace/packages/test",
|
|
489
|
+
internalDepPackageNames: [],
|
|
459
490
|
targetPackageManifest: targetManifest,
|
|
460
491
|
isolateDir: "/workspace/isolate",
|
|
461
492
|
packagesRegistry: {},
|
|
@@ -509,6 +540,8 @@ describe("copyPatches", () => {
|
|
|
509
540
|
|
|
510
541
|
const result = await copyPatches({
|
|
511
542
|
workspaceRootDir: "/workspace",
|
|
543
|
+
targetPackageDir: "/workspace/packages/test",
|
|
544
|
+
internalDepPackageNames: [],
|
|
512
545
|
targetPackageManifest: consumerManifest,
|
|
513
546
|
isolateDir: "/workspace/isolate",
|
|
514
547
|
packagesRegistry: {
|
|
@@ -536,4 +569,88 @@ describe("copyPatches", () => {
|
|
|
536
569
|
expect(reachable!.has("firebase-package")).toBe(true);
|
|
537
570
|
expect(reachable!.has("tslib")).toBe(true);
|
|
538
571
|
});
|
|
572
|
+
|
|
573
|
+
it("should pick up deep external-to-external transitives from the pnpm lockfile (regression: issue #167 follow-up)", async () => {
|
|
574
|
+
/**
|
|
575
|
+
* Target depends on `@react-pdf/renderer` (external). The patched
|
|
576
|
+
* `@react-pdf/render` is only a transitive of `@react-pdf/renderer`. The
|
|
577
|
+
* manifest walker can't see it because it can't open external manifests,
|
|
578
|
+
* so the lockfile walker has to surface it.
|
|
579
|
+
*/
|
|
580
|
+
const targetManifest: PackageManifest = {
|
|
581
|
+
name: "consumer",
|
|
582
|
+
version: "1.0.0",
|
|
583
|
+
dependencies: { "@react-pdf/renderer": "^4.0.0" },
|
|
584
|
+
};
|
|
585
|
+
|
|
586
|
+
readTypedYamlSync.mockReturnValue({
|
|
587
|
+
patchedDependencies: {
|
|
588
|
+
"@react-pdf/render@4.3.0": "patches/@react-pdf__render@4.3.0.patch",
|
|
589
|
+
},
|
|
590
|
+
});
|
|
591
|
+
readTypedJson.mockResolvedValue({
|
|
592
|
+
name: "root",
|
|
593
|
+
version: "1.0.0",
|
|
594
|
+
} as PackageManifest);
|
|
595
|
+
|
|
596
|
+
filterPatchedDependencies.mockReturnValue({
|
|
597
|
+
"@react-pdf/render@4.3.0": "patches/@react-pdf__render@4.3.0.patch",
|
|
598
|
+
});
|
|
599
|
+
|
|
600
|
+
fs.existsSync.mockReturnValue(true);
|
|
601
|
+
|
|
602
|
+
usePackageManager.mockReturnValue({
|
|
603
|
+
name: "pnpm",
|
|
604
|
+
majorVersion: 9,
|
|
605
|
+
version: "9.0.0",
|
|
606
|
+
packageManagerString: "pnpm@9.0.0",
|
|
607
|
+
});
|
|
608
|
+
|
|
609
|
+
/**
|
|
610
|
+
* Fake v9 lockfile: target importer depends on @react-pdf/renderer, which
|
|
611
|
+
* has @react-pdf/render as its only resolved dep.
|
|
612
|
+
*/
|
|
613
|
+
readWantedLockfile_v9.mockResolvedValue({
|
|
614
|
+
lockfileVersion: "9.0",
|
|
615
|
+
importers: {
|
|
616
|
+
"packages/consumer": {
|
|
617
|
+
specifiers: { "@react-pdf/renderer": "^4.0.0" },
|
|
618
|
+
dependencies: { "@react-pdf/renderer": "4.0.0" },
|
|
619
|
+
},
|
|
620
|
+
},
|
|
621
|
+
packages: {
|
|
622
|
+
"@react-pdf/renderer@4.0.0": {
|
|
623
|
+
resolution: { integrity: "sha512-x" },
|
|
624
|
+
dependencies: { "@react-pdf/render": "4.3.0" },
|
|
625
|
+
},
|
|
626
|
+
"@react-pdf/render@4.3.0": {
|
|
627
|
+
resolution: { integrity: "sha512-y" },
|
|
628
|
+
},
|
|
629
|
+
},
|
|
630
|
+
} as unknown as Awaited<ReturnType<typeof readWantedLockfile_v9>>);
|
|
631
|
+
|
|
632
|
+
const result = await copyPatches({
|
|
633
|
+
workspaceRootDir: "/workspace",
|
|
634
|
+
targetPackageDir: "/workspace/packages/consumer",
|
|
635
|
+
internalDepPackageNames: [],
|
|
636
|
+
targetPackageManifest: targetManifest,
|
|
637
|
+
isolateDir: "/workspace/isolate",
|
|
638
|
+
packagesRegistry: {},
|
|
639
|
+
includeDevDependencies: false,
|
|
640
|
+
});
|
|
641
|
+
|
|
642
|
+
expect(result).toEqual({
|
|
643
|
+
"@react-pdf/render@4.3.0": {
|
|
644
|
+
path: "patches/@react-pdf__render@4.3.0.patch",
|
|
645
|
+
hash: "",
|
|
646
|
+
},
|
|
647
|
+
});
|
|
648
|
+
|
|
649
|
+
const filterCall = filterPatchedDependencies.mock.calls[0]?.[0];
|
|
650
|
+
expect(filterCall).toBeDefined();
|
|
651
|
+
const reachable = filterCall!.reachableDependencyNames;
|
|
652
|
+
expect(reachable).toBeInstanceOf(Set);
|
|
653
|
+
expect(reachable!.has("@react-pdf/renderer")).toBe(true);
|
|
654
|
+
expect(reachable!.has("@react-pdf/render")).toBe(true);
|
|
655
|
+
});
|
|
539
656
|
});
|