electron-forge で Mac App Store に申請できる macOS アプリのパッケージを作る

Electron で作ったアプリを Mac App Store に提出するために、アプリをインストールできるパッケージを作成する必要があります。パッケージを作成するツールとして electron-builder や electron-forge がありますが、今回は electron-forge を選択しました。しかし実際にやってみようとすると、ドキュメントがかなりあっさりしていてつまずきやすい点も多く、かなり大変な作業となりました。今回はその過程を飛ばし、Mac App Store に申請できる electron-forge の設定について具体的に紹介します。

使用バージョン

  "devDependencies": {
    "@electron-forge/cli": "7.4.0",
    "@electron-forge/maker-pkg": "^7.4.0",
  }

forge.config.js

ビルド実行時に platform 引数でプラットフォームを分岐できるようにしています。appCategoryType や ignore などは必要に応じて変更して下さい。

require("dotenv").config()

// darwin または mas を指定できます
const platform = process.argv[process.argv.indexOf("--platform") + 1]

const packagerConfig = {
  // ここに記載されているいずれかのカテゴリ https://developer.apple.com/documentation/bundleresources/information_property_list/lsapplicationcategorytype
  appCategoryType: "public.app-category.music", 
  buildVersion: process.env.BUILD_VERSION,
  icon: "./icons/icon",
  // パッケージに含めないファイルを列挙
  ignore: [
    "^/.gitignore",
    "^/src",
    "^/README.md",
    "^/tsconfig.json",
    "^/tsconfig.preload.json",
    "^/node_modules",
    "^/scripts",
    "^/out",
    "^/rollup.config.js",
    "^/forge.config.js",
    "^/entitlements.plist",
    "^/entitlements.child.plist",
    "^/nodemon.json",
    "^/icons",
    "^/.env",
  ],
  overwrite: true,
  prune: false,
  // 実行ファイルやライブラリ等で使われるネイティブのバイナリがある場合にファイル名のパターンを指定 (オプション)
  osxUniversal: {
    x64ArchFiles: "*_mac",
  },
  extendInfo: {
    // ファイルの関連付け (オプション)
    CFBundleDocumentTypes: [
      {
        CFBundleTypeExtensions: ["mid"],
        CFBundleTypeName: "MIDI File",
        CFBundleTypeRole: "Editor",
        LSHandlerRank: "Owner",
      },
    ],
    // 多重起動の禁止
    LSMultipleInstancesProhibited: true,
  },
}

switch (platform) {
  // 開発用のパッケージの設定
  case "darwin":
    packagerConfig.platform = "darwin"
    packagerConfig.appBundleId = "com.example.myapp.dev"
    packagerConfig.osxSign = {
      platform: "darwin",
      identity: process.env.APPLE_DEVELOPER_CERTIFICATE_NAME,
      preEmbedProvisioningProfile: true,
      provisioningProfile: process.env.APPLE_PROVISIONING_PROFILE,
      optionsForFile: (filePath) => {
        const entitlements = filePath.includes(".app/")
          ? "entitlements.child.plist"
          : "entitlements.plist"
        return {
          hardenedRuntime: false,
          entitlements,
        }
      },
    }
    break
  // 申請用のパッケージの設定
  case "mas":
    packagerConfig.platform = "mas"
    packagerConfig.appBundleId = "com.example.myapp"
    packagerConfig.osxSign = {
      platform: "mas",
      identity: process.env.APPLE_DISTRIBUTION_CERTIFICATE_NAME,
      preEmbedProvisioningProfile: true,
      provisioningProfile: process.env.APPLE_PROVISIONING_PROFILE,
      optionsForFile: (filePath) => {
        const entitlements = filePath.includes(".app/")
          ? "entitlements.child.plist"
          : "entitlements.plist"
        return {
          hardenedRuntime: false,
          entitlements,
        }
      },
    }
    break
}

module.exports = {
  packagerConfig: {
    ...packagerConfig,
    protocols: [
      // URL Scheme の設定 (オプション)
      {
        name: "myapp",
        schemes: [packagerConfig.appBundleId],
      },
    ],
  },
  makers: [
    {
      name: "@electron-forge/maker-pkg",
      config: {
        identity: process.env.APPLE_INSTALLER_CERTIFICATE_NAME,
      },
    },
  ],
}

