create-dwy 0.20.0 → 0.22.0
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/CHANGELOG.md +24 -0
- package/bin/index.js +6 -3
- package/package.json +1 -1
- package/src/cli-help.js +18 -8
- package/src/self-upgrade.js +7 -0
- package/src/skill-store.js +182 -0
- package/src/sync-all.js +232 -51
- package/src/sync-codex.js +47 -30
- package/src/sync-cursor.js +0 -25
- package/src/sync-global-skills.js +65 -14
- package/src/sync-opencode.js +10 -18
- package/src/sync.js +23 -92
- package/src/utils.js +0 -1
- package/templates/ai-tools/{CLAUDE.md → AGENTS.md} +33 -1
- package/templates/ai-tools/CHANGELOG.md +6 -0
- package/templates/ai-tools/VERSION +1 -1
- package/templates/ai-tools/skills/DolphinDB/dwy-dolphindb/references/official/progr/sql/createExternalTable.md +2 -2
- package/templates/ai-tools/skills/DolphinDB/dwy-dolphindb/references/official/tools/superset.md +1 -1
- package/templates/ai-tools/skills/DolphinDB/dwy-dolphindb/references/official/tutorials/Migrate_data_from_InfluxDB_to_DolphinDB.md +1 -1
- package/templates/ai-tools/skills/Python/dwy-eapi/references/tasks-integration-guide.md +4 -4
- package/templates/ai-tools/skills/Vue/dwy-eui/references/eui-integration-guide.md +12 -0
- package/templates/ai-tools/skills/Vue/dwy-eui/shared/config-theme.md +2 -0
- package/templates/ai-tools/skills/Vue/dwy-eui/shared/form.md +10 -0
- package/templates/ai-tools/skills/Vue/dwy-eui/v2/index.md +5 -0
- package/templates/ai-tools/skills//344/272/247/345/223/2010/345/210/2601/dwy-stage-dev/SKILL.md +1 -1
- package/templates/ai-tools/skills//345/217/221/345/270/203/345/217/221/347/211/210/dwy-publish/SKILL.md +18 -25
- package/templates/ai-tools/skills//345/217/221/345/270/203/345/217/221/347/211/210/dwy-publish/references/android.md +3 -3
- package/templates/ai-tools/skills//345/217/221/345/270/203/345/217/221/347/211/210/dwy-publish/references/backend-deploy.md +4 -4
- package/templates/ai-tools/skills//345/217/221/345/270/203/345/217/221/347/211/210/dwy-publish/references/changelog.md +6 -70
- package/templates/ai-tools/skills//345/217/221/345/270/203/345/217/221/347/211/210/dwy-publish/references/frontend-deploy.md +3 -3
- package/templates/ai-tools/skills//345/217/221/345/270/203/345/217/221/347/211/210/dwy-publish/references/generic-release.md +5 -5
- package/templates/ai-tools/skills//345/217/221/345/270/203/345/217/221/347/211/210/dwy-publish/references/git-tag.md +4 -79
- package/templates/ai-tools/skills//345/217/221/345/270/203/345/217/221/347/211/210/dwy-publish/references/harmony.md +2 -2
- package/templates/ai-tools/skills//345/217/221/345/270/203/345/217/221/347/211/210/dwy-publish/references/ios.md +3 -3
- package/templates/ai-tools/skills//345/217/221/345/270/203/345/217/221/347/211/210/dwy-publish/references/publish-config.md +4 -4
- package/templates/ai-tools/skills//345/217/221/345/270/203/345/217/221/347/211/210/dwy-publish/references/version-sync.md +7 -7
- package/templates/ai-tools/skills//345/217/221/345/270/203/345/217/221/347/211/210/dwy-semver/SKILL.md +3 -7
- package/templates/ai-tools/skills//351/200/232/347/224/250/dwy-development-review/SKILL.md +58 -0
- package/templates/ai-tools/skills//351/200/232/347/224/250/dwy-development-review/references/delegation-guide.md +46 -0
- package/templates/ai-tools/skills//351/200/232/347/224/250/dwy-development-review/references/review-guide.md +57 -0
- package/templates/ai-tools/skills//351/200/232/347/224/250/dwy-shared/SKILL.md +1 -1
- package/templates/project/fullstack-monorepo/{CLAUDE.md.hbs → AGENTS.md.hbs} +1 -1
- /package/templates/project/backend/{CLAUDE.md.hbs → AGENTS.md.hbs} +0 -0
- /package/templates/project/fullstack-monorepo/backend/{CLAUDE.md.hbs → AGENTS.md.hbs} +0 -0
- /package/templates/project/fullstack-monorepo/frontend/{CLAUDE.md.hbs → AGENTS.md.hbs} +0 -0
- /package/templates/project/web/{CLAUDE.md.hbs → AGENTS.md.hbs} +0 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,29 @@
|
|
|
1
1
|
# create-dwy
|
|
2
2
|
|
|
3
|
+
## [0.22.0] - 2026-11-02
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- Skills 改为用户目录 `.dwy/skills/<name>` 内的一份共享副本,项目和全局入口使用整个目录的软链接;同平台已有有效全局入口时清理项目重复入口。原真实目录先备份,自定义 Skills、Rules 和 Hooks 保留。
|
|
8
|
+
- 新增 `dwy migrate`,在老项目根目录一键迁移已有托管 Skills,无需重新勾选;重复执行不重复备份,已移除的历史模板和已有整个 Skills 根目录链接保留并报告。
|
|
9
|
+
- 新增 `dwy refresh`,使用当前 CLI 包内模板刷新所有已安装的共享副本;`dwy upgrade` 升级成功后自动调用新进程刷新,所有已迁移项目无需分别更新。
|
|
10
|
+
- 共享副本刷新加入互斥及整批失败恢复;扫描和清理统一保护目录链接目标,防止自链和穿透根链接误删文件。全局默认选择覆盖自定义 `CODEX_HOME`。
|
|
11
|
+
- CHANGELOG 规则明确要求每个版本条目包含版本号和日期,禁止 `Unreleased` 等占位标题,与是否发布无关;发布记录提取兼容带日期和方括号的版本标题。
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
## 0.21.0
|
|
16
|
+
|
|
17
|
+
### Minor Changes
|
|
18
|
+
|
|
19
|
+
- 通用基线默认写入所选 AI CLI 的全局入口:Claude Code 使用 `~/.claude/CLAUDE.md`,Codex 使用 `$CODEX_HOME/AGENTS.md`(默认 `~/.codex/AGENTS.md`),OpenCode 使用 `~/.config/opencode/AGENTS.md`。
|
|
20
|
+
- Claude Code、Codex、OpenCode 的所选 Rules 共用项目根 `AGENTS.md` 的托管规则块,项目不再重复注入通用基线。位置选择仅控制 Skills / Hooks;Cursor Rules 始终写入项目。
|
|
21
|
+
- 基线源文件及项目脚手架模板统一使用 `AGENTS.md`,新增新功能开发前的官方/成熟社区工具发现与风险评估规则。
|
|
22
|
+
- 迁移可识别的旧托管基线和 Rules,保留个人内容;全局历史 Rules 不再成为新项目的默认选择,切换 CLI 后取消的共享规则不会重新勾选。
|
|
23
|
+
- 更新确认预览、命令行帮助和接入说明,覆盖全局基线、项目 Rules、旧配置迁移及取消无写入的验证。
|
|
24
|
+
|
|
25
|
+
---
|
|
26
|
+
|
|
3
27
|
## 0.20.0
|
|
4
28
|
|
|
5
29
|
### Minor Changes
|
package/bin/index.js
CHANGED
|
@@ -3,7 +3,8 @@
|
|
|
3
3
|
import { readFileSync } from 'fs'
|
|
4
4
|
import { fileURLToPath } from 'url'
|
|
5
5
|
import { dirname, resolve } from 'path'
|
|
6
|
-
import { runDwy } from '../src/sync-all.js'
|
|
6
|
+
import { runDwy, migrateSkills } from '../src/sync-all.js'
|
|
7
|
+
import { refreshSharedSkills } from '../src/skill-store.js'
|
|
7
8
|
import { selfUpgrade } from '../src/self-upgrade.js'
|
|
8
9
|
import { buildHelpText } from '../src/cli-help.js'
|
|
9
10
|
|
|
@@ -24,14 +25,16 @@ if (wantsHelp(argv)) {
|
|
|
24
25
|
printHelp()
|
|
25
26
|
} else if (argv.includes('--version') || argv.includes('-V')) {
|
|
26
27
|
console.log(pkg.version)
|
|
27
|
-
} else if (
|
|
28
|
+
} else if (['upgrade', 'refresh', 'migrate'].includes(argv[0])) {
|
|
28
29
|
if (argv.length > 1) {
|
|
29
30
|
console.error(`未知命令: dwy ${argv.join(' ')}`)
|
|
30
31
|
printHelp()
|
|
31
32
|
process.exitCode = 1
|
|
32
33
|
} else {
|
|
33
34
|
try {
|
|
34
|
-
await selfUpgrade()
|
|
35
|
+
if (argv[0] === 'upgrade') await selfUpgrade()
|
|
36
|
+
if (argv[0] === 'refresh') await refreshSharedSkills()
|
|
37
|
+
if (argv[0] === 'migrate') await migrateSkills()
|
|
35
38
|
} catch (error) {
|
|
36
39
|
console.error(error.message)
|
|
37
40
|
process.exitCode = 1
|
package/package.json
CHANGED
package/src/cli-help.js
CHANGED
|
@@ -14,16 +14,19 @@ export function buildHelpText(version) {
|
|
|
14
14
|
`dwy ${version}`,
|
|
15
15
|
'',
|
|
16
16
|
'用法:',
|
|
17
|
-
' dwy
|
|
17
|
+
' dwy 全局更新通用基线,项目注入所选 Rules;Skills / Hooks 可选位置',
|
|
18
18
|
' dwy sync 同上',
|
|
19
|
-
' dwy upgrade
|
|
19
|
+
' dwy upgrade 升级到 npm 最新正式版,并刷新共享 Skills',
|
|
20
|
+
' dwy refresh 用当前 CLI 模板刷新已安装的共享 Skills',
|
|
21
|
+
' dwy migrate 一键迁移老项目已有的托管 Skills,原目录先备份',
|
|
20
22
|
' dwy --help 显示本说明',
|
|
21
23
|
' dwy --version 显示版本',
|
|
22
24
|
'',
|
|
23
25
|
'同步步骤:',
|
|
24
26
|
' 1. 选择 Skills,再选择 Rules;按分类逐项勾选。',
|
|
25
|
-
' 2.
|
|
27
|
+
' 2. 选择 Skills / Hooks 写入当前项目或全局,再选择平台。',
|
|
26
28
|
' 3. 查看更新、删除项,确认后才写入;取消不写文件。',
|
|
29
|
+
' 通用基线默认写入所选 CLI 的全局入口;所选 Rules 写入项目根 AGENTS.md。',
|
|
27
30
|
' Hooks 按平台自动同步,无需选择;Commands 不参与同步。',
|
|
28
31
|
'',
|
|
29
32
|
'交互按键:',
|
|
@@ -33,18 +36,25 @@ export function buildHelpText(version) {
|
|
|
33
36
|
'',
|
|
34
37
|
'常见用法:',
|
|
35
38
|
' 同步当前项目:cd your-project 后执行 dwy,在位置步骤选择「当前项目」。',
|
|
36
|
-
'
|
|
39
|
+
' 同步全局 Skills / Hooks:在项目目录执行 dwy,在位置步骤选择「全局」。',
|
|
37
40
|
' 更新或调整配置:再次执行 dwy sync,检查勾选项,再确认同步。',
|
|
38
41
|
' 更新 CLI:dwy upgrade(全局安装时可用;更新后执行 dwy --version 查看版本)。',
|
|
42
|
+
' 迁移老项目:cd your-project 后执行 dwy migrate,无需重新勾选。',
|
|
43
|
+
' 手动升级包后:执行一次 dwy refresh,所有已迁移的链接同时生效。',
|
|
39
44
|
' 首次安装:npm install -g create-dwy@latest',
|
|
40
45
|
'',
|
|
41
46
|
'平台支持:',
|
|
42
|
-
' Claude Code /
|
|
43
|
-
'
|
|
47
|
+
' Claude Code:全局 ~/.claude/CLAUDE.md;项目 Rules 使用根 AGENTS.md。',
|
|
48
|
+
' Codex:全局 $CODEX_HOME/AGENTS.md(默认 ~/.codex/AGENTS.md);项目根 AGENTS.md。',
|
|
49
|
+
' OpenCode:全局 ~/.config/opencode/AGENTS.md;项目根 AGENTS.md;当前不支持 Hooks。',
|
|
44
50
|
' Cursor:仅项目 Rules;全局规则在 Cursor 设置中管理。',
|
|
45
51
|
'',
|
|
46
|
-
'
|
|
47
|
-
'
|
|
52
|
+
'同步仅处理所选平台;基线固定全局、Rules 固定项目,位置选择只影响 Skills / Hooks。',
|
|
53
|
+
'取消已勾选的托管项会在确认后删除,保留手写内容。',
|
|
54
|
+
'Skills 副本集中在 ~/.dwy/skills;项目和全局使用目录链接,同平台全局入口优先。',
|
|
55
|
+
'旧真实目录备份到同步根的 .dwy/skill-backups;个人 Skills、Rules、Hooks 不参与 migrate。',
|
|
56
|
+
'旧项目首次需执行 dwy migrate;迁移后无需逐项目刷新 Skills。',
|
|
57
|
+
'Rules / Hooks 的模板更新仍需执行 dwy sync;已有会话缓存的 Skill 可能需要重新加载。',
|
|
48
58
|
'',
|
|
49
59
|
].join('\n')
|
|
50
60
|
}
|
package/src/self-upgrade.js
CHANGED
|
@@ -141,6 +141,7 @@ export async function selfUpgrade({
|
|
|
141
141
|
const latest = await fetchLatest()
|
|
142
142
|
const cmp = compareVersions(current, latest)
|
|
143
143
|
if (cmp === 0) {
|
|
144
|
+
await runCommand('dwy', ['refresh'])
|
|
144
145
|
console.log(chalk.green(`已是最新正式版 ${current}`))
|
|
145
146
|
return { status: 'up-to-date', current, latest, installer }
|
|
146
147
|
}
|
|
@@ -153,6 +154,12 @@ export async function selfUpgrade({
|
|
|
153
154
|
console.log(chalk.blue(`升级 ${PACKAGE_NAME} ${current} → ${latest}`))
|
|
154
155
|
console.log(chalk.gray(` ${cmd} ${args.join(' ')}`))
|
|
155
156
|
await runCommand(cmd, args)
|
|
157
|
+
try {
|
|
158
|
+
// 必须启动新安装的 CLI;pnpm 升级后旧进程的模块路径仍可能指向旧版本。
|
|
159
|
+
await runCommand('dwy', ['refresh'])
|
|
160
|
+
} catch (error) {
|
|
161
|
+
throw new Error(`CLI 已安装新版本,但共享 Skills 刷新失败:${error.message}`, { cause: error })
|
|
162
|
+
}
|
|
156
163
|
console.log(chalk.green(`已升级到 ${latest}`))
|
|
157
164
|
return { status: 'upgraded', current, latest, installer }
|
|
158
165
|
}
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
import fs from 'fs-extra'
|
|
2
|
+
import os from 'node:os'
|
|
3
|
+
import path from 'node:path'
|
|
4
|
+
import { randomUUID } from 'node:crypto'
|
|
5
|
+
import { copyFilter, resolveSourceDir, scanSkills } from './sync.js'
|
|
6
|
+
import { chalk } from './utils.js'
|
|
7
|
+
|
|
8
|
+
function validateName(name) {
|
|
9
|
+
if (!/^[\p{L}\p{N}_-]+$/u.test(name)) throw new Error(`无效 Skill 名称:${name}`)
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
async function readInstalledNames(homeDir) {
|
|
13
|
+
const file = path.join(homeDir, '.dwy', 'skill-store.json')
|
|
14
|
+
if (!await fs.pathExists(file)) return []
|
|
15
|
+
const names = await fs.readJson(file)
|
|
16
|
+
if (!Array.isArray(names) || names.some(name => typeof name !== 'string')) {
|
|
17
|
+
throw new Error(`共享 Skills 清单格式无效:${file}`)
|
|
18
|
+
}
|
|
19
|
+
names.forEach(validateName)
|
|
20
|
+
return names
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* 将包内 Skills 准备到稳定用户目录,返回指向共享副本的条目。
|
|
25
|
+
* 同一用户的刷新互斥;完整副本准备失败时不替换旧内容。外部 Skills 缓存不纳入本模块清单。
|
|
26
|
+
*/
|
|
27
|
+
export async function prepareSharedSkills({ skills, homeDir = os.homedir() }) {
|
|
28
|
+
if (skills.length === 0) return []
|
|
29
|
+
for (const skill of skills) {
|
|
30
|
+
validateName(skill.name)
|
|
31
|
+
if (!(await fs.stat(path.join(skill.sourcePath, 'SKILL.md'))).isFile()) {
|
|
32
|
+
throw new Error(`Skill 缺少有效 SKILL.md:${skill.sourcePath}`)
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
const root = path.join(homeDir, '.dwy')
|
|
36
|
+
const store = path.join(root, 'skills')
|
|
37
|
+
await fs.ensureDir(store)
|
|
38
|
+
const lockPath = path.join(root, 'skill-store.lock')
|
|
39
|
+
let lock
|
|
40
|
+
try {
|
|
41
|
+
lock = await fs.open(lockPath, 'wx')
|
|
42
|
+
} catch (error) {
|
|
43
|
+
if (error.code === 'EEXIST') throw new Error(`另一个进程正在刷新 Skills;若前次异常退出,请移除 ${lockPath} 后重试`)
|
|
44
|
+
throw error
|
|
45
|
+
}
|
|
46
|
+
let staging
|
|
47
|
+
let preserveStaging = false
|
|
48
|
+
try {
|
|
49
|
+
staging = await fs.mkdtemp(path.join(root, '.skills-'))
|
|
50
|
+
const installed = new Set(await readInstalledNames(homeDir))
|
|
51
|
+
const prepared = []
|
|
52
|
+
const replacements = []
|
|
53
|
+
for (const skill of skills) {
|
|
54
|
+
const dest = path.join(store, skill.name)
|
|
55
|
+
const next = path.join(staging, skill.name)
|
|
56
|
+
if (path.resolve(skill.sourcePath) !== path.resolve(dest)) {
|
|
57
|
+
await fs.copy(skill.sourcePath, next, { dereference: true, filter: copyFilter(skill.sourcePath) })
|
|
58
|
+
replacements.push({ dest, next, old: path.join(staging, `${skill.name}.old`), saved: false, replaced: false })
|
|
59
|
+
}
|
|
60
|
+
installed.add(skill.name)
|
|
61
|
+
prepared.push({ ...skill, sourcePath: dest })
|
|
62
|
+
}
|
|
63
|
+
const manifest = path.join(staging, 'skill-store.json')
|
|
64
|
+
await fs.writeJson(manifest, [...installed].sort(), { spaces: 2 })
|
|
65
|
+
try {
|
|
66
|
+
for (const entry of replacements) {
|
|
67
|
+
const existing = await fs.lstat(entry.dest).catch(error => {
|
|
68
|
+
if (error.code === 'ENOENT') return null
|
|
69
|
+
throw error
|
|
70
|
+
})
|
|
71
|
+
if (existing) {
|
|
72
|
+
await fs.rename(entry.dest, entry.old)
|
|
73
|
+
entry.saved = true
|
|
74
|
+
}
|
|
75
|
+
await fs.rename(entry.next, entry.dest)
|
|
76
|
+
entry.replaced = true
|
|
77
|
+
}
|
|
78
|
+
await fs.rename(manifest, path.join(root, 'skill-store.json'))
|
|
79
|
+
} catch (error) {
|
|
80
|
+
const errors = [error]
|
|
81
|
+
for (const entry of [...replacements].reverse()) {
|
|
82
|
+
try {
|
|
83
|
+
if (entry.replaced) await fs.remove(entry.dest)
|
|
84
|
+
if (entry.saved) await fs.rename(entry.old, entry.dest)
|
|
85
|
+
} catch (restoreError) {
|
|
86
|
+
errors.push(restoreError)
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
if (errors.length > 1) {
|
|
90
|
+
preserveStaging = true
|
|
91
|
+
throw new AggregateError(errors, `刷新失败且自动恢复未完成,旧副本保留在 ${staging}`)
|
|
92
|
+
}
|
|
93
|
+
throw error
|
|
94
|
+
}
|
|
95
|
+
return prepared
|
|
96
|
+
} finally {
|
|
97
|
+
try {
|
|
98
|
+
if (staging && !preserveStaging) await fs.remove(staging)
|
|
99
|
+
} finally {
|
|
100
|
+
await fs.close(lock)
|
|
101
|
+
await fs.remove(lockPath)
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
async function entryParent(dest, backupRoot) {
|
|
107
|
+
const parent = await fs.realpath(path.dirname(dest))
|
|
108
|
+
const boundary = path.resolve(backupRoot, '..', '..')
|
|
109
|
+
const realBoundary = await fs.realpath(boundary)
|
|
110
|
+
const expected = path.join(realBoundary, path.relative(boundary, path.dirname(dest)))
|
|
111
|
+
if (parent !== expected) throw new Error(`Skills 根目录链接指向外部位置,保留原入口,不修改目标:${path.dirname(dest)}`)
|
|
112
|
+
return parent
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* 清理托管入口:真实目录移到备份根,软链接仅移除入口,不遍历其目标。
|
|
117
|
+
* 返回真实目录的备份路径;入口不存在或原为链接时返回 null。
|
|
118
|
+
*/
|
|
119
|
+
export async function removeSkillEntry({ dest, backupRoot }) {
|
|
120
|
+
const stat = await fs.lstat(dest).catch(error => {
|
|
121
|
+
if (error.code === 'ENOENT') return null
|
|
122
|
+
throw error
|
|
123
|
+
})
|
|
124
|
+
if (!stat) return null
|
|
125
|
+
await entryParent(dest, backupRoot)
|
|
126
|
+
if (stat.isSymbolicLink()) {
|
|
127
|
+
await fs.unlink(dest)
|
|
128
|
+
return null
|
|
129
|
+
}
|
|
130
|
+
await fs.ensureDir(backupRoot)
|
|
131
|
+
const backup = path.join(backupRoot, `${path.basename(dest)}-${randomUUID()}`)
|
|
132
|
+
await fs.rename(dest, backup)
|
|
133
|
+
console.log(chalk.gray(`已备份 ${dest} → ${backup}`))
|
|
134
|
+
return backup
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/** 将整个 Skill 目录链接到共享副本;原真实目录先备份,链接失败时恢复原入口。 */
|
|
138
|
+
export async function linkSkill({ skill, dest, backupRoot }) {
|
|
139
|
+
if (!(await fs.stat(path.join(skill.sourcePath, 'SKILL.md'))).isFile()) {
|
|
140
|
+
throw new Error(`Skill 缺少有效 SKILL.md:${skill.sourcePath}`)
|
|
141
|
+
}
|
|
142
|
+
await fs.ensureDir(path.dirname(dest))
|
|
143
|
+
const actualParent = await fs.realpath(path.dirname(dest))
|
|
144
|
+
// 整个全局 skills 根可能已链接到共享目录;此时入口就是源目录,不能把它再改为自链。
|
|
145
|
+
if (path.join(actualParent, path.basename(dest)) === await fs.realpath(skill.sourcePath)) return null
|
|
146
|
+
await entryParent(dest, backupRoot)
|
|
147
|
+
const stat = await fs.lstat(dest).catch(error => {
|
|
148
|
+
if (error.code === 'ENOENT') return null
|
|
149
|
+
throw error
|
|
150
|
+
})
|
|
151
|
+
const previousLink = stat?.isSymbolicLink() ? await fs.readlink(dest) : null
|
|
152
|
+
if (previousLink && path.resolve(path.dirname(dest), previousLink) === path.resolve(skill.sourcePath)) return null
|
|
153
|
+
const temporary = path.join(path.dirname(dest), `.${path.basename(dest)}.link-${randomUUID()}`)
|
|
154
|
+
await fs.symlink(path.resolve(skill.sourcePath), temporary, process.platform === 'win32' ? 'junction' : 'dir')
|
|
155
|
+
let backup
|
|
156
|
+
try {
|
|
157
|
+
backup = await removeSkillEntry({ dest, backupRoot })
|
|
158
|
+
try {
|
|
159
|
+
await fs.rename(temporary, dest)
|
|
160
|
+
} catch (error) {
|
|
161
|
+
if (backup) await fs.rename(backup, dest)
|
|
162
|
+
else if (previousLink) await fs.symlink(previousLink, dest, process.platform === 'win32' ? 'junction' : 'dir')
|
|
163
|
+
throw error
|
|
164
|
+
}
|
|
165
|
+
return backup
|
|
166
|
+
} finally {
|
|
167
|
+
await fs.remove(temporary)
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/** 使用当前 CLI 包内模板刷新已有共享副本;新包移除的条目保留,避免其他项目坏链。 */
|
|
172
|
+
export async function refreshSharedSkills({ sourceDir, homeDir = os.homedir() } = {}) {
|
|
173
|
+
const names = await readInstalledNames(homeDir)
|
|
174
|
+
const scans = await scanSkills(sourceDir || await resolveSourceDir())
|
|
175
|
+
const available = new Map(scans.map(skill => [skill.name, skill]))
|
|
176
|
+
const updated = names.filter(name => available.has(name))
|
|
177
|
+
const retained = names.filter(name => !available.has(name))
|
|
178
|
+
await prepareSharedSkills({ skills: updated.map(name => available.get(name)), homeDir })
|
|
179
|
+
for (const name of retained) console.log(chalk.yellow(`当前 CLI 已移除 ${name},保留共享旧副本以免其他项目链接失效。`))
|
|
180
|
+
console.log(chalk.green(`已刷新 ${updated.length} 个共享 Skills,保留 ${retained.length} 个旧副本。`))
|
|
181
|
+
return { updated, retained }
|
|
182
|
+
}
|