isolate-package 1.31.0 → 1.32.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/{isolate-DtNAHzfa.mjs → isolate-BRD2AgVJ.mjs} +385 -123
- package/dist/isolate-BRD2AgVJ.mjs.map +1 -0
- package/dist/isolate-bin.mjs +1 -1
- package/package.json +1 -1
- package/src/isolate.ts +1 -0
- package/src/lib/lockfile/helpers/__fixtures__/internal-deps/workspace/package-lock.json +82 -0
- package/src/lib/lockfile/helpers/__fixtures__/internal-deps/workspace/package.json +8 -0
- package/src/lib/lockfile/helpers/__fixtures__/internal-deps/workspace/packages/api/package.json +12 -0
- package/src/lib/lockfile/helpers/__fixtures__/internal-deps/workspace/packages/shared/package.json +12 -0
- package/src/lib/lockfile/helpers/__fixtures__/internal-deps/workspace/packages/utils/package.json +11 -0
- package/src/lib/lockfile/helpers/__fixtures__/nested-version-override/workspace/package-lock.json +56 -0
- package/src/lib/lockfile/helpers/__fixtures__/nested-version-override/workspace/package.json +8 -0
- package/src/lib/lockfile/helpers/__fixtures__/nested-version-override/workspace/packages/api/package.json +11 -0
- package/src/lib/lockfile/helpers/__fixtures__/nested-version-override/workspace/packages/other/package.json +11 -0
- package/src/lib/lockfile/helpers/generate-npm-lockfile.integration.test.ts +243 -0
- package/src/lib/lockfile/helpers/generate-npm-lockfile.test.ts +604 -0
- package/src/lib/lockfile/helpers/generate-npm-lockfile.ts +417 -21
- package/src/lib/lockfile/process-lockfile.test.ts +4 -0
- package/src/lib/lockfile/process-lockfile.ts +14 -16
- package/src/lib/patches/copy-patches.test.ts +78 -0
- package/src/lib/patches/copy-patches.ts +22 -1
- package/src/lib/registry/collect-reachable-package-names.test.ts +239 -0
- package/src/lib/registry/collect-reachable-package-names.ts +60 -0
- package/src/lib/registry/index.ts +1 -0
- package/src/lib/utils/filter-patched-dependencies.test.ts +77 -0
- package/src/lib/utils/filter-patched-dependencies.ts +41 -17
- package/dist/isolate-DtNAHzfa.mjs.map +0 -1
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
import fs from "fs-extra";
|
|
2
|
+
import os from "node:os";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { fileURLToPath } from "node:url";
|
|
5
|
+
import { afterEach, beforeEach, describe, expect, it } from "vitest";
|
|
6
|
+
import { generateNpmLockfile } from "./generate-npm-lockfile";
|
|
7
|
+
|
|
8
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
9
|
+
const __dirname = path.dirname(__filename);
|
|
10
|
+
|
|
11
|
+
const FIXTURES_DIR = path.join(__dirname, "__fixtures__");
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Copy a fixture's `workspace/` tree into a fresh tmp directory so that the
|
|
15
|
+
* integration test can run Arborist against real files without polluting the
|
|
16
|
+
* checked-in fixture.
|
|
17
|
+
*/
|
|
18
|
+
async function setupFixture(name: string) {
|
|
19
|
+
const srcWorkspace = path.join(FIXTURES_DIR, name, "workspace");
|
|
20
|
+
const tmpBase = await fs.mkdtemp(
|
|
21
|
+
path.join(os.tmpdir(), `isolate-package-${name}-`),
|
|
22
|
+
);
|
|
23
|
+
const workspaceRoot = path.join(tmpBase, "workspace");
|
|
24
|
+
await fs.copy(srcWorkspace, workspaceRoot);
|
|
25
|
+
return { tmpBase, workspaceRoot };
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
describe("generateNpmLockfile integration", () => {
|
|
29
|
+
let cleanupPaths: string[] = [];
|
|
30
|
+
|
|
31
|
+
beforeEach(() => {
|
|
32
|
+
cleanupPaths = [];
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
afterEach(async () => {
|
|
36
|
+
for (const p of cleanupPaths) {
|
|
37
|
+
await fs.remove(p).catch(() => undefined);
|
|
38
|
+
}
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Reproduction of https://github.com/0x80/isolate-package/issues/111.
|
|
43
|
+
*
|
|
44
|
+
* The fixture is a real npm workspaces monorepo (produced by `npm install`)
|
|
45
|
+
* where the target package `api` pins `semver@^6` while a sibling `other`
|
|
46
|
+
* pins `semver@^7`. npm hoists 7.7.4 to the root `node_modules/semver` and
|
|
47
|
+
* places 6.3.1 at `packages/api/node_modules/semver` as a nested override.
|
|
48
|
+
*
|
|
49
|
+
* When isolating `api`, the isolated lockfile must surface the nested 6.3.1
|
|
50
|
+
* at the isolate's root `node_modules/semver` (with the original resolved
|
|
51
|
+
* and integrity preserved), and must not include the hoisted 7.7.4 that
|
|
52
|
+
* only `other` needs.
|
|
53
|
+
*/
|
|
54
|
+
it("preserves the target's nested dependency version (#111)", async () => {
|
|
55
|
+
const { tmpBase, workspaceRoot } = await setupFixture(
|
|
56
|
+
"nested-version-override",
|
|
57
|
+
);
|
|
58
|
+
cleanupPaths.push(tmpBase);
|
|
59
|
+
|
|
60
|
+
const isolateDir = path.join(workspaceRoot, "packages/api/isolate");
|
|
61
|
+
await fs.ensureDir(isolateDir);
|
|
62
|
+
|
|
63
|
+
const targetManifest = (await fs.readJson(
|
|
64
|
+
path.join(workspaceRoot, "packages/api/package.json"),
|
|
65
|
+
)) as {
|
|
66
|
+
name: string;
|
|
67
|
+
version: string;
|
|
68
|
+
dependencies: Record<string, string>;
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
/** Write the adapted manifest into the isolate dir (no internal deps so no adaptation needed). */
|
|
72
|
+
await fs.writeJson(path.join(isolateDir, "package.json"), targetManifest);
|
|
73
|
+
|
|
74
|
+
await generateNpmLockfile({
|
|
75
|
+
workspaceRootDir: workspaceRoot,
|
|
76
|
+
isolateDir,
|
|
77
|
+
targetPackageName: "api",
|
|
78
|
+
targetPackageManifest: targetManifest,
|
|
79
|
+
packagesRegistry: {},
|
|
80
|
+
internalDepPackageNames: [],
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
const output = (await fs.readJson(
|
|
84
|
+
path.join(isolateDir, "package-lock.json"),
|
|
85
|
+
)) as {
|
|
86
|
+
name: string;
|
|
87
|
+
version: string;
|
|
88
|
+
lockfileVersion: number;
|
|
89
|
+
packages: Record<
|
|
90
|
+
string,
|
|
91
|
+
{ version?: string; resolved?: string; integrity?: string }
|
|
92
|
+
>;
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
/** Top-level metadata reflects the target package, not the monorepo root. */
|
|
96
|
+
expect(output.name).toBe("api");
|
|
97
|
+
expect(output.version).toBe("1.0.0");
|
|
98
|
+
expect(output.lockfileVersion).toBe(3);
|
|
99
|
+
|
|
100
|
+
/** The nested 6.3.1 is surfaced at the isolate's root node_modules. */
|
|
101
|
+
const semverEntry = output.packages["node_modules/semver"];
|
|
102
|
+
expect(semverEntry?.version).toBe("6.3.1");
|
|
103
|
+
expect(semverEntry?.resolved).toBe(
|
|
104
|
+
"https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
|
|
105
|
+
);
|
|
106
|
+
expect(semverEntry?.integrity).toMatch(/^sha512-/);
|
|
107
|
+
|
|
108
|
+
/** The original nested path must not leak into the output. */
|
|
109
|
+
expect(output.packages["packages/api/node_modules/semver"]).toBeUndefined();
|
|
110
|
+
|
|
111
|
+
/** The sibling workspace and its hoisted semver@7 must not appear. */
|
|
112
|
+
expect(output.packages["packages/other"]).toBeUndefined();
|
|
113
|
+
expect(output.packages["node_modules/other"]).toBeUndefined();
|
|
114
|
+
|
|
115
|
+
/** No stray references to the unrelated hoisted 7.7.4. */
|
|
116
|
+
for (const entry of Object.values(output.packages)) {
|
|
117
|
+
if (entry.version === "7.7.4") {
|
|
118
|
+
throw new Error(
|
|
119
|
+
"isolated lockfile unexpectedly contains hoisted semver 7.7.4",
|
|
120
|
+
);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Covers the internal-dep overlay path: when an internal dep's manifest
|
|
127
|
+
* has been adapted (workspace references rewritten to `file:`), that
|
|
128
|
+
* overlay must propagate into the isolated lockfile's entry for that
|
|
129
|
+
* dep — otherwise `npm ci` would try to resolve `utils: "*"` from the
|
|
130
|
+
* registry instead of linking the local `../utils` directory.
|
|
131
|
+
*/
|
|
132
|
+
it("rewrites workspace refs to file: refs in internal dep lockfile entries", async () => {
|
|
133
|
+
const { tmpBase, workspaceRoot } = await setupFixture("internal-deps");
|
|
134
|
+
cleanupPaths.push(tmpBase);
|
|
135
|
+
|
|
136
|
+
const isolateDir = path.join(workspaceRoot, "packages/api/isolate");
|
|
137
|
+
await fs.ensureDir(isolateDir);
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Simulate what adaptInternalPackageManifests + unpackDependencies
|
|
141
|
+
* produce: the isolate dir contains the target manifest at the root
|
|
142
|
+
* and each internal dep's adapted manifest at its rootRelativeDir.
|
|
143
|
+
*/
|
|
144
|
+
await fs.writeJson(path.join(isolateDir, "package.json"), {
|
|
145
|
+
name: "api",
|
|
146
|
+
version: "1.0.0",
|
|
147
|
+
dependencies: {
|
|
148
|
+
shared: "file:./packages/shared",
|
|
149
|
+
semver: "^7.6.0",
|
|
150
|
+
},
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
await fs.ensureDir(path.join(isolateDir, "packages/shared"));
|
|
154
|
+
await fs.writeJson(path.join(isolateDir, "packages/shared/package.json"), {
|
|
155
|
+
name: "shared",
|
|
156
|
+
version: "1.0.0",
|
|
157
|
+
dependencies: {
|
|
158
|
+
utils: "file:../utils",
|
|
159
|
+
ms: "^2.1.3",
|
|
160
|
+
},
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
await fs.ensureDir(path.join(isolateDir, "packages/utils"));
|
|
164
|
+
await fs.writeJson(path.join(isolateDir, "packages/utils/package.json"), {
|
|
165
|
+
name: "utils",
|
|
166
|
+
version: "1.0.0",
|
|
167
|
+
dependencies: {
|
|
168
|
+
debug: "^4.3.0",
|
|
169
|
+
},
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
await generateNpmLockfile({
|
|
173
|
+
workspaceRootDir: workspaceRoot,
|
|
174
|
+
isolateDir,
|
|
175
|
+
targetPackageName: "api",
|
|
176
|
+
targetPackageManifest: {
|
|
177
|
+
name: "api",
|
|
178
|
+
version: "1.0.0",
|
|
179
|
+
dependencies: {
|
|
180
|
+
shared: "file:./packages/shared",
|
|
181
|
+
semver: "^7.6.0",
|
|
182
|
+
},
|
|
183
|
+
},
|
|
184
|
+
packagesRegistry: {
|
|
185
|
+
shared: {
|
|
186
|
+
absoluteDir: path.join(workspaceRoot, "packages/shared"),
|
|
187
|
+
rootRelativeDir: "packages/shared",
|
|
188
|
+
manifest: { name: "shared", version: "1.0.0" },
|
|
189
|
+
},
|
|
190
|
+
utils: {
|
|
191
|
+
absoluteDir: path.join(workspaceRoot, "packages/utils"),
|
|
192
|
+
rootRelativeDir: "packages/utils",
|
|
193
|
+
manifest: { name: "utils", version: "1.0.0" },
|
|
194
|
+
},
|
|
195
|
+
},
|
|
196
|
+
internalDepPackageNames: ["shared", "utils"],
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
const output = (await fs.readJson(
|
|
200
|
+
path.join(isolateDir, "package-lock.json"),
|
|
201
|
+
)) as {
|
|
202
|
+
packages: Record<
|
|
203
|
+
string,
|
|
204
|
+
{
|
|
205
|
+
version?: string;
|
|
206
|
+
dependencies?: Record<string, string>;
|
|
207
|
+
link?: boolean;
|
|
208
|
+
resolved?: string;
|
|
209
|
+
}
|
|
210
|
+
>;
|
|
211
|
+
};
|
|
212
|
+
|
|
213
|
+
/** Internal dep entries are present at their original relative paths. */
|
|
214
|
+
expect(output.packages["packages/shared"]).toBeDefined();
|
|
215
|
+
expect(output.packages["packages/utils"]).toBeDefined();
|
|
216
|
+
|
|
217
|
+
/** The adapted manifest's file: refs are applied to shared's entry. */
|
|
218
|
+
expect(output.packages["packages/shared"]!.dependencies).toEqual({
|
|
219
|
+
utils: "file:../utils",
|
|
220
|
+
ms: "^2.1.3",
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
/** utils keeps its external-only deps unchanged (no cross-internal refs). */
|
|
224
|
+
expect(output.packages["packages/utils"]!.dependencies).toEqual({
|
|
225
|
+
debug: "^4.3.0",
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
/** Link entries for internal deps point at the expected relative paths. */
|
|
229
|
+
expect(output.packages["node_modules/shared"]).toEqual({
|
|
230
|
+
resolved: "packages/shared",
|
|
231
|
+
link: true,
|
|
232
|
+
});
|
|
233
|
+
expect(output.packages["node_modules/utils"]).toEqual({
|
|
234
|
+
resolved: "packages/utils",
|
|
235
|
+
link: true,
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
/** External transitive deps needed by the closure are preserved verbatim. */
|
|
239
|
+
expect(output.packages["node_modules/semver"]?.version).toBe("7.7.4");
|
|
240
|
+
expect(output.packages["node_modules/ms"]?.version).toBe("2.1.3");
|
|
241
|
+
expect(output.packages["node_modules/debug"]?.version).toMatch(/^4\./);
|
|
242
|
+
});
|
|
243
|
+
});
|