Skip to content

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
    [![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:

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);

    ```

    ```

基于 MIT 许可发布 | 永久导航