Skip to content

Commit a2a42a1

Browse files
committed
fix: drop node <=18 require's
1 parent 0aa9eae commit a2a42a1

6 files changed

Lines changed: 36 additions & 83 deletions

File tree

src/cli.ts

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ import type { AgentType, OptimizeModel } from './agent'
33
import type { PackageUsage } from './agent/detect-imports'
44
import type { ProjectState } from './core'
55
import { existsSync, readFileSync, realpathSync } from 'node:fs'
6-
import { createRequire } from 'node:module'
76
import * as p from '@clack/prompts'
87
import { defineCommand, runMain } from 'citty'
98
import pLimit from 'p-limit'
@@ -14,16 +13,15 @@ import { getProjectState, hasConfig, isOutdated, readConfig, updateConfig } from
1413
import { timedSpinner } from './core/formatting'
1514
import { fetchLatestVersion, fetchNpmRegistryMeta } from './sources'
1615

16+
import { version } from './version'
17+
1718
// Suppress node:sqlite ExperimentalWarning (loaded lazily by retriv)
1819
const _emit = process.emit
1920
process.emit = (event: string, ...args: any[]) =>
2021
event === 'warning' && args[0]?.name === 'ExperimentalWarning' && args[0]?.message?.includes('SQLite')
2122
? false
2223
: _emit.apply(process, [event, ...args])
2324

24-
const require = createRequire(import.meta.url)
25-
const { version } = require('../package.json')
26-
2725
// ── Helpers ──
2826

2927
function getRepoHint(name: string, cwd: string): string | undefined {

src/commands/install.ts

Lines changed: 29 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,8 @@
1111

1212
import type { AgentType, CustomPrompt, SkillSection } from '../agent'
1313
import type { SkillInfo } from '../core/lockfile'
14-
import { existsSync, lstatSync, mkdirSync, readdirSync, readFileSync, symlinkSync, unlinkSync, writeFileSync } from 'node:fs'
14+
import { copyFileSync, existsSync, lstatSync, mkdirSync, readdirSync, readFileSync, symlinkSync, unlinkSync, writeFileSync } from 'node:fs'
15+
import { homedir } from 'node:os'
1516
import * as p from '@clack/prompts'
1617
import { join } from 'pathe'
1718
import { agents, getModelLabel, optimizeDocs } from '../agent'
@@ -57,8 +58,7 @@ export async function installCommand(opts: InstallOptions): Promise<void> {
5758
const cwd = process.cwd()
5859
const agent = agents[opts.agent]
5960
const skillsDir = opts.global
60-
// eslint-disable-next-line ts/no-require-imports
61-
? join(require('node:os').homedir(), '.skilld', 'skills')
61+
? join(homedir(), '.skilld', 'skills')
6262
: join(cwd, agent.skillsDir)
6363

6464
// Collect lockfiles from all agent skill dirs and merge
@@ -186,8 +186,10 @@ export async function installCommand(opts: InstallOptions): Promise<void> {
186186
unlinkSync(sectionsLink)
187187
if (existsSync(cachedSections))
188188
symlinkSync(cachedSections, sectionsLink, 'junction')
189-
if (regenerateBaseSkillMd(skillDir, pkgName, version, cwd, allSkillNames, info.source, info.packages))
190-
regenerated.push({ name, pkgName, version, skillDir, packages: info.packages })
189+
if (!copyFromExistingAgent(skillDir, name, allSkillsDirs)) {
190+
if (regenerateBaseSkillMd(skillDir, pkgName, version, cwd, allSkillNames, info.source, info.packages))
191+
regenerated.push({ name, pkgName, version, skillDir, packages: info.packages })
192+
}
191193
spin.stop(`Linked ${name}`)
192194
continue
193195
}
@@ -316,8 +318,10 @@ export async function installCommand(opts: InstallOptions): Promise<void> {
316318
})), { dbPath: getPackageDbPath(pkgName, version) })
317319
}
318320

