@42ailab/42plugin 0.1.24 → 0.1.25

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": "@42ailab/42plugin",
3
- "version": "0.1.24",
3
+ "version": "0.1.25",
4
4
  "description": "活水插件",
5
5
  "type": "module",
6
6
  "main": "src/index.ts",
@@ -20,7 +20,7 @@ import {
20
20
  getSessionToken,
21
21
  } from '../db';
22
22
  import { config } from '../config';
23
- import { parseTarget, getInstallPath, formatBytes, getTypeIcon } from '../utils';
23
+ import { parseTarget, getInstallPath, formatBytes, getTypeIcon, normalizeInstallPath } from '../utils';
24
24
  import { TargetType, type PluginDownloadInfo } from '../types';
25
25
 
26
26
  export const installCommand = new Command('install')
@@ -152,7 +152,8 @@ async function installPlugin(
152
152
 
153
153
  // 确定安装路径
154
154
  const projectPath = options.global ? config.globalDir : process.cwd();
155
- const linkPath = path.join(projectPath, downloadInfo.installPath);
155
+ const normalizedPath = normalizeInstallPath(downloadInfo.installPath, !!options.global);
156
+ const linkPath = path.join(projectPath, normalizedPath);
156
157
 
157
158
  // 检查是否有同名冲突(不同插件安装到同一路径)
158
159
  const conflictPlugin = await checkInstallConflict(projectPath, linkPath, downloadInfo.fullName);
@@ -209,11 +210,11 @@ async function installPlugin(
209
210
 
210
211
  if (options.global) {
211
212
  console.log(chalk.green('✓ 已全局安装,对所有项目生效'));
212
- console.log(chalk.gray(` → ~/.claude/${downloadInfo.installPath}`));
213
+ console.log(chalk.gray(` → ~/.claude/${normalizedPath}`));
213
214
  console.log(chalk.gray(' 提示: 使用 42plugin list --global 查看全局插件'));
214
215
  } else {
215
216
  console.log(chalk.green(`✓ 已安装到当前项目: ${path.basename(projectPath)}`));
216
- console.log(chalk.gray(` → ${downloadInfo.installPath}`));
217
+ console.log(chalk.gray(` → ${normalizedPath}`));
217
218
  }
218
219
  } catch (error) {
219
220
  spinner.fail('安装失败');
@@ -260,11 +261,13 @@ async function installKit(
260
261
  const project = await getOrCreateProject(projectPath);
261
262
 
262
263
  // 第一阶段:检查所有冲突(并行)
264
+ const isGlobal = !!options.global;
263
265
  const conflictCheckSpinner = ora('检查安装冲突...').start();
264
266
  const conflictResults = await Promise.all(
265
267
  plugins.map(async (item) => {
266
268
  const downloadInfo = item.download;
267
- const linkPath = path.join(projectPath, downloadInfo.installPath);
269
+ const normalizedPath = normalizeInstallPath(downloadInfo.installPath, isGlobal);
270
+ const linkPath = path.join(projectPath, normalizedPath);
268
271
  const conflictPlugin = await checkInstallConflict(projectPath, linkPath, downloadInfo.fullName);
269
272
  return { item, linkPath, conflictPlugin };
270
273
  })
package/src/db.ts CHANGED
@@ -583,11 +583,22 @@ export async function downloadAndExtract(
583
583
  // 解压
584
584
  await tar.extract({ file: tempFile, cwd: targetDir, strip: 0 });
585
585
 
586
+ if (config.debug) {
587
+ const entries = await fs.readdir(targetDir);
588
+ console.log(`[DEBUG] 解压完成,目录内容: ${entries.join(', ')}`);
589
+ }
590
+
586
591
  // 清理
587
592
  await fs.unlink(tempFile).catch(() => {});
588
593
 
589
594
  // 返回最终路径(如果只有一个子目录则进入)
590
- return resolveFinalPath(targetDir, pluginType);
595
+ const finalPath = await resolveFinalPath(targetDir, pluginType);
596
+ if (config.debug) {
597
+ console.log(`[DEBUG] 最终缓存路径: ${finalPath}`);
598
+ const finalEntries = await fs.readdir(finalPath).catch(() => []);
599
+ console.log(`[DEBUG] 最终路径内容: ${finalEntries.join(', ') || '(空)'}`);
600
+ }
601
+ return finalPath;
591
602
  } catch (error) {
592
603
  await fs.rm(targetDir, { recursive: true, force: true }).catch(() => {});
593
604
  throw error;
@@ -754,6 +765,17 @@ export async function createLink(
754
765
  const targetDir = path.dirname(normalizedTarget);
755
766
  await fs.mkdir(targetDir, { recursive: true });
756
767
 
768
+ if (config.debug) {
769
+ console.log(`[DEBUG] createLink: ${sourcePath} -> ${normalizedTarget}`);
770
+ }
771
+
772
+ // 验证源路径存在
773
+ try {
774
+ await fs.access(sourcePath);
775
+ } catch {
776
+ throw new Error(`源路径不存在: ${sourcePath}`);
777
+ }
778
+
757
779
  // 移除已存在的链接
758
780
  try {
759
781
  const stat = await fs.lstat(normalizedTarget);
@@ -768,6 +790,11 @@ export async function createLink(
768
790
  const sourceStat = await fs.stat(sourcePath);
769
791
  const isDirectory = sourceStat.isDirectory();
770
792
 
793
+ if (config.debug && isDirectory) {
794
+ const entries = await fs.readdir(sourcePath);
795
+ console.log(`[DEBUG] 源目录内容 (${entries.length} 项): ${entries.join(', ')}`);
796
+ }
797
+
771
798
  // 尝试创建符号链接
772
799
  try {
773
800
  const linkType = isDirectory ? 'dir' : 'file';
@@ -788,9 +815,18 @@ export async function createLink(
788
815
  // 回退方案 1: 对于目录,尝试使用 Junction
789
816
  if (isDirectory) {
790
817
  try {
818
+ if (config.debug) {
819
+ console.log(`[DEBUG] 尝试创建 Junction: ${normalizedTarget} -> ${sourcePath}`);
820
+ }
791
821
  await createJunction(sourcePath, normalizedTarget);
822
+ if (config.debug) {
823
+ console.log(`[DEBUG] Junction 创建成功`);
824
+ }
792
825
  return;
793
- } catch {
826
+ } catch (junctionError) {
827
+ if (config.debug) {
828
+ console.log(`[DEBUG] Junction 创建失败: ${(junctionError as Error).message}`);
829
+ }
794
830
  // Junction 也失败,继续尝试复制
795
831
  }
796
832
  }
@@ -801,7 +837,14 @@ export async function createLink(
801
837
  `[Windows] 符号链接创建失败,回退到文件复制模式\n` +
802
838
  ` 提示: 启用 Windows 开发者模式可使用符号链接,节省磁盘空间`
803
839
  );
840
+ if (config.debug) {
841
+ console.log(`[DEBUG] 开始复制: ${sourcePath} -> ${normalizedTarget}`);
842
+ }
804
843
  await copyRecursive(sourcePath, normalizedTarget);
844
+ if (config.debug) {
845
+ const destEntries = await fs.readdir(normalizedTarget).catch(() => []);
846
+ console.log(`[DEBUG] 复制完成,目标目录内容: ${destEntries.join(', ') || '(空)'}`);
847
+ }
805
848
  }
806
849
  }
807
850
 
package/src/utils.ts CHANGED
@@ -69,6 +69,28 @@ export function parseTarget(target: string): ParsedTarget {
69
69
  // 安装路径
70
70
  // ============================================================================
71
71
 
72
+ /**
73
+ * 规范化安装路径(用于全局安装)
74
+ *
75
+ * API 返回的 installPath 包含 .claude/ 前缀(如 .claude/skills/xxx/)
76
+ * 全局安装目录本身就是 ~/.claude,所以需要去掉前缀避免路径重复
77
+ *
78
+ * @param installPath API 返回的安装路径
79
+ * @param isGlobal 是否全局安装
80
+ * @returns 规范化后的路径
81
+ */
82
+ export function normalizeInstallPath(installPath: string, isGlobal: boolean): string {
83
+ if (!isGlobal) {
84
+ return installPath;
85
+ }
86
+ // 全局安装时去掉 .claude/ 前缀
87
+ const prefix = '.claude/';
88
+ if (installPath.startsWith(prefix)) {
89
+ return installPath.slice(prefix.length);
90
+ }
91
+ return installPath;
92
+ }
93
+
72
94
  /**
73
95
  * 获取插件安装路径(.claude/ 目录下)
74
96
  */