Skip to content

Commit 9bf30a9

Browse files
committed
test(sync): cover stable skill link targets
1 parent 815cf15 commit 9bf30a9

2 files changed

Lines changed: 58 additions & 2 deletions

File tree

‎src/commands/sync.test.ts‎

Lines changed: 54 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1-
import { lstat, readlink } from 'node:fs/promises';
1+
import { lstat, readlink, realpath, symlink } from 'node:fs/promises';
22
import { fileURLToPath } from 'node:url';
33
import { describe, it, expect } from 'vitest';
44
import { createFixture, createMocks } from '../test-utils/index.ts';
5-
import { copySkills, findParentPackage } from './sync.ts';
5+
import { copySkills, findParentPackage, resolveSkillsSource } from './sync.ts';
66

77
describe('copySkills', () => {
88
it('symlinks each skill into the destination', async () => {
@@ -53,6 +53,58 @@ describe('copySkills', () => {
5353
expect(await fixture.text('source-skills/build/SKILL.md')).toContain('name: build');
5454
expect(await fixture.text('project/skills/build/SKILL.md')).toContain('name: build');
5555
});
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+
});
56108
});
57109

58110
describe('findParentPackage', () => {

‎src/commands/sync.ts‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,10 @@ export async function sync(_ctx: CommandContext): Promise<void> {
3636
console.info(`Synced ${skills.length} skills to skills/`);
3737
}
3838

39+
export async function resolveSkillsSource(_root: URL, fallback: URL): Promise<URL> {
40+
return fallback;
41+
}
42+
3943
interface SkillInfo {
4044
name: string;
4145
description: string;

0 commit comments

Comments
 (0)