22.5 插件发布与维护
第 22 章 · 高级插件开发 · 第6部分:插件系统
22.5.1 插件打包#
基本打包配置#
// package.json { "name": "my-claude-plugin", "version": "1.0.0", "description": "My Claude Code Plugin", "main": "dist/index.js", "types": "dist/index.d.ts", "files": [ "dist", "plugin.yaml" ], "scripts": { "build": "tsc", "prepack": "npm run build", "pack": "npm pack", "publish": "npm publish" }, "keywords": [ "claude-code", "plugin" ], "author": "Your Name", "license": "MIT", "devDependencies": { "@claude-code/plugin-sdk": "^1.0.0", "typescript": "^4.9.0" } }
TypeScript 配置#
bash复制插件清单 # plugin.yaml name: my-claude-plugin version: 1.0.0 description: My Claude Code Plugin author: Your Name license: MIT homepage: https://github.com/yourname/my-claude-plugin repository: https://github.com/yourname/my-claude-plugin # 插件入口 main: dist/index.js types: dist/index.d.ts # 插件权限 permissions: file: - read: "/" network: - https: ["api.example.com"] # 插件依赖 dependencies: claude-code: ">=1.0.0" # 插件元数据 metadata: category: development tags: - code-generation - productivity keywords: - plugin - claude-code
打包脚本#
bash复制bash #!/bin/bash # scripts/build.sh echo "Building plugin..." # 清理构建目录 rm -rf dist # 编译 TypeScript npm run build # 复制插件清单 cp plugin.yaml dist/ # 复制 README cp README.md dist/ # 复制 LICENSE cp LICENSE dist/ 2>/dev/null || true echo "Build complete!" #!/bin/bash # scripts/pack.sh echo "Packing plugin..." # 构建 ./scripts/build.sh # 打包 cd dist npm pack # 移动包到项目根目录 mv *.tgz ../ echo "Pack complete!"
22.5.2 版本管理#
语义化版本#
bash复制typescript // src/version.ts /** * 语义化版本 */ export class SemanticVersion { major: number; minor: number; patch: number; prerelease?: string; build?: string; constructor(version: string) { const parsed = this.parse(version); this.major = parsed.major; this.minor = parsed.minor; this.patch = parsed.patch; this.prerelease = parsed.prerelease; this.build = parsed.build; } /** * 解析版本字符串转换为字符串 */ toString(): string { let version = `${this.major}.${this.minor}.${this.patch}`; if (this.prerelease) { version += `-${this.prerelease}`; } if (this.build) { version += `+${this.build}`; } return version; } /** * 主版本升级 */ bumpMajor(): SemanticVersion { return new SemanticVersion( `${this.major + 1}.0.0` ); } /** * 次版本升级 */ bumpMinor(): SemanticVersion { return new SemanticVersion( `${this.major}.${this.minor + 1}.0` ); } /** * 补丁版本升级 */ bumpPatch(): SemanticVersion { return new SemanticVersion( `${this.major}.${this.minor}.${this.patch + 1}` ); } /** * 比较版本 */ compare(other: SemanticVersion): number { if (this.major !== other.major) { return this.major - other.major; } if (this.minor !== other.minor) { return this.minor - other.minor; } if (this.patch !== other.patch) { return this.patch - other.patch; } return 0; } /** * 检查是否大于 */ greaterThan(other: SemanticVersion): boolean { return this.compare(other) > 0; } /** * 检查是否小于 */ lessThan(other: SemanticVersion): boolean { return this.compare(other) " exit 1 fi echo "Releasing version $VERSION..." # 更新 package.json npm version $VERSION --no-git-tag-version # 更新 plugin.yaml sed -i.bak "s/^version: .*/version: $VERSION/" plugin.yaml rm plugin.yaml.bak # 构建和打包 ./scripts/pack.sh # 提交更改 git add package.json plugin.yaml git commit -m "Release version $VERSION" # 创建标签 git tag -a "v$VERSION" -m "Release version $VERSION" # 推送到远程 git push origin main git push origin "v$VERSION" # 发布到 npm npm publish echo "Release $VERSION complete!"
22.5.3 文档生成#
API 文档生成#
bash复制typescript // scripts/generate-docs.ts import { Project, TSConfigReader, TypeDocReader } from 'typedoc'; import { MarkdownRenderer } from 'typedoc-plugin-markdown'; /** * 生成 API 文档模板 # My Claude Code Plugin [](https://www.npmjs.com/package/my-claude-plugin) [](LICENSE) My Claude Code Plugin is a powerful plugin for Claude Code that provides [brief description]. ## Features - Feature 1 - Feature 2 - Feature 3 ## Installation ````bash `bash claude plugin install my-claude-plugin ```> Or install from npm:
bash
npm install -g my-claude-plugin
Usage#
Basic Usage#
typescript复制````typescript import{ MyPlugin }from'my-claude-plugin'; const plugin =newMyPlugin();await plugin.initialize({});await plugin.start(); ```### Advanced Usage ```typescript const plugin =newMyPlugin({ option1:'value1', option2:'value2'}); await plugin.initialize(config);await plugin.start(); ## Configuration The plugin can be configured with the following options: | Option | Type | Default | Description ||--------|------|---------|-------------|| option1 |string|'default'| Description of option1 || option2 |number|100| Description of option2 | ## API ### Methods #### `initialize(config: PluginConfig): Promise` Initializes the plugin with the given configuration. #### `start(): Promise` Starts the plugin. #### `stop(): Promise` Stops the plugin. ## Development ### Prerequisites >- Node.js >=14>- npm >=6 ### Setup ````bash ````bash # Clone the repository git clone https://github.com/yourname/my-claude-plugin.git cd my-claude-plugin # Install dependencies npm install # Build the project npm run build ```### Testing ```bash # Run tests npm test # Run tests with coverage npm run test:coverage ### Building ````bash ````bash # Build the project npm run build # Pack the project npm run pack ```## Contributing Contributions are welcome! Please feel free to submit a Pull Request. ## License MIT © [Your Name] ## Support For support, please open an issue on GitHub or contact [your-email@example.com]. ```## 22.5.4 持续集成 ### GitHub Actions 配置 # .github/workflows/ci.yml name:CIon:push:branches:[ main, develop ]pull_request:branches:[ main, develop ]jobs:test:runs-on: ubuntu-latest strategy:matrix:node-version:[14.x,16.x,18.x]steps:- uses: actions/checkout@v2- name: Use Node.js ${{ matrix.node-version }}uses: actions/setup-node@v2with:node-version: ${{ matrix.node-version }}cache:'npm'- name: Install dependencies run: npm ci - name: Run linter run: npm run lint - name: Run tests run: npm test - name: Generate coverage run: npm run test:coverage - name: Upload coverage uses: codecov/codecov-action@v2with:files:./coverage/lcov.info build:needs: test runs-on: ubuntu-latest steps:- uses: actions/checkout@v2- name: Use Node.js uses: actions/setup-node@v2with:node-version:'18.x'cache:'npm'- name: Install dependencies run: npm ci - name: Build run: npm run build - name: Pack run: npm run pack - name: Upload artifact uses: actions/upload-artifact@v2with:name:packagepath:'*.tgz' ```### 发布工作流 ```yaml # .github/workflows/release.yml name: Release on: push: tags:-'v*' jobs: release: runs-on: ubuntu-latest steps:- uses: actions/checkout@v2 - name: Use Node.js uses: actions/setup-node@v2with: node-version:'18.x' cache:'npm' registry-url:'https://registry.npmjs.org' - name: Install dependencies run: npm ci - name: Build run: npm run build - name: Run tests run: npm test - name: Publish to npm run: npm publish env:NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN}} - name: Create GitHub Release uses: actions/create-release@v1 env:GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN}}with: tag_name: ${{ github.ref }} release_name: Release ${{ github.ref }} draft:false prerelease:false ## 22.5.5 错误监控 ### 错误追踪集成 // src/monitoring/error-tracker.ts/** * 错误追踪器 */exportclassErrorTracker{private errors: ErrorReport[]=[];private maxErrors:number=100;/** * 追踪错误 */track(error: Error, context?: ErrorContext):void{const report: ErrorReport ={id:this.generateId(),message: error.message,stack: error.stack,timestamp:newDate(),context: context ||{}};this.errors.push(report);// 限制错误数量if(this.errors.length >this.maxErrors){this.errors.shift();}// 发送到错误监控服务this.sendToMonitoringService(report);}/** * 生成 ID */privategenerateId():string{return`error-${Date.now()}-${Math.random().toString(36).substr(2,9)}`;}/** * 发送到监控服务 */privatesendToMonitoringService(report: ErrorReport):void{// 集成到 Sentry、Bugsnag 等console.log('Sending error to monitoring service:', report.id);}/** * 获取错误报告 */getReports(limit?:number): ErrorReport[]{if(limit){returnthis.errors.slice(-limit);}return[...this.errors];}/** * 清除错误报告 */clearReports():void{this.errors =[];}/** * 获取错误统计 */getStats(): ErrorStats {const stats: ErrorStats ={total:this.errors.length,byType:{},byHour:{}};for(const error ofthis.errors){// 按类型统计const type = error.context.type ||'unknown';stats.byType[type]=(stats.byType[type]||0)+1;// 按小时统计const hour = error.timestamp.getHours();stats.byHour[hour]=(stats.byHour[hour]||0)+1;}return stats;}}/** * 错误报告 */interfaceErrorReport{id:string;message:string;stack?:string;timestamp: Date;context: ErrorContext;}/** * 错误上下文 */interfaceErrorContext{type?:string;plugin?:string;user?:string;[key:string]:any;}/** * 错误统计 */interfaceErrorStats{total:number;byType: Recordstring,number>;byHour: Recordnumber,number>;}// 使用示例const tracker =newErrorTracker();try{// 可能出错的代码thrownewError('Something went wrong');}catch(error){tracker.track(error,{type:'runtime',plugin:'my-plugin',user:'user123'});}// 获取错误报告const reports = tracker.getReports(10);console.log('Recent errors:', reports);// 获取错误统计const stats = tracker.getStats();console.log('Error stats:', stats); ```### 性能监控 ```typescript // src/monitoring/performance-monitor.ts /** * 性能监控器 */exportclassPerformanceMonitor{private metrics: Mapstring, Metric[]>=newMap();private maxMetrics:number=1000; /** * 记录指标 */record(name:string, value:number, tags?: Recordstring,string>):void{const metric: Metric ={ name, value, timestamp:newDate(), tags: tags ||{}}; if(!this.metrics.has(name)){this.metrics.set(name,[]);} const metrics =this.metrics.get(name)!; metrics.push(metric); // 限制指标数量if(metrics.length >this.maxMetrics){ metrics.shift();}} /** * 测量函数执行时间 */asyncmeasureT>(name:string,fn:()=>PromiseT>, tags?: Recordstring,string>):PromiseT>{const start = Date.now(); try{const result =awaitfn();const duration = Date.now()- start; this.record(name, duration, tags); return result;}catch(error){const duration = Date.now()- start; this.record(`${name}.error`, duration,{...tags, error: error.message }); throw error;}
}
/**
- 获取指标 */ getMetrics(name: string, limit?: number): Metric[] { const metrics = this.metrics.get(name);
bash复制if (!metrics) {
bash复制return []; } if (limit) { return metrics.slice(-limit); } return [...metrics];
}
/**
- 获取指标统计 */ getStats(name: string): MetricStats | null { const metrics = this.metrics.get(name);
bash复制if (!metrics || metrics.length === 0) {
bash复制return null; } const values = metrics.map(m => m.value); const sum = values.reduce((a, b) => a + b, 0); const avg = sum / values.length; const min = Math.min(...values); const max = Math.max(...values); // 计算百分位数 const sorted = [...values].sort((a, b) =>
}
/**
- 清除指标 */ clearMetrics(name?: string): void { if (name) { this.metrics.delete(name); } else { this.metrics.clear(); } } }
/**
- 指标 */ interface Metric { name: string; value: number; timestamp: Date; tags: Record; }
/**
- 指标统计 */ interface MetricStats { count: number; sum: number; avg: number; min: number; max: number; p50: number; p95: number; p99: number; }
// 使用示例 const monitor = new PerformanceMonitor();
// 记录指标 monitor.record('request.duration', 123, { method: 'GET', endpoint: '/api/users' });
// 测量函数执行时间 const result = await monitor.measure('database.query', async () => { // 数据库查询 return { id: 1, name: 'John' }; });
// 获取指标统计 const stats = monitor.getStats('request.duration'); console.log('Stats:', stats);
22.5.6 用户反馈#
反馈收集#
// src/feedback/feedback-collector.ts /**
- 反馈收集器 / export class FeedbackCollector { private feedbacks: Feedback[] = []; /*
- 收集反馈 / collect(feedback: Omit): string { const newFeedback: Feedback = { id: this.generateId(), timestamp: new Date(), ...feedback }; this.feedbacks.push(newFeedback); // 发送到反馈服务 this.sendToFeedbackService(newFeedback); return newFeedback.id; } /*
- 生成 ID
/
private generateId(): string {
return
feedback-${Date.now()}-${Math.random().toString(36).substr(2, 9)}; } /* - 发送到反馈服务 / private sendToFeedbackService(feedback: Feedback): void { // 发送到反馈服务 console.log('Sending feedback:', feedback.id); } /*
- 获取反馈 / getFeedbacks(limit?: number): Feedback[] { if (limit) { return this.feedbacks.slice(-limit); } return [...this.feedbacks]; } /*
- 获取反馈统计 / getStats(): FeedbackStats { const stats: FeedbackStats = { total: this.feedbacks.length, byType: {}, byRating: {}, averageRating: 0 }; let totalRating = 0; let ratingCount = 0; for (const feedback of this.feedbacks) { // 按类型统计 stats.byType[feedback.type] = (stats.byType[feedback.type] || 0) + 1; // 按评分统计 if (feedback.rating) { stats.byRating[feedback.rating] = (stats.byRating[feedback.rating] || 0) + 1; totalRating += feedback.rating; ratingCount++; } } // 计算平均评分 if (ratingCount > 0) { stats.averageRating = totalRating / ratingCount; } return stats; } } /*
- 反馈 / interface Feedback { id: string; type: 'bug' | 'feature' | 'improvement' | 'other'; rating?: number; title: string; description: string; user?: string; timestamp: Date; } /*
- 反馈统计 */ interface FeedbackStats { total: number; byType: Record; byRating: Record; averageRating: number; } // 使用示例 const collector = new FeedbackCollector(); // 收集反馈 const feedbackId = collector.collect({ type: 'feature', rating: 5, title: 'Add new feature', description: 'Please add this feature', user: 'user123' }); console.log('Feedback ID:', feedbackId); // 获取反馈统计 const stats = collector.getStats(); console.log('Feedback stats:', stats);
bash复制### 用户调查
typescript
// src/feedback/survey.ts
/**
- 用户调查
/**
- 添加问题 */ addQuestion(question: SurveyQuestion): void { this.questions.push(question); }
/**
- 提交响应 */ submitResponse(response: Omit): string { const newResponse: SurveyResponse = { id: this.generateId(), timestamp: new Date(), ...response };
bash复制this.responses.push(newResponse);
bash复制// 发送到调查服务 this.sendToSurveyService(newResponse); return newResponse.id;
}
/**
- 生成 ID
*/
private generateId(): string {
return
response-${Date.now()}-${Math.random().toString(36).substr(2, 9)}; }
/**
- 发送到调查服务 */ private sendToSurveyService(response: SurveyResponse): void { // 发送到调查服务 console.log('Sending survey response:', response.id); }
/**
- 获取响应 */ getResponses(limit?: number): SurveyResponse[] { if (limit) { return this.responses.slice(-limit); } return [...this.responses]; }
/**
- 分析响应 */ analyzeResponses(): SurveyAnalysis { const analysis: SurveyAnalysis = { totalResponses: this.responses.length, averageRating: 0, byQuestion: {} };
bash复制let totalRating = 0;
bash复制let ratingCount = 0; for (const question of this.questions) { const questionResponses = this.responses.filter( r => r.answers[question.id] !== undefined ); if (question.type === 'rating') { const ratings = questionResponses.map( r => r.answers[question.id] as number ); const sum = ratings.reduce((a, b) => a + b, 0); const avg = ratings.length > 0 ? sum / ratings.length : 0; analysis.byQuestion[question.id] = { count: ratings.length, average: avg, min: Math.min(...ratings), max: Math.max(...ratings) }; totalRating += sum; ratingCount += ratings.length; } } // 计算平均评分 if (ratingCount > 0) { analysis.averageRating = totalRating / ratingCount; } return analysis;
} }
/**
- 调查问题 */ interface SurveyQuestion { id: string; type: 'text' | 'rating' | 'choice' | 'multiple'; question: string; options?: string[]; required: boolean; }
/**
- 调查响应 */ interface SurveyResponse { id: string; userId?: string; answers: Record; timestamp: Date; }
/**
- 调查分析 */ interface SurveyAnalysis { totalResponses: number; averageRating: number; byQuestion: Record; }
// 使用示例 const survey = new UserSurvey();
// 添加问题 survey.addQuestion({ id: 'q1', type: 'rating', question: 'How satisfied are you with the plugin?', required: true });
survey.addQuestion({ id: 'q2', type: 'text', question: 'What do you like most about the plugin?', required: false });
survey.addQuestion({ id: 'q3', type: 'choice', question: 'How often do you use the plugin?', options: ['Daily', 'Weekly', 'Monthly', 'Rarely'], required: true });
// 提交响应 const responseId = survey.submitResponse({ userId: 'user123', answers: { q1: 5, q2: 'Easy to use and powerful', q3: 'Daily' } });
console.log('Response ID:', responseId);
// 分析响应 const analysis = survey.analyzeResponses(); console.log('Survey analysis:', analysis);