|
1 | | -import fsSync from "node:fs"; |
| 1 | +import fsSync, { type Dirent } from "node:fs"; |
2 | 2 | import fs from "node:fs/promises"; |
3 | 3 | import path from "node:path"; |
4 | 4 | import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; |
@@ -366,7 +366,18 @@ describe("resolvePluginSkillDirs", () => { |
366 | 366 | }); |
367 | 367 |
|
368 | 368 | describe("publishPluginSkills", () => { |
369 | | - const { publishPluginSkills } = __testing; |
| 369 | + const { isGeneratedPluginSkillEntry, publishPluginSkills, resolvePluginSkillLinkType } = |
| 370 | + __testing; |
| 371 | + |
| 372 | + function withPlatform<T>(platform: NodeJS.Platform, fn: () => T): T { |
| 373 | + const originalPlatform = process.platform; |
| 374 | + Object.defineProperty(process, "platform", { configurable: true, value: platform }); |
| 375 | + try { |
| 376 | + return fn(); |
| 377 | + } finally { |
| 378 | + Object.defineProperty(process, "platform", { configurable: true, value: originalPlatform }); |
| 379 | + } |
| 380 | + } |
370 | 381 |
|
371 | 382 | async function writeSkillDir( |
372 | 383 | parentDir: string, |
@@ -399,6 +410,12 @@ describe("publishPluginSkills", () => { |
399 | 410 | expect(fsSync.readlinkSync(linkB)).toBe(dirB); |
400 | 411 | }); |
401 | 412 |
|
| 413 | + it("uses junction links for plugin skill directories on Windows", async () => { |
| 414 | + expect(resolvePluginSkillLinkType("win32")).toBe("junction"); |
| 415 | + expect(resolvePluginSkillLinkType("linux")).toBe("dir"); |
| 416 | + expect(resolvePluginSkillLinkType("darwin")).toBe("dir"); |
| 417 | + }); |
| 418 | + |
402 | 419 | it("is idempotent: skips symlinks that already point to the same target", async () => { |
403 | 420 | const skillParent = await tempDirs.make("plugin-skills-"); |
404 | 421 | const managedDir = await tempDirs.make("managed-skills-"); |
@@ -446,6 +463,37 @@ describe("publishPluginSkills", () => { |
446 | 463 | expect(fsSync.existsSync(path.join(managedDir, "stale-skill"))).toBe(false); |
447 | 464 | }); |
448 | 465 |
|
| 466 | + it("cleans up stale generated junction-like directories on Windows", async () => { |
| 467 | + const skillParent = await tempDirs.make("plugin-skills-"); |
| 468 | + const managedDir = await tempDirs.make("managed-skills-"); |
| 469 | + |
| 470 | + const dir = await writeSkillDir(skillParent, "current-skill"); |
| 471 | + const staleDir = path.join(managedDir, "stale-skill"); |
| 472 | + await fs.mkdir(staleDir, { recursive: true }); |
| 473 | + |
| 474 | + await withPlatform("win32", async () => { |
| 475 | + publishPluginSkills([dir], { pluginSkillsDir: managedDir }); |
| 476 | + }); |
| 477 | + |
| 478 | + expect(fsSync.existsSync(path.join(managedDir, "current-skill"))).toBe(true); |
| 479 | + expect(fsSync.existsSync(staleDir)).toBe(false); |
| 480 | + }); |
| 481 | + |
| 482 | + it("treats Windows directory entries as generated plugin skill entries", () => { |
| 483 | + const directoryEntry = { |
| 484 | + isDirectory: () => true, |
| 485 | + isSymbolicLink: () => false, |
| 486 | + } as Dirent; |
| 487 | + const regularEntry = { |
| 488 | + isDirectory: () => false, |
| 489 | + isSymbolicLink: () => false, |
| 490 | + } as Dirent; |
| 491 | + |
| 492 | + expect(withPlatform("win32", () => isGeneratedPluginSkillEntry(directoryEntry))).toBe(true); |
| 493 | + expect(withPlatform("linux", () => isGeneratedPluginSkillEntry(directoryEntry))).toBe(false); |
| 494 | + expect(withPlatform("win32", () => isGeneratedPluginSkillEntry(regularEntry))).toBe(false); |
| 495 | + }); |
| 496 | + |
449 | 497 | it("cleans up broken symlinks (dangling)", async () => { |
450 | 498 | const skillParent = await tempDirs.make("plugin-skills-"); |
451 | 499 | const managedDir = await tempDirs.make("managed-skills-"); |
|
0 commit comments