319-
if (regenerateBaseSkillMd(skillDir, pkgName, version, cwd, allSkillNames, info.source, info.packages))
320-
regenerated.push({ name, pkgName, version, skillDir, packages: info.packages })
321+
if (!copyFromExistingAgent(skillDir, name, allSkillsDirs)) {
322+
if (regenerateBaseSkillMd(skillDir, pkgName, version, cwd, allSkillNames, info.source, info.packages))
323+
regenerated.push({ name, pkgName, version, skillDir, packages: info.packages })
324+
}
321325
spin.stop(`Downloaded and linked ${name}`)
322326
}
323327
else {
@@ -348,6 +352,24 @@ export async function installCommand(opts: InstallOptions): Promise<void> {
348352
p.outro('Install complete')
349353
}
350354

355+
/** Copy SKILL.md from another agent's skill dir if one exists */
356+
function copyFromExistingAgent(skillDir: string, name: string, allSkillsDirs: string[]): boolean {
357+
const targetMd = join(skillDir, 'SKILL.md')
358+
if (existsSync(targetMd))
359+
return false
360+
for (const dir of allSkillsDirs) {
361+
if (dir === skillDir)
362+
continue
363+
const candidateMd = join(dir, name, 'SKILL.md')
364+
if (existsSync(candidateMd) && !lstatSync(candidateMd).isSymbolicLink()) {
365+
mkdirSync(skillDir, { recursive: true })
366+
copyFileSync(candidateMd, targetMd)
367+
return true
368+
}
369+
}
370+
return false
371+
}
372+
351373
/** Try to recover original package name from sanitized name + source */
352374
function unsanitizeName(sanitized: string, source?: string): string {
353375
if (source?.includes('ungh://')) {

src/commands/status.ts

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import type { AgentType } from '../agent'
22
import type { SkillInfo } from '../core/lockfile'
33
import { existsSync, readdirSync, statSync } from 'node:fs'
4-
import { createRequire } from 'node:module'
54
import * as p from '@clack/prompts'
65
import { join } from 'pathe'
76
import { agents, getAgentVersion } from '../agent'
@@ -12,9 +11,7 @@ import { formatSource, timeAgo } from '../core/formatting'
1211
import { parsePackages } from '../core/lockfile'
1312

1413
import { iterateSkills } from '../core/skills'
15-
16-
const require = createRequire(import.meta.url)
17-
const { version: skilldVersion } = require('../package.json')
14+
import { version as skilldVersion } from '../version'
1815

1916
export interface StatusOptions {
2017
global?: boolean

src/version.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
import { createRequire } from 'node:module'
2+
3+
const require = createRequire(import.meta.url)
4+
export const version = require('../package.json').version

test/unit/cache-storage.test.ts

Lines changed: 0 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -74,42 +74,6 @@ describe('cache/storage', () => {
7474
})
7575
})
7676

77-
describe('linkReferences', () => {
78-
it('removes existing link before creating new one', async () => {
79-
const { existsSync, lstatSync, unlinkSync, symlinkSync } = await import('node:fs')
80-
const { linkReferences } = await import('../../src/cache/storage')
81-
// cachedDocsPath exists
82-
vi.mocked(existsSync).mockReturnValue(true)
83-
// lstatSync returns a symlink stat for existing link
84-
vi.mocked(lstatSync).mockReturnValue({ isSymbolicLink: () => true, isFile: () => false } as any)
85-
86-
linkReferences('/project/.claude/skills/vue', 'vue', '3.4.0')
87-
88-
expect(unlinkSync).toHaveBeenCalled()
89-
expect(symlinkSync).toHaveBeenCalledWith(
90-
expect.stringContaining('vue@3.4'),
91-
expect.stringContaining('.skilld/docs'),
92-
'junction',
93-
)
94-
})
95-
96-
it('skips unlink if no existing link', async () => {
97-
const { existsSync, lstatSync, unlinkSync, symlinkSync } = await import('node:fs')
98-
const { linkReferences } = await import('../../src/cache/storage')
99-
// cachedDocsPath exists
100-
vi.mocked(existsSync).mockReturnValue(true)
101-
// lstatSync throws ENOENT (no existing link)
102-
vi.mocked(lstatSync).mockImplementation(() => {
103-
throw new Error('ENOENT')
104-
})
105-
106-
linkReferences('/project/.claude/skills/vue', 'vue', '3.4.0')
107-
108-
expect(unlinkSync).not.toHaveBeenCalled()
109-
expect(symlinkSync).toHaveBeenCalled()
110-
})
111-
})
112-
11377
describe('listCached', () => {
11478
it('returns empty array when references dir missing', async () => {
11579
const { existsSync } = await import('node:fs')

test/unit/cache.test.ts

Lines changed: 0 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -124,36 +124,4 @@ describe('cache', () => {
124124
expect(writeFileSync).toHaveBeenCalledTimes(2)
125125
})
126126
})
127-
128-
describe('linkReferences', () => {
129-
beforeEach(() => {
130-
vi.resetAllMocks()
131-
})
132-
133-
it('removes existing link before creating new one', async () => {
134-
const { existsSync, lstatSync, unlinkSync, symlinkSync } = await import('node:fs')
135-
const { linkReferences } = await import('../../src/cache')
136-
vi.mocked(existsSync).mockReturnValue(true)
137-
vi.mocked(lstatSync).mockReturnValue({ isSymbolicLink: () => true, isFile: () => false } as any)
138-
139-
linkReferences('/project/.claude/skills/vue', 'vue', '3.4.0')
140-
141-
expect(unlinkSync).toHaveBeenCalled()
142-
expect(symlinkSync).toHaveBeenCalled()
143-
})
144-
145-
it('creates symlink without unlinking if no existing link', async () => {
146-
const { existsSync, lstatSync, unlinkSync, symlinkSync } = await import('node:fs')
147-
const { linkReferences } = await import('../../src/cache')
148-
vi.mocked(existsSync).mockReturnValue(true)
149-
vi.mocked(lstatSync).mockImplementation(() => {
150-
throw new Error('ENOENT')
151-
})
152-
153-
linkReferences('/project/.claude/skills/vue', 'vue', '3.4.0')
154-
155-
expect(unlinkSync).not.toHaveBeenCalled()
156-
expect(symlinkSync).toHaveBeenCalled()
157-
})
158-
})
159127
})

0 commit comments

Comments
 (0)