@meistrari/tela-build 1.52.0 → 1.52.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -82,16 +82,65 @@ describe('ensureClaudeSymlink', () => {
|
|
|
82
82
|
expect(existsSync(skillsLink)).toBe(true)
|
|
83
83
|
})
|
|
84
84
|
|
|
85
|
-
it('
|
|
85
|
+
it('links tela-build individually when .claude/skills already exists as a real directory', () => {
|
|
86
86
|
const root = makeTempRoot()
|
|
87
87
|
|
|
88
|
-
mkdirSync(join(root, '.agents/skills'), { recursive: true })
|
|
89
|
-
|
|
88
|
+
mkdirSync(join(root, '.agents/skills/tela-build'), { recursive: true })
|
|
89
|
+
writeFileSync(join(root, '.agents/skills/tela-build/SKILL.md'), '# Test')
|
|
90
|
+
// Developer keeps their own real .claude/skills dir with a personal skill.
|
|
91
|
+
mkdirSync(join(root, '.claude/skills/my-skill'), { recursive: true })
|
|
90
92
|
|
|
91
93
|
ensureClaudeSymlink(root, logger, colors)
|
|
92
94
|
|
|
93
|
-
|
|
95
|
+
// The real directory is preserved, not replaced by a symlink.
|
|
94
96
|
expect(lstatSync(join(root, '.claude/skills')).isSymbolicLink()).toBe(false)
|
|
97
|
+
// The developer's own skill is untouched.
|
|
98
|
+
expect(existsSync(join(root, '.claude/skills/my-skill'))).toBe(true)
|
|
99
|
+
// The generated skill is linked in and readable through .claude/skills.
|
|
100
|
+
expect(lstatSync(join(root, '.claude/skills/tela-build')).isSymbolicLink()).toBe(true)
|
|
101
|
+
expect(readFileSync(join(root, '.claude/skills/tela-build/SKILL.md'), 'utf-8')).toBe('# Test')
|
|
102
|
+
})
|
|
103
|
+
|
|
104
|
+
it('leaves a real .claude/skills/tela-build directory untouched', () => {
|
|
105
|
+
const root = makeTempRoot()
|
|
106
|
+
|
|
107
|
+
mkdirSync(join(root, '.agents/skills/tela-build'), { recursive: true })
|
|
108
|
+
// A stale real copy already sits at the destination.
|
|
109
|
+
mkdirSync(join(root, '.claude/skills/tela-build'), { recursive: true })
|
|
110
|
+
writeFileSync(join(root, '.claude/skills/tela-build/SKILL.md'), '# Stale')
|
|
111
|
+
|
|
112
|
+
ensureClaudeSymlink(root, logger, colors)
|
|
113
|
+
|
|
114
|
+
expect(lstatSync(join(root, '.claude/skills/tela-build')).isSymbolicLink()).toBe(false)
|
|
115
|
+
expect(readFileSync(join(root, '.claude/skills/tela-build/SKILL.md'), 'utf-8')).toBe('# Stale')
|
|
116
|
+
})
|
|
117
|
+
|
|
118
|
+
it('is idempotent when .claude/skills/tela-build is already a symlink', () => {
|
|
119
|
+
const root = makeTempRoot()
|
|
120
|
+
|
|
121
|
+
mkdirSync(join(root, '.agents/skills/tela-build'), { recursive: true })
|
|
122
|
+
mkdirSync(join(root, '.claude/skills'), { recursive: true })
|
|
123
|
+
symlinkSync('../../.agents/skills/tela-build', join(root, '.claude/skills/tela-build'))
|
|
124
|
+
|
|
125
|
+
ensureClaudeSymlink(root, logger, colors)
|
|
126
|
+
|
|
127
|
+
expect(lstatSync(join(root, '.claude/skills/tela-build')).isSymbolicLink()).toBe(true)
|
|
128
|
+
})
|
|
129
|
+
|
|
130
|
+
it('re-points a stale .claude/skills/tela-build symlink at the generated skill', () => {
|
|
131
|
+
const root = makeTempRoot()
|
|
132
|
+
|
|
133
|
+
mkdirSync(join(root, '.agents/skills/tela-build'), { recursive: true })
|
|
134
|
+
writeFileSync(join(root, '.agents/skills/tela-build/SKILL.md'), '# Test')
|
|
135
|
+
mkdirSync(join(root, '.claude/skills'), { recursive: true })
|
|
136
|
+
// A symlink from an earlier setup that points somewhere wrong/broken.
|
|
137
|
+
symlinkSync('../../.agents/skills/stale-target', join(root, '.claude/skills/tela-build'))
|
|
138
|
+
|
|
139
|
+
ensureClaudeSymlink(root, logger, colors)
|
|
140
|
+
|
|
141
|
+
expect(lstatSync(join(root, '.claude/skills/tela-build')).isSymbolicLink()).toBe(true)
|
|
142
|
+
expect(readlinkSync(join(root, '.claude/skills/tela-build'))).toBe('../../.agents/skills/tela-build')
|
|
143
|
+
expect(readFileSync(join(root, '.claude/skills/tela-build/SKILL.md'), 'utf-8')).toBe('# Test')
|
|
95
144
|
})
|
|
96
145
|
|
|
97
146
|
it('creates .agents/skills when .claude is a directory but .agents/skills does not exist', () => {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/* eslint-disable no-console */
|
|
2
|
-
import { existsSync, writeFileSync, mkdirSync, lstatSync, readFileSync, symlinkSync } from 'node:fs'
|
|
2
|
+
import { existsSync, writeFileSync, mkdirSync, lstatSync, readFileSync, symlinkSync, readlinkSync, unlinkSync } from 'node:fs'
|
|
3
3
|
import { defineNuxtModule, createResolver } from 'nuxt/kit'
|
|
4
4
|
import { dirname, resolve } from 'pathe'
|
|
5
5
|
import { collectComponentDocs, generateMarkdown, generateDocsToDirectory } from '../../lib/doc-generator'
|
|
@@ -248,6 +248,10 @@ export function ensureClaudeSymlink(
|
|
|
248
248
|
logger.log(`${colors.gray}[tela/build] ${colors.orange}✗${colors.gray} .claude exists and is not a symlink or directory; keeping .agents as canonical output${colors.reset}`)
|
|
249
249
|
}
|
|
250
250
|
|
|
251
|
+
// The single Skill produced by doc generation. Linked individually so it stays
|
|
252
|
+
// discoverable even when the developer keeps their own real .claude/skills dir.
|
|
253
|
+
const GENERATED_SKILL_NAME = 'tela-build'
|
|
254
|
+
|
|
251
255
|
function ensureSkillSymlinks(
|
|
252
256
|
repositoryRoot: string,
|
|
253
257
|
logger: Pick<Console, 'log'>,
|
|
@@ -266,10 +270,58 @@ function ensureSkillSymlinks(
|
|
|
266
270
|
}
|
|
267
271
|
catch {}
|
|
268
272
|
|
|
269
|
-
|
|
273
|
+
// No .claude/skills yet → link the whole directory in one shot.
|
|
274
|
+
if (!claudeSkillsStats) {
|
|
275
|
+
symlinkSync('../.agents/skills', claudeSkillsPath)
|
|
276
|
+
logger.log(`${colors.gray}[tela/build] ${colors.green}●${colors.gray} Linked .claude/skills → .agents/skills${colors.reset}`)
|
|
277
|
+
return
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
// Already a symlink (e.g. a previous run linked the whole dir) → the generated
|
|
281
|
+
// skill resolves through it; nothing to do.
|
|
282
|
+
if (claudeSkillsStats.isSymbolicLink()) {
|
|
283
|
+
return
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
// .claude/skills is a real directory the developer manages themselves. Don't
|
|
287
|
+
// touch it — just drop a per-skill symlink so the generated skill stays
|
|
288
|
+
// discoverable alongside their own skills.
|
|
289
|
+
ensureGeneratedSkillSymlink(claudeSkillsPath, logger, colors)
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
function ensureGeneratedSkillSymlink(
|
|
293
|
+
claudeSkillsPath: string,
|
|
294
|
+
logger: Pick<Console, 'log'>,
|
|
295
|
+
colors: Record<'gray' | 'orange' | 'green' | 'reset', string>,
|
|
296
|
+
): void {
|
|
297
|
+
const generatedSkillLink = resolve(claudeSkillsPath, GENERATED_SKILL_NAME)
|
|
298
|
+
const expectedTarget = `../../.agents/skills/${GENERATED_SKILL_NAME}`
|
|
299
|
+
|
|
300
|
+
let linkStats: import('node:fs').Stats | null = null
|
|
301
|
+
try {
|
|
302
|
+
linkStats = lstatSync(generatedSkillLink)
|
|
303
|
+
}
|
|
304
|
+
catch {}
|
|
305
|
+
|
|
306
|
+
// Already a symlink → keep it only if it points where we want. A stale or
|
|
307
|
+
// broken target from an earlier setup gets re-pointed at the generated skill.
|
|
308
|
+
if (linkStats?.isSymbolicLink()) {
|
|
309
|
+
if (readlinkSync(generatedSkillLink) === expectedTarget) {
|
|
310
|
+
return
|
|
311
|
+
}
|
|
312
|
+
unlinkSync(generatedSkillLink)
|
|
313
|
+
linkStats = null
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
// A real directory/file already sits here (e.g. a stale copy). Don't clobber
|
|
317
|
+
// developer-owned content; surface it instead.
|
|
318
|
+
if (linkStats) {
|
|
319
|
+
logger.log(`${colors.gray}[tela/build] ${colors.orange}✗${colors.gray} .claude/skills/${GENERATED_SKILL_NAME} exists and is not a symlink; skipping link${colors.reset}`)
|
|
270
320
|
return
|
|
271
321
|
}
|
|
272
322
|
|
|
273
|
-
|
|
274
|
-
|
|
323
|
+
// Target is relative to the link's own directory (.claude/skills):
|
|
324
|
+
// ../../.agents/skills/<skill> → <root>/.agents/skills/<skill>
|
|
325
|
+
symlinkSync(expectedTarget, generatedSkillLink)
|
|
326
|
+
logger.log(`${colors.gray}[tela/build] ${colors.green}●${colors.gray} Linked .claude/skills/${GENERATED_SKILL_NAME} → .agents/skills/${GENERATED_SKILL_NAME}${colors.reset}`)
|
|
275
327
|
}
|