isolate-package 1.29.0 → 1.30.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.d.mts +7 -1
- package/dist/index.mjs +2 -3
- package/dist/index.mjs.map +1 -1
- package/dist/{isolate-3GcdAuUK.mjs → isolate-CJy3YyKG.mjs} +261 -64
- package/dist/isolate-CJy3YyKG.mjs.map +1 -0
- package/dist/isolate-bin.mjs +3 -5
- package/dist/isolate-bin.mjs.map +1 -1
- package/package.json +21 -20
- package/src/get-internal-package-names.test.ts +0 -10
- package/src/index.ts +6 -0
- package/src/isolate.ts +38 -8
- package/src/lib/lockfile/helpers/generate-bun-lockfile.test.ts +619 -0
- package/src/lib/lockfile/helpers/generate-bun-lockfile.ts +354 -0
- package/src/lib/lockfile/helpers/generate-pnpm-lockfile.test.ts +387 -0
- package/src/lib/lockfile/helpers/index.ts +1 -0
- package/src/lib/lockfile/helpers/pnpm-map-importer.test.ts +183 -0
- package/src/lib/lockfile/process-lockfile.test.ts +238 -0
- package/src/lib/lockfile/process-lockfile.ts +6 -6
- package/src/lib/manifest/adapt-target-package-manifest.ts +6 -4
- package/src/lib/manifest/helpers/adapt-internal-package-manifests.test.ts +49 -0
- package/src/lib/manifest/helpers/adapt-internal-package-manifests.ts +15 -3
- package/src/lib/manifest/helpers/adopt-pnpm-fields-from-root.test.ts +61 -0
- package/src/lib/manifest/helpers/adopt-pnpm-fields-from-root.ts +42 -3
- package/src/lib/patches/copy-patches.test.ts +49 -11
- package/src/lib/patches/copy-patches.ts +38 -17
- package/src/lib/types.ts +5 -0
- package/src/lib/utils/filter-patched-dependencies.test.ts +1 -11
- package/src/testing/setup.ts +13 -0
- package/dist/isolate-3GcdAuUK.mjs.map +0 -1
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach, afterEach, vi } from "vitest";
|
|
2
|
+
import { processLockfile } from "./process-lockfile";
|
|
3
|
+
import type { IsolateConfigResolved } from "../config";
|
|
4
|
+
|
|
5
|
+
/** Mock the package manager detection */
|
|
6
|
+
vi.mock("~/lib/package-manager", () => ({
|
|
7
|
+
usePackageManager: vi.fn(),
|
|
8
|
+
}));
|
|
9
|
+
|
|
10
|
+
/** Mock all lockfile generators */
|
|
11
|
+
vi.mock("./helpers", () => ({
|
|
12
|
+
generateBunLockfile: vi.fn(),
|
|
13
|
+
generateNpmLockfile: vi.fn(),
|
|
14
|
+
generatePnpmLockfile: vi.fn(),
|
|
15
|
+
generateYarnLockfile: vi.fn(),
|
|
16
|
+
}));
|
|
17
|
+
|
|
18
|
+
const { usePackageManager } = vi.mocked(await import("~/lib/package-manager"));
|
|
19
|
+
const {
|
|
20
|
+
generateBunLockfile,
|
|
21
|
+
generateNpmLockfile,
|
|
22
|
+
generatePnpmLockfile,
|
|
23
|
+
generateYarnLockfile,
|
|
24
|
+
} = vi.mocked(await import("./helpers"));
|
|
25
|
+
|
|
26
|
+
/** Minimal config for testing */
|
|
27
|
+
function createConfig(
|
|
28
|
+
overrides?: Partial<IsolateConfigResolved>,
|
|
29
|
+
): IsolateConfigResolved {
|
|
30
|
+
return {
|
|
31
|
+
buildDirName: "dist",
|
|
32
|
+
forceNpm: false,
|
|
33
|
+
includeDevDependencies: false,
|
|
34
|
+
isolateDirName: "isolate",
|
|
35
|
+
logLevel: "info",
|
|
36
|
+
omitPackageManager: false,
|
|
37
|
+
tsconfigPath: undefined,
|
|
38
|
+
workspacePackages: undefined,
|
|
39
|
+
workspaceRoot: undefined,
|
|
40
|
+
...overrides,
|
|
41
|
+
} as IsolateConfigResolved;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
describe("processLockfile", () => {
|
|
45
|
+
beforeEach(() => {
|
|
46
|
+
vi.clearAllMocks();
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
afterEach(() => {
|
|
50
|
+
vi.restoreAllMocks();
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
it("should route to npm generator for npm package manager", async () => {
|
|
54
|
+
usePackageManager.mockReturnValue({
|
|
55
|
+
name: "npm",
|
|
56
|
+
majorVersion: 10,
|
|
57
|
+
version: "10.0.0",
|
|
58
|
+
packageManagerString: "npm@10.0.0",
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
const result = await processLockfile({
|
|
62
|
+
workspaceRootDir: "/workspace",
|
|
63
|
+
isolateDir: "/workspace/apps/my-app/isolate",
|
|
64
|
+
packagesRegistry: {},
|
|
65
|
+
internalDepPackageNames: [],
|
|
66
|
+
targetPackageDir: "/workspace/apps/my-app",
|
|
67
|
+
targetPackageName: "my-app",
|
|
68
|
+
targetPackageManifest: { name: "my-app", version: "1.0.0" },
|
|
69
|
+
config: createConfig(),
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
expect(generateNpmLockfile).toHaveBeenCalledWith({
|
|
73
|
+
workspaceRootDir: "/workspace",
|
|
74
|
+
isolateDir: "/workspace/apps/my-app/isolate",
|
|
75
|
+
});
|
|
76
|
+
expect(result).toBe(false);
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
it("should route to pnpm generator for pnpm package manager", async () => {
|
|
80
|
+
usePackageManager.mockReturnValue({
|
|
81
|
+
name: "pnpm",
|
|
82
|
+
majorVersion: 9,
|
|
83
|
+
version: "9.0.0",
|
|
84
|
+
packageManagerString: "pnpm@9.0.0",
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
const config = createConfig({ includeDevDependencies: true });
|
|
88
|
+
|
|
89
|
+
await processLockfile({
|
|
90
|
+
workspaceRootDir: "/workspace",
|
|
91
|
+
isolateDir: "/workspace/apps/my-app/isolate",
|
|
92
|
+
packagesRegistry: { shared: {} as never },
|
|
93
|
+
internalDepPackageNames: ["shared"],
|
|
94
|
+
targetPackageDir: "/workspace/apps/my-app",
|
|
95
|
+
targetPackageName: "my-app",
|
|
96
|
+
targetPackageManifest: { name: "my-app", version: "1.0.0" },
|
|
97
|
+
config,
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
expect(generatePnpmLockfile).toHaveBeenCalledWith({
|
|
101
|
+
workspaceRootDir: "/workspace",
|
|
102
|
+
targetPackageDir: "/workspace/apps/my-app",
|
|
103
|
+
isolateDir: "/workspace/apps/my-app/isolate",
|
|
104
|
+
internalDepPackageNames: ["shared"],
|
|
105
|
+
packagesRegistry: { shared: {} as never },
|
|
106
|
+
targetPackageManifest: { name: "my-app", version: "1.0.0" },
|
|
107
|
+
majorVersion: 9,
|
|
108
|
+
includeDevDependencies: true,
|
|
109
|
+
patchedDependencies: undefined,
|
|
110
|
+
});
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
it("should route to yarn generator for yarn v1", async () => {
|
|
114
|
+
usePackageManager.mockReturnValue({
|
|
115
|
+
name: "yarn",
|
|
116
|
+
majorVersion: 1,
|
|
117
|
+
version: "1.22.0",
|
|
118
|
+
packageManagerString: "yarn@1.22.0",
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
const result = await processLockfile({
|
|
122
|
+
workspaceRootDir: "/workspace",
|
|
123
|
+
isolateDir: "/workspace/apps/my-app/isolate",
|
|
124
|
+
packagesRegistry: {},
|
|
125
|
+
internalDepPackageNames: [],
|
|
126
|
+
targetPackageDir: "/workspace/apps/my-app",
|
|
127
|
+
targetPackageName: "my-app",
|
|
128
|
+
targetPackageManifest: { name: "my-app", version: "1.0.0" },
|
|
129
|
+
config: createConfig(),
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
expect(generateYarnLockfile).toHaveBeenCalledWith({
|
|
133
|
+
workspaceRootDir: "/workspace",
|
|
134
|
+
isolateDir: "/workspace/apps/my-app/isolate",
|
|
135
|
+
});
|
|
136
|
+
expect(result).toBe(false);
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
it("should fall back to npm for modern yarn (v2+)", async () => {
|
|
140
|
+
usePackageManager.mockReturnValue({
|
|
141
|
+
name: "yarn",
|
|
142
|
+
majorVersion: 4,
|
|
143
|
+
version: "4.0.0",
|
|
144
|
+
packageManagerString: "yarn@4.0.0",
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
const result = await processLockfile({
|
|
148
|
+
workspaceRootDir: "/workspace",
|
|
149
|
+
isolateDir: "/workspace/apps/my-app/isolate",
|
|
150
|
+
packagesRegistry: {},
|
|
151
|
+
internalDepPackageNames: [],
|
|
152
|
+
targetPackageDir: "/workspace/apps/my-app",
|
|
153
|
+
targetPackageName: "my-app",
|
|
154
|
+
targetPackageManifest: { name: "my-app", version: "1.0.0" },
|
|
155
|
+
config: createConfig(),
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
expect(generateNpmLockfile).toHaveBeenCalled();
|
|
159
|
+
expect(result).toBe(true);
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
it("should route to bun generator for bun package manager", async () => {
|
|
163
|
+
usePackageManager.mockReturnValue({
|
|
164
|
+
name: "bun",
|
|
165
|
+
majorVersion: 1,
|
|
166
|
+
version: "1.2.0",
|
|
167
|
+
packageManagerString: "bun@1.2.0",
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
const result = await processLockfile({
|
|
171
|
+
workspaceRootDir: "/workspace",
|
|
172
|
+
isolateDir: "/workspace/apps/my-app/isolate",
|
|
173
|
+
packagesRegistry: {},
|
|
174
|
+
internalDepPackageNames: ["shared"],
|
|
175
|
+
targetPackageDir: "/workspace/apps/my-app",
|
|
176
|
+
targetPackageName: "my-app",
|
|
177
|
+
targetPackageManifest: { name: "my-app", version: "1.0.0" },
|
|
178
|
+
config: createConfig(),
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
expect(generateBunLockfile).toHaveBeenCalledWith({
|
|
182
|
+
workspaceRootDir: "/workspace",
|
|
183
|
+
targetPackageDir: "/workspace/apps/my-app",
|
|
184
|
+
isolateDir: "/workspace/apps/my-app/isolate",
|
|
185
|
+
internalDepPackageNames: ["shared"],
|
|
186
|
+
packagesRegistry: {},
|
|
187
|
+
includeDevDependencies: false,
|
|
188
|
+
});
|
|
189
|
+
expect(result).toBe(false);
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
it("should use npm when forceNpm is true regardless of package manager", async () => {
|
|
193
|
+
usePackageManager.mockReturnValue({
|
|
194
|
+
name: "pnpm",
|
|
195
|
+
majorVersion: 9,
|
|
196
|
+
version: "9.0.0",
|
|
197
|
+
packageManagerString: "pnpm@9.0.0",
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
const result = await processLockfile({
|
|
201
|
+
workspaceRootDir: "/workspace",
|
|
202
|
+
isolateDir: "/workspace/apps/my-app/isolate",
|
|
203
|
+
packagesRegistry: {},
|
|
204
|
+
internalDepPackageNames: [],
|
|
205
|
+
targetPackageDir: "/workspace/apps/my-app",
|
|
206
|
+
targetPackageName: "my-app",
|
|
207
|
+
targetPackageManifest: { name: "my-app", version: "1.0.0" },
|
|
208
|
+
config: createConfig({ forceNpm: true }),
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
expect(generateNpmLockfile).toHaveBeenCalled();
|
|
212
|
+
expect(generatePnpmLockfile).not.toHaveBeenCalled();
|
|
213
|
+
expect(result).toBe(true);
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
it("should fall back to npm for unknown package manager", async () => {
|
|
217
|
+
usePackageManager.mockReturnValue({
|
|
218
|
+
name: "unknown" as never,
|
|
219
|
+
majorVersion: 1,
|
|
220
|
+
version: "1.0.0",
|
|
221
|
+
packageManagerString: "unknown@1.0.0",
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
const result = await processLockfile({
|
|
225
|
+
workspaceRootDir: "/workspace",
|
|
226
|
+
isolateDir: "/workspace/apps/my-app/isolate",
|
|
227
|
+
packagesRegistry: {},
|
|
228
|
+
internalDepPackageNames: [],
|
|
229
|
+
targetPackageDir: "/workspace/apps/my-app",
|
|
230
|
+
targetPackageName: "my-app",
|
|
231
|
+
targetPackageManifest: { name: "my-app", version: "1.0.0" },
|
|
232
|
+
config: createConfig(),
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
expect(generateNpmLockfile).toHaveBeenCalled();
|
|
236
|
+
expect(result).toBe(true);
|
|
237
|
+
});
|
|
238
|
+
});
|
|
@@ -3,6 +3,7 @@ import { useLogger } from "../logger";
|
|
|
3
3
|
import { usePackageManager } from "../package-manager";
|
|
4
4
|
import type { PackageManifest, PackagesRegistry, PatchFile } from "../types";
|
|
5
5
|
import {
|
|
6
|
+
generateBunLockfile,
|
|
6
7
|
generateNpmLockfile,
|
|
7
8
|
generatePnpmLockfile,
|
|
8
9
|
generateYarnLockfile,
|
|
@@ -97,15 +98,14 @@ export async function processLockfile({
|
|
|
97
98
|
break;
|
|
98
99
|
}
|
|
99
100
|
case "bun": {
|
|
100
|
-
|
|
101
|
-
`Ouput lockfiles for Bun are not yet supported. Using NPM for output`,
|
|
102
|
-
);
|
|
103
|
-
await generateNpmLockfile({
|
|
101
|
+
await generateBunLockfile({
|
|
104
102
|
workspaceRootDir,
|
|
103
|
+
targetPackageDir,
|
|
105
104
|
isolateDir,
|
|
105
|
+
internalDepPackageNames,
|
|
106
|
+
packagesRegistry,
|
|
107
|
+
includeDevDependencies: config.includeDevDependencies,
|
|
106
108
|
});
|
|
107
|
-
|
|
108
|
-
usedFallbackToNpm = true;
|
|
109
109
|
break;
|
|
110
110
|
}
|
|
111
111
|
default:
|
|
@@ -51,11 +51,13 @@ export async function adaptTargetPackageManifest({
|
|
|
51
51
|
};
|
|
52
52
|
|
|
53
53
|
const adaptedManifest =
|
|
54
|
-
packageManager.name === "pnpm" &&
|
|
54
|
+
(packageManager.name === "pnpm" || packageManager.name === "bun") &&
|
|
55
|
+
!forceNpm
|
|
55
56
|
? /**
|
|
56
|
-
* For PNPM the output itself is a workspace so we can preserve
|
|
57
|
-
* with "workspace:*" in the output manifest, but we do
|
|
58
|
-
*
|
|
57
|
+
* For PNPM and Bun the output itself is a workspace so we can preserve
|
|
58
|
+
* the specifiers with "workspace:*" in the output manifest, but we do
|
|
59
|
+
* want to adopt workspace-level fields from the root package.json
|
|
60
|
+
* (pnpm.overrides for PNPM, top-level overrides for Bun).
|
|
59
61
|
*/
|
|
60
62
|
await adoptPnpmFieldsFromRoot(
|
|
61
63
|
manifestWithResolvedCatalogs,
|
|
@@ -95,6 +95,55 @@ describe("adaptInternalPackageManifests", () => {
|
|
|
95
95
|
});
|
|
96
96
|
});
|
|
97
97
|
|
|
98
|
+
it("should strip the prepare script from internal dependency manifests", async () => {
|
|
99
|
+
const manifest: PackageManifest = {
|
|
100
|
+
name: "@repo/common",
|
|
101
|
+
version: "1.0.0",
|
|
102
|
+
scripts: {
|
|
103
|
+
prepare: "npm run clean && npm run build",
|
|
104
|
+
clean: "del-cli dist",
|
|
105
|
+
build: "tsdown",
|
|
106
|
+
postinstall: "prisma generate",
|
|
107
|
+
},
|
|
108
|
+
dependencies: {
|
|
109
|
+
ky: "^1.0.0",
|
|
110
|
+
},
|
|
111
|
+
devDependencies: {
|
|
112
|
+
"del-cli": "^7.0.0",
|
|
113
|
+
tsdown: "^0.20.0",
|
|
114
|
+
},
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
const packagesRegistry = createRegistry({
|
|
118
|
+
"@repo/common": {
|
|
119
|
+
rootRelativeDir: "packages/common",
|
|
120
|
+
manifest,
|
|
121
|
+
},
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
await adaptInternalPackageManifests({
|
|
125
|
+
internalPackageNames: ["@repo/common"],
|
|
126
|
+
packagesRegistry,
|
|
127
|
+
isolateDir: "/output",
|
|
128
|
+
forceNpm: false,
|
|
129
|
+
workspaceRootDir: "/workspace",
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
expect(writeManifest).toHaveBeenCalledOnce();
|
|
133
|
+
|
|
134
|
+
const writtenManifest = writeManifest.mock.calls[0]![1];
|
|
135
|
+
|
|
136
|
+
/** prepare should be stripped because it depends on devDependency binaries */
|
|
137
|
+
expect(writtenManifest.scripts?.prepare).toBeUndefined();
|
|
138
|
+
|
|
139
|
+
/** Other scripts should be preserved */
|
|
140
|
+
expect(writtenManifest.scripts).toEqual({
|
|
141
|
+
clean: "del-cli dist",
|
|
142
|
+
build: "tsdown",
|
|
143
|
+
postinstall: "prisma generate",
|
|
144
|
+
});
|
|
145
|
+
});
|
|
146
|
+
|
|
98
147
|
it("should strip devDependencies from internal dependency manifests", async () => {
|
|
99
148
|
const manifest: PackageManifest = {
|
|
100
149
|
name: "@repo/shared",
|
|
@@ -34,6 +34,17 @@ export async function adaptInternalPackageManifests({
|
|
|
34
34
|
/** Dev dependencies are never included for internal deps */
|
|
35
35
|
const strippedManifest = omit(manifest, ["devDependencies"]);
|
|
36
36
|
|
|
37
|
+
/**
|
|
38
|
+
* Strip the `prepare` script because it runs during `pnpm install` and
|
|
39
|
+
* typically depends on devDependency binaries (e.g. tsdown, del-cli)
|
|
40
|
+
* which are not available in the isolated output. Other lifecycle
|
|
41
|
+
* scripts like `postinstall` are preserved because they handle runtime
|
|
42
|
+
* setup (e.g. Prisma client generation).
|
|
43
|
+
*/
|
|
44
|
+
if (strippedManifest.scripts) {
|
|
45
|
+
strippedManifest.scripts = omit(strippedManifest.scripts, ["prepare"]);
|
|
46
|
+
}
|
|
47
|
+
|
|
37
48
|
/** Resolve catalog dependencies before adapting internal deps */
|
|
38
49
|
const manifestWithResolvedCatalogs = {
|
|
39
50
|
...strippedManifest,
|
|
@@ -44,10 +55,11 @@ export async function adaptInternalPackageManifests({
|
|
|
44
55
|
};
|
|
45
56
|
|
|
46
57
|
const outputManifest =
|
|
47
|
-
packageManager.name === "pnpm" &&
|
|
58
|
+
(packageManager.name === "pnpm" || packageManager.name === "bun") &&
|
|
59
|
+
!forceNpm
|
|
48
60
|
? /**
|
|
49
|
-
* For PNPM the output itself is a workspace so we can preserve
|
|
50
|
-
* with "workspace:*" in the output manifest.
|
|
61
|
+
* For PNPM and Bun the output itself is a workspace so we can preserve
|
|
62
|
+
* the specifiers with "workspace:*" in the output manifest.
|
|
51
63
|
*/
|
|
52
64
|
manifestWithResolvedCatalogs
|
|
53
65
|
: /** For other package managers we replace the links to internal dependencies */
|
|
@@ -12,10 +12,16 @@ vi.mock("~/lib/utils", () => ({
|
|
|
12
12
|
readTypedJson: vi.fn(),
|
|
13
13
|
}));
|
|
14
14
|
|
|
15
|
+
vi.mock("~/lib/package-manager", () => ({
|
|
16
|
+
usePackageManager: vi.fn(() => ({ name: "pnpm", majorVersion: 9 })),
|
|
17
|
+
}));
|
|
18
|
+
|
|
15
19
|
const { isRushWorkspace, readTypedJson } = vi.mocked(
|
|
16
20
|
await import("~/lib/utils"),
|
|
17
21
|
);
|
|
18
22
|
|
|
23
|
+
const { usePackageManager } = vi.mocked(await import("~/lib/package-manager"));
|
|
24
|
+
|
|
19
25
|
describe("adoptPnpmFieldsFromRoot", () => {
|
|
20
26
|
beforeEach(() => {
|
|
21
27
|
vi.clearAllMocks();
|
|
@@ -209,4 +215,59 @@ describe("adoptPnpmFieldsFromRoot", () => {
|
|
|
209
215
|
},
|
|
210
216
|
});
|
|
211
217
|
});
|
|
218
|
+
|
|
219
|
+
it("should adopt top-level overrides for Bun", async () => {
|
|
220
|
+
usePackageManager.mockReturnValue({
|
|
221
|
+
name: "bun",
|
|
222
|
+
majorVersion: 1,
|
|
223
|
+
version: "1.0.0",
|
|
224
|
+
packageManagerString: "bun@1.0.0",
|
|
225
|
+
} as ReturnType<typeof usePackageManager>);
|
|
226
|
+
isRushWorkspace.mockReturnValue(false);
|
|
227
|
+
readTypedJson.mockResolvedValue({
|
|
228
|
+
name: "root",
|
|
229
|
+
version: "1.0.0",
|
|
230
|
+
overrides: {
|
|
231
|
+
foo: "^1.0.0",
|
|
232
|
+
},
|
|
233
|
+
} as unknown as ProjectManifest);
|
|
234
|
+
|
|
235
|
+
const targetManifest: PackageManifest = {
|
|
236
|
+
name: "test-package",
|
|
237
|
+
version: "1.0.0",
|
|
238
|
+
};
|
|
239
|
+
|
|
240
|
+
const result = await adoptPnpmFieldsFromRoot(targetManifest, "/workspace");
|
|
241
|
+
|
|
242
|
+
expect(result).toEqual({
|
|
243
|
+
name: "test-package",
|
|
244
|
+
version: "1.0.0",
|
|
245
|
+
overrides: {
|
|
246
|
+
foo: "^1.0.0",
|
|
247
|
+
},
|
|
248
|
+
});
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
it("should return original manifest for Bun when no overrides are present", async () => {
|
|
252
|
+
usePackageManager.mockReturnValue({
|
|
253
|
+
name: "bun",
|
|
254
|
+
majorVersion: 1,
|
|
255
|
+
version: "1.0.0",
|
|
256
|
+
packageManagerString: "bun@1.0.0",
|
|
257
|
+
} as ReturnType<typeof usePackageManager>);
|
|
258
|
+
isRushWorkspace.mockReturnValue(false);
|
|
259
|
+
readTypedJson.mockResolvedValue({
|
|
260
|
+
name: "root",
|
|
261
|
+
version: "1.0.0",
|
|
262
|
+
} as ProjectManifest);
|
|
263
|
+
|
|
264
|
+
const targetManifest: PackageManifest = {
|
|
265
|
+
name: "test-package",
|
|
266
|
+
version: "1.0.0",
|
|
267
|
+
};
|
|
268
|
+
|
|
269
|
+
const result = await adoptPnpmFieldsFromRoot(targetManifest, "/workspace");
|
|
270
|
+
|
|
271
|
+
expect(result).toEqual(targetManifest);
|
|
272
|
+
});
|
|
212
273
|
});
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
import type { ProjectManifest, PnpmSettings } from "@pnpm/types";
|
|
2
2
|
import path from "path";
|
|
3
|
+
import { usePackageManager } from "~/lib/package-manager";
|
|
3
4
|
import type { PackageManifest } from "~/lib/types";
|
|
4
5
|
import { isRushWorkspace, readTypedJson } from "~/lib/utils";
|
|
5
6
|
|
|
6
7
|
/**
|
|
7
|
-
* Adopts
|
|
8
|
-
* overrides, onlyBuiltDependencies, and ignoredBuiltDependencies
|
|
9
|
-
*
|
|
8
|
+
* Adopts workspace-level fields from the root package manifest. For pnpm this
|
|
9
|
+
* reads overrides, onlyBuiltDependencies, and ignoredBuiltDependencies from the
|
|
10
|
+
* `pnpm` key. For Bun it reads `overrides` from the top level.
|
|
10
11
|
*/
|
|
11
12
|
export async function adoptPnpmFieldsFromRoot(
|
|
12
13
|
targetPackageManifest: PackageManifest,
|
|
@@ -20,6 +21,44 @@ export async function adoptPnpmFieldsFromRoot(
|
|
|
20
21
|
path.join(workspaceRootDir, "package.json"),
|
|
21
22
|
);
|
|
22
23
|
|
|
24
|
+
const packageManager = usePackageManager();
|
|
25
|
+
|
|
26
|
+
if (packageManager.name === "bun") {
|
|
27
|
+
return adoptBunFieldsFromRoot(targetPackageManifest, rootPackageManifest);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
return adoptPnpmFieldsOnly(targetPackageManifest, rootPackageManifest);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** Adopt Bun's top-level overrides from the root manifest */
|
|
34
|
+
function adoptBunFieldsFromRoot(
|
|
35
|
+
targetPackageManifest: PackageManifest,
|
|
36
|
+
rootPackageManifest: ProjectManifest,
|
|
37
|
+
): PackageManifest {
|
|
38
|
+
/**
|
|
39
|
+
* Bun supports `overrides` at the top level of package.json (same as npm).
|
|
40
|
+
* Read from the root manifest and set them on the output manifest so that
|
|
41
|
+
* `bun install --frozen-lockfile` succeeds.
|
|
42
|
+
*/
|
|
43
|
+
const overrides = (rootPackageManifest as Record<string, unknown>)[
|
|
44
|
+
"overrides"
|
|
45
|
+
] as Record<string, string> | undefined;
|
|
46
|
+
|
|
47
|
+
if (!overrides) {
|
|
48
|
+
return targetPackageManifest;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return {
|
|
52
|
+
...targetPackageManifest,
|
|
53
|
+
overrides,
|
|
54
|
+
} as PackageManifest;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/** Adopt pnpm-specific fields from the root manifest */
|
|
58
|
+
function adoptPnpmFieldsOnly(
|
|
59
|
+
targetPackageManifest: PackageManifest,
|
|
60
|
+
rootPackageManifest: ProjectManifest,
|
|
61
|
+
): PackageManifest {
|
|
23
62
|
const { overrides, onlyBuiltDependencies, ignoredBuiltDependencies } =
|
|
24
63
|
rootPackageManifest.pnpm || {};
|
|
25
64
|
|
|
@@ -23,7 +23,7 @@ vi.mock("~/lib/utils", () => ({
|
|
|
23
23
|
|
|
24
24
|
/** Mock the package manager */
|
|
25
25
|
vi.mock("~/lib/package-manager", () => ({
|
|
26
|
-
usePackageManager: vi.fn(() => ({ majorVersion: 9 })),
|
|
26
|
+
usePackageManager: vi.fn(() => ({ name: "pnpm", majorVersion: 9 })),
|
|
27
27
|
}));
|
|
28
28
|
|
|
29
29
|
/** Mock the pnpm lockfile readers */
|
|
@@ -35,19 +35,10 @@ vi.mock("pnpm_lockfile_file_v9", () => ({
|
|
|
35
35
|
readWantedLockfile: vi.fn(() => Promise.resolve(null)),
|
|
36
36
|
}));
|
|
37
37
|
|
|
38
|
-
/** Mock the logger */
|
|
39
|
-
vi.mock("~/lib/logger", () => ({
|
|
40
|
-
useLogger: () => ({
|
|
41
|
-
debug: vi.fn(),
|
|
42
|
-
info: vi.fn(),
|
|
43
|
-
warn: vi.fn(),
|
|
44
|
-
error: vi.fn(),
|
|
45
|
-
}),
|
|
46
|
-
}));
|
|
47
|
-
|
|
48
38
|
const fs = vi.mocked((await import("fs-extra")).default);
|
|
49
39
|
const { filterPatchedDependencies, readTypedJson, readTypedYamlSync } =
|
|
50
40
|
vi.mocked(await import("~/lib/utils"));
|
|
41
|
+
const { usePackageManager } = vi.mocked(await import("~/lib/package-manager"));
|
|
51
42
|
|
|
52
43
|
describe("copyPatches", () => {
|
|
53
44
|
beforeEach(() => {
|
|
@@ -420,4 +411,51 @@ describe("copyPatches", () => {
|
|
|
420
411
|
expect(fs.copy).toHaveBeenCalledTimes(2);
|
|
421
412
|
});
|
|
422
413
|
});
|
|
414
|
+
|
|
415
|
+
it("should read top-level patchedDependencies for Bun projects", async () => {
|
|
416
|
+
usePackageManager.mockReturnValue({
|
|
417
|
+
name: "bun",
|
|
418
|
+
majorVersion: 1,
|
|
419
|
+
version: "1.2.0",
|
|
420
|
+
packageManagerString: "bun@1.2.0",
|
|
421
|
+
});
|
|
422
|
+
|
|
423
|
+
const targetManifest: PackageManifest = {
|
|
424
|
+
name: "test",
|
|
425
|
+
version: "1.0.0",
|
|
426
|
+
dependencies: { lodash: "^4.0.0" },
|
|
427
|
+
};
|
|
428
|
+
|
|
429
|
+
/** No patches in pnpm-workspace.yaml, so it falls back to package.json */
|
|
430
|
+
readTypedYamlSync.mockReturnValue({});
|
|
431
|
+
|
|
432
|
+
readTypedJson.mockResolvedValue({
|
|
433
|
+
name: "root",
|
|
434
|
+
version: "1.0.0",
|
|
435
|
+
patchedDependencies: {
|
|
436
|
+
"lodash@4.17.21": "patches/lodash.patch",
|
|
437
|
+
},
|
|
438
|
+
} as PackageManifest);
|
|
439
|
+
|
|
440
|
+
filterPatchedDependencies.mockReturnValue({
|
|
441
|
+
"lodash@4.17.21": "patches/lodash.patch",
|
|
442
|
+
});
|
|
443
|
+
|
|
444
|
+
fs.existsSync.mockReturnValue(true);
|
|
445
|
+
|
|
446
|
+
const result = await copyPatches({
|
|
447
|
+
workspaceRootDir: "/workspace",
|
|
448
|
+
targetPackageManifest: targetManifest,
|
|
449
|
+
isolateDir: "/workspace/isolate",
|
|
450
|
+
includeDevDependencies: false,
|
|
451
|
+
});
|
|
452
|
+
|
|
453
|
+
expect(result).toEqual({
|
|
454
|
+
"lodash@4.17.21": { path: "patches/lodash.patch", hash: "" },
|
|
455
|
+
});
|
|
456
|
+
expect(fs.copy).toHaveBeenCalledWith(
|
|
457
|
+
"/workspace/patches/lodash.patch",
|
|
458
|
+
"/workspace/isolate/patches/lodash.patch",
|
|
459
|
+
);
|
|
460
|
+
});
|
|
423
461
|
});
|
|
@@ -26,30 +26,46 @@ export async function copyPatches({
|
|
|
26
26
|
}): Promise<Record<string, PatchFile>> {
|
|
27
27
|
const log = useLogger();
|
|
28
28
|
|
|
29
|
+
const { name: packageManagerName } = usePackageManager();
|
|
30
|
+
|
|
29
31
|
let patchedDependencies: Record<string, string> | undefined;
|
|
30
32
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
33
|
+
/**
|
|
34
|
+
* Only try reading pnpm-workspace.yaml for pnpm workspaces. Bun workspaces
|
|
35
|
+
* don't have this file and the warning would be noisy.
|
|
36
|
+
*/
|
|
37
|
+
if (packageManagerName === "pnpm") {
|
|
38
|
+
try {
|
|
39
|
+
const pnpmSettings = readTypedYamlSync<PnpmSettings>(
|
|
40
|
+
path.join(workspaceRootDir, "pnpm-workspace.yaml"),
|
|
41
|
+
);
|
|
42
|
+
patchedDependencies = pnpmSettings?.patchedDependencies;
|
|
43
|
+
} catch (error) {
|
|
44
|
+
log.warn(
|
|
45
|
+
`Could not read pnpm-workspace.yaml: ${error instanceof Error ? error.message : String(error)}`,
|
|
46
|
+
);
|
|
47
|
+
}
|
|
41
48
|
}
|
|
42
49
|
|
|
43
50
|
if (!patchedDependencies || Object.keys(patchedDependencies).length === 0) {
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
51
|
+
if (packageManagerName === "pnpm") {
|
|
52
|
+
log.debug(
|
|
53
|
+
"No patched dependencies found in pnpm-workspace.yaml; Falling back to workspace root package.json",
|
|
54
|
+
);
|
|
55
|
+
} else {
|
|
56
|
+
log.debug(
|
|
57
|
+
"Reading patched dependencies from workspace root package.json",
|
|
58
|
+
);
|
|
59
|
+
}
|
|
47
60
|
|
|
48
61
|
try {
|
|
49
62
|
const workspaceRootManifest = await readTypedJson<PackageManifest>(
|
|
50
63
|
path.join(workspaceRootDir, "package.json"),
|
|
51
64
|
);
|
|
52
|
-
|
|
65
|
+
/** PNPM stores patches under pnpm.patchedDependencies, Bun at the top level */
|
|
66
|
+
patchedDependencies =
|
|
67
|
+
workspaceRootManifest?.pnpm?.patchedDependencies ??
|
|
68
|
+
workspaceRootManifest?.patchedDependencies;
|
|
53
69
|
} catch (error) {
|
|
54
70
|
log.warn(
|
|
55
71
|
`Could not read workspace root package.json: ${error instanceof Error ? error.message : String(error)}`,
|
|
@@ -76,9 +92,14 @@ export async function copyPatches({
|
|
|
76
92
|
return {};
|
|
77
93
|
}
|
|
78
94
|
|
|
79
|
-
/**
|
|
95
|
+
/**
|
|
96
|
+
* Read the pnpm lockfile to get patch hashes. Bun doesn't store hashes in
|
|
97
|
+
* its lockfile so we skip this for Bun.
|
|
98
|
+
*/
|
|
80
99
|
const lockfilePatchedDependencies =
|
|
81
|
-
|
|
100
|
+
packageManagerName === "pnpm"
|
|
101
|
+
? await readLockfilePatchedDependencies(workspaceRootDir)
|
|
102
|
+
: undefined;
|
|
82
103
|
|
|
83
104
|
const copiedPatches: Record<string, PatchFile> = {};
|
|
84
105
|
|
|
@@ -102,7 +123,7 @@ export async function copyPatches({
|
|
|
102
123
|
const originalPatchFile = lockfilePatchedDependencies?.[packageSpec];
|
|
103
124
|
const hash = originalPatchFile?.hash ?? "";
|
|
104
125
|
|
|
105
|
-
if (!hash) {
|
|
126
|
+
if (packageManagerName === "pnpm" && !hash) {
|
|
106
127
|
log.warn(`No hash found for patch ${packageSpec} in lockfile`);
|
|
107
128
|
}
|
|
108
129
|
|