dk-frontend-skills 2.1.1 → 2.1.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dk-frontend-skills",
3
- "version": "2.1.1",
3
+ "version": "2.1.3",
4
4
  "description": "dk-engineer - 幽默沉稳靠谱的前端开发助手 Claude Skills 配置包,一键注入 .claude/ 技能目录和 CLAUDE.md 人设配置",
5
5
  "author": "XiaoMa",
6
6
  "license": "MIT",
package/scripts/cli.js CHANGED
@@ -6,7 +6,14 @@ const { getPackageSkills, getUserSkills } = require("./core");
6
6
 
7
7
  // npx 运行时:process.cwd() 是用户项目目录
8
8
  // __dirname 是包内 scripts/ 目录
9
- const projectRoot = process.cwd();
9
+ // fs.realpathSync.native() 归一化 Windows 路径,处理中文/Unicode 编码问题
10
+ const projectRoot = (() => {
11
+ try {
12
+ return fs.realpathSync.native(process.cwd());
13
+ } catch {
14
+ return process.cwd();
15
+ }
16
+ })();
10
17
  const packageDir = path.join(__dirname, "..");
11
18
  const claudeDest = path.join(projectRoot, ".claude");
12
19
  const skillsDest = path.join(claudeDest, "skills");
@@ -16,7 +23,8 @@ const pkgSkillsSource = path.join(packageDir, ".claude", "skills");
16
23
  // ---------- 文件写入 ----------
17
24
 
