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.
Files changed (28) hide show
  1. package/dist/index.mjs +1 -1
  2. package/dist/{isolate-DtNAHzfa.mjs → isolate-BRD2AgVJ.mjs} +385 -123
  3. package/dist/isolate-BRD2AgVJ.mjs.map +1 -0
  4. package/dist/isolate-bin.mjs +1 -1
  5. package/package.json +1 -1
  6. package/src/isolate.ts +1 -0
  7. package/src/lib/lockfile/helpers/__fixtures__/internal-deps/workspace/package-lock.json +82 -0
  8. package/src/lib/lockfile/helpers/__fixtures__/internal-deps/workspace/package.json +8 -0
  9. package/src/lib/lockfile/helpers/__fixtures__/internal-deps/workspace/packages/api/package.json +12 -0
  10. package/src/lib/lockfile/helpers/__fixtures__/internal-deps/workspace/packages/shared/package.json +12 -0
  11. package/src/lib/lockfile/helpers/__fixtures__/internal-deps/workspace/packages/utils/package.json +11 -0
  12. package/src/lib/lockfile/helpers/__fixtures__/nested-version-override/workspace/package-lock.json +56 -0
  13. package/src/lib/lockfile/helpers/__fixtures__/nested-version-override/workspace/package.json +8 -0
  14. package/src/lib/lockfile/helpers/__fixtures__/nested-version-override/workspace/packages/api/package.json +11 -0
  15. package/src/lib/lockfile/helpers/__fixtures__/nested-version-override/workspace/packages/other/package.json +11 -0
  16. package/src/lib/lockfile/helpers/generate-npm-lockfile.integration.test.ts +243 -0
  17. package/src/lib/lockfile/helpers/generate-npm-lockfile.test.ts +604 -0
  18. package/src/lib/lockfile/helpers/generate-npm-lockfile.ts +417 -21
  19. package/src/lib/lockfile/process-lockfile.test.ts +4 -0
  20. package/src/lib/lockfile/process-lockfile.ts +14 -16
  21. package/src/lib/patches/copy-patches.test.ts +78 -0
  22. package/src/lib/patches/copy-patches.ts +22 -1
  23. package/src/lib/registry/collect-reachable-package-names.test.ts +239 -0
  24. package/src/lib/registry/collect-reachable-package-names.ts +60 -0
  25. package/src/lib/registry/index.ts +1 -0
  26. package/src/lib/utils/filter-patched-dependencies.test.ts +77 -0
  27. package/src/lib/utils/filter-patched-dependencies.ts +41 -17
  28. 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
+ });