itismyskillmarket 1.0.10
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/.github/workflows/publish-npm.yml +54 -0
- package/.github/workflows/publish-skill.yml +70 -0
- package/5e51cb7aa8b8e60d49d86f4689f5d4d1.png +0 -0
- package/DEVELOPMENT.md +376 -0
- package/README.md +87 -0
- package/SKILLMARKET-GUIDE.md +277 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +561 -0
- package/docs/plans/2026-04-01-skillmarket-design.md +267 -0
- package/docs/plans/2026-04-01-skillmarket-implementation.md +1031 -0
- package/package.json +24 -0
- package/skills/README.md +52 -0
- package/skills/test-skill/SKILL.md +25 -0
- package/skills/test-skill/index.js +66 -0
- package/skills/test-skill/metadata.json +9 -0
- package/skills/test-skill/package.json +19 -0
- package/src/cli.ts +300 -0
- package/src/commands/info.ts +154 -0
- package/src/commands/install.ts +237 -0
- package/src/commands/ls.ts +169 -0
- package/src/commands/npm.ts +261 -0
- package/src/commands/registry.ts +159 -0
- package/src/commands/sync.ts +137 -0
- package/src/commands/uninstall.ts +102 -0
- package/src/commands/update.ts +113 -0
- package/src/constants.ts +126 -0
- package/src/index.ts +62 -0
- package/src/types.ts +137 -0
- package/src/utils/dirs.ts +166 -0
- package/src/utils/platform.ts +139 -0
- package/tsconfig.json +10 -0
- package/tsup.config.ts +22 -0
- package/wanxuchen-skillmarket-1.0.1.tgz +0 -0
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* =============================================================================
|
|
3
|
+
* SkillMarket 安装命令模块
|
|
4
|
+
* =============================================================================
|
|
5
|
+
*
|
|
6
|
+
* 本模块实现 `skm install` 命令,用于安装 skill 到本地。
|
|
7
|
+
*
|
|
8
|
+
* 安装流程:
|
|
9
|
+
* 1. 确保目录结构存在
|
|
10
|
+
* 2. 从 npm 获取包信息
|
|
11
|
+
* 3. 下载包到缓存
|
|
12
|
+
* 4. 解压并复制到 skills 目录
|
|
13
|
+
* 5. 创建 latest 软链接
|
|
14
|
+
* 6. 更新本地注册表
|
|
15
|
+
*
|
|
16
|
+
* 安装后的目录结构:
|
|
17
|
+
* ~/.skillmarket/
|
|
18
|
+
* ├── skills/
|
|
19
|
+
* │ └── <skillId>/
|
|
20
|
+
* │ ├── latest -> <version>/ (软链接)
|
|
21
|
+
* │ └── <version>/
|
|
22
|
+
* │ ├── SKILL.md
|
|
23
|
+
* │ └── metadata.json
|
|
24
|
+
* └── ...
|
|
25
|
+
*
|
|
26
|
+
* @module commands/install
|
|
27
|
+
*/
|
|
28
|
+
|
|
29
|
+
// -----------------------------------------------------------------------------
|
|
30
|
+
// 导入依赖
|
|
31
|
+
// -----------------------------------------------------------------------------
|
|
32
|
+
|
|
33
|
+
import fs from 'fs-extra'; // 文件系统操作
|
|
34
|
+
import path from 'path'; // 路径处理
|
|
35
|
+
import { exec } from 'child_process'; // 执行 shell 命令
|
|
36
|
+
import { promisify } from 'util'; // Promise 化工具
|
|
37
|
+
|
|
38
|
+
// 模块导入
|
|
39
|
+
import { fetchNpmPackage } from './npm.js'; // npm 查询
|
|
40
|
+
import { loadRegistry, saveRegistry } from './registry.js'; // 注册表操作
|
|
41
|
+
import { getCacheDir, getSkillsDir, ensureMarketDirs } from '../utils/dirs.js'; // 目录工具
|
|
42
|
+
import { detectPlatform } from '../utils/platform.js'; // 平台检测
|
|
43
|
+
import { LATEST_LINK } from '../constants.js'; // 常量
|
|
44
|
+
import type { InstalledSkill } from '../types.js'; // 类型定义
|
|
45
|
+
|
|
46
|
+
// 将 exec 转为 Promise 形式
|
|
47
|
+
const execAsync = promisify(exec);
|
|
48
|
+
|
|
49
|
+
// -----------------------------------------------------------------------------
|
|
50
|
+
// 安装函数
|
|
51
|
+
// -----------------------------------------------------------------------------
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* 安装指定的 skill
|
|
55
|
+
*
|
|
56
|
+
* @param {string} skillId - Skill 标识符(支持短格式或 scoped 格式)
|
|
57
|
+
* @param {string} [version] - 指定版本号(可选,不指定则安装最新版本)
|
|
58
|
+
* @returns {Promise<void>}
|
|
59
|
+
*
|
|
60
|
+
* @example
|
|
61
|
+
* // 安装最新版本的 brainstorming
|
|
62
|
+
* await installSkill('brainstorming');
|
|
63
|
+
*
|
|
64
|
+
* // 安装指定版本
|
|
65
|
+
* await installSkill('brainstorming', '1.0.0');
|
|
66
|
+
*
|
|
67
|
+
* // 安装 scoped 包
|
|
68
|
+
* await installSkill('@custom/skill');
|
|
69
|
+
*/
|
|
70
|
+
export async function installSkill(
|
|
71
|
+
skillId: string,
|
|
72
|
+
version?: string
|
|
73
|
+
): Promise<void> {
|
|
74
|
+
// ==========================================================================
|
|
75
|
+
// 步骤 0: 准备
|
|
76
|
+
// ==========================================================================
|
|
77
|
+
|
|
78
|
+
// 确保所有必要的目录都已创建
|
|
79
|
+
await ensureMarketDirs();
|
|
80
|
+
|
|
81
|
+
// 转换包名格式
|
|
82
|
+
// 支持 @itismyskillmarket/ 和 @skillmarket/ 两种 scope
|
|
83
|
+
let packageName: string;
|
|
84
|
+
if (skillId.startsWith('@')) {
|
|
85
|
+
// 直接使用用户提供的 scoped 包名
|
|
86
|
+
packageName = skillId;
|
|
87
|
+
} else {
|
|
88
|
+
// 默认尝试 @itismyskillmarket/,失败后回退到 @skillmarket/
|
|
89
|
+
packageName = `@itismyskillmarket/${skillId}`;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
console.log(`Installing ${packageName}${version ? `@${version}` : ''}...`);
|
|
93
|
+
|
|
94
|
+
// ==========================================================================
|
|
95
|
+
// 步骤 1: 获取包信息
|
|
96
|
+
// ==========================================================================
|
|
97
|
+
|
|
98
|
+
// 从 npm 查询包的元信息
|
|
99
|
+
const pkgInfo = await fetchNpmPackage(packageName);
|
|
100
|
+
if (!pkgInfo) {
|
|
101
|
+
throw new Error(`Package ${packageName} not found`);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// 确定要安装的版本(用户指定版本 > 最新版本)
|
|
105
|
+
const targetVersion = version || pkgInfo['dist-tags']?.latest;
|
|
106
|
+
if (!targetVersion) {
|
|
107
|
+
throw new Error(`No version found for ${packageName}`);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// ==========================================================================
|
|
111
|
+
// 步骤 2: 下载包到缓存
|
|
112
|
+
// ==========================================================================
|
|
113
|
+
|
|
114
|
+
const cacheDir = getCacheDir();
|
|
115
|
+
|
|
116
|
+
// 计算目标缓存目录路径
|
|
117
|
+
// 例如: ~/.skillmarket/cache/@skillmarket%2Fbrainstorming@1.0.0/
|
|
118
|
+
const targetDir = path.join(cacheDir, `${packageName}@${targetVersion}`);
|
|
119
|
+
|
|
120
|
+
// 如果缓存已存在,跳过下载
|
|
121
|
+
if (!(await fs.pathExists(targetDir))) {
|
|
122
|
+
console.log('Downloading package...');
|
|
123
|
+
await fs.ensureDir(cacheDir);
|
|
124
|
+
|
|
125
|
+
try {
|
|
126
|
+
// 使用 npm pack 下载包到指定目录
|
|
127
|
+
// npm pack 会生成 .tgz 文件
|
|
128
|
+
await execAsync(`npm pack ${packageName}@${targetVersion} --pack-destination ${cacheDir}`);
|
|
129
|
+
|
|
130
|
+
// 查找下载的 tarball 文件
|
|
131
|
+
const files = await fs.readdir(cacheDir);
|
|
132
|
+
|
|
133
|
+
// npm pack 生成的文件名格式: <package-name>-<version>.tgz
|
|
134
|
+
// scoped 包格式: @scope-package-name-<version>.tgz
|
|
135
|
+
const tarball = files.find(f =>
|
|
136
|
+
f.endsWith('.tgz') &&
|
|
137
|
+
f.includes(packageName.replace('/', '-'))
|
|
138
|
+
);
|
|
139
|
+
|
|
140
|
+
if (tarball) {
|
|
141
|
+
// 解压 tarball
|
|
142
|
+
await execAsync(`tar -xzf "${path.join(cacheDir, tarball)}" -C "${cacheDir}"`);
|
|
143
|
+
|
|
144
|
+
// 删除 tarball(不再需要)
|
|
145
|
+
await fs.remove(path.join(cacheDir, tarball));
|
|
146
|
+
|
|
147
|
+
// npm 解压后目录名固定为 'package',需要重命名为目标版本目录
|
|
148
|
+
const extractedDir = path.join(cacheDir, 'package');
|
|
149
|
+
const finalDir = targetDir;
|
|
150
|
+
|
|
151
|
+
// 移动并覆盖(如果已存在)
|
|
152
|
+
await fs.move(extractedDir, finalDir, { overwrite: true });
|
|
153
|
+
}
|
|
154
|
+
} catch (err) {
|
|
155
|
+
throw new Error(`Failed to download package: ${err}`);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// ==========================================================================
|
|
160
|
+
// 步骤 3: 复制到 skills 目录
|
|
161
|
+
// ==========================================================================
|
|
162
|
+
|
|
163
|
+
const skillsDir = getSkillsDir();
|
|
164
|
+
|
|
165
|
+
// 创建版本目录: ~/.skillmarket/skills/<skillId>@<version>/
|
|
166
|
+
const skillVersionDir = path.join(skillsDir, `${skillId}@${targetVersion}`);
|
|
167
|
+
|
|
168
|
+
console.log('Setting up skill...');
|
|
169
|
+
await fs.ensureDir(skillVersionDir);
|
|
170
|
+
|
|
171
|
+
// 从缓存复制必要的文件到 skills 目录
|
|
172
|
+
const pkgRoot = targetDir;
|
|
173
|
+
|
|
174
|
+
// 复制 SKILL.md(skill 定义文件)
|
|
175
|
+
if (await fs.pathExists(path.join(pkgRoot, 'SKILL.md'))) {
|
|
176
|
+
await fs.copy(
|
|
177
|
+
path.join(pkgRoot, 'SKILL.md'),
|
|
178
|
+
path.join(skillVersionDir, 'SKILL.md')
|
|
179
|
+
);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// 复制 metadata.json(可选元数据文件)
|
|
183
|
+
if (await fs.pathExists(path.join(pkgRoot, 'metadata.json'))) {
|
|
184
|
+
await fs.copy(
|
|
185
|
+
path.join(pkgRoot, 'metadata.json'),
|
|
186
|
+
path.join(skillVersionDir, 'metadata.json')
|
|
187
|
+
);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// ==========================================================================
|
|
191
|
+
// 步骤 4: 创建 latest 软链接
|
|
192
|
+
// ==========================================================================
|
|
193
|
+
|
|
194
|
+
// skill 主目录: ~/.skillmarket/skills/<skillId>/
|
|
195
|
+
const skillDir = path.join(skillsDir, skillId);
|
|
196
|
+
await fs.ensureDir(skillDir);
|
|
197
|
+
|
|
198
|
+
// latest 软链接路径: ~/.skillmarket/skills/<skillId>/latest
|
|
199
|
+
const latestLink = path.join(skillDir, LATEST_LINK);
|
|
200
|
+
|
|
201
|
+
try {
|
|
202
|
+
// 删除已存在的软链接(如果有)
|
|
203
|
+
await fs.remove(latestLink);
|
|
204
|
+
|
|
205
|
+
// 创建软链接指向版本目录
|
|
206
|
+
// 'junction' 类型在 Windows 上不需要管理员权限
|
|
207
|
+
await fs.symlink(skillVersionDir, latestLink, 'junction');
|
|
208
|
+
} catch {
|
|
209
|
+
// Windows 上 junction 可能失败,降级为目录复制
|
|
210
|
+
await fs.copy(skillVersionDir, path.join(skillDir, LATEST_LINK), { overwrite: true });
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// ==========================================================================
|
|
214
|
+
// 步骤 5: 更新注册表
|
|
215
|
+
// ==========================================================================
|
|
216
|
+
|
|
217
|
+
const registry = await loadRegistry();
|
|
218
|
+
const currentPlatform = detectPlatform();
|
|
219
|
+
|
|
220
|
+
// 添加/更新注册表中的 skill 记录
|
|
221
|
+
registry.skills[skillId] = {
|
|
222
|
+
id: skillId,
|
|
223
|
+
version: targetVersion,
|
|
224
|
+
installedAt: new Date().toISOString(),
|
|
225
|
+
platforms: [currentPlatform]
|
|
226
|
+
} as InstalledSkill;
|
|
227
|
+
|
|
228
|
+
// 保存注册表
|
|
229
|
+
await saveRegistry(registry);
|
|
230
|
+
|
|
231
|
+
// ==========================================================================
|
|
232
|
+
// 完成
|
|
233
|
+
// ==========================================================================
|
|
234
|
+
|
|
235
|
+
console.log(`\n✅ ${skillId}@${targetVersion} installed successfully!`);
|
|
236
|
+
console.log(` Use "skm info ${skillId}" for more details`);
|
|
237
|
+
}
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* =============================================================================
|
|
3
|
+
* SkillMarket 列表命令模块
|
|
4
|
+
* =============================================================================
|
|
5
|
+
*
|
|
6
|
+
* 本模块实现 `skm ls` 命令,用于:
|
|
7
|
+
* - 列出 npm registry 上可用的 skills
|
|
8
|
+
* - 显示本地已安装的 skills
|
|
9
|
+
*
|
|
10
|
+
* @module commands/ls
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
// -----------------------------------------------------------------------------
|
|
14
|
+
// 导入依赖
|
|
15
|
+
// -----------------------------------------------------------------------------
|
|
16
|
+
|
|
17
|
+
import {
|
|
18
|
+
loadRegistry, // 加载本地注册表
|
|
19
|
+
getInstalledSkills // 获取已安装 skills 列表
|
|
20
|
+
} from './registry.js';
|
|
21
|
+
|
|
22
|
+
import {
|
|
23
|
+
searchSkillmarketPackages, // 搜索 npm 上的 skill 包
|
|
24
|
+
fetchNpmPackage // 获取单个包的详细信息
|
|
25
|
+
} from './npm.js';
|
|
26
|
+
|
|
27
|
+
// -----------------------------------------------------------------------------
|
|
28
|
+
// 类型定义
|
|
29
|
+
// -----------------------------------------------------------------------------
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* ls 命令选项接口
|
|
33
|
+
*/
|
|
34
|
+
interface LsOptions {
|
|
35
|
+
/** 仅显示已安装的 skills */
|
|
36
|
+
installed?: boolean;
|
|
37
|
+
|
|
38
|
+
/** 检查更新(预留功能) */
|
|
39
|
+
updates?: boolean;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// -----------------------------------------------------------------------------
|
|
43
|
+
// 命令实现
|
|
44
|
+
// -----------------------------------------------------------------------------
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* 列出 skills
|
|
48
|
+
*
|
|
49
|
+
* 根据选项显示 npm registry 上可用的 skills
|
|
50
|
+
* 或本地已安装的 skills
|
|
51
|
+
*
|
|
52
|
+
* @param {LsOptions} options - 命令选项
|
|
53
|
+
* @returns {Promise<void>}
|
|
54
|
+
*
|
|
55
|
+
* @example
|
|
56
|
+
* // 列出 npm 上的可用 skills
|
|
57
|
+
* await listSkills({});
|
|
58
|
+
*
|
|
59
|
+
* // 列出已安装的 skills
|
|
60
|
+
* await listSkills({ installed: true });
|
|
61
|
+
*/
|
|
62
|
+
export async function listSkills(options: LsOptions): Promise<void> {
|
|
63
|
+
const { installed, updates } = options;
|
|
64
|
+
|
|
65
|
+
// -------------------------------------------------------------------------
|
|
66
|
+
// 模式1: 显示已安装的 skills
|
|
67
|
+
// -------------------------------------------------------------------------
|
|
68
|
+
if (installed) {
|
|
69
|
+
const skills = await getInstalledSkills();
|
|
70
|
+
|
|
71
|
+
// 无已安装 skills 时给出提示
|
|
72
|
+
if (skills.length === 0) {
|
|
73
|
+
console.log('No skills installed yet. Run "skm --ls" to see available skills.');
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// 打印表头
|
|
78
|
+
console.log('Installed Skills:\n');
|
|
79
|
+
|
|
80
|
+
// 遍历并打印每个 skill 的详细信息
|
|
81
|
+
for (const skill of skills) {
|
|
82
|
+
// skill 名称和版本
|
|
83
|
+
console.log(` ${skill.id}@${skill.version}`);
|
|
84
|
+
|
|
85
|
+
// 支持的平台列表
|
|
86
|
+
console.log(` Platforms: ${skill.platforms.join(', ')}`);
|
|
87
|
+
|
|
88
|
+
// 安装时间
|
|
89
|
+
console.log(` Installed: ${skill.installedAt}`);
|
|
90
|
+
|
|
91
|
+
// 空行分隔
|
|
92
|
+
console.log();
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// -------------------------------------------------------------------------
|
|
99
|
+
// 模式2: 显示 npm 上可用的 skills
|
|
100
|
+
// -------------------------------------------------------------------------
|
|
101
|
+
|
|
102
|
+
// 提示用户正在搜索
|
|
103
|
+
console.log('Searching npm registry...\n');
|
|
104
|
+
|
|
105
|
+
try {
|
|
106
|
+
// 调用 npm search API 搜索 skillmarket 相关包
|
|
107
|
+
const packages = await searchSkillmarketPackages();
|
|
108
|
+
|
|
109
|
+
// 无搜索结果时
|
|
110
|
+
if (packages.length === 0) {
|
|
111
|
+
console.log('No skills found. Check back later!');
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// 打印找到的包数量
|
|
116
|
+
console.log(`Found ${packages.length} skill(s):\n`);
|
|
117
|
+
|
|
118
|
+
// 遍历每个包,获取详细信息并显示
|
|
119
|
+
for (const pkgName of packages) {
|
|
120
|
+
try {
|
|
121
|
+
const info = await fetchNpmPackage(pkgName);
|
|
122
|
+
|
|
123
|
+
if (!info) {
|
|
124
|
+
// 如果获取失败,仍然显示包名
|
|
125
|
+
console.log(`📦 ${pkgName} (信息获取失败)`);
|
|
126
|
+
console.log();
|
|
127
|
+
continue;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// 获取最新版本号
|
|
131
|
+
const latestVersion = info['dist-tags']?.latest || 'unknown';
|
|
132
|
+
|
|
133
|
+
// 获取该版本的详细信息
|
|
134
|
+
const pkg = info.versions?.[latestVersion];
|
|
135
|
+
|
|
136
|
+
// 获取 skillmarket 元数据
|
|
137
|
+
const skillMeta = pkg?.skillmarket;
|
|
138
|
+
|
|
139
|
+
// 打印包名和版本
|
|
140
|
+
console.log(`📦 ${info.name}@${latestVersion}`);
|
|
141
|
+
|
|
142
|
+
// 打印显示名称
|
|
143
|
+
const displayName = skillMeta?.displayName || info.name;
|
|
144
|
+
console.log(` 名称: ${displayName}`);
|
|
145
|
+
|
|
146
|
+
// 打印描述
|
|
147
|
+
console.log(` 描述: ${pkg?.description || 'N/A'}`);
|
|
148
|
+
|
|
149
|
+
// 打印支持平台
|
|
150
|
+
const platforms = skillMeta?.platforms || [];
|
|
151
|
+
console.log(` 平台: ${platforms.length > 0 ? platforms.join(', ') : 'N/A'}`);
|
|
152
|
+
|
|
153
|
+
// 打印 npm 链接
|
|
154
|
+
const npmLink = pkg?.links?.npm || `https://www.npmjs.com/package/${info.name}`;
|
|
155
|
+
console.log(` 链接: ${npmLink}`);
|
|
156
|
+
|
|
157
|
+
// 空行分隔
|
|
158
|
+
console.log();
|
|
159
|
+
} catch (e) {
|
|
160
|
+
// 错误时仍显示包名
|
|
161
|
+
console.log(`📦 ${pkgName} (获取失败: ${e})`);
|
|
162
|
+
console.log();
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
} catch (error) {
|
|
166
|
+
// 网络错误处理
|
|
167
|
+
console.log(`Error fetching skills: ${error}`);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* =============================================================================
|
|
3
|
+
* SkillMarket NPM Registry 查询模块
|
|
4
|
+
* =============================================================================
|
|
5
|
+
*
|
|
6
|
+
* 本模块负责与 npm registry 通信,实现以下功能:
|
|
7
|
+
* - 获取指定 npm 包的信息
|
|
8
|
+
* - 搜索带有 skillmarket 关键字的包
|
|
9
|
+
*
|
|
10
|
+
* 使用原生 Node.js https 模块,无需额外依赖。
|
|
11
|
+
*
|
|
12
|
+
* npm Registry API 文档: https://docs.npmjs.com/cli/v8/using-npm/registry
|
|
13
|
+
*
|
|
14
|
+
* @module commands/npm
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
// -----------------------------------------------------------------------------
|
|
18
|
+
// 导入依赖
|
|
19
|
+
// -----------------------------------------------------------------------------
|
|
20
|
+
|
|
21
|
+
import https from 'https'; // Node.js 原生 HTTPS 模块,用于发送 HTTP 请求
|
|
22
|
+
import { URL } from 'url'; // URL 解析和构建工具
|
|
23
|
+
|
|
24
|
+
// -----------------------------------------------------------------------------
|
|
25
|
+
// 类型定义
|
|
26
|
+
// -----------------------------------------------------------------------------
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* npm 包版本信息
|
|
30
|
+
*
|
|
31
|
+
* 定义从 npm registry 返回的单个版本对象的结构
|
|
32
|
+
*/
|
|
33
|
+
interface NpmPackage {
|
|
34
|
+
/** 包名称 */
|
|
35
|
+
name: string;
|
|
36
|
+
|
|
37
|
+
/** 版本号,遵循 semver 规范 */
|
|
38
|
+
version: string;
|
|
39
|
+
|
|
40
|
+
/** 包描述信息 */
|
|
41
|
+
description?: string;
|
|
42
|
+
|
|
43
|
+
/** npm 链接 */
|
|
44
|
+
links?: {
|
|
45
|
+
npm?: string;
|
|
46
|
+
homepage?: string;
|
|
47
|
+
repository?: string;
|
|
48
|
+
bugs?: string;
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* SkillMarket 特有的元数据
|
|
53
|
+
*
|
|
54
|
+
* 在 package.json 的 skillmarket 字段中定义,
|
|
55
|
+
* 包含 skill 的平台兼容性等信息
|
|
56
|
+
*/
|
|
57
|
+
skillmarket?: {
|
|
58
|
+
/** Skill 唯一标识符 */
|
|
59
|
+
id?: string;
|
|
60
|
+
|
|
61
|
+
/** 友好显示名称 */
|
|
62
|
+
displayName?: string;
|
|
63
|
+
|
|
64
|
+
/** 详细描述 */
|
|
65
|
+
description?: string;
|
|
66
|
+
|
|
67
|
+
/** 支持的平台列表 */
|
|
68
|
+
platforms?: string[];
|
|
69
|
+
|
|
70
|
+
/** 默认版本标识 */
|
|
71
|
+
defaultVersion?: string;
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* npm registry API 响应结构
|
|
77
|
+
*
|
|
78
|
+
* 对应 npm registry 的完整包信息响应
|
|
79
|
+
*/
|
|
80
|
+
interface NpmRegistryResponse {
|
|
81
|
+
/** 包名称 */
|
|
82
|
+
name: string;
|
|
83
|
+
|
|
84
|
+
/** 所有版本的字典,key 是版本号 */
|
|
85
|
+
versions: Record<string, NpmPackage>;
|
|
86
|
+
|
|
87
|
+
/** 发行标签,如 { latest: "1.0.0", beta: "1.1.0-beta.1" } */
|
|
88
|
+
'dist-tags': Record<string, string>;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* npm search API 响应中的包对象
|
|
93
|
+
*/
|
|
94
|
+
interface SearchPackage {
|
|
95
|
+
/** 包名 */
|
|
96
|
+
name: string;
|
|
97
|
+
/** 版本 */
|
|
98
|
+
version: string;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// -----------------------------------------------------------------------------
|
|
102
|
+
// 获取包信息
|
|
103
|
+
// -----------------------------------------------------------------------------
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* 获取指定 npm 包的完整信息
|
|
107
|
+
*
|
|
108
|
+
* 向 npm registry API 发送请求,获取包的详细信息,
|
|
109
|
+
* 包括所有版本、描述、依赖等。
|
|
110
|
+
*
|
|
111
|
+
* API 端点: GET https://registry.npmjs.org/{package}
|
|
112
|
+
*
|
|
113
|
+
* @param {string} packageName - 包名称(支持 @scope/name 格式)
|
|
114
|
+
* @returns {Promise<NpmRegistryResponse | null>} 包信息,失败返回 null
|
|
115
|
+
*
|
|
116
|
+
* @example
|
|
117
|
+
* // 获取官方 commander 包信息
|
|
118
|
+
* const info = await fetchNpmPackage('commander');
|
|
119
|
+
* if (info) {
|
|
120
|
+
* console.log(`最新版本: ${info['dist-tags'].latest}`);
|
|
121
|
+
* }
|
|
122
|
+
*
|
|
123
|
+
* // 获取 scoped 包
|
|
124
|
+
* const scoped = await fetchNpmPackage('@skillmarket/brainstorming');
|
|
125
|
+
*/
|
|
126
|
+
export async function fetchNpmPackage(packageName: string): Promise<NpmRegistryResponse | null> {
|
|
127
|
+
return new Promise((resolve, reject) => {
|
|
128
|
+
// 构建 npm registry URL
|
|
129
|
+
// scoped 包(如 @foo/bar)需要特殊处理,保留 @ 符号
|
|
130
|
+
// @foo/bar -> @foo%2Fbar (URL encoded)
|
|
131
|
+
const isScoped = packageName.startsWith('@');
|
|
132
|
+
let encodedName: string;
|
|
133
|
+
|
|
134
|
+
if (isScoped) {
|
|
135
|
+
// 对 scoped 包进行正确编码:@foo/bar -> @foo%2Fbar
|
|
136
|
+
const scopeAndName = packageName.substring(1); // 去掉 @
|
|
137
|
+
const slashIndex = scopeAndName.indexOf('/');
|
|
138
|
+
if (slashIndex > 0) {
|
|
139
|
+
const scope = scopeAndName.substring(0, slashIndex);
|
|
140
|
+
const name = scopeAndName.substring(slashIndex + 1);
|
|
141
|
+
encodedName = `@${encodeURIComponent(scope)}%2F${encodeURIComponent(name)}`;
|
|
142
|
+
} else {
|
|
143
|
+
encodedName = packageName; // fallback
|
|
144
|
+
}
|
|
145
|
+
} else {
|
|
146
|
+
encodedName = encodeURIComponent(packageName);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
const url = new URL(`https://registry.npmjs.org/${encodedName}`);
|
|
150
|
+
|
|
151
|
+
// 发送 HTTPS GET 请求
|
|
152
|
+
const req = https.get(url.toString(), { timeout: 10000 }, (res) => {
|
|
153
|
+
let data = '';
|
|
154
|
+
|
|
155
|
+
// 收集响应数据
|
|
156
|
+
res.on('data', chunk => { data += chunk; });
|
|
157
|
+
|
|
158
|
+
res.on('end', () => {
|
|
159
|
+
try {
|
|
160
|
+
// 解析 JSON 响应
|
|
161
|
+
const parsed = JSON.parse(data);
|
|
162
|
+
|
|
163
|
+
// 检查 npm 返回的错误
|
|
164
|
+
// npm 对于不存在的包也返回 200,但 body 中有 error 字段
|
|
165
|
+
if (parsed.error) {
|
|
166
|
+
resolve(null);
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// 成功解析,返回包信息
|
|
171
|
+
resolve(parsed);
|
|
172
|
+
} catch {
|
|
173
|
+
// JSON 解析失败,返回 null
|
|
174
|
+
resolve(null);
|
|
175
|
+
}
|
|
176
|
+
});
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
// 处理网络错误
|
|
180
|
+
req.on('error', reject);
|
|
181
|
+
|
|
182
|
+
// 处理请求超时(10秒)
|
|
183
|
+
req.on('timeout', () => {
|
|
184
|
+
req.destroy();
|
|
185
|
+
reject(new Error('Request timeout'));
|
|
186
|
+
});
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// -----------------------------------------------------------------------------
|
|
191
|
+
// 搜索包
|
|
192
|
+
// -----------------------------------------------------------------------------
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* 搜索 SkillMarket 相关的 npm 包
|
|
196
|
+
*
|
|
197
|
+
* 使用 npm search API 搜索带有 'skillmarket' 关键字的包
|
|
198
|
+
*
|
|
199
|
+
* API 端点: GET https://registry.npmjs.org/-/v1/search
|
|
200
|
+
*
|
|
201
|
+
* @returns {Promise<string[]>} 匹配的包名数组
|
|
202
|
+
*
|
|
203
|
+
* @example
|
|
204
|
+
* const packages = await searchSkillmarketPackages();
|
|
205
|
+
* console.log(`找到 ${packages.length} 个 skill 包`);
|
|
206
|
+
* packages.forEach(name => {
|
|
207
|
+
* console.log(`- ${name}`);
|
|
208
|
+
* });
|
|
209
|
+
*/
|
|
210
|
+
export async function searchSkillmarketPackages(): Promise<string[]> {
|
|
211
|
+
const packages: string[] = [];
|
|
212
|
+
|
|
213
|
+
return new Promise((resolve, reject) => {
|
|
214
|
+
// 构建 search API URL
|
|
215
|
+
const url = new URL('https://registry.npmjs.org/-/v1/search');
|
|
216
|
+
|
|
217
|
+
// 设置搜索参数
|
|
218
|
+
// text: 搜索关键字
|
|
219
|
+
// size: 返回结果数量上限
|
|
220
|
+
url.searchParams.set('text', 'keywords:skillmarket');
|
|
221
|
+
url.searchParams.set('size', '100');
|
|
222
|
+
|
|
223
|
+
const req = https.get(url.toString(), { timeout: 10000 }, (res) => {
|
|
224
|
+
let data = '';
|
|
225
|
+
|
|
226
|
+
// 收集响应数据
|
|
227
|
+
res.on('data', chunk => { data += chunk; });
|
|
228
|
+
|
|
229
|
+
res.on('end', () => {
|
|
230
|
+
try {
|
|
231
|
+
// 解析搜索结果
|
|
232
|
+
const result = JSON.parse(data);
|
|
233
|
+
|
|
234
|
+
// 提取所有匹配的包名
|
|
235
|
+
// npm search 返回结构: { objects: [{ package: { name: "..." } }] }
|
|
236
|
+
if (result.objects) {
|
|
237
|
+
for (const item of result.objects) {
|
|
238
|
+
if (item?.package?.name) {
|
|
239
|
+
packages.push(item.package.name);
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
resolve(packages);
|
|
245
|
+
} catch {
|
|
246
|
+
// 解析失败返回空数组
|
|
247
|
+
resolve([]);
|
|
248
|
+
}
|
|
249
|
+
});
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
// 处理网络错误
|
|
253
|
+
req.on('error', reject);
|
|
254
|
+
|
|
255
|
+
// 处理请求超时
|
|
256
|
+
req.on('timeout', () => {
|
|
257
|
+
req.destroy();
|
|
258
|
+
reject(new Error('Request timeout'));
|
|
259
|
+
});
|
|
260
|
+
});
|
|
261
|
+
}
|