joyskills-cli 0.3.3 → 0.3.4
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.
- package/package.json +1 -1
- package/src/commands/install.js +24 -0
- package/src/commands/update.js +42 -6
- package/src/version-checker.js +36 -8
package/package.json
CHANGED
package/src/commands/install.js
CHANGED
|
@@ -14,6 +14,22 @@ import chalk from 'chalk';
|
|
|
14
14
|
// Cache directory for cloned repos
|
|
15
15
|
const CACHE_DIR = process.env.JOYSKILL_CACHE_DIR || path.join(os.homedir(), '.joyskill', 'cache');
|
|
16
16
|
|
|
17
|
+
/**
|
|
18
|
+
* Get git commit hash for a directory
|
|
19
|
+
*/
|
|
20
|
+
async function getGitCommit(dirPath) {
|
|
21
|
+
try {
|
|
22
|
+
const git = simpleGit(dirPath);
|
|
23
|
+
const isRepo = await git.checkIsRepo();
|
|
24
|
+
if (!isRepo) return null;
|
|
25
|
+
|
|
26
|
+
const log = await git.log({ maxCount: 1 });
|
|
27
|
+
return log.latest?.hash || null;
|
|
28
|
+
} catch {
|
|
29
|
+
return null;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
17
33
|
export function installCommand(program) {
|
|
18
34
|
program
|
|
19
35
|
.command('install [skill]')
|
|
@@ -252,6 +268,9 @@ async function tryInstallFromRegistryDir(skillName, registryDirPath, targetDir,
|
|
|
252
268
|
const installInfo = fs.lstatSync(targetPath);
|
|
253
269
|
const installMethod = installInfo.isSymbolicLink() ? 'symlink' : 'copy';
|
|
254
270
|
|
|
271
|
+
// Get git commit for the skill source
|
|
272
|
+
const commitHash = await getGitCommit(sourcePath);
|
|
273
|
+
|
|
255
274
|
const lockfileManager = new LockfileManager(projectRoot);
|
|
256
275
|
await lockfileManager.load();
|
|
257
276
|
lockfileManager.updateSkill(skillName, {
|
|
@@ -259,6 +278,7 @@ async function tryInstallFromRegistryDir(skillName, registryDirPath, targetDir,
|
|
|
259
278
|
source: sourceLabel,
|
|
260
279
|
registry: registryManager.getRegistryInfo().registryId,
|
|
261
280
|
installMethod,
|
|
281
|
+
commit: commitHash,
|
|
262
282
|
installedAt: new Date().toISOString()
|
|
263
283
|
});
|
|
264
284
|
await lockfileManager.save();
|
|
@@ -279,11 +299,15 @@ async function tryInstallFromRegistryDir(skillName, registryDirPath, targetDir,
|
|
|
279
299
|
const targetPath = path.join(targetDir, skillName);
|
|
280
300
|
copyRecursive(skill.path, targetPath);
|
|
281
301
|
|
|
302
|
+
// Get git commit for the skill source
|
|
303
|
+
const commitHash = await getGitCommit(skill.path);
|
|
304
|
+
|
|
282
305
|
const lockfileManager = new LockfileManager(projectRoot);
|
|
283
306
|
await lockfileManager.load();
|
|
284
307
|
lockfileManager.updateSkill(skillName, {
|
|
285
308
|
version: skill.version || '1.0.0',
|
|
286
309
|
source: sourceLabel,
|
|
310
|
+
commit: commitHash,
|
|
287
311
|
installedAt: new Date().toISOString()
|
|
288
312
|
});
|
|
289
313
|
await lockfileManager.save();
|
package/src/commands/update.js
CHANGED
|
@@ -119,6 +119,22 @@ async function getAllUpdatableSkills(projectRoot) {
|
|
|
119
119
|
return updatable;
|
|
120
120
|
}
|
|
121
121
|
|
|
122
|
+
/**
|
|
123
|
+
* 获取目录的 git commit hash
|
|
124
|
+
*/
|
|
125
|
+
async function getDirCommit(dirPath) {
|
|
126
|
+
try {
|
|
127
|
+
const git = simpleGit(dirPath);
|
|
128
|
+
const isRepo = await git.checkIsRepo();
|
|
129
|
+
if (!isRepo) return null;
|
|
130
|
+
|
|
131
|
+
const log = await git.log({ maxCount: 1 });
|
|
132
|
+
return log.latest?.hash || null;
|
|
133
|
+
} catch {
|
|
134
|
+
return null;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
122
138
|
/**
|
|
123
139
|
* 更新 team:// 类型的 Skill
|
|
124
140
|
*/
|
|
@@ -126,7 +142,10 @@ async function updateTeamSkill(skill, projectRoot) {
|
|
|
126
142
|
const lockfile = new LockfileManager(projectRoot);
|
|
127
143
|
await lockfile.load();
|
|
128
144
|
|
|
129
|
-
const
|
|
145
|
+
const lockData = lockfile.getSkill(skill.name);
|
|
146
|
+
const localCommit = lockData?.commit;
|
|
147
|
+
|
|
148
|
+
const updateInfo = await checkTeamSkillUpdate(skill, skill.source, localCommit);
|
|
130
149
|
|
|
131
150
|
if (!updateInfo.hasUpdate) {
|
|
132
151
|
throw new Error('No update available');
|
|
@@ -140,10 +159,14 @@ async function updateTeamSkill(skill, projectRoot) {
|
|
|
140
159
|
// 复制新版本
|
|
141
160
|
copyRecursive(updateInfo.registrySkillPath, skill.path);
|
|
142
161
|
|
|
162
|
+
// 获取新版本的 commit
|
|
163
|
+
const newCommit = await getDirCommit(updateInfo.registrySkillPath);
|
|
164
|
+
|
|
143
165
|
// 更新 lockfile
|
|
144
166
|
lockfile.updateSkill(skill.name, {
|
|
145
167
|
version: updateInfo.latestVersion,
|
|
146
168
|
source: skill.source,
|
|
169
|
+
commit: newCommit,
|
|
147
170
|
updatedAt: new Date().toISOString(),
|
|
148
171
|
});
|
|
149
172
|
await lockfile.save();
|
|
@@ -199,7 +222,8 @@ async function getSpecifiedSkills(projectRoot, skillNames) {
|
|
|
199
222
|
|
|
200
223
|
if (teamSource) {
|
|
201
224
|
// team:// 类型
|
|
202
|
-
const
|
|
225
|
+
const localCommit = lockData?.commit;
|
|
226
|
+
const updateInfo = await checkTeamSkillUpdate(skill, teamSource, localCommit);
|
|
203
227
|
if (updateInfo.hasUpdate) {
|
|
204
228
|
skills.push({
|
|
205
229
|
name: skill.name,
|
|
@@ -238,7 +262,7 @@ async function getSpecifiedSkills(projectRoot, skillNames) {
|
|
|
238
262
|
/**
|
|
239
263
|
* 检查 team:// 类型的 Skill 是否有更新
|
|
240
264
|
*/
|
|
241
|
-
async function checkTeamSkillUpdate(skill, source) {
|
|
265
|
+
async function checkTeamSkillUpdate(skill, source, localCommit = null) {
|
|
242
266
|
const registryName = source.replace('team:', '');
|
|
243
267
|
const JOYSKILL_CONFIG_DIR = process.env.JOYSKILL_CONFIG_DIR || path.join(os.homedir(), '.joyskill');
|
|
244
268
|
const configPath = path.join(JOYSKILL_CONFIG_DIR, 'config.json');
|
|
@@ -254,7 +278,7 @@ async function checkTeamSkillUpdate(skill, source) {
|
|
|
254
278
|
return { hasUpdate: false, error: `Registry ${registryName} not found` };
|
|
255
279
|
}
|
|
256
280
|
|
|
257
|
-
// 从 registry
|
|
281
|
+
// 从 registry 查找 skill
|
|
258
282
|
const { findSkills } = await import('./install.js');
|
|
259
283
|
const registrySkills = await findSkills(reg.path);
|
|
260
284
|
const registrySkill = registrySkills.find(s => s.name === skill.name);
|
|
@@ -263,11 +287,22 @@ async function checkTeamSkillUpdate(skill, source) {
|
|
|
263
287
|
return { hasUpdate: false, error: 'Skill not found in registry' };
|
|
264
288
|
}
|
|
265
289
|
|
|
266
|
-
|
|
290
|
+
// 获取 registry 中 skill 的 commit
|
|
291
|
+
const remoteCommit = await getDirCommit(registrySkill.path);
|
|
292
|
+
|
|
293
|
+
// 使用 commit 对比,如果没有 commit 则 fallback 到 version 对比
|
|
294
|
+
let hasUpdate = false;
|
|
295
|
+
if (localCommit && remoteCommit) {
|
|
296
|
+
hasUpdate = localCommit !== remoteCommit;
|
|
297
|
+
} else {
|
|
298
|
+
hasUpdate = registrySkill.version && registrySkill.version !== skill.version;
|
|
299
|
+
}
|
|
267
300
|
|
|
268
301
|
return {
|
|
269
302
|
hasUpdate,
|
|
270
303
|
latestVersion: registrySkill.version,
|
|
304
|
+
localCommit,
|
|
305
|
+
remoteCommit,
|
|
271
306
|
registryPath: reg.path,
|
|
272
307
|
registrySkillPath: registrySkill.path,
|
|
273
308
|
};
|
|
@@ -348,7 +383,8 @@ async function getUpdatableTeamSkills(projectRoot) {
|
|
|
348
383
|
for (const skill of allSkills) {
|
|
349
384
|
const lockData = lockfile.getSkill(skill.name);
|
|
350
385
|
if (lockData?.source?.startsWith('team:')) {
|
|
351
|
-
const
|
|
386
|
+
const localCommit = lockData?.commit;
|
|
387
|
+
const updateInfo = await checkTeamSkillUpdate(skill, lockData.source, localCommit);
|
|
352
388
|
if (updateInfo.hasUpdate) {
|
|
353
389
|
updatable.push({
|
|
354
390
|
name: skill.name,
|
package/src/version-checker.js
CHANGED
|
@@ -80,8 +80,9 @@ export async function checkRemoteUpdate(skillPath, currentCommit) {
|
|
|
80
80
|
* 检查单个 Skill
|
|
81
81
|
*/
|
|
82
82
|
export async function checkSkill(skill, projectRoot) {
|
|
83
|
-
// 首先尝试从 lockfile 获取 source
|
|
83
|
+
// 首先尝试从 lockfile 获取 source 和 commit
|
|
84
84
|
let teamSource = null;
|
|
85
|
+
let localCommit = null;
|
|
85
86
|
|
|
86
87
|
if (projectRoot) {
|
|
87
88
|
const lockfile = new LockfileManager(projectRoot);
|
|
@@ -89,6 +90,7 @@ export async function checkSkill(skill, projectRoot) {
|
|
|
89
90
|
const lockData = lockfile.getSkill(skill.name);
|
|
90
91
|
if (lockData?.source?.startsWith('team:')) {
|
|
91
92
|
teamSource = lockData.source;
|
|
93
|
+
localCommit = lockData.commit;
|
|
92
94
|
}
|
|
93
95
|
}
|
|
94
96
|
|
|
@@ -97,9 +99,9 @@ export async function checkSkill(skill, projectRoot) {
|
|
|
97
99
|
teamSource = await findSkillInRegistries(skill.name);
|
|
98
100
|
}
|
|
99
101
|
|
|
100
|
-
// 如果是 team:// skill,对比 registry
|
|
102
|
+
// 如果是 team:// skill,对比 registry commit
|
|
101
103
|
if (teamSource) {
|
|
102
|
-
return await checkTeamSkill(skill, teamSource);
|
|
104
|
+
return await checkTeamSkill(skill, teamSource, localCommit);
|
|
103
105
|
}
|
|
104
106
|
|
|
105
107
|
// Git skill: 对比 commit
|
|
@@ -157,10 +159,26 @@ async function findSkillInRegistries(skillName) {
|
|
|
157
159
|
return null;
|
|
158
160
|
}
|
|
159
161
|
|
|
162
|
+
/**
|
|
163
|
+
* 获取目录的 git commit hash
|
|
164
|
+
*/
|
|
165
|
+
async function getDirCommit(dirPath) {
|
|
166
|
+
try {
|
|
167
|
+
const git = simpleGit(dirPath);
|
|
168
|
+
const isRepo = await git.checkIsRepo();
|
|
169
|
+
if (!isRepo) return null;
|
|
170
|
+
|
|
171
|
+
const log = await git.log({ maxCount: 1 });
|
|
172
|
+
return log.latest?.hash || null;
|
|
173
|
+
} catch {
|
|
174
|
+
return null;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
160
178
|
/**
|
|
161
179
|
* 检查 team:// skill 更新
|
|
162
180
|
*/
|
|
163
|
-
async function checkTeamSkill(skill, source) {
|
|
181
|
+
async function checkTeamSkill(skill, source, localCommit) {
|
|
164
182
|
const registryName = source.replace('team:', '');
|
|
165
183
|
const JOYSKILL_CONFIG_DIR = process.env.JOYSKILL_CONFIG_DIR || path.join(os.homedir(), '.joyskill');
|
|
166
184
|
const configPath = path.join(JOYSKILL_CONFIG_DIR, 'config.json');
|
|
@@ -188,7 +206,7 @@ async function checkTeamSkill(skill, source) {
|
|
|
188
206
|
};
|
|
189
207
|
}
|
|
190
208
|
|
|
191
|
-
// 从 registry
|
|
209
|
+
// 从 registry 查找 skill
|
|
192
210
|
const { findSkills } = await import('./commands/install.js');
|
|
193
211
|
const registrySkills = await findSkills(reg.path);
|
|
194
212
|
const registrySkill = registrySkills.find(s => s.name === skill.name);
|
|
@@ -203,14 +221,24 @@ async function checkTeamSkill(skill, source) {
|
|
|
203
221
|
};
|
|
204
222
|
}
|
|
205
223
|
|
|
206
|
-
|
|
224
|
+
// 获取 registry 中 skill 的最新 commit
|
|
225
|
+
const remoteCommit = await getDirCommit(registrySkill.path);
|
|
226
|
+
|
|
227
|
+
// 如果没有 localCommit(旧版本安装的),使用 version 对比作为 fallback
|
|
228
|
+
let hasUpdate = false;
|
|
229
|
+
if (localCommit && remoteCommit) {
|
|
230
|
+
hasUpdate = localCommit !== remoteCommit;
|
|
231
|
+
} else {
|
|
232
|
+
hasUpdate = registrySkill.version && registrySkill.version !== skill.version;
|
|
233
|
+
}
|
|
207
234
|
|
|
208
235
|
return {
|
|
209
236
|
name: skill.name,
|
|
210
237
|
hasUpdate,
|
|
211
238
|
currentVersion: skill.version,
|
|
212
|
-
|
|
213
|
-
|
|
239
|
+
localCommit: typeof localCommit === 'string' ? localCommit.slice(0, 7) : 'unknown',
|
|
240
|
+
remoteCommit: typeof remoteCommit === 'string' ? remoteCommit.slice(0, 7) : 'unknown',
|
|
241
|
+
commitsBehind: hasUpdate ? 1 : 0,
|
|
214
242
|
source: `team:${registryName}`,
|
|
215
243
|
};
|
|
216
244
|
}
|