Skip to content

Commit 83f3f67

Browse files
committed
fix(sync): link skills through the project's node_modules
import.meta.url resolves to the real path, which under pnpm is a hashed node_modules/.pnpm/<hash>/ directory. Links into it dangle once a reinstall changes the hash.
1 parent 9bf30a9 commit 83f3f67

2 files changed

Lines changed: 21 additions & 6 deletions

File tree

‎.changeset/sync-stable-links.md‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@bomb.sh/tools': patch
3+
---
4+
5+
Fixes `bsh sync` creating skill symlinks that break after reinstalling dependencies with pnpm

‎src/commands/sync.ts‎

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { readlink, rm, symlink } from 'node:fs/promises';
1+
import { readlink, realpath, rm, symlink } from 'node:fs/promises';
22
import { findPackageJSON } from 'node:module';
33
import { cwd, env, platform } from 'node:process';
44
import { fileURLToPath, pathToFileURL } from 'node:url';
@@ -22,7 +22,7 @@ export async function sync(_ctx: CommandContext): Promise<void> {
2222
}
2323

2424
const root = new URL('./', pathToFileURL(parentPkg));
25-
const source = new URL('../../skills/', import.meta.url);
25+
const source = await resolveSkillsSource(root, new URL('../../skills/', import.meta.url));
2626

2727
if (!(await hfs.isDirectory(source))) {
2828
console.error('Could not locate bundled skills directory.');
@@ -36,8 +36,15 @@ 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;
39+
/**
40+
* Prefer linking through the project's `node_modules/@bomb.sh/tools` over
41+
* `import.meta.url`, which resolves to the real path. Under pnpm that is a
42+
* versioned `node_modules/.pnpm/<hash>/` directory, so links into it dangle
43+
* once a reinstall changes the hash.
44+
*/
45+
export async function resolveSkillsSource(root: URL, fallback: URL): Promise<URL> {
46+
const linked = new URL('node_modules/@bomb.sh/tools/skills/', root);
47+
return (await hfs.isDirectory(linked)) ? linked : fallback;
4148
}
4249

4350
interface SkillInfo {
@@ -93,14 +100,17 @@ async function pruneStaleLinks(options: {
93100
const { dest, source, keep } = options;
94101
if (!(await hfs.isDirectory(dest))) return;
95102

103+
// Links from older syncs may target the real path rather than `source`.
104+
const sources = [source.href, `${pathToFileURL(await realpath(source)).href}/`];
105+
96106
for await (const entry of hfs.list(dest)) {
97107
if (!entry.isSymlink) continue;
98108
if (keep.has(entry.name)) continue;
99109

100110
const linkPath = fileURLToPath(new URL(entry.name, dest));
101111
try {
102-
const target = await readlink(linkPath);
103-
if (resolveLinkTarget(dest, target).href.startsWith(source.href)) {
112+
const { href } = resolveLinkTarget(dest, await readlink(linkPath));
113+
if (sources.some((s) => href.startsWith(s))) {
104114
await hfs.deleteAll(linkPath);
105115
}
106116
} catch {

0 commit comments

Comments
 (0)