| 1 | import * as NodeURL from "node:url"; |
| 2 | |
| 3 | import * as NodeServices from "@effect/platform-node/NodeServices"; |
| 4 | import { assert, describe, it } from "@effect/vitest"; |
| 5 | import * as Effect from "effect/Effect"; |
| 6 | import * as FileSystem from "effect/FileSystem"; |
| 7 | import * as Path from "effect/Path"; |
| 8 | import * as Schema from "effect/Schema"; |
| 9 | |
| 10 | import serverPackageJson from "../../apps/server/package.json" with { type: "json" }; |
| 11 | |
| 12 | import { findEsmImportsOfExternalPackages } from "./cli-executable-imports.ts"; |
| 13 | |
| 14 | import { |
| 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. |
| 26 | const 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 | }); |
| 31 | type PackageManifest = typeof PackageManifest.Type; |
| 32 | |
| 33 | const decodeManifest = Schema.decodeUnknownSync(Schema.fromJsonString(PackageManifest)); |
| 34 | |
| 35 | describe("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 | |
| 68 | describe("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. |
| 99 | it.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. |
| 217 | describe("findInlinedExternalPackages", () => { |
| 218 | const region = (path: string) => `//#region ${path} |
| 219 | var 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. |
| 286 | describe("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 | }); |