Claude Code Plugins

Community-maintained marketplace

Feedback

.claude/skills/electron-distribution/SKILL.md

@mattnigh/skills_collection
0
0

|

Install Skill

1Download skill
2Enable skills in Claude

Open claude.ai/settings/capabilities and find the "Skills" section

3Upload to Claude

Click "Upload skill" and select the downloaded ZIP file

Note: Please verify skill by going through its instructions before using it.

SKILL.md

name .claude/skills/electron-distribution/SKILL.md
description Electronアプリケーションの配布・自動更新専門知識 📚 リソース参照: このスキルには以下のリソースが含まれています。 必要に応じて該当するリソースを参照してください: - `.claude/skills/electron-distribution/resources/auto-update.md`: 自動更新実装詳細 - `.claude/skills/electron-distribution/resources/release-channels.md`: リリースチャネル管理 - `.claude/skills/electron-distribution/resources/store-distribution.md`: ストア配布ガイド - `.claude/skills/electron-distribution/templates/update-server.ts`: 更新サーバーテンプレート - `.claude/skills/electron-distribution/scripts/release.sh`: リリーススクリプト 専門分野: - 自動更新: electron-updater設定 - リリース管理: バージョニング、チャネル - ストア配布: Mac App Store、Microsoft Store - アップデートサーバー: 自前・GitHub Releases 使用タイミング: - 自動更新機能を実装する時 - リリースフローを構築する時 - アプリストアに配布する時 - 更新サーバーを設定する時
version 1.0.0

.claude/skills/electron-distribution/SKILL.md

Electronアプリケーションの配布・自動更新専門知識


概要

目的

Electronアプリケーションを効率的に配布し、 シームレスな自動更新を提供する。

対象者

  • Electronアプリ開発者
  • DevOpsエンジニア
  • リリースマネージャー

配布方法の選択

配布チャネル比較

方法 メリット デメリット 推奨ケース
GitHub Releases 無料、簡単 帯域制限あり OSS、小規模
S3/CloudFront 高速、スケーラブル コスト 中〜大規模
Mac App Store 信頼性、発見性 審査、制約 macOSメイン
Microsoft Store 信頼性、自動更新 審査 Windowsメイン
自前サーバー 完全制御 運用負荷 エンタープライズ

自動更新(electron-updater)

基本設定

# electron-builder.yml
publish:
  - provider: github
    owner: your-username
    repo: your-repo
    releaseType: release

更新サービス実装

// main/services/updater.ts
import { autoUpdater, UpdateInfo } from "electron-updater";
import { app, BrowserWindow, dialog } from "electron";
import log from "electron-log";

// ログ設定
autoUpdater.logger = log;
log.transports.file.level = "info";

// 更新チェック設定
autoUpdater.autoDownload = false;
autoUpdater.autoInstallOnAppQuit = true;

export class UpdateService {
  private mainWindow: BrowserWindow | null = null;

  constructor(win: BrowserWindow) {
    this.mainWindow = win;
    this.setupEventListeners();
  }

  private setupEventListeners(): void {
    // 更新確認中
    autoUpdater.on("checking-for-update", () => {
      this.sendToRenderer("update-status", { status: "checking" });
    });

    // 更新あり
    autoUpdater.on("update-available", (info: UpdateInfo) => {
      this.sendToRenderer("update-status", {
        status: "available",
        version: info.version,
        releaseNotes: info.releaseNotes,
      });

      // ユーザーに確認
      dialog
        .showMessageBox(this.mainWindow!, {
          type: "info",
          title: "更新があります",
          message: `バージョン ${info.version} が利用可能です。ダウンロードしますか?`,
          buttons: ["後で", "ダウンロード"],
          defaultId: 1,
        })
        .then(({ response }) => {
          if (response === 1) {
            autoUpdater.downloadUpdate();
          }
        });
    });

    // 更新なし
    autoUpdater.on("update-not-available", () => {
      this.sendToRenderer("update-status", { status: "up-to-date" });
    });

    // ダウンロード進捗
    autoUpdater.on("download-progress", (progress) => {
      this.sendToRenderer("update-progress", {
        percent: progress.percent,
        bytesPerSecond: progress.bytesPerSecond,
        total: progress.total,
        transferred: progress.transferred,
      });
    });

    // ダウンロード完了
    autoUpdater.on("update-downloaded", (info: UpdateInfo) => {
      this.sendToRenderer("update-status", {
        status: "downloaded",
        version: info.version,
      });

      dialog
        .showMessageBox(this.mainWindow!, {
          type: "info",
          title: "更新の準備完了",
          message: "更新をインストールするにはアプリを再起動してください。",
          buttons: ["後で", "今すぐ再起動"],
          defaultId: 1,
        })
        .then(({ response }) => {
          if (response === 1) {
            autoUpdater.quitAndInstall(false, true);
          }
        });
    });

    // エラー
    autoUpdater.on("error", (error) => {
      log.error("Update error:", error);
      this.sendToRenderer("update-status", {
        status: "error",
        message: error.message,
      });
    });
  }

