isolate-package 1.32.1 → 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.
@@ -8,25 +8,13 @@ import {
8
8
  getPackageName,
9
9
  readTypedJsonSync,
10
10
  } from "~/lib/utils";
11
-
12
- type BunWorkspaceEntry = {
13
- name?: string;
14
- version?: string;
15
- dependencies?: Record<string, string>;
16
- devDependencies?: Record<string, string>;
17
- optionalDependencies?: Record<string, string>;
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
+ }