22.5 插件发布与维护
python
## 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
json
// tsconfig.json
{
"compilerOptions": {
"target": "ES2020",
"module": "commonjs",
"lib": ["ES2020"],
"outDir": "./dist",
"rootDir": "./src",
"declaration": true,
"declarationMap": true,
"sourceMap": true,
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"resolveJsonModule": true,
"moduleResolution": "node"
},
"include": ["src/**/*"],
"exclude": ["node_modules", "dist", "**/*.test.ts"]
}
### 插件清单
# 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;
}
/**
* 解析版本字符串
*/
private parse(version: string): {
major: number;
minor: number;
patch: number;
prerelease?: string;
build?: string;
} {
const regex = /^(\d+)\.(\d+)\.(\d+)(?:-([0-9A-Za-z-]+))?(?:\+([0-9A-Za-z-]+))?$/;
const match = version.match(regex);
if (!match) {
throw new Error(`Invalid version: ${version}`);
}
return {
major: parseInt(match[1]),
minor: parseInt(match[2]),
patch: parseInt(match[3]),
prerelease: match[4],
build: match[5]
};
}
/**
* 转换为字符串
*/
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) < 0;
}
/**
* 检查是否等于
*/
equals(other: SemanticVersion): boolean {
return this.compare(other) === 0;
}
}
// 使用示例
const version = new SemanticVersion('1.2.3');
console.log(version.toString()); // 1.2.3
const nextMajor = version.bumpMajor();
console.log(nextMajor.toString()); // 2.0.0
const nextMinor = version.bumpMinor();
console.log(nextMinor.toString()); // 1.3.0
const nextPatch = version.bumpPatch();
console.log(nextPatch.toString()); // 1.2.4
const v1 = new SemanticVersion('1.2.3');
const v2 = new SemanticVersion('1.2.4');
console.log(v1.lessThan(v2)); // true
console.log(v2.greaterThan(v1)); // true
### 版本发布脚本
#!/bin/bash
# scripts/release.sh
VERSION=$1
if [ -z "$VERSION" ]; then
echo "Usage: ./scripts/release.sh <version>"
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 文档
*/
async function generateDocs() {
const project = new Project({
tsconfig: 'tsconfig.json',
entryPoints: ['src/index.ts'],
readme: 'README.md',
out: 'docs/api',
plugin: [TypeDocReader, MarkdownRenderer],
exclude: ['**/*.test.ts', '**/node_modules/**'],
theme: 'markdown',
gitRevision: 'main'
});
await project.generate();
}
generateDocs().catch(console.error);
### README 模板
# 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
```python
claude plugin install my-claude-plugin
```> Or install from npm:
bash
npm install -g my-claude-plugin
```python
## Usage
### Basic Usage
typescript
````typescript
```python
import { MyPlugin } from 'my-claude-plugin';
const plugin = new MyPlugin();
await plugin.initialize({});
await plugin.start();
```### Advanced Usage
```
typescript
const plugin = new MyPlugin({
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<void>`
Initializes the plugin with the given configuration.
#### `start(): Promise<void>`
Starts the plugin.
#### `stop(): Promise<void>`
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: CI
on:
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@v2
with:
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@v2
with:
files: ./coverage/lcov.info
build:
needs: test
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Use Node.js
uses: actions/setup-node@v2
with:
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@v2
with:
name: package
path: '*.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@v2
with:
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
/**
* 错误追踪器
*/
export class ErrorTracker {
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: new Date(),
context: context || {}
};
this.errors.push(report);
// 限制错误数量
if (this.errors.length > this.maxErrors) {
this.errors.shift();
}
// 发送到错误监控服务
this.sendToMonitoringService(report);
}
/**
* 生成 ID
*/
private generateId(): string {
return `error-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
}
/**
* 发送到监控服务
*/
private sendToMonitoringService(report: ErrorReport): void {
// 集成到 Sentry、Bugsnag 等
console.log('Sending error to monitoring service:', report.id);
}
/**
* 获取错误报告
*/
getReports(limit?: number): ErrorReport[] {
if (limit) {
return this.errors.slice(-limit);
}
return [...this.errors];
}
/**
* 清除错误报告
*/
clearReports(): void {
this.errors = [];
}
/**
* 获取错误统计
*/
getStats(): ErrorStats {
const stats: ErrorStats = {
total: this.errors.length,
byType: {},
byHour: {}
};
for (const error of this.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;
}
}
/**
* 错误报告
*/
interface ErrorReport {
id: string;
message: string;
stack?: string;
timestamp: Date;
context: ErrorContext;
}
/**
* 错误上下文
*/
interface ErrorContext {
type?: string;
plugin?: string;
user?: string;
[key: string]: any;
}
/**
* 错误统计
*/
interface ErrorStats {
total: number;
byType: Record<string, number>;
byHour: Record<number, number>;
}
// 使用示例
const tracker = new ErrorTracker();
try {
// 可能出错的代码
throw new Error('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
/**
* 性能监控器
*/
export class PerformanceMonitor {
private metrics: Map<string, Metric[]> = new Map();
private maxMetrics: number = 1000;
/**
* 记录指标
*/
record(name: string, value: number, tags?: Record<string, string>): void {
const metric: Metric = {
name,
value,
timestamp: new Date(),
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();
}
}
/**
* 测量函数执行时间
*/
async measure<T>(name: string, fn: () => Promise<T>, tags?: Record<string, string>): Promise<T> {
const start = Date.now();
try {
const result = await fn();
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);
if (!metrics) {
return [];
}
if (limit) {
return metrics.slice(-limit);
}
return [...metrics];
}
/**
* 获取指标统计
*/
getStats(name: string): MetricStats | null {
const metrics = this.metrics.get(name);
if (!metrics || metrics.length === 0) {
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) => a - b);
const p50 = sorted[Math.floor(sorted.length * 0.5)];
const p95 = sorted[Math.floor(sorted.length * 0.95)];
const p99 = sorted[Math.floor(sorted.length * 0.99)];
return {
count: metrics.length,
sum,
avg,
min,
max,
p50,
p95,
p99
};
}
/**
* 清除指标
*/
clearMetrics(name?: string): void {
if (name) {
this.metrics.delete(name);
} else {
this.metrics.clear();
}
}
}
/**
* 指标
*/
interface Metric {
name: string;
value: number;
timestamp: Date;
tags: Record<string, string>;
}
/**
* 指标统计
*/
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<Feedback, 'id' | 'timestamp'>): 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<string, number>;
byRating: Record<number, number>;
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);
### 用户调查
typescript
// src/feedback/survey.ts
/**
* 用户调查
*/
export class UserSurvey {
private questions: SurveyQuestion[] = [];
private responses: SurveyResponse[] = [];
/**
* 添加问题
*/
addQuestion(question: SurveyQuestion): void {
this.questions.push(question);
}
/**
* 提交响应
*/
submitResponse(response: Omit<SurveyResponse, 'id' | 'timestamp'>): string {
const newResponse: SurveyResponse = {
id: this.generateId(),
timestamp: new Date(),
...response
};
this.responses.push(newResponse);
// 发送到调查服务
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: {}
};
let totalRating = 0;
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<string, any>;
timestamp: Date;
}
/**
* 调查分析
*/
interface SurveyAnalysis {
totalResponses: number;
averageRating: number;
byQuestion: Record<string, any>;
}
// 使用示例
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);
```
```