// @vitest-environment node import assert from "node:assert/strict"; import fs from "node:fs"; import os from "node:os"; import path from "node:path"; import { afterEach, describe, test } from "vitest"; import { prunePnpmRuntimeClosure } from "./prune-pnpm-runtime-closure.mjs"; const fixtures = []; afterEach(() => { for (const fixture of fixtures.splice(0)) fs.rmSync(fixture, { force: true, recursive: true }); }); describe("pnpm runtime closure pruning", () => { test("keeps reachable native packages while removing an excluded optional peer toolchain and its orphans", async () => { const fixture = createFixture(); const nodeModulesPath = path.join(fixture, "node_modules"); const client = addPackage(nodeModulesPath, "@prisma+client@7.8.0_peer", "@prisma/client"); const prisma = addPackage(nodeModulesPath, "prisma@7.8.0_peer", "prisma"); const prismaDev = addPackage(nodeModulesPath, "@prisma+dev@0.24.3", "@prisma/dev"); const pglite = addPackage(nodeModulesPath, "@electric-sql+pglite@0.4.1", "@electric-sql/pglite"); const typescript = addPackage(nodeModulesPath, "typescript@5.9.3", "typescript"); const sharp = addPackage(nodeModulesPath, "sharp@0.35.2", "sharp"); const sharpBinary = addPackage(nodeModulesPath, "@img+sharp-linux-x64@0.35.2", "@img/sharp-linux-x64"); const unrelatedOrphan = addPackage(nodeModulesPath, "orphan@1.0.0", "orphan"); linkPackage(nodeModulesPath, "@prisma/client", client.packagePath); linkPackage(nodeModulesPath, "prisma", prisma.packagePath); linkPackage(nodeModulesPath, "typescript", typescript.packagePath); linkPackage(nodeModulesPath, "sharp", sharp.packagePath); linkPackage(path.join(client.entryPath, "node_modules"), "prisma", prisma.packagePath); linkPackage(path.join(prisma.entryPath, "node_modules"), "@prisma/dev", prismaDev.packagePath); linkPackage(path.join(prismaDev.entryPath, "node_modules"), "@electric-sql/pglite", pglite.packagePath); linkPackage(path.join(sharp.entryPath, "node_modules"), "@img/sharp-linux-x64", sharpBinary.packagePath); linkPackage(path.join(nodeModulesPath, ".bin"), "prisma", path.join(prisma.packagePath, "index.js")); const result = await prunePnpmRuntimeClosure({ excludedPackageNames: ["prisma", "typescript"], nodeModulesPath, }); assert.deepEqual( result.removedEntries, [ "@electric-sql+pglite@0.4.1", "@prisma+dev@0.24.3", "orphan@1.0.0", "prisma@7.8.0_peer", "typescript@5.9.3", ].sort() ); assert.ok(result.bytesRemoved > 0); assert.ok(result.filesRemoved > 0); assert.ok(result.brokenLinksRemoved > 0); assert.ok(fs.existsSync(client.packagePath)); assert.ok(fs.existsSync(sharp.packagePath)); assert.ok(fs.existsSync(sharpBinary.packagePath)); assert.ok(!fs.existsSync(prisma.packagePath)); assert.ok(!fs.existsSync(prismaDev.packagePath)); assert.ok(!fs.existsSync(pglite.packagePath)); assert.ok(!fs.existsSync(typescript.packagePath)); assert.ok(!fs.existsSync(unrelatedOrphan.packagePath)); assert.doesNotThrow(() => fs.realpathSync(path.join(nodeModulesPath, "@prisma/client"))); assert.doesNotThrow(() => fs.realpathSync(path.join(nodeModulesPath, "sharp"))); assert.doesNotThrow(() => fs.realpathSync(path.join(sharp.entryPath, "node_modules/@img/sharp-linux-x64"))); assert.ok(!fs.existsSync(path.join(nodeModulesPath, ".bin/prisma"))); }); test("fails closed instead of deleting a store with no reachable runtime roots", async () => { const fixture = createFixture(); const nodeModulesPath = path.join(fixture, "node_modules"); const orphan = addPackage(nodeModulesPath, "orphan@1.0.0", "orphan"); await assert.rejects( () => prunePnpmRuntimeClosure({ excludedPackageNames: [], nodeModulesPath }), /refusing to prune 1 pnpm entries without a reachable runtime root/ ); assert.ok(fs.existsSync(orphan.packagePath)); }); }); function createFixture() { const fixture = fs.mkdtempSync(path.join(os.tmpdir(), "pnpm-runtime-prune-")); fixtures.push(fixture); fs.mkdirSync(path.join(fixture, "node_modules/.pnpm"), { recursive: true }); return fixture; } function addPackage(nodeModulesPath, entryName, packageName) { const entryPath = path.join(nodeModulesPath, ".pnpm", entryName); const packagePath = path.join(entryPath, "node_modules", ...packageName.split("/")); fs.mkdirSync(packagePath, { recursive: true }); fs.writeFileSync(path.join(packagePath, "index.js"), `module.exports = ${JSON.stringify(packageName)};\n`); return { entryPath, packagePath }; } function linkPackage(nodeModulesPath, packageName, target) { const linkPath = path.join(nodeModulesPath, ...packageName.split("/")); fs.mkdirSync(path.dirname(linkPath), { recursive: true }); fs.symlinkSync(path.relative(path.dirname(linkPath), target), linkPath); }