isolate-package 1.28.2 → 1.29.0-1
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-B0zQOSqE.mjs → isolate-CqPWp-1k.mjs} +211 -19
- package/dist/isolate-CqPWp-1k.mjs.map +1 -0
- package/dist/isolate-bin.mjs +1 -1
- package/package.json +1 -1
- package/src/get-internal-package-names.test.ts +0 -10
- package/src/isolate.ts +25 -3
- package/src/lib/lockfile/helpers/generate-bun-lockfile.test.ts +619 -0
- package/src/lib/lockfile/helpers/generate-bun-lockfile.ts +340 -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 +5 -4
- package/src/lib/manifest/helpers/adapt-internal-package-manifests.ts +4 -3
- package/src/lib/manifest/validate-manifest.test.ts +10 -19
- package/src/lib/manifest/validate-manifest.ts +1 -13
- package/src/lib/patches/copy-patches.test.ts +46 -11
- package/src/lib/patches/copy-patches.ts +13 -4
- package/src/lib/types.ts +3 -0
- package/src/lib/utils/filter-patched-dependencies.test.ts +1 -11
- package/src/testing/setup.ts +13 -0
- package/dist/isolate-B0zQOSqE.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,12 @@ 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
|
-
* pnpm.overrides field from the root package.json.
|
|
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 the pnpm.overrides field from the root package.json.
|
|
59
60
|
*/
|
|
60
61
|
await adoptPnpmFieldsFromRoot(
|
|
61
62
|
manifestWithResolvedCatalogs,
|
|
@@ -44,10 +44,11 @@ export async function adaptInternalPackageManifests({
|
|
|
44
44
|
};
|
|
45
45
|
|
|
46
46
|
const outputManifest =
|
|
47
|
-
packageManager.name === "pnpm" &&
|
|
47
|
+
(packageManager.name === "pnpm" || packageManager.name === "bun") &&
|
|
48
|
+
!forceNpm
|
|
48
49
|
? /**
|
|
49
|
-
* For PNPM the output itself is a workspace so we can preserve
|
|
50
|
-
* with "workspace:*" in the output manifest.
|
|
50
|
+
* For PNPM and Bun the output itself is a workspace so we can preserve
|
|
51
|
+
* the specifiers with "workspace:*" in the output manifest.
|
|
51
52
|
*/
|
|
52
53
|
manifestWithResolvedCatalogs
|
|
53
54
|
: /** For other package managers we replace the links to internal dependencies */
|
|
@@ -43,7 +43,7 @@ describe("validateManifestMandatoryFields", () => {
|
|
|
43
43
|
|
|
44
44
|
expect(() =>
|
|
45
45
|
validateManifestMandatoryFields(invalidDevManifest, packagePath, false),
|
|
46
|
-
).toThrow(/missing
|
|
46
|
+
).toThrow(/missing mandatory fields: version/);
|
|
47
47
|
});
|
|
48
48
|
|
|
49
49
|
it("should throw error when version field is missing", () => {
|
|
@@ -54,7 +54,7 @@ describe("validateManifestMandatoryFields", () => {
|
|
|
54
54
|
|
|
55
55
|
expect(() =>
|
|
56
56
|
validateManifestMandatoryFields(invalidManifest, packagePath),
|
|
57
|
-
).toThrow(/missing
|
|
57
|
+
).toThrow(/missing mandatory fields: version/);
|
|
58
58
|
});
|
|
59
59
|
|
|
60
60
|
it("should throw error when files field is missing", () => {
|
|
@@ -65,7 +65,7 @@ describe("validateManifestMandatoryFields", () => {
|
|
|
65
65
|
|
|
66
66
|
expect(() =>
|
|
67
67
|
validateManifestMandatoryFields(invalidManifest, packagePath),
|
|
68
|
-
).toThrow(/missing
|
|
68
|
+
).toThrow(/missing mandatory fields: files/);
|
|
69
69
|
});
|
|
70
70
|
|
|
71
71
|
it("should throw error when files field is empty array", () => {
|
|
@@ -77,7 +77,7 @@ describe("validateManifestMandatoryFields", () => {
|
|
|
77
77
|
|
|
78
78
|
expect(() =>
|
|
79
79
|
validateManifestMandatoryFields(invalidManifest, packagePath),
|
|
80
|
-
).toThrow(/missing
|
|
80
|
+
).toThrow(/missing mandatory fields: files/);
|
|
81
81
|
});
|
|
82
82
|
|
|
83
83
|
it("should throw error when files field is not an array", () => {
|
|
@@ -89,7 +89,7 @@ describe("validateManifestMandatoryFields", () => {
|
|
|
89
89
|
|
|
90
90
|
expect(() =>
|
|
91
91
|
validateManifestMandatoryFields(invalidManifest, packagePath),
|
|
92
|
-
).toThrow(/missing
|
|
92
|
+
).toThrow(/missing mandatory fields: files/);
|
|
93
93
|
});
|
|
94
94
|
|
|
95
95
|
it("should throw error when both fields are missing", () => {
|
|
@@ -99,29 +99,20 @@ describe("validateManifestMandatoryFields", () => {
|
|
|
99
99
|
|
|
100
100
|
expect(() =>
|
|
101
101
|
validateManifestMandatoryFields(invalidManifest, packagePath),
|
|
102
|
-
).toThrow(/missing mandatory fields
|
|
102
|
+
).toThrow(/missing mandatory fields: version, files/);
|
|
103
103
|
});
|
|
104
104
|
|
|
105
|
-
it("should include
|
|
105
|
+
it("should include helpful error message", () => {
|
|
106
106
|
const invalidManifest = {
|
|
107
107
|
name: "test-package",
|
|
108
108
|
} as PackageManifest;
|
|
109
109
|
|
|
110
110
|
expect(() =>
|
|
111
111
|
validateManifestMandatoryFields(invalidManifest, packagePath),
|
|
112
|
-
).toThrow(
|
|
113
|
-
/See https:\/\/isolate-package\.codecompose\.dev\/getting-started#prerequisites/,
|
|
114
|
-
);
|
|
115
|
-
|
|
116
|
-
const singleFieldManifest = {
|
|
117
|
-
name: "test-package",
|
|
118
|
-
version: "1.0.0",
|
|
119
|
-
} as PackageManifest;
|
|
112
|
+
).toThrow(/missing mandatory fields: version, files/);
|
|
120
113
|
|
|
121
114
|
expect(() =>
|
|
122
|
-
validateManifestMandatoryFields(
|
|
123
|
-
).toThrow(
|
|
124
|
-
/See https:\/\/isolate-package\.codecompose\.dev\/getting-started#define-files-field-in-each-package-manifest/,
|
|
125
|
-
);
|
|
115
|
+
validateManifestMandatoryFields(invalidManifest, packagePath),
|
|
116
|
+
).toThrow(/See the documentation for more details/);
|
|
126
117
|
});
|
|
127
118
|
});
|
|
@@ -1,14 +1,6 @@
|
|
|
1
1
|
import { useLogger } from "../logger";
|
|
2
2
|
import type { PackageManifest } from "../types";
|
|
3
3
|
|
|
4
|
-
/** Maps field names to their documentation URLs */
|
|
5
|
-
const fieldDocUrls: Record<string, string> = {
|
|
6
|
-
version:
|
|
7
|
-
"https://isolate-package.codecompose.dev/getting-started#define-version-field-in-each-package-manifest",
|
|
8
|
-
files:
|
|
9
|
-
"https://isolate-package.codecompose.dev/getting-started#define-files-field-in-each-package-manifest",
|
|
10
|
-
};
|
|
11
|
-
|
|
12
4
|
/**
|
|
13
5
|
* Validate that mandatory fields are present in the package manifest. These
|
|
14
6
|
* fields are required for the isolate process to work properly.
|
|
@@ -46,11 +38,7 @@ export function validateManifestMandatoryFields(
|
|
|
46
38
|
}
|
|
47
39
|
|
|
48
40
|
if (missingFields.length > 0) {
|
|
49
|
-
const
|
|
50
|
-
const errorMessage =
|
|
51
|
-
missingFields.length === 1
|
|
52
|
-
? `Package at ${packagePath} is missing the "${field}" field in its package.json. See ${fieldDocUrls[field] ?? "https://isolate-package.codecompose.dev/getting-started#prerequisites"}`
|
|
53
|
-
: `Package at ${packagePath} is missing mandatory fields in its package.json: ${missingFields.join(", ")}. See https://isolate-package.codecompose.dev/getting-started#prerequisites`;
|
|
41
|
+
const errorMessage = `Package at ${packagePath} is missing mandatory fields: ${missingFields.join(", ")}. See the documentation for more details.`;
|
|
54
42
|
|
|
55
43
|
log.error(errorMessage);
|
|
56
44
|
throw new Error(errorMessage);
|
|
@@ -22,7 +22,7 @@ vi.mock("~/lib/utils", () => ({
|
|
|
22
22
|
|
|
23
23
|
/** Mock the package manager */
|
|
24
24
|
vi.mock("~/lib/package-manager", () => ({
|
|
25
|
-
usePackageManager: vi.fn(() => ({ majorVersion: 9 })),
|
|
25
|
+
usePackageManager: vi.fn(() => ({ name: "pnpm", majorVersion: 9 })),
|
|
26
26
|
}));
|
|
27
27
|
|
|
28
28
|
/** Mock the pnpm lockfile readers */
|
|
@@ -34,20 +34,11 @@ vi.mock("pnpm_lockfile_file_v9", () => ({
|
|
|
34
34
|
readWantedLockfile: vi.fn(() => Promise.resolve(null)),
|
|
35
35
|
}));
|
|
36
36
|
|
|
37
|
-
/** Mock the logger */
|
|
38
|
-
vi.mock("~/lib/logger", () => ({
|
|
39
|
-
useLogger: () => ({
|
|
40
|
-
debug: vi.fn(),
|
|
41
|
-
info: vi.fn(),
|
|
42
|
-
warn: vi.fn(),
|
|
43
|
-
error: vi.fn(),
|
|
44
|
-
}),
|
|
45
|
-
}));
|
|
46
|
-
|
|
47
37
|
const fs = vi.mocked((await import("fs-extra")).default);
|
|
48
38
|
const { filterPatchedDependencies, readTypedJson } = vi.mocked(
|
|
49
39
|
await import("~/lib/utils"),
|
|
50
40
|
);
|
|
41
|
+
const { usePackageManager } = vi.mocked(await import("~/lib/package-manager"));
|
|
51
42
|
|
|
52
43
|
describe("copyPatches", () => {
|
|
53
44
|
beforeEach(() => {
|
|
@@ -395,4 +386,48 @@ describe("copyPatches", () => {
|
|
|
395
386
|
});
|
|
396
387
|
expect(fs.copy).toHaveBeenCalledTimes(2);
|
|
397
388
|
});
|
|
389
|
+
|
|
390
|
+
it("should read top-level patchedDependencies for Bun projects", async () => {
|
|
391
|
+
usePackageManager.mockReturnValue({
|
|
392
|
+
name: "bun",
|
|
393
|
+
majorVersion: 1,
|
|
394
|
+
version: "1.2.0",
|
|
395
|
+
packageManagerString: "bun@1.2.0",
|
|
396
|
+
});
|
|
397
|
+
|
|
398
|
+
const targetManifest: PackageManifest = {
|
|
399
|
+
name: "test",
|
|
400
|
+
version: "1.0.0",
|
|
401
|
+
dependencies: { lodash: "^4.0.0" },
|
|
402
|
+
};
|
|
403
|
+
|
|
404
|
+
readTypedJson.mockResolvedValue({
|
|
405
|
+
name: "root",
|
|
406
|
+
version: "1.0.0",
|
|
407
|
+
patchedDependencies: {
|
|
408
|
+
"lodash@4.17.21": "patches/lodash.patch",
|
|
409
|
+
},
|
|
410
|
+
} as PackageManifest);
|
|
411
|
+
|
|
412
|
+
filterPatchedDependencies.mockReturnValue({
|
|
413
|
+
"lodash@4.17.21": "patches/lodash.patch",
|
|
414
|
+
});
|
|
415
|
+
|
|
416
|
+
fs.existsSync.mockReturnValue(true);
|
|
417
|
+
|
|
418
|
+
const result = await copyPatches({
|
|
419
|
+
workspaceRootDir: "/workspace",
|
|
420
|
+
targetPackageManifest: targetManifest,
|
|
421
|
+
isolateDir: "/workspace/isolate",
|
|
422
|
+
includeDevDependencies: false,
|
|
423
|
+
});
|
|
424
|
+
|
|
425
|
+
expect(result).toEqual({
|
|
426
|
+
"lodash@4.17.21": { path: "patches/lodash.patch", hash: "" },
|
|
427
|
+
});
|
|
428
|
+
expect(fs.copy).toHaveBeenCalledWith(
|
|
429
|
+
"/workspace/patches/lodash.patch",
|
|
430
|
+
"/workspace/isolate/patches/lodash.patch",
|
|
431
|
+
);
|
|
432
|
+
});
|
|
398
433
|
});
|
|
@@ -37,7 +37,10 @@ export async function copyPatches({
|
|
|
37
37
|
return {};
|
|
38
38
|
}
|
|
39
39
|
|
|
40
|
-
|
|
40
|
+
/** PNPM stores patches under pnpm.patchedDependencies, Bun at the top level */
|
|
41
|
+
const patchedDependencies =
|
|
42
|
+
workspaceRootManifest.pnpm?.patchedDependencies ??
|
|
43
|
+
workspaceRootManifest.patchedDependencies;
|
|
41
44
|
|
|
42
45
|
if (!patchedDependencies || Object.keys(patchedDependencies).length === 0) {
|
|
43
46
|
log.debug("No patched dependencies found in workspace root package.json");
|
|
@@ -58,9 +61,15 @@ export async function copyPatches({
|
|
|
58
61
|
return {};
|
|
59
62
|
}
|
|
60
63
|
|
|
61
|
-
/**
|
|
64
|
+
/**
|
|
65
|
+
* Read the pnpm lockfile to get patch hashes. Bun doesn't store hashes in
|
|
66
|
+
* its lockfile so we skip this for Bun.
|
|
67
|
+
*/
|
|
68
|
+
const { name: packageManagerName } = usePackageManager();
|
|
62
69
|
const lockfilePatchedDependencies =
|
|
63
|
-
|
|
70
|
+
packageManagerName === "pnpm"
|
|
71
|
+
? await readLockfilePatchedDependencies(workspaceRootDir)
|
|
72
|
+
: undefined;
|
|
64
73
|
|
|
65
74
|
const copiedPatches: Record<string, PatchFile> = {};
|
|
66
75
|
|
|
@@ -84,7 +93,7 @@ export async function copyPatches({
|
|
|
84
93
|
const originalPatchFile = lockfilePatchedDependencies?.[packageSpec];
|
|
85
94
|
const hash = originalPatchFile?.hash ?? "";
|
|
86
95
|
|
|
87
|
-
if (!hash) {
|
|
96
|
+
if (packageManagerName === "pnpm" && !hash) {
|
|
88
97
|
log.warn(`No hash found for patch ${packageSpec} in lockfile`);
|
|
89
98
|
}
|
|
90
99
|
|
package/src/lib/types.ts
CHANGED
|
@@ -11,6 +11,9 @@ export interface PatchFile {
|
|
|
11
11
|
|
|
12
12
|
export type PackageManifest = PnpmPackageManifest & {
|
|
13
13
|
packageManager?: string;
|
|
14
|
+
workspaces?: string[];
|
|
15
|
+
/** Bun stores patchedDependencies at the top level */
|
|
16
|
+
patchedDependencies?: Record<string, string>;
|
|
14
17
|
pnpm?: {
|
|
15
18
|
patchedDependencies?: Record<string, string>;
|
|
16
19
|
[key: string]: unknown;
|
|
@@ -1,17 +1,7 @@
|
|
|
1
|
-
import { describe, expect, it
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
2
|
import type { PackageManifest } from "~/lib/types";
|
|
3
3
|
import { filterPatchedDependencies } from "./filter-patched-dependencies";
|
|
4
4
|
|
|
5
|
-
/** Mock the logger */
|
|
6
|
-
vi.mock("~/lib/logger", () => ({
|
|
7
|
-
useLogger: () => ({
|
|
8
|
-
debug: vi.fn(),
|
|
9
|
-
info: vi.fn(),
|
|
10
|
-
warn: vi.fn(),
|
|
11
|
-
error: vi.fn(),
|
|
12
|
-
}),
|
|
13
|
-
}));
|
|
14
|
-
|
|
15
5
|
describe("filterPatchedDependencies", () => {
|
|
16
6
|
it("should return undefined when patchedDependencies is undefined", () => {
|
|
17
7
|
const manifest: PackageManifest = { name: "test", version: "1.0.0" };
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { vi } from "vitest";
|
|
2
|
+
|
|
3
|
+
/** Mock the logger for all tests to prevent console output during tests */
|
|
4
|
+
vi.mock("~/lib/logger", () => ({
|
|
5
|
+
useLogger: () => ({
|
|
6
|
+
debug: vi.fn(),
|
|
7
|
+
info: vi.fn(),
|
|
8
|
+
warn: vi.fn(),
|
|
9
|
+
error: vi.fn(),
|
|
10
|
+
}),
|
|
11
|
+
setLogLevel: vi.fn(),
|
|
12
|
+
setLogger: vi.fn(),
|
|
13
|
+
}));
|