@vibe-forge/workspace-assets 2.0.1 → 2.0.3

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.
@@ -1,208 +0,0 @@
1
- import { access, copyFile, lstat, mkdir, readdir, rename, rm } from 'node:fs/promises'
2
- import { dirname, resolve } from 'node:path'
3
- import process from 'node:process'
4
- import { setTimeout as delay } from 'node:timers/promises'
5
-
6
- import type { Config, SkillsCliConfig } from '@vibe-forge/types'
7
- import { resolveSkillsCliRuntimeConfig } from '@vibe-forge/utils'
8
- import { resolveProjectSharedCachePath } from '@vibe-forge/utils/project-cache-path'
9
- import {
10
- findSkillsCli,
11
- installSkillsCliRefToTemp,
12
- installSkillsCliSkillToTemp,
13
- resolveSkillsCliRegistry,
14
- toSkillSlug
15
- } from '@vibe-forge/utils/skills-cli'
16
-
17
- import type { NormalizedSkillDependency } from './skill-dependencies'
18
-
19
- const INSTALL_LOCK_TIMEOUT_MS = 30_000
20
- const INSTALL_LOCK_RETRY_MS = 100
21
-
22
- const toCacheSegment = (value: string) => (
23
- value
24
- .trim()
25
- .toLowerCase()
26
- .replace(/[^a-z0-9._-]+/g, '-')
27
- .replace(/^-+|-+$/g, '') || 'default'
28
- )
29
-
30
- const pathExists = async (targetPath: string) => {
31
- try {
32
- await access(targetPath)
33
- return true
34
- } catch {
35
- return false
36
- }
37
- }
38
-
39
- const withInstallLock = async <T>(lockDir: string, callback: () => Promise<T>) => {
40
- const start = Date.now()
41
- await mkdir(dirname(lockDir), { recursive: true })
42
-
43
- while (true) {
44
- try {
45
- await mkdir(lockDir)
46
- break
47
- } catch (error) {
48
- if ((error as NodeJS.ErrnoException).code !== 'EEXIST') throw error
49
- if (Date.now() - start > INSTALL_LOCK_TIMEOUT_MS) {
50
- throw new Error(`Timed out waiting for skill dependency install lock ${lockDir}`)
51
- }
52
- await delay(INSTALL_LOCK_RETRY_MS)
53
- }
54
- }
55
-
56
- try {
57
- return await callback()
58
- } finally {
59
- await rm(lockDir, { recursive: true, force: true })
60
- }
61
- }
62
-
63
- const copyRegularFiles = async (sourceDir: string, targetDir: string) => {
64
- let fileCount = 0
65
- const entries = await readdir(sourceDir, { withFileTypes: true })
66
-
67
- await mkdir(targetDir, { recursive: true })
68
-
69
- for (const entry of entries) {
70
- const sourcePath = resolve(sourceDir, entry.name)
71
- const targetPath = resolve(targetDir, entry.name)
72
- const stat = await lstat(sourcePath)
73
-
74
- if (stat.isDirectory()) {
75
- fileCount += await copyRegularFiles(sourcePath, targetPath)
76
- continue
77
- }
78
-
79
- if (!stat.isFile()) continue
80
-
81
- await mkdir(dirname(targetPath), { recursive: true })
82
- await copyFile(sourcePath, targetPath)
83
- fileCount += 1
84
- }
85
-
86
- return fileCount
87
- }
88
-
89
- const pickSearchResult = (results: Awaited<ReturnType<typeof findSkillsCli>>, name: string) => {
90
- const slug = toSkillSlug(name)
91
- return results.find(result => (
92
- result.skill === name ||
93
- toSkillSlug(result.skill) === slug
94
- )) ?? results[0]
95
- }
96
-
97
- const resolveConfiguredSkillsCliConfig = (configs: [Config?, Config?]) => {
98
- const [projectConfig, userConfig] = configs
99
- const merged = {
100
- ...(resolveSkillsCliRuntimeConfig(projectConfig) ?? {}),
101
- ...(resolveSkillsCliRuntimeConfig(userConfig) ?? {})
102
- } satisfies SkillsCliConfig
103
-
104
- return Object.keys(merged).length === 0 ? undefined : merged
105
- }
106
-
107
- const buildInstallDir = (params: {
108
- config?: SkillsCliConfig
109
- cwd: string
110
- skill: string
111
- source: string
112
- }) => {
113
- const registry = resolveSkillsCliRegistry({
114
- config: params.config
115
- }) ?? 'default'
116
- return resolveProjectSharedCachePath(
117
- params.cwd,
118
- process.env,
119
- 'skill-dependencies',
120
- 'skills-cli',
121
- toCacheSegment(params.config?.package ?? 'skills'),
122
- toCacheSegment(params.config?.version ?? 'latest'),
123
- toCacheSegment(registry),
124
- ...params.source.split('/').map(toCacheSegment),
125
- toCacheSegment(params.skill)
126
- )
127
- }
128
-
129
- export const installSkillsCliDependency = async (params: {
130
- cwd: string
131
- configs: [Config?, Config?]
132
- dependency: NormalizedSkillDependency
133
- }) => {
134
- const config = resolveConfiguredSkillsCliConfig(params.configs)
135
- const resolvedTarget = params.dependency.source != null
136
- ? {
137
- skill: params.dependency.name,
138
- source: params.dependency.source
139
- }
140
- : await (async () => {
141
- const searchResults = await findSkillsCli({
142
- config,
143
- query: params.dependency.name
144
- })
145
- const selected = pickSearchResult(searchResults, params.dependency.name)
146
- if (selected == null) {
147
- throw new Error(`Skill ${params.dependency.name} was not found by the skills CLI search.`)
148
- }
149
-
150
- return {
151
- installRef: selected.installRef,
152
- skill: selected.skill,
153
- source: selected.source
154
- }
155
- })()
156
-
157
- const installDir = buildInstallDir({
158
- config,
159
- cwd: params.cwd,
160
- skill: resolvedTarget.skill,
161
- source: resolvedTarget.source
162
- })
163
- const skillPath = resolve(installDir, 'SKILL.md')
164
-
165
- return await withInstallLock(`${installDir}.lock`, async () => {
166
- if (await pathExists(skillPath)) {
167
- return {
168
- installDir,
169
- skillPath
170
- }
171
- }
172
-
173
- const tempInstallDir = `${installDir}.tmp-${process.pid}-${Date.now()}-${Math.random().toString(36).slice(2)}`
174
- await rm(tempInstallDir, { recursive: true, force: true })
175
- await mkdir(tempInstallDir, { recursive: true })
176
-
177
- const installResult = 'installRef' in resolvedTarget
178
- ? await installSkillsCliRefToTemp({
179
- config,
180
- installRef: resolvedTarget.installRef
181
- })
182
- : await installSkillsCliSkillToTemp({
183
- config,
184
- skill: resolvedTarget.skill,
185
- source: resolvedTarget.source
186
- })
187
-
188
- try {
189
- await copyRegularFiles(installResult.installedSkill.sourcePath, tempInstallDir)
190
- if (!await pathExists(resolve(tempInstallDir, 'SKILL.md'))) {
191
- throw new Error(`Skill dependency ${params.dependency.ref} did not include SKILL.md`)
192
- }
193
-
194
- await rm(installDir, { recursive: true, force: true })
195
- await rename(tempInstallDir, installDir)
196
- } catch (error) {
197
- await rm(tempInstallDir, { recursive: true, force: true })
198
- throw error
199
- } finally {
200
- await rm(installResult.tempDir, { recursive: true, force: true })
201
- }
202
-
203
- return {
204
- installDir,
205
- skillPath
206
- }
207
- })
208
- }