18
25
  function readSettings() {
19
- if (!fs.existsSync(settingsPath)) return { skills: {}, always_apply_skills: [] };
26
+ if (!fs.existsSync(settingsPath))
27
+ return { skills: {}, always_apply_skills: [] };
20
28
  try {
21
29
  return JSON.parse(fs.readFileSync(settingsPath, "utf-8"));
22
30
  } catch {
@@ -28,11 +36,29 @@ function writeSettings(settings) {
28
36
  if (!fs.existsSync(claudeDest)) {
29
37
  fs.mkdirSync(claudeDest, { recursive: true });
30
38
  }
31
- fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + "\n", "utf-8");
39
+ fs.writeFileSync(
40
+ settingsPath,
41
+ JSON.stringify(settings, null, 2) + "\n",
42
+ "utf-8",
43
+ );
32
44
  }
33
45
 
34
46
  // ---------- 安装勾选的技能 ----------
35
47
 
48
+ // 手动递归拷贝目录,绕开 Windows 中文路径下 fs.cpSync({ recursive: true }) 死锁的 bug
49
+ function copyDirSync(src, dest) {
50
+ fs.mkdirSync(dest, { recursive: true });
51
+ for (const item of fs.readdirSync(src)) {
52
+ const srcPath = path.join(src, item);
53
+ const destPath = path.join(dest, item);
54
+ if (fs.statSync(srcPath).isDirectory()) {
55
+ copyDirSync(srcPath, destPath);
56
+ } else {
57
+ fs.copyFileSync(srcPath, destPath);
58
+ }
59
+ }
60
+ }
61
+
36
62
  function installSelectedSkills(selectedNames) {
37
63
  // 创建 .claude/ 和 skills/ 目录
38
64
  if (!fs.existsSync(skillsDest)) {
@@ -49,8 +75,12 @@ function installSelectedSkills(selectedNames) {
49
75
  if (selectedNames.includes(name)) {
50
76
  // 勾选了的 → 安装(不存在才装)
51
77
  if (!fs.existsSync(dest)) {
52
- fs.cpSync(src, dest, { recursive: true });
53
- installedCount++;
78
+ try {
79
+ copyDirSync(src, dest);
80
+ installedCount++;
81
+ } catch (err) {
82
+ console.error(` ❌ 安装技能 "${name}" 失败:`, err.message);
83
+ }
54
84
  }
55
85
  } else {
56
86
  // 没勾选的 → 如果之前装了,删掉
@@ -108,13 +138,15 @@ async function cmdList() {
108
138
  const allSkills = [...skillMap.values()];
109
139
 
110
140
  console.log(`\n${chalk.bold("📋 dk-frontend-skills 技能清单")}\n`);
111
- console.log(` ${chalk.dim("状态 技能名 版本 描述")}`);
112
- console.log(` ${chalk.dim("─────────────────────────────────────────────────────")}`);
141
+ console.log(
142
+ ` ${chalk.dim("状态 技能名 版本 描述")}`,
143
+ );
144
+ console.log(
145
+ ` ${chalk.dim("─────────────────────────────────────────────────────")}`,
146
+ );
113
147
 
114
148
  for (const skill of allSkills) {
115
- const status = skill.enabled
116
- ? chalk.green("● 启用")
117
- : chalk.gray("○ 停用");
149
+ const status = skill.enabled ? chalk.green("● 启用") : chalk.gray("○ 停用");
118
150
  const name = chalk.white(skill.name.padEnd(20));
119
151
  const ver = chalk.dim(skill.version.padEnd(7));
120
152
  const desc = skill.description
@@ -123,7 +155,9 @@ async function cmdList() {
123
155
  console.log(` ${status} ${name} ${ver} ${desc}`);
124
156
  }
125
157
 
126
- console.log(`\n ${chalk.cyan("💡")} ${chalk.dim("运行 npx dk-frontend-skills 进入交互选择模式")}\n`);
158
+ console.log(
159
+ `\n ${chalk.cyan("💡")} ${chalk.dim("运行 npx dk-frontend-skills 进入交互选择模式")}\n`,
160
+ );
127
161
  }
128
162
 
129
163
  // ---------- 交互式菜单(选完即装) ----------
@@ -172,7 +206,9 @@ async function startInteractiveMenu() {
172
206
  });
173
207
 
174
208
  const newCount = selected.filter((name) => !installedSet.has(name)).length;
175
- const removedCount = [...installedSet].filter((name) => !selected.includes(name)).length;
209
+ const removedCount = [...installedSet].filter(
210
+ (name) => !selected.includes(name),
211
+ ).length;
176
212
 
177
213
  const installed = installSelectedSkills(selected);
178
214
 
@@ -212,7 +248,9 @@ async function main() {
212
248
  } else if (command === "list") {
213
249
  await cmdList();
214
250
  } else if (command === "enable" || command === "disable") {
215
- console.log(` ⚠️ 该命令已废弃,请直接运行 npx dk-frontend-skills 使用交互菜单\n`);
251
+ console.log(
252
+ ` ⚠️ 该命令已废弃,请直接运行 npx dk-frontend-skills 使用交互菜单\n`,
253
+ );
216
254
  } else {
217
255
  console.log(` 未知命令: ${command}\n 可用: ${COMMANDS.join(", ")}\n`);
218
256
  process.exit(1);
@@ -4,12 +4,15 @@ const {
4
4
  backupTimestamp,
5
5
  appendLog,
6
6
  backupDir,
7
+ copyDirSync,
7
8
  installSkills,
8
9
  installSettings,
9
10
  } = require("./core");
10
11
 
11
12
  // 获取用户项目根目录
12
- const projectRoot = path.resolve(__dirname, "..", "..", "..");
13
+ // npm lifecycle 脚本会设置 INIT_CWD 为执行命令时的目录,比从 __dirname 爬三级更可靠
14
+ const projectRoot =
15
+ process.env.INIT_CWD || path.resolve(__dirname, "..", "..", "..");
13
16
  const packageDir = path.join(__dirname, "..");
14
17
 
15
18
  // 源路径
@@ -41,7 +44,7 @@ if (fs.existsSync(claudeSource)) {
41
44
  } else {
42
45
  // 全新安装
43
46
  appendLog(logFile, "INFO", "Install started (fresh)");
44
- fs.cpSync(claudeSource, claudeDest, { recursive: true });
47
+ copyDirSync(claudeSource, claudeDest);
45
48
  appendLog(logFile, "INFO", `.claude/ directory created`);
46
49
  }
47
50
  }
@@ -59,7 +62,11 @@ if (fs.existsSync(mdSource)) {
59
62
  const bakName = `CLAUDE.md.${backupTimestamp()}`;
60
63
  const bakPath = path.join(backupsDir, bakName);
61
64
  fs.copyFileSync(mdDest, bakPath);
62
- appendLog(logFile, "BACKUP", `backups/${bakName} (CLAUDE.md user version preserved)`);
65
+ appendLog(
66
+ logFile,
67
+ "BACKUP",
68
+ `backups/${bakName} (CLAUDE.md user version preserved)`,
69
+ );
63
70
  }
64
71
  }
65
72
 
package/scripts/core.js CHANGED
@@ -46,7 +46,7 @@ function backupDir(dir) {
46
46
 
47
47
  for (const item of fs.readdirSync(dir)) {
48
48
  if (item === "backups") continue;
49
- fs.cpSync(path.join(dir, item), path.join(bakPath, item), { recursive: true });
49
+ copyDirSync(path.join(dir, item), path.join(bakPath, item));
50
50
  }
51
51
 
52
52
  // 清理旧备份,只保留最近 3 份
@@ -70,6 +70,22 @@ function isPlainObject(val) {
70
70
  return Object.prototype.toString.call(val) === "[object Object]";
71
71
  }
72
72
 
73
+ /**
74
+ * 手动递归拷贝目录,绕开 Windows 中文路径下 fs.cpSync({ recursive: true }) 死锁的 bug
75
+ */
76
+ function copyDirSync(src, dest) {
77
+ fs.mkdirSync(dest, { recursive: true });
78
+ for (const item of fs.readdirSync(src)) {
79
+ const srcPath = path.join(src, item);
80
+ const destPath = path.join(dest, item);
81
+ if (fs.statSync(srcPath).isDirectory()) {
82
+ copyDirSync(srcPath, destPath);
83
+ } else {
84
+ fs.copyFileSync(srcPath, destPath);
85
+ }
86
+ }
87
+ }
88
+
73
89
  /**
74
90
  * 递归深合并,用户值优先,目标新增字段补充
75
91
  */
@@ -122,7 +138,7 @@ function installSkills(skillsSource, skillsDest, logFile) {
122
138
  const version = meta ? meta.version : "?";
123
139
 
124
140
  if (!fs.existsSync(destSkill)) {
125
- fs.cpSync(srcSkill, destSkill, { recursive: true });
141
+ copyDirSync(srcSkill, destSkill);
126
142
  appendLog(logFile, "SKILL", `${dir} (${version}) → installed`);
127
143
  results.push({ name: dir, version, action: "installed" });
128
144
  } else {
@@ -132,9 +148,9 @@ function installSkills(skillsSource, skillsDest, logFile) {
132
148
  if (needUpdate) {
133
149
  const bakName = `${dir}.bak.${backupTimestamp()}`;
134
150
  const bakPath = path.join(skillsDest, bakName);
135
- fs.cpSync(destSkill, bakPath, { recursive: true });
151
+ copyDirSync(destSkill, bakPath);
136
152
  fs.rmSync(destSkill, { recursive: true, force: true });
137
- fs.cpSync(srcSkill, destSkill, { recursive: true });
153
+ copyDirSync(srcSkill, destSkill);
138
154
  appendLog(logFile, "SKILL", `${dir} (${destMeta.version} → ${meta.version}) → updated`);
139
155
  results.push({ name: dir, version: meta.version, action: "updated" });
140
156
  } else {
@@ -230,6 +246,7 @@ module.exports = {
230
246
  backupTimestamp,
231
247
  appendLog,
232
248
  backupDir,
249
+ copyDirSync,
233
250
  deepMerge,
234
251
  readMeta,
235
252
  installSkills,