22.5 插件发布与维护

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 配置

 
    // 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

打包脚本

 
    #!/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 版本管理

语义化版本

 
    // 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 文档生成

 
    // 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
    [![NPM Version](https://img.shields.io/npm/v/my-claude-plugin.svg)](https://www.npmjs.com/package/my-claude-plugin)
    [![License](https://img.shields.io/npm/l/my-claude-plugin.svg)](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:

npm install -g my-claude-plugin

Usage

Basic Usage

 
    ````typescript
 
```python
```python
    import { MyPlugin } from 'my-claude-plugin';
 
    const plugin = new MyPlugin();
    await plugin.initialize({});
    await plugin.start();
 
 
````typescript
 
```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:

OptionTypeDefaultDescription
option1string'default'Description of option1
option2number100Description of option2

API

Methods

initialize(config: PluginConfig): Promise&lt;void&gt;

Initializes the plugin with the given configuration.

start(): Promise&lt;void&gt;

Starts the plugin.

stop(): Promise&lt;void&gt;

Stops the plugin.

Development

Prerequisites

  • Node.js >= 14
  • npm >= 6

Setup

 
````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 [[email protected]].
 
## 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: $&#123;&#123; secrets.NPM_TOKEN &#125;&#125;
 
    - name: Create GitHub Release
      uses: actions/create-release@v1
      env:
        GITHUB_TOKEN: $&#123;&#123; secrets.GITHUB_TOKEN &#125;&#125;
      with:
        tag_name: $&#123;&#123; github.ref &#125;&#125;
        release_name: Release $&#123;&#123; github.ref &#125;&#125;
        draft: false
        prerelease: false
 
## 22.5.5 错误监控
 
### 错误追踪集成
 
```javascript
// 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);

性能监控

 
    // 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
 
```javascript
    // 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);