Claude-skill-registry .claude/skills/electron-distribution/SKILL.md
install
source · Clone the upstream repo
git clone https://github.com/majiayu000/claude-skill-registry
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/majiayu000/claude-skill-registry "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/data/claude-skills-electron-distribution-skill-md" ~/.claude/skills/majiayu000-claude-skill-registry-claude-skills-electron-distribution-skill-md && rm -rf "$T"
manifest:
skills/data/claude-skills-electron-distribution-skill-md/SKILL.mdsource content
.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