1import * as NodeURL from "node:url";
2
3import * as NodeServices from "@effect/platform-node/NodeServices";
4import { assert, describe, it } from "@effect/vitest";
5import * as Effect from "effect/Effect";
6import * as FileSystem from "effect/FileSystem";
7import * as Path from "effect/Path";
8import * as Schema from "effect/Schema";
9
10import serverPackageJson from "../../apps/server/package.json" with { type: "json" };
11
12import { findEsmImportsOfExternalPackages } from "./cli-executable-imports.ts";
13
14import {
15 isRuntimeExternalCliDependency,
16 findInlinedExternalPackages,
17 selectCliRuntimeExternalDependencies,
18 shouldBundleCliDependency,
19} from "./cli-external-packages.ts";
20
21// Only the field this test cares about; decoding ignores everything else.
22// optionalDependencies matter as much as dependencies here: every native family
23// in the list declares its actual platform bindings there (ffi-rs -> @yuuang/*,
24// fff-node -> @ff-labs/fff-bin-*), so reading only `dependencies` would check
25// nothing for exactly those packages.
26const PackageManifest = Schema.Struct({
27 dependencies: Schema.optional(Schema.Record(Schema.String, Schema.String)),
28 optionalDependencies: Schema.optional(Schema.Record(Schema.String, Schema.String)),
29 peerDependencies: Schema.optional(Schema.Record(Schema.String, Schema.String)),
30});
31type PackageManifest = typeof PackageManifest.Type;
32
33const decodeManifest = Schema.decodeUnknownSync(Schema.fromJsonString(PackageManifest));
34
35describe("shouldBundleCliDependency", () => {
36 it("bundles ordinary runtime dependencies", () => {
37 for (const id of ["effect", "@effect/platform", "hono", "@t3tools/shared/HostProcess"]) {
38 assert.strictEqual(shouldBundleCliDependency(id), true, id);
39 }
40 });
41
42 it("never bundles node: builtins", () => {
43 assert.strictEqual(shouldBundleCliDependency("node:fs"), false);
44 });
45
46 it("leaves native addons and their dlopen wrappers external", () => {
47 for (const id of [
48 "node-pty",
49 "ffi-rs",
50 "@yuuang/ffi-rs-win32-x64-msvc",
51 "@ff-labs/fff-node",
52 "@napi-rs/keyring",
53 "@clerk/electron-passkeys",
54 "node-addon-api",
55 ]) {
56 assert.strictEqual(shouldBundleCliDependency(id), false, id);
57 }
58 });
59
60 // The real package is `node-gyp-build-optional-packages`, reached by prefix.
61 // It is transitive to a selected dependency root, so the runtime closure test
62 // below ensures it follows that root into the sidecar.
63 it("treats prefix-matched siblings as external", () => {
64 assert.strictEqual(shouldBundleCliDependency("node-gyp-build-optional-packages"), false);
65 });
66});
67
68describe("selectCliRuntimeExternalDependencies", () => {
69 it("keeps only runtime-external dependency roots for the Windows sidecar", () => {
70 assert.deepStrictEqual(
71 selectCliRuntimeExternalDependencies({
72 "@ff-labs/fff-node": "2.0.0",
73 effect: "3.0.0",
74 "node-pty": "4.0.0",
75 }),
76 {
77 "@ff-labs/fff-node": "2.0.0",
78 "node-pty": "4.0.0",
79 },
80 );
81 });
82
83 it("selects every external root declared by the server", () => {
84 assert.deepStrictEqual(
85 Object.keys(selectCliRuntimeExternalDependencies(serverPackageJson.dependencies)).sort(),
86 ["@cursor/sdk", "@ff-labs/fff-node", "@napi-rs/keyring", "node-pty", "playwright-core"],
87 );
88 });
89});
90
91// An external package is loaded from the real filesystem, so its own `require`
92// also resolves from the real filesystem. If one of its dependencies was
93// bundled away instead of left external, that dependency does not follow the
94// selected root into the sidecar.
95//
96// Found the hard way: msgpackr-extract's node-gyp-build-optional-packages
97// required detect-libc, which was bundled. Windows was fine; WSL got
98// MODULE_NOT_FOUND.
99it.layer(NodeServices.layer)("external package dependency closure", (it) => {
100 // Read manifests off disk from the pnpm store rather than resolving them.
101 // `require("<name>/package.json")` cannot do this job: under pnpm isolation a
102 // transitive package (node-addon-api, ffi-rs) is not reachable
103 // by name from this file at all, and an `exports` map can refuse the
104 // `/package.json` subpath outright (@ff-labs/fff-node). Both surface as "not
105 // installed", which would let this test skip everything and pass while
106 // checking nothing. The store contains the dependency graph the sidecar's
107 // minimal production install resolves.
108 const readInstalledPackages = Effect.gen(function* () {
109 const fileSystem = yield* FileSystem.FileSystem;
110 const path = yield* Path.Path;
111 const storeDir = path.resolve(
112 path.dirname(NodeURL.fileURLToPath(import.meta.url)),
113 "../../node_modules/.pnpm",
114 );
115
116 // The store holds regular files too (lock.yaml), so a path built under one
117 // raises ENOTDIR rather than reporting absence. That throws on Linux while
118 // Windows quietly returns false, which is exactly the kind of difference
119 // this test exists to catch, so treat any failure as "not there".
120 const isPresent = (candidate: string) =>
121 fileSystem.exists(candidate).pipe(Effect.orElseSucceed(() => false));
122
123 const installed = new Map<string, PackageManifest>();
124 if (!(yield* isPresent(storeDir))) return installed;
125
126 for (const entry of yield* fileSystem.readDirectory(storeDir)) {
127 const modulesDir = path.join(storeDir, entry, "node_modules");
128 if (!(yield* isPresent(modulesDir))) continue;
129
130 for (const owner of yield* fileSystem.readDirectory(modulesDir)) {
131 const names = owner.startsWith("@")
132 ? (yield* fileSystem.readDirectory(path.join(modulesDir, owner))).map(
133 (scoped) => `${owner}/${scoped}`,
134 )
135 : [owner];
136
137 for (const name of names) {
138 if (installed.has(name)) continue;
139 const manifestPath = path.join(modulesDir, name, "package.json");
140 if (!(yield* isPresent(manifestPath))) continue;
141 installed.set(name, decodeManifest(yield* fileSystem.readFileString(manifestPath)));
142 }
143 }
144 }
145 return installed;
146 }).pipe(Effect.cached, Effect.runSync);
147
148 // Runtime-external only. The build-only entries resolve `bun:*` and are never
149 // loaded by Node, so their closure genuinely does not need to be external.
150 const isRuntimeExternal = isRuntimeExternalCliDependency;
151
152 // A cold walk of the pnpm store can exceed the root timeout when the Windows
153 // lane runs four filesystem-heavy workspace suites at once.
154 it.effect(
155 "finds the runtime-external packages on disk",
156 () =>
157 Effect.gen(function* () {
158 const installed = yield* readInstalledPackages;
159 const found = [...installed.keys()].filter(isRuntimeExternal);
160
161 // Without this the closure check below can pass vacuously: if nothing is
162 // read, nothing is checked. node-pty is the one native root every
163 // platform ships, and node-addon-api is its transitive runtime
164 // dependency, so require them by name.
165 for (const required of ["node-pty", "node-addon-api"]) {
166 assert.ok(
167 found.includes(required),
168 `expected ${required} in the pnpm store; the closure check is only meaningful if it can read these (found ${found.length})`,
169 );
170 }
171 }),
172 120_000,
173 );
174
175 it.effect("keeps every runtime dependency of an external package external too", () =>
176 Effect.gen(function* () {
177 const installed = yield* readInstalledPackages;
178 const violations: string[] = [];
179 const seen = new Set<string>();
180 // Seeded from what is actually installed and matches a prefix, so scoped
181 // prefixes like "@yuuang/" and "@ff-labs/" are covered too. Seeding from
182 // the prefix strings themselves would skip every scoped entry, since a
183 // prefix is not a package name.
184 const queue = [...installed.keys()].filter(isRuntimeExternal);
185
186 for (const name of queue) {
187 if (seen.has(name)) continue;
188 seen.add(name);
189
190 const manifest = installed.get(name);
191 if (!manifest) continue;
192
193 const declared = {
194 ...(manifest.dependencies ?? {}),
195 ...(manifest.optionalDependencies ?? {}),
196 ...(manifest.peerDependencies ?? {}),
197 };
198 for (const dependency of Object.keys(declared)) {
199 if (!isRuntimeExternal(dependency)) {
200 violations.push(`${name} -> ${dependency}`);
201 }
202 if (!seen.has(dependency)) queue.push(dependency);
203 }
204 }
205
206 assert.deepStrictEqual(
207 violations,
208 [],
209 `these dependencies of external packages would be bundled away and fail to resolve under WSL: ${violations.join(", ")}`,
210 );
211 }),
212 );
213});
214
215// Configuring the bundler is not the same as checking what it emitted. These
216// exercise the scanner against the marker shape rolldown actually produces.
217describe("findInlinedExternalPackages", () => {
218 const region = (path: string) => `//#region ${path}
219var x = 1;
220//#endregion
221`;
222
223 it("flags an external package that was inlined", () => {
224 const source =
225 region("../../node_modules/.pnpm/node-addon-api@7.1.1/node_modules/node-addon-api/index.js") +
226 region(
227 "../../node_modules/.pnpm/node-gyp-build-optional-packages@5.2.2/node_modules/node-gyp-build-optional-packages/index.js",
228 );
229 const result = findInlinedExternalPackages(source);
230
231 assert.deepStrictEqual(result.inlined, ["node-addon-api", "node-gyp-build-optional-packages"]);
232 assert.strictEqual(result.regionCount, 2);
233 });
234
235 it("flags scoped external packages", () => {
236 const result = findInlinedExternalPackages(
237 region("../../node_modules/@ff-labs/fff-node/dist/src/index.js"),
238 );
239 assert.deepStrictEqual(result.inlined, ["@ff-labs/fff-node"]);
240 });
241
242 it("ignores packages that are meant to be bundled", () => {
243 const source =
244 region("../../node_modules/.pnpm/effect@4.0.0/node_modules/effect/dist/index.js") +
245 region("../../src/server/main.ts");
246 const result = findInlinedExternalPackages(source);
247
248 assert.deepStrictEqual(result.inlined, []);
249 assert.strictEqual(result.regionCount, 2);
250 });
251
252 // regionCount is what separates "clean" from "this scan went blind because the
253 // marker format changed". A caller that ignores it gets a vacuous pass.
254 // The scan has to answer both directions. Checking only that externals are
255 // absent still passes on a bundle that externalized everything, which is the
256 // failure this whole change prevents.
257 it("reports the packages that were inlined, not just the violations", () => {
258 const source =
259 region("../../node_modules/.pnpm/effect@4.0.0/node_modules/effect/dist/index.js") +
260 region("../../node_modules/.pnpm/yaml@2.4.0/node_modules/yaml/dist/index.js") +
261 region("../../src/server/main.ts");
262 const result = findInlinedExternalPackages(source);
263
264 assert.deepStrictEqual(result.inlinedPackages, ["effect", "yaml"]);
265 assert.deepStrictEqual(result.inlined, []);
266 });
267
268 it("does not report the pnpm store directory as a package", () => {
269 const result = findInlinedExternalPackages(
270 region("../../node_modules/.pnpm/effect@4.0.0/node_modules/effect/dist/index.js"),
271 );
272 assert.deepStrictEqual(result.inlinedPackages, ["effect"]);
273 });
274
275 it("reports no regions when the marker format is absent", () => {
276 const result = findInlinedExternalPackages("var x = 1; // node_modules/node-pty/lib.js");
277 assert.strictEqual(result.regionCount, 0);
278 assert.deepStrictEqual(result.inlined, []);
279 });
280});
281
282// The single-executable build can only `import` built-ins. A file-backed
283// import of an external package passes every bundler check and the regular
284// `node dist/bin.mjs` path, then fails inside the executable, so the scan
285// reads the emitted module graph instead.
286describe("findEsmImportsOfExternalPackages", () => {
287 it("flags static and dynamic imports of file-backed packages", () => {
288 const source = [
289 'import { FileFinder } from "@ff-labs/fff-node";',
290 'import * as fs from "fs";',
291 'import { createRequire } from "node:module";',
292 'const pty = () => import("node-pty");',
293 'const data = () => import("@ff-labs/fff-bin-linux-x64-gnu", { with: { type: "json" } });',
294 'const lazy = () => import(/* @vite-ignore */ "ffi-rs");',
295 'const local = () => import("./chunk-abc.mjs");',
296 ].join("\n");
297
298 assert.deepStrictEqual(findEsmImportsOfExternalPackages(source), [
299 "@ff-labs/fff-bin-linux-x64-gnu",
300 "@ff-labs/fff-node",
301 "ffi-rs",
302 "node-pty",
303 ]);
304 });
305
306 it("flags side-effect imports and re-exports too", () => {
307 const source = ['import "msgpackr-extract";', 'export { load } from "ffi-rs";'].join("\n");
308 assert.deepStrictEqual(findEsmImportsOfExternalPackages(source), [
309 "ffi-rs",
310 "msgpackr-extract",
311 ]);
312 });
313
314 it("ignores imports inside generated extension source and comments", () => {
315 const source = [
316 'const extension = `import { Type } from "typebox";\nimport type { ExtensionAPI } from "@earendil-works/pi-coding-agent";`;',
317 '// import "comment-only";',
318 "const example = 'import(\"string-only\")';",
319 'const interpolated = `source ${import("real-package")}`;',
320 ].join("\n");
321 assert.deepStrictEqual(findEsmImportsOfExternalPackages(source), ["real-package"]);
322 });
323
324 it("allows optional dynamic Bun built-ins but rejects static imports", () => {
325 assert.deepStrictEqual(
326 findEsmImportsOfExternalPackages('const load = () => import("bun:sqlite");'),
327 [],
328 );
329 assert.deepStrictEqual(
330 findEsmImportsOfExternalPackages('import { Database } from "bun:sqlite";'),
331 ["bun:sqlite"],
332 );
333 });
334
335 it("does not mistake createRequire calls for imports", () => {
336 const source = 'const { FileFinder } = createRequire(import.meta.url)("@ff-labs/fff-node");';
337 assert.deepStrictEqual(findEsmImportsOfExternalPackages(source), []);
338 });
339});