.env

証明書などの設定を .env ファイルに記載します。ここに指定した証明書はビルドを行うマシンにインストールされている必要があります。BUILD_VERSION はアップロードのたびにインクリメントする必要があります。

APPLE_DEVELOPER_CERTIFICATE_NAME=Apple Development: FirstName LastName (XXXXXXXX)
APPLE_DISTRIBUTION_CERTIFICATE_NAME=Apple Distribution: FirstName LastName (XXXXXXXX)
APPLE_INSTALLER_CERTIFICATE_NAME=3rd Party Mac Developer Installer: FirstName LastName (XXXXXXXX)
APPLE_PROVISIONING_PROFILE=/path/to/Mac_AppStore_myapp.provisionprofile
BUILD_VERSION=1

証明書について

maker-pkg の identitiy は Apple Distribution ではなく 3rd Party Mac Developer Installer を指定します。Apple Distributionを指定すると下記のエラーが出ます。

An installer signing identity (not an application signing identity) is required for signing flat-style products.)

証明書は Xcode で Manage Certificates から Mac Installer Distribution を指定すると作ることができます。

entitlement

forge.config.js で指定している entitlement ファイルは下記のようになります。

entitlement.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.cs.allow-jit</key>
    <true/>
    <key>com.apple.security.network.client</key>
    <true/>
    <key>com.apple.security.files.user-selected.read-only</key>
    <true/>
    <key>com.apple.security.files.user-selected.read-write</key>
    <true/>
    <key>com.apple.security.cs.allow-dyld-environment-variables</key>
    <true/>
  </dict>
</plist>

entitlement.child.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.inherit</key>
    <true/>
  </dict>
</plist>

com.apple.security.app-sandboxtrue にしないとアップロード時の検証で次のようなエラーが出ます。

App sandbox not enabled. The following executables must include the "com.apple.security.app-sandbox" entitlement with a Boolean value of true in the entitlements property list: [( "com.example.myapp.pkg/Payload/myapp.app/Contents/Frameworks/Electron Framework.framework/Versions/A/Helpers/chrome_crashpad_handler", "com.example.myapp.pkg/Payload/myapp.app/Contents/Frameworks/Squirrel.framework/Versions/A/Resources/ShipIt", "com.example.myapp.pkg/Payload/myapp.app/Contents/Frameworks/myapp Helper (GPU).app/Contents/MacOS/myapp Helper (GPU)", "com.example.myapp.pkg/Payload/myapp.app/Contents/Frameworks/myapp Helper (Plugin).app/Contents/MacOS/myapp Helper (Plugin)", "com.example.myapp.pkg/Payload/myapp.app/Contents/Frameworks/myapp Helper (Renderer).app/Contents/MacOS/myapp Helper (Renderer)", "com.example.myapp.pkg/Payload/myapp.app/Contents/Frameworks/myapp Helper.app/Contents/MacOS/myapp Helper", "com.example.myapp.pkg/Payload/myapp.app/Contents/MacOS/myapp" )] Refer to App Sandbox page at https://developer.apple.com/documentation/security/app_sandbox for more information on sandboxing your app.(ID: XXXX)

パッケージスクリプト

scripts: {
    "build": "ここに自分のビルドスクリプトを書く"
    "make": "npm run build && electron-forge make",
    "make:mas": "npm run make -- --arch=universal --platform=mas",
    "make:darwin": "npm run make -- --platform=darwin"
}

npm run make:darwin で開発用のパッケージを生成します。 npm run make:mas で Mac App Store (MAS) 向けのパッケージを生成します。

申請方法

上記のスクリプトで生成した MAS 向けのパッケージを Apple が提供する Transporter アプリでアップロードします。あとは通常の macOS アプリや iOS アプリと同じ流れになります。

notarize について

開発したアプリのアイコンをダブルクリックで起動しようとした際に、セキュリティの警告が表示されます。これを防ぐためには、ちゃんとしたアプリですよということを示す notarize という手順を行う必要があります。しかし、Mac App Store に公開する場合はサーバ上でこの処理が行われるため、アップロードする前に notarize する必要はありません。

もし pkg ファイルを自前の Web サイト等で配布する場合には、forge.config.js に osxNotarize の項目を追加して notarize を行う必要があります。