  private sendToRenderer(channel: string, data: unknown): void {
    this.mainWindow?.webContents.send(channel, data);
  }

  // 手動更新チェック
  async checkForUpdates(): Promise<void> {
    try {
      await autoUpdater.checkForUpdates();
    } catch (error) {
      log.error("Check for updates failed:", error);
    }
  }

  // ダウンロード開始
  downloadUpdate(): void {
    autoUpdater.downloadUpdate();
  }

  // インストール
  quitAndInstall(): void {
    autoUpdater.quitAndInstall(false, true);
  }
}

// メインプロセスで初期化
app.whenReady().then(() => {
  const mainWindow = createMainWindow();
  const updateService = new UpdateService(mainWindow);

  // 起動時に更新チェック(遅延)
  setTimeout(() => {
    updateService.checkForUpdates();
  }, 3000);

  // IPC登録
  ipcMain.handle("update:check", () => updateService.checkForUpdates());
  ipcMain.handle("update:download", () => updateService.downloadUpdate());
  ipcMain.handle("update:install", () => updateService.quitAndInstall());
});

Renderer側UI

// renderer/components/UpdateNotification.tsx
import { useEffect, useState } from "react";

interface UpdateStatus {
  status:
    | "checking"
    | "available"
    | "downloading"
    | "downloaded"
    | "up-to-date"
    | "error";
  version?: string;
  message?: string;
}

interface UpdateProgress {
  percent: number;
  bytesPerSecond: number;
  total: number;
  transferred: number;
}

export function UpdateNotification() {
  const [status, setStatus] = useState<UpdateStatus | null>(null);
  const [progress, setProgress] = useState<UpdateProgress | null>(null);

  useEffect(() => {
    const cleanupStatus = window.electronAPI.onUpdateStatus(setStatus);
    const cleanupProgress = window.electronAPI.onUpdateProgress(setProgress);

    return () => {
      cleanupStatus();
      cleanupProgress();
    };
  }, []);

  if (!status || status.status === "up-to-date") {
    return null;
  }

  return (
    <div className="update-notification">
      {status.status === "checking" && <p>更新を確認中...</p>}

      {status.status === "available" && (
        <div>
          <p>バージョン {status.version} が利用可能です</p>
          <button onClick={() => window.electronAPI.downloadUpdate()}>
            ダウンロード
          </button>
        </div>
      )}

      {status.status === "downloading" && progress && (
        <div>
          <p>ダウンロード中: {Math.round(progress.percent)}%</p>
          <progress value={progress.percent} max={100} />
        </div>
      )}

      {status.status === "downloaded" && (
        <div>
          <p>更新の準備完了</p>
          <button onClick={() => window.electronAPI.installUpdate()}>
            再起動してインストール
          </button>
        </div>
      )}

      {status.status === "error" && (
        <p className="error">更新エラー: {status.message}</p>
      )}
    </div>
  );
}

リリースチャネル

チャネル設定

# electron-builder.yml
publish:
  - provider: github
    owner: your-username
    repo: your-repo
    channel: ${channel}
// main/services/updater.ts
// チャネル設定
function setUpdateChannel(channel: "stable" | "beta" | "alpha"): void {
  autoUpdater.channel = channel;
  autoUpdater.allowPrerelease = channel !== "stable";
  autoUpdater.allowDowngrade = false;
}

// ユーザー設定から読み込み
const userSettings = store.get("updateChannel", "stable");
setUpdateChannel(userSettings);

バージョニング戦略

# Semantic Versioning
stable:  1.0.0, 1.0.1, 1.1.0, 2.0.0
beta:    1.1.0-beta.1, 1.1.0-beta.2
alpha:   1.1.0-alpha.1, 2.0.0-alpha.1

# package.json
{
  "version": "1.1.0-beta.1"
}

GitHub Releases配布

electron-builder.yml

publish:
  - provider: github
    owner: your-org
    repo: your-app
    releaseType: release # or draft, prerelease
    private: false

リリースワークフロー

# .github/workflows/release.yml
name: Release

on:
  push:
    tags:
      - "v*"

jobs:
  release:
    strategy:
      matrix:
        include:
          - os: macos-latest
            platform: mac
          - os: windows-latest
            platform: win
          - os: ubuntu-latest
            platform: linux

    runs-on: ${{ matrix.os }}

    steps:
      - uses: actions/checkout@v4

      - uses: actions/setup-node@v4
        with:
          node-version: "20"
          cache: "npm"

      - run: npm ci
      - run: npm run build

      - name: Publish
        env:
          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
          # macOS署名
          APPLE_ID: ${{ secrets.APPLE_ID }}
          APPLE_ID_PASSWORD: ${{ secrets.APPLE_ID_PASSWORD }}
          CSC_LINK: ${{ secrets.MAC_CERTS }}
          CSC_KEY_PASSWORD: ${{ secrets.MAC_CERTS_PASSWORD }}
          # Windows署名
          WIN_CERT_FILE: ${{ secrets.WIN_CERT_FILE }}
          WIN_CERT_PASSWORD: ${{ secrets.WIN_CERT_PASSWORD }}
        run: npm run publish -- --${{ matrix.platform }}

