isolate-package 1.32.0 → 1.33.0-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-BRD2AgVJ.mjs → isolate-DTwgcMAN.mjs} +357 -36
- package/dist/isolate-DTwgcMAN.mjs.map +1 -0
- package/dist/isolate-bin.mjs +1 -1
- package/package.json +1 -1
- package/src/isolate.ts +8 -4
- package/src/lib/lockfile/helpers/bun-lockfile.ts +143 -0
- package/src/lib/lockfile/helpers/generate-bun-lockfile.ts +7 -139
- 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 +364 -0
- package/src/lib/patches/copy-patches.test.ts +125 -0
- package/src/lib/patches/copy-patches.ts +38 -1
- package/src/lib/patches/write-isolate-pnpm-workspace.test.ts +189 -0
- package/src/lib/patches/write-isolate-pnpm-workspace.ts +80 -0
- package/dist/isolate-BRD2AgVJ.mjs.map +0 -1
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared types and walker logic for the Bun workspace lockfile (`bun.lock`).
|
|
3
|
+
*
|
|
4
|
+
* Used both when generating the isolated lockfile and when computing the set
|
|
5
|
+
* of package names that will end up installed in the isolate (so that patches
|
|
6
|
+
* for deep transitive deps are preserved).
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
export type BunWorkspaceEntry = {
|
|
10
|
+
name?: string;
|
|
11
|
+
version?: string;
|
|
12
|
+
dependencies?: Record<string, string>;
|
|
13
|
+
devDependencies?: Record<string, string>;
|
|
14
|
+
optionalDependencies?: Record<string, string>;
|
|
15
|
+
peerDependencies?: Record<string, string>;
|
|
16
|
+
optionalPeers?: string[];
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
export type BunLockfile = {
|
|
20
|
+
lockfileVersion: number;
|
|
21
|
+
workspaces: Record<string, BunWorkspaceEntry>;
|
|
22
|
+
packages: Record<string, unknown[]>;
|
|
23
|
+
trustedDependencies?: string[];
|
|
24
|
+
patchedDependencies?: Record<string, string>;
|
|
25
|
+
overrides?: Record<string, string>;
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
/** Extract dependency names from a workspace entry. */
|
|
29
|
+
export function collectDependencyNames(
|
|
30
|
+
entry: BunWorkspaceEntry,
|
|
31
|
+
includeDevDependencies: boolean,
|
|
32
|
+
): string[] {
|
|
33
|
+
const names = new Set<string>();
|
|
34
|
+
|
|
35
|
+
for (const name of Object.keys(entry.dependencies ?? {})) {
|
|
36
|
+
names.add(name);
|
|
37
|
+
}
|
|
38
|
+
for (const name of Object.keys(entry.optionalDependencies ?? {})) {
|
|
39
|
+
names.add(name);
|
|
40
|
+
}
|
|
41
|
+
for (const name of Object.keys(entry.peerDependencies ?? {})) {
|
|
42
|
+
names.add(name);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
if (includeDevDependencies) {
|
|
46
|
+
for (const name of Object.keys(entry.devDependencies ?? {})) {
|
|
47
|
+
names.add(name);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return [...names];
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Check whether a package entry represents a workspace package by examining
|
|
56
|
+
* its identifier string (first element in the entry array).
|
|
57
|
+
*/
|
|
58
|
+
export function isWorkspacePackageEntry(entry: unknown[]): boolean {
|
|
59
|
+
const ident = entry[0];
|
|
60
|
+
return typeof ident === "string" && ident.includes("@workspace:");
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Extract the info object from a packages entry. The position varies by type:
|
|
65
|
+
* - npm packages: [ident, registry, info, checksum] -> index 2
|
|
66
|
+
* - workspace packages: [ident, info] -> index 1
|
|
67
|
+
* - git/github packages: [ident, info, checksum] -> index 1
|
|
68
|
+
*
|
|
69
|
+
* Detection: if the second element is a string (registry URL or checksum),
|
|
70
|
+
* the info object is deeper. Workspace entries have only 2 elements.
|
|
71
|
+
*/
|
|
72
|
+
export function getPackageInfoObject(
|
|
73
|
+
entry: unknown[],
|
|
74
|
+
): Record<string, unknown> | undefined {
|
|
75
|
+
if (entry.length <= 1) return undefined;
|
|
76
|
+
|
|
77
|
+
/** Workspace entries: [ident, info] */
|
|
78
|
+
if (isWorkspacePackageEntry(entry)) {
|
|
79
|
+
return typeof entry[1] === "object"
|
|
80
|
+
? (entry[1] as Record<string, unknown>)
|
|
81
|
+
: undefined;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* npm entries with registry URL: [ident, registryUrl, info, checksum]. The
|
|
86
|
+
* second element is a string (the registry URL).
|
|
87
|
+
*/
|
|
88
|
+
if (typeof entry[1] === "string") {
|
|
89
|
+
return typeof entry[2] === "object"
|
|
90
|
+
? (entry[2] as Record<string, unknown>)
|
|
91
|
+
: undefined;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/** git/tarball entries: [ident, info, checksum] */
|
|
95
|
+
return typeof entry[1] === "object"
|
|
96
|
+
? (entry[1] as Record<string, unknown>)
|
|
97
|
+
: undefined;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Recursively collect all package keys that are required, starting from a set
|
|
102
|
+
* of direct dependency names and walking through their transitive
|
|
103
|
+
* dependencies in the packages section.
|
|
104
|
+
*/
|
|
105
|
+
export function collectRequiredPackages(
|
|
106
|
+
directDependencyNames: Set<string>,
|
|
107
|
+
packages: Record<string, unknown[]>,
|
|
108
|
+
): Set<string> {
|
|
109
|
+
const required = new Set<string>();
|
|
110
|
+
const queue = [...directDependencyNames];
|
|
111
|
+
|
|
112
|
+
while (queue.length > 0) {
|
|
113
|
+
const name = queue.pop()!;
|
|
114
|
+
|
|
115
|
+
if (required.has(name)) continue;
|
|
116
|
+
|
|
117
|
+
const entry = packages[name];
|
|
118
|
+
if (!entry) continue;
|
|
119
|
+
|
|
120
|
+
required.add(name);
|
|
121
|
+
|
|
122
|
+
const info = getPackageInfoObject(entry);
|
|
123
|
+
if (!info) continue;
|
|
124
|
+
|
|
125
|
+
/** Walk transitive dependencies from the info object */
|
|
126
|
+
for (const depField of [
|
|
127
|
+
"dependencies",
|
|
128
|
+
"optionalDependencies",
|
|
129
|
+
"peerDependencies",
|
|
130
|
+
]) {
|
|
131
|
+
const deps = info[depField];
|
|
132
|
+
if (deps && typeof deps === "object") {
|
|
133
|
+
for (const depName of Object.keys(deps as Record<string, unknown>)) {
|
|
134
|
+
if (!required.has(depName)) {
|
|
135
|
+
queue.push(depName);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
return required;
|
|
143
|
+
}
|
|
@@ -8,25 +8,13 @@ import {
|
|
|
8
8
|
getPackageName,
|
|
9
9
|
readTypedJsonSync,
|
|
10
10
|
} from "~/lib/utils";
|
|
11
|
-
|
|
12
|
-
type
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
peerDependencies?: Record<string, string>;
|
|
19
|
-
optionalPeers?: string[];
|
|
20
|
-
};
|
|
21
|
-
|
|
22
|
-
type BunLockfile = {
|
|
23
|
-
lockfileVersion: number;
|
|
24
|
-
workspaces: Record<string, BunWorkspaceEntry>;
|
|
25
|
-
packages: Record<string, unknown[]>;
|
|
26
|
-
trustedDependencies?: string[];
|
|
27
|
-
patchedDependencies?: Record<string, string>;
|
|
28
|
-
overrides?: Record<string, string>;
|
|
29
|
-
};
|
|
11
|
+
import {
|
|
12
|
+
type BunLockfile,
|
|
13
|
+
type BunWorkspaceEntry,
|
|
14
|
+
collectDependencyNames,
|
|
15
|
+
collectRequiredPackages,
|
|
16
|
+
isWorkspacePackageEntry,
|
|
17
|
+
} from "./bun-lockfile";
|
|
30
18
|
|
|
31
19
|
/**
|
|
32
20
|
* Serialize a value to JSON with trailing commas after every array element and
|
|
@@ -54,126 +42,6 @@ export function serializeWithTrailingCommas(
|
|
|
54
42
|
return result;
|
|
55
43
|
}
|
|
56
44
|
|
|
57
|
-
/**
|
|
58
|
-
* Extract dependency names from a workspace entry, optionally including
|
|
59
|
-
* devDependencies.
|
|
60
|
-
*/
|
|
61
|
-
function collectDependencyNames(
|
|
62
|
-
entry: BunWorkspaceEntry,
|
|
63
|
-
includeDevDependencies: boolean,
|
|
64
|
-
): string[] {
|
|
65
|
-
const names = new Set<string>();
|
|
66
|
-
|
|
67
|
-
for (const name of Object.keys(entry.dependencies ?? {})) {
|
|
68
|
-
names.add(name);
|
|
69
|
-
}
|
|
70
|
-
for (const name of Object.keys(entry.optionalDependencies ?? {})) {
|
|
71
|
-
names.add(name);
|
|
72
|
-
}
|
|
73
|
-
for (const name of Object.keys(entry.peerDependencies ?? {})) {
|
|
74
|
-
names.add(name);
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
if (includeDevDependencies) {
|
|
78
|
-
for (const name of Object.keys(entry.devDependencies ?? {})) {
|
|
79
|
-
names.add(name);
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
return [...names];
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
/**
|
|
87
|
-
* Check whether a package entry represents a workspace package by examining its
|
|
88
|
-
* identifier string (first element in the entry array).
|
|
89
|
-
*/
|
|
90
|
-
function isWorkspacePackageEntry(entry: unknown[]): boolean {
|
|
91
|
-
const ident = entry[0];
|
|
92
|
-
return typeof ident === "string" && ident.includes("@workspace:");
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
/**
|
|
96
|
-
* Extract the info object from a packages entry. The position varies by type:
|
|
97
|
-
* - npm packages: [ident, registry, info, checksum] -> index 2
|
|
98
|
-
* - workspace packages: [ident, info] -> index 1
|
|
99
|
-
* - git/github packages: [ident, info, checksum] -> index 1
|
|
100
|
-
*
|
|
101
|
-
* Detection: if the second element is a string (registry URL or checksum), the
|
|
102
|
-
* info object is deeper. Workspace entries have only 2 elements.
|
|
103
|
-
*/
|
|
104
|
-
function getPackageInfoObject(
|
|
105
|
-
entry: unknown[],
|
|
106
|
-
): Record<string, unknown> | undefined {
|
|
107
|
-
if (entry.length <= 1) return undefined;
|
|
108
|
-
|
|
109
|
-
/** Workspace entries: [ident, info] */
|
|
110
|
-
if (isWorkspacePackageEntry(entry)) {
|
|
111
|
-
return typeof entry[1] === "object"
|
|
112
|
-
? (entry[1] as Record<string, unknown>)
|
|
113
|
-
: undefined;
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
/**
|
|
117
|
-
* npm entries with registry URL: [ident, registryUrl, info, checksum].
|
|
118
|
-
* The second element is a string (the registry URL).
|
|
119
|
-
*/
|
|
120
|
-
if (typeof entry[1] === "string") {
|
|
121
|
-
return typeof entry[2] === "object"
|
|
122
|
-
? (entry[2] as Record<string, unknown>)
|
|
123
|
-
: undefined;
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
/** git/tarball entries: [ident, info, checksum] */
|
|
127
|
-
return typeof entry[1] === "object"
|
|
128
|
-
? (entry[1] as Record<string, unknown>)
|
|
129
|
-
: undefined;
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
/**
|
|
133
|
-
* Recursively collect all package keys that are required, starting from a set
|
|
134
|
-
* of direct dependency names and walking through their transitive dependencies
|
|
135
|
-
* in the packages section.
|
|
136
|
-
*/
|
|
137
|
-
function collectRequiredPackages(
|
|
138
|
-
directDependencyNames: Set<string>,
|
|
139
|
-
packages: Record<string, unknown[]>,
|
|
140
|
-
): Set<string> {
|
|
141
|
-
const required = new Set<string>();
|
|
142
|
-
const queue = [...directDependencyNames];
|
|
143
|
-
|
|
144
|
-
while (queue.length > 0) {
|
|
145
|
-
const name = queue.pop()!;
|
|
146
|
-
|
|
147
|
-
if (required.has(name)) continue;
|
|
148
|
-
|
|
149
|
-
const entry = packages[name];
|
|
150
|
-
if (!entry) continue;
|
|
151
|
-
|
|
152
|
-
required.add(name);
|
|
153
|
-
|
|
154
|
-
const info = getPackageInfoObject(entry);
|
|
155
|
-
if (!info) continue;
|
|
156
|
-
|
|
157
|
-
/** Walk transitive dependencies from the info object */
|
|
158
|
-
for (const depField of [
|
|
159
|
-
"dependencies",
|
|
160
|
-
"optionalDependencies",
|
|
161
|
-
"peerDependencies",
|
|
162
|
-
]) {
|
|
163
|
-
const deps = info[depField];
|
|
164
|
-
if (deps && typeof deps === "object") {
|
|
165
|
-
for (const depName of Object.keys(deps as Record<string, unknown>)) {
|
|
166
|
-
if (!required.has(depName)) {
|
|
167
|
-
queue.push(depName);
|
|
168
|
-
}
|
|
169
|
-
}
|
|
170
|
-
}
|
|
171
|
-
}
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
return required;
|
|
175
|
-
}
|
|
176
|
-
|
|
177
45
|
export async function generateBunLockfile({
|
|
178
46
|
workspaceRootDir,
|
|
179
47
|
targetPackageDir,
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
2
|
+
import { collectInstalledNamesFromBunLockfile } from "./collect-installed-names-bun";
|
|
3
|
+
|
|
4
|
+
vi.mock("fs-extra", () => ({
|
|
5
|
+
default: {
|
|
6
|
+
existsSync: vi.fn(),
|
|
7
|
+
},
|
|
8
|
+
}));
|
|
9
|
+
|
|
10
|
+
vi.mock("~/lib/utils", () => ({
|
|
11
|
+
readTypedJsonSync: vi.fn(),
|
|
12
|
+
}));
|
|
13
|
+
|
|
14
|
+
const fs = vi.mocked((await import("fs-extra")).default);
|
|
15
|
+
const { readTypedJsonSync } = vi.mocked(await import("~/lib/utils"));
|
|
16
|
+
|
|
17
|
+
const baseArgs = {
|
|
18
|
+
workspaceRootDir: "/workspace",
|
|
19
|
+
targetPackageDir: "/workspace/packages/consumer",
|
|
20
|
+
internalDepPackageNames: [],
|
|
21
|
+
packagesRegistry: {},
|
|
22
|
+
includeDevDependencies: false,
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
describe("collectInstalledNamesFromBunLockfile", () => {
|
|
26
|
+
beforeEach(() => {
|
|
27
|
+
vi.clearAllMocks();
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
afterEach(() => {
|
|
31
|
+
vi.restoreAllMocks();
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it("returns an empty set when bun.lock is missing", () => {
|
|
35
|
+
fs.existsSync.mockReturnValue(false);
|
|
36
|
+
|
|
37
|
+
const result = collectInstalledNamesFromBunLockfile({
|
|
38
|
+
...baseArgs,
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
expect(result).toEqual(new Set());
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it("walks external-to-external transitives from the target workspace entry", () => {
|
|
45
|
+
fs.existsSync.mockReturnValue(true);
|
|
46
|
+
readTypedJsonSync.mockReturnValue({
|
|
47
|
+
lockfileVersion: 1,
|
|
48
|
+
workspaces: {
|
|
49
|
+
"packages/consumer": {
|
|
50
|
+
name: "consumer",
|
|
51
|
+
dependencies: { "@react-pdf/renderer": "^4.0.0" },
|
|
52
|
+
},
|
|
53
|
+
},
|
|
54
|
+
packages: {
|
|
55
|
+
"@react-pdf/renderer": [
|
|
56
|
+
"@react-pdf/renderer@4.0.0",
|
|
57
|
+
"https://registry/...",
|
|
58
|
+
{ dependencies: { "@react-pdf/render": "4.3.0" } },
|
|
59
|
+
"checksum",
|
|
60
|
+
],
|
|
61
|
+
"@react-pdf/render": [
|
|
62
|
+
"@react-pdf/render@4.3.0",
|
|
63
|
+
"https://registry/...",
|
|
64
|
+
{},
|
|
65
|
+
"checksum",
|
|
66
|
+
],
|
|
67
|
+
},
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
const result = collectInstalledNamesFromBunLockfile({
|
|
71
|
+
...baseArgs,
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
expect(result.has("@react-pdf/renderer")).toBe(true);
|
|
75
|
+
expect(result.has("@react-pdf/render")).toBe(true);
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
it("walks transitives reachable through internal workspace entries", () => {
|
|
79
|
+
fs.existsSync.mockReturnValue(true);
|
|
80
|
+
readTypedJsonSync.mockReturnValue({
|
|
81
|
+
lockfileVersion: 1,
|
|
82
|
+
workspaces: {
|
|
83
|
+
"packages/consumer": {
|
|
84
|
+
name: "consumer",
|
|
85
|
+
dependencies: { "firebase-package": "workspace:*" },
|
|
86
|
+
},
|
|
87
|
+
"packages/firebase-package": {
|
|
88
|
+
name: "firebase-package",
|
|
89
|
+
dependencies: { tslib: "^2.0.0" },
|
|
90
|
+
},
|
|
91
|
+
},
|
|
92
|
+
packages: {
|
|
93
|
+
tslib: ["tslib@2.0.0", "https://registry/...", {}, "checksum"],
|
|
94
|
+
},
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
const result = collectInstalledNamesFromBunLockfile({
|
|
98
|
+
...baseArgs,
|
|
99
|
+
internalDepPackageNames: ["firebase-package"],
|
|
100
|
+
packagesRegistry: {
|
|
101
|
+
"firebase-package": {
|
|
102
|
+
absoluteDir: "/workspace/packages/firebase-package",
|
|
103
|
+
rootRelativeDir: "packages/firebase-package",
|
|
104
|
+
manifest: { name: "firebase-package", version: "1.0.0" },
|
|
105
|
+
},
|
|
106
|
+
},
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
expect(result.has("tslib")).toBe(true);
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
it("skips devDependencies of the target when includeDevDependencies is false", () => {
|
|
113
|
+
fs.existsSync.mockReturnValue(true);
|
|
114
|
+
readTypedJsonSync.mockReturnValue({
|
|
115
|
+
lockfileVersion: 1,
|
|
116
|
+
workspaces: {
|
|
117
|
+
"packages/consumer": {
|
|
118
|
+
name: "consumer",
|
|
119
|
+
dependencies: { lodash: "^4.0.0" },
|
|
120
|
+
devDependencies: { typescript: "^5.0.0" },
|
|
121
|
+
},
|
|
122
|
+
},
|
|
123
|
+
packages: {
|
|
124
|
+
lodash: ["lodash@4.17.21", "https://registry/...", {}, "checksum"],
|
|
125
|
+
typescript: [
|
|
126
|
+
"typescript@5.5.0",
|
|
127
|
+
"https://registry/...",
|
|
128
|
+
{},
|
|
129
|
+
"checksum",
|
|
130
|
+
],
|
|
131
|
+
},
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
const result = collectInstalledNamesFromBunLockfile({
|
|
135
|
+
...baseArgs,
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
expect(result.has("lodash")).toBe(true);
|
|
139
|
+
expect(result.has("typescript")).toBe(false);
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
it("returns an empty set when the lockfile read throws", () => {
|
|
143
|
+
fs.existsSync.mockReturnValue(true);
|
|
144
|
+
readTypedJsonSync.mockImplementation(() => {
|
|
145
|
+
throw new Error("invalid json");
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
const result = collectInstalledNamesFromBunLockfile({
|
|
149
|
+
...baseArgs,
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
expect(result).toEqual(new Set());
|
|
153
|
+
});
|
|
154
|
+
});
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import fs from "fs-extra";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import {
|
|
4
|
+
type BunLockfile,
|
|
5
|
+
collectDependencyNames,
|
|
6
|
+
collectRequiredPackages,
|
|
7
|
+
} from "~/lib/lockfile/helpers/bun-lockfile";
|
|
8
|
+
import { useLogger } from "~/lib/logger";
|
|
9
|
+
import type { PackagesRegistry } from "~/lib/types";
|
|
10
|
+
import { readTypedJsonSync } from "~/lib/utils";
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Walk the workspace bun.lock starting from the target package and its
|
|
14
|
+
* internal workspace dependencies, returning the set of every package name
|
|
15
|
+
* that will end up installed in the isolate (including deep
|
|
16
|
+
* external-to-external transitives).
|
|
17
|
+
*
|
|
18
|
+
* Used by `copyPatches` to preserve patches for transitive deps that aren't
|
|
19
|
+
* directly listed on any internal manifest. Returns an empty set on any
|
|
20
|
+
* failure so the caller falls back to manifest-based reachability.
|
|
21
|
+
*/
|
|
22
|
+
export function collectInstalledNamesFromBunLockfile({
|
|
23
|
+
workspaceRootDir,
|
|
24
|
+
targetPackageDir,
|
|
25
|
+
internalDepPackageNames,
|
|
26
|
+
packagesRegistry,
|
|
27
|
+
includeDevDependencies,
|
|
28
|
+
}: {
|
|
29
|
+
workspaceRootDir: string;
|
|
30
|
+
targetPackageDir: string;
|
|
31
|
+
internalDepPackageNames: string[];
|
|
32
|
+
packagesRegistry: PackagesRegistry;
|
|
33
|
+
includeDevDependencies: boolean;
|
|
34
|
+
}): Set<string> {
|
|
35
|
+
const log = useLogger();
|
|
36
|
+
|
|
37
|
+
try {
|
|
38
|
+
const lockfilePath = path.join(workspaceRootDir, "bun.lock");
|
|
39
|
+
if (!fs.existsSync(lockfilePath)) {
|
|
40
|
+
log.debug("No bun.lock available for installed-names walk");
|
|
41
|
+
return new Set();
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const lockfile = readTypedJsonSync<BunLockfile>(lockfilePath);
|
|
45
|
+
|
|
46
|
+
const targetWorkspaceKey = path
|
|
47
|
+
.relative(workspaceRootDir, targetPackageDir)
|
|
48
|
+
.split(path.sep)
|
|
49
|
+
.join(path.posix.sep);
|
|
50
|
+
|
|
51
|
+
const internalWorkspaceKeys = internalDepPackageNames
|
|
52
|
+
.map((name) => {
|
|
53
|
+
const pkg = packagesRegistry[name];
|
|
54
|
+
if (!pkg) return null;
|
|
55
|
+
return pkg.rootRelativeDir.split(path.sep).join(path.posix.sep);
|
|
56
|
+
})
|
|
57
|
+
.filter((key): key is string => Boolean(key));
|
|
58
|
+
|
|
59
|
+
const directDependencyNames = new Set<string>();
|
|
60
|
+
|
|
61
|
+
const targetEntry = lockfile.workspaces[targetWorkspaceKey];
|
|
62
|
+
if (targetEntry) {
|
|
63
|
+
for (const name of collectDependencyNames(
|
|
64
|
+
targetEntry,
|
|
65
|
+
includeDevDependencies,
|
|
66
|
+
)) {
|
|
67
|
+
directDependencyNames.add(name);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
for (const workspaceKey of internalWorkspaceKeys) {
|
|
72
|
+
const entry = lockfile.workspaces[workspaceKey];
|
|
73
|
+
if (!entry) continue;
|
|
74
|
+
/** Internal workspace deps never bring in their devDependencies */
|
|
75
|
+
for (const name of collectDependencyNames(entry, false)) {
|
|
76
|
+
directDependencyNames.add(name);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
return collectRequiredPackages(directDependencyNames, lockfile.packages);
|
|
81
|
+
} catch (err) {
|
|
82
|
+
log.debug(
|
|
83
|
+
`Failed to walk bun.lock for installed names: ${err instanceof Error ? err.message : String(err)}`,
|
|
84
|
+
);
|
|
85
|
+
return new Set();
|
|
86
|
+
}
|
|
87
|
+
}
|