|
1 | | -import { lstat, readlink } from 'node:fs/promises'; |
| 1 | +import { lstat, readlink, realpath, symlink } from 'node:fs/promises'; |
2 | 2 | import { fileURLToPath } from 'node:url'; |
3 | 3 | import { describe, it, expect } from 'vitest'; |
4 | 4 | import { createFixture, createMocks } from '../test-utils/index.ts'; |
5 | | -import { copySkills, findParentPackage } from './sync.ts'; |
| 5 | +import { copySkills, findParentPackage, resolveSkillsSource } from './sync.ts'; |
6 | 6 |
|
7 | 7 | describe('copySkills', () => { |
8 | 8 | it('symlinks each skill into the destination', async () => { |
@@ -53,6 +53,58 @@ describe('copySkills', () => { |
53 | 53 | expect(await fixture.text('source-skills/build/SKILL.md')).toContain('name: build'); |
54 | 54 | expect(await fixture.text('project/skills/build/SKILL.md')).toContain('name: build'); |
55 | 55 | }); |
| 56 | + |
| 57 | + it('prunes stale links that target the real path of the source', async () => { |
| 58 | + const fixture = await createFixture({ |
| 59 | + store: { |
| 60 | + skills: { |
| 61 | + build: { 'SKILL.md': '---\nname: build\ndescription: Build.\n---\n' }, |
| 62 | + }, |
| 63 | + }, |
| 64 | + project: { |
| 65 | + tools: ({ symlink }) => symlink('../store'), |
| 66 | + skills: {}, |
| 67 | + }, |
| 68 | + }); |
| 69 | + // Left behind by an older sync that linked through the real path. |
| 70 | + await symlink( |
| 71 | + `${await realpath(fileURLToPath(new URL('store/skills/', fixture.root)))}/removed`, |
| 72 | + fileURLToPath(new URL('project/skills/removed', fixture.root)), |
| 73 | + ); |
| 74 | + |
| 75 | + await copySkills({ |
| 76 | + source: new URL('project/tools/skills/', fixture.root), |
| 77 | + dest: new URL('project/skills/', fixture.root), |
| 78 | + }); |
| 79 | + |
| 80 | + await expect( |
| 81 | + lstat(fileURLToPath(new URL('project/skills/removed', fixture.root))), |
| 82 | + ).rejects.toThrow(); |
| 83 | + }); |
| 84 | +}); |
| 85 | + |
| 86 | +describe('resolveSkillsSource', () => { |
| 87 | + it('prefers the project node_modules path over the real store path', async () => { |
| 88 | + const fixture = await createFixture({ |
| 89 | + store: { tools: { skills: { build: { 'SKILL.md': '' } } } }, |
| 90 | + project: { |
| 91 | + 'node_modules/@bomb.sh/tools': ({ symlink }) => symlink('../../../store/tools'), |
| 92 | + }, |
| 93 | + }); |
| 94 | + const root = new URL('project/', fixture.root); |
| 95 | + const fallback = new URL('store/tools/skills/', fixture.root); |
| 96 | + |
| 97 | + expect((await resolveSkillsSource(root, fallback)).href).toBe( |
| 98 | + new URL('node_modules/@bomb.sh/tools/skills/', root).href, |
| 99 | + ); |
| 100 | + }); |
| 101 | + |
| 102 | + it('falls back when the project has no linked @bomb.sh/tools', async () => { |
| 103 | + const fixture = await createFixture({ project: {} }); |
| 104 | + const fallback = new URL('store/tools/skills/', fixture.root); |
| 105 | + |
| 106 | + expect(await resolveSkillsSource(new URL('project/', fixture.root), fallback)).toBe(fallback); |
| 107 | + }); |
56 | 108 | }); |
57 | 109 |
|
58 | 110 | describe('findParentPackage', () => { |
|
0 commit comments