S3/CloudFront配布

設定

# electron-builder.yml
publish:
  - provider: s3
    bucket: your-app-releases
    region: us-east-1
    acl: public-read
    path: /releases/${os}/${arch}

CloudFront設定(Terraform例)

resource "aws_cloudfront_distribution" "releases" {
  origin {
    domain_name = aws_s3_bucket.releases.bucket_regional_domain_name
    origin_id   = "S3-releases"
  }

  enabled = true

  default_cache_behavior {
    allowed_methods        = ["GET", "HEAD"]
    cached_methods         = ["GET", "HEAD"]
    target_origin_id       = "S3-releases"
    viewer_protocol_policy = "redirect-to-https"

    forwarded_values {
      query_string = false
      cookies {
        forward = "none"
      }
    }
  }

  restrictions {
    geo_restriction {
      restriction_type = "none"
    }
  }

  viewer_certificate {
    cloudfront_default_certificate = true
  }
}

カスタム更新サーバー

// update-server/server.ts
import express from "express";
import { S3Client, GetObjectCommand } from "@aws-sdk/client-s3";

const app = express();
const s3 = new S3Client({ region: "us-east-1" });

// 更新情報エンドポイント
app.get("/update/:platform/:version", async (req, res) => {
  const { platform, version } = req.params;

  try {
    // 最新バージョン取得
    const latestVersion = await getLatestVersion(platform);

    if (compareVersions(latestVersion, version) > 0) {
      res.json({
        version: latestVersion,
        files: await getDownloadUrls(platform, latestVersion),
        releaseDate: await getReleaseDate(latestVersion),
        releaseNotes: await getReleaseNotes(latestVersion),
      });
    } else {
      res.status(204).send();
    }
  } catch (error) {
    res.status(500).json({ error: "Failed to check updates" });
  }
});

// ダウンロードエンドポイント
app.get("/download/:platform/:version/:file", async (req, res) => {
  const { platform, version, file } = req.params;

  // 署名付きURL生成
  const signedUrl = await generateSignedUrl(
    `releases/${platform}/${version}/${file}`,
  );

  res.redirect(signedUrl);
});

app.listen(3000);

Mac App Store配布

制約事項

機能 通常配布 MAS
自動更新 electron-updater App Store
サンドボックス 任意 必須
ハードニングランタイム 推奨 必須
署名 Developer ID Mac App Store
ネイティブモジュール 制限あり

electron-builder設定

# electron-builder.yml
mas:
  target: mas

  hardenedRuntime: true
  gatekeeperAssess: false

  entitlements: build/entitlements.mas.plist
  entitlementsInherit: build/entitlements.mas.inherit.plist

  # MAS用署名
  identity: "3rd Party Mac Developer Application: Company (TEAM_ID)"

  # プロビジョニングプロファイル
  provisioningProfile: build/embedded.provisionprofile

masDev:
  identity: "Mac Developer: Your Name (XXXXXXXXXX)"
  provisioningProfile: build/embedded-dev.provisionprofile
<!-- build/entitlements.mas.plist -->
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>com.apple.security.app-sandbox</key>
    <true/>
    <key>com.apple.security.network.client</key>
    <true/>
    <key>com.apple.security.files.user-selected.read-write</key>
    <true/>
</dict>
</plist>

Microsoft Store配布

AppX設定

# electron-builder.yml
win:
  target:
    - target: appx
      arch:
        - x64
        - arm64

appx:
  applicationId: CompanyName.AppName
  displayName: My Electron App
  identityName: 12345CompanyName.AppName
  publisher: CN=XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX
  publisherDisplayName: Company Name
  languages:
    - ja-JP
    - en-US
  addAutoLaunchExtension: false
  showNameOnTiles: true
  backgroundColor: "#ffffff"

リリースチェックリスト

リリース前

  • バージョン番号更新
  • CHANGELOGの更新
  • 全プラットフォームでテスト
  • コード署名の確認
  • 依存関係のセキュリティ監査
  • リリースノート作成

リリース後

  • ダウンロードリンク確認
  • 自動更新テスト
  • インストールテスト(クリーン環境)
  • アップグレードテスト(既存インストール)
  • クラッシュレポート監視

関連リソース

詳細ドキュメント

  • resources/auto-update.md - 自動更新詳細
  • resources/release-channels.md - チャネル管理
  • resources/store-distribution.md - ストア配布

テンプレート・スクリプト

  • templates/update-server.ts - 更新サーバー
  • scripts/release.sh - リリーススクリプト