diff --git a/build/npm/postinstall.ts b/build/npm/postinstall.ts index 5ab9d682857..db659fa78a4 100644 --- a/build/npm/postinstall.ts +++ b/build/npm/postinstall.ts @@ -182,6 +182,32 @@ function clearInheritedNpmrcConfig(dir: string, env: NodeJS.ProcessEnv): void { } } +function ensureAgentHarnessLink(sourceRelativePath: string, linkPath: string): 'existing' | 'junction' | 'symlink' | 'hard link' { + if (fs.existsSync(linkPath)) { + return 'existing'; + } + + const sourcePath = path.resolve(path.dirname(linkPath), sourceRelativePath); + const isDirectory = fs.statSync(sourcePath).isDirectory(); + + try { + if (process.platform === 'win32' && isDirectory) { + fs.symlinkSync(sourcePath, linkPath, 'junction'); + return 'junction'; + } + + fs.symlinkSync(sourceRelativePath, linkPath, isDirectory ? 'dir' : 'file'); + return 'symlink'; + } catch (error) { + if (process.platform === 'win32' && !isDirectory && (error as NodeJS.ErrnoException).code === 'EPERM') { + fs.linkSync(sourcePath, linkPath); + return 'hard link'; + } + + throw error; + } +} + async function runWithConcurrency(tasks: (() => Promise)[], concurrency: number): Promise { const errors: Error[] = []; let index = 0; @@ -294,15 +320,15 @@ async function main() { fs.mkdirSync(claudeDir, { recursive: true }); const claudeMdLink = path.join(claudeDir, 'CLAUDE.md'); - if (!fs.existsSync(claudeMdLink)) { - fs.symlinkSync(path.join('..', '.github', 'copilot-instructions.md'), claudeMdLink); - log('.', 'Symlinked .claude/CLAUDE.md -> .github/copilot-instructions.md'); + const claudeMdLinkType = ensureAgentHarnessLink(path.join('..', '.github', 'copilot-instructions.md'), claudeMdLink); + if (claudeMdLinkType !== 'existing') { + log('.', `Created ${claudeMdLinkType} .claude/CLAUDE.md -> .github/copilot-instructions.md`); } const claudeSkillsLink = path.join(claudeDir, 'skills'); - if (!fs.existsSync(claudeSkillsLink)) { - fs.symlinkSync(path.join('..', '.agents', 'skills'), claudeSkillsLink); - log('.', 'Symlinked .claude/skills -> .agents/skills'); + const claudeSkillsLinkType = ensureAgentHarnessLink(path.join('..', '.agents', 'skills'), claudeSkillsLink); + if (claudeSkillsLinkType !== 'existing') { + log('.', `Created ${claudeSkillsLinkType} .claude/skills -> .agents/skills`); } // Temporary: patch @github/copilot-sdk session.js to fix ESM import