AIエージェント Google Julesお試ししてみた(リファクタリング編)

1. Google Julesとは

Googleの発表したAIコーディングエージェント。現在はβ版で無料でお試しできる。 Google Jules公式サイト


2. 実際に使ってみる

Google JulesはGitHub上のレポジトリを対象にして動作する。初めてで何が起きるか分からないので、壊されても痛くない小さめなプロジェクトを題材にしてみる。

  • MCPで今週の日付を返すプログラム。
  • CursorのAIで週報を操作していたら、正しい日付に書き換えてくれなかったので、作ってみたもの。
  • mcp-thisweek GitHubリポジトリ

この短時間で適当に作ったプログラムを使いやすいようにリファクタリングして貰う。


2.1 Google Julesに目的を通知

mcp-thisweekレポジトリを指定して、Google Julesに次のように依頼。

# 目的
mcp-thisweekをリファクタリングして、より良いプロダクトにします。

# やりたいこと
- 英語のREADMEを作成する
- 日本語のREADMEをリネームする
- fastmcpを使用していますが、もっと導入しやすい形式に実装を変更する
- Dockerを利用した機能の導入方法も追加でサポートしたい
- その他、導入すべきことがあれば導入

2.2 Google Julesがプランを作成して最初のコードを生成

Google Julesが以下を自動で実施:
- GitHubのレポジトリを読み取り
- プランを作成
- コードをリファクタリング
- gitのbranchを作成して、GitHubにコードをpush
https://github.com/taku-o/mcp-thisweek/tree/refactor/improve-mcp-thisweek


3. 作成されたコードを確認(Docker編)

Google Julesが作成したコードを確認していく。まずはDocker経由でMCPを利用する機能から。

git clone https://github.com/taku-o/mcp-thisweek.git
cd mcp-thisweek
# 作業ブランチに切り替え
git switch refactor/improve-mcp-thisweek
pip install -r requirements.txt
# [notice] A new release of pip is available: 24.0 -> 25.1.1
# [notice] To update, run: pip install --upgrade pip
pip install --upgrade pip
docker build -t mcp-thisweek .

3.1 エラー発生

ERROR: failed to solve: process "/bin/sh -c pip install --no-cache-dir -r requirements.txt" did not complete successfully: exit code: 1

Google Julesにエラーを直させてみる。発生したエラーの文面を貼り付けて依頼。 ペタリ。

docker build -t mcp-thisweek .
で、次のエラーが出たけれど、原因は分かりますか?

 > [4/5] RUN pip install --no-cache-dir -r requirements.txt:
1.068 Collecting python-dateutil>=2.8.2
...
1.375 ERROR: Could not find a version that satisfies the requirement fastmcp>=0.4.1 (from versions: none)
1.376 ERROR: No matching distribution found for fastmcp>=0.4.1
...
ERROR: failed to solve: process "/bin/sh -c pip install --no-cache-dir -r requirements.txt" did not complete successfully: exit code: 1

Google Julesが修正を始めて新しいコードを作成してくれた。
内容を確認してpublish branch。


3.2 修正コードの確認(Docker編2回目)

git pull origin refactor/improve-mcp-thisweek
pip install -r requirements.txt

前回失敗したところが、今度は成功。

docker build -t mcp-thisweek .

しかし、Dockerで走らせた処理がすぐ止まってしまう。

docker run -d --name mcp-thisweek-container mcp-thisweek

ログを拾うためにforegroundで実行。

docker run --name mcp-thisweek-container mcp-thisweek
#> [06/02/25 06:12:32] INFO Starting MCP server 'mcp-thisweek' server.py:799
#> with transport 'stdio'

再びGoogle Julesに修正依頼。

docker run -d --name mcp-thisweek-container mcp-thisweek
がすぐ止まってしまう。

docker run --name mcp-thisweek-container mcp-thisweek
と実行したら
[06/02/25 06:12:32] INFO Starting MCP server 'mcp-thisweek' server.py:799
with transport 'stdio'
というログが帰ってきた。

直せますか?

Google JulesがDockerfileに問題があると断定。修正してくれた。
内容を確認して再度publish branch。


3.3 修正コードの確認(Docker編3回目)

git pull origin refactor/improve-mcp-thisweek
docker build -t mcp-thisweek .

今度はDockerの処理が動いているようだ。

docker run -d --name mcp-thisweek-container mcp-thisweek
docker ps

3.4 Cursor上でMCPを動作させてみる

Cursorのmcp.jsonを変更。

"mcp-thisweek": {
  "command": "docker",
  "args": [
    "exec",
    "-i",
    "mcp-thisweek-container",
    "python",
    "/app/server.py"
  ]
}

mcp.jsonを変更したらCursorを再起動。Cursor SettingsのMCP Toolsを確認。
mcp-thisweekが有効になった。

Cursorに聞いて、MCPが動作しているかチェックする。

今週の月曜日から金曜日の日付を教えてください

> Called MCP tool get_this_week_dates

> 今週の月曜日から金曜日の日付は以下の通りです。
> 月曜日:06/02
> 火曜日:06/03
> 水曜日:06/04
> 木曜日:06/05
> 金曜日:06/06
> 他に知りたいことがあれば教えてください。

うまく修正してくれたようだ。 Docker版の機能はOK。


4. 作成されたコードを確認(python編)

pythonコマンドで動作する方の機能の修正も動作確認していく。

mcp.jsonpythonを使う版に切り替える。

which python
#> /Users/taku.omi/.pyenv/shims/python

mcp.jsonを書き換えた後、Cursorを再起動する。

{
  "mcpServers": {
    "mcp-thisweek": {
      "command": "/Users/taku.omi/.pyenv/shims/python",
      "args": [
        "/Users/taku.omi/Documents/workspaces/mcp-thisweek/server.py"
      ]
    }
  }
}

残念ながらMCPが動かない。

MCPのエラーログは、Command-Shift-Pでコマンドパレット開いて、 Output: Focus on Output Viewを選択、開かれたビューでMCP Logsを選択すれば確認出来る。

2025-06-01 15:39:41.862 [error] week: No server info found

このログをGoogle Julesに投げて修正依頼する。

pipとPythonを使用する版は、
2025-06-01 15:39:41.862 [error] week: No server info found
と出て、MCP Toolとしての起動に失敗しました。
原因は分かりますか?

意訳だが、お前のエラーだろ、と言われた

The error 'No server info found' usually means the MCP client can't find or correctly read the configuration for 'mcp-thisweek' in its mcp.json file. This could be due to an incorrect path, a typo in the server name, or incorrect command/arguments for starting the server.

To help me understand the cause, could you please:

Confirm the exact location of the mcp.json file your MCP client is using?
Share the content of this mcp.json file?
Provide the full, correct absolute path to the mcp-thisweek/server.py script on your system?
Confirm that the python command in your mcp.json correctly points to a Python 3.10+ interpreter?

Google Julesと続けてやりとりする。

mcp.jsonでは、次のように指定しました。
{
  "mcpServers": {
    "mcp-thisweek": {
      "command": "/Users/taku.omi/.pyenv/shims/python",
      "args": [
        "/Users/taku.omi/Documents/workspaces/mcp-thisweek/server.py"
      ]
    }
  }
}

どちらもファイルはある。
ls /Users/taku.omi/.pyenv/shims/python
> /Users/taku.omi/.pyenv/shims/python*
ls /Users/taku.omi/Documents/workspaces/mcp-thisweek/server.py
> /Users/taku.omi/Documents/workspaces/mcp-thisweek/server.py

Google Jules回答。

Thanks for the details! The mcp.json entry looks correct, and the file paths are valid.

The 'No server info found' error strongly suggests that the MCP client application is either not loading this specific mcp.json file, or it's trying to call the server by a name that doesn't exactly match 'mcp-thisweek' as it appears in your mcp.json.

To narrow this down, could you please tell me:

How are you trying to use or call the 'mcp-thisweek' server? (e.g., from another Python script, a specific application, a command-line interface?)
Is there a chance the client application is configured to load its mcp.json from a different path than where this file is located?
What is the name of the specific tool or application that is reporting the 'No server info found' error?
Cursorのmcp.jsonを直接編集後、
Cursorを再起動。
Cursor SettingsでMCP Toolsとして、mcp-thisweekが有効になるか確認。
という手順を踏んでいます。
Thanks! This is very helpful. Since you're using Cursor, there are a couple of things to check:

Python Interpreter Path: GUI applications like Cursor sometimes don't inherit the full shell environment, so pyenv shims might not work as expected. Could you try changing the command in your mcp.json to the direct path of the Python interpreter within your pyenv version? For example, if your Python version is 3.10.7, the path might be /Users/taku.omi/.pyenv/versions/3.10.7/bin/python. Please update this path in your mcp.json, restart Cursor, and see if it resolves the issue.

Your mcp.json would look like:

{
  "mcpServers": {
    "mcp-thisweek": {
      "command": "/Users/taku.omi/.pyenv/versions/YOUR_PYTHON_VERSION_HERE/bin/python",
      "args": [
        "/Users/taku.omi/Documents/workspaces/mcp-thisweek/server.py"
      ]
    }
  }
}
(Replace YOUR_PYTHON_VERSION_HERE with the actual version like 3.10.7)

Cursor's mcp.json Location: Can you confirm the full path to the mcp.json file that you are editing for Cursor? I want to ensure it's the one Cursor is actively using.

Let's try updating the Python command path first.

何度かやりとりしてたら、
This is puzzling.
とか言いだした。
これはアカン。


自力で直す

Google Julesと話し合いをした結果、解決しそうにないので、自力で修正してしまう。
調査結果をGoogle Julesに説明。

調査していろいろ分かったよ。
server.pyのログ出力のところが良くなかったよ。
そこのパスを直したら、
当初のpipとPythonを使用する(python)版、
pipとPythonを使用する(fastmcp)版、
両方動作するようになったよ。

いろいろ修正のお願いが続いて悪いんだけど。
・server.pyのログ出力の、logging.basicConfigのfilenameパラメータの所、うまく回避できない?
・当初のpipとPythonを使用する(python)版に記載を戻して欲しい。

Google Julesが褒めてくれた。

That's brilliant!

けど、その後のGoogle Julesがお出しした修正の動作確認を押しつけられた。
その面倒な作業こそ、Google Jules、君にやって貰いたいのだけどね

Could you please pull these latest changes and verify all three methods to ensure everything is working as expected for you?

Direct execution with pip/Python: Using your MCP client (Cursor) configured with command: "python" and args: ["/path/to/mcp-thisweek/server.py"].
fastmcp run server.py with pip/Python: Using your MCP client configured with command: "/path/to/fastmcp" and args: ["run", "/path/to/mcp-thisweek/server.py"] (this should also work now).
Docker execution: As previously, using docker exec ... python /app/server.py.
Your confirmation on these would be greatly appreciated!

4.3 最後に少し手直し

少し気になる箇所をGoogle Julesに修正して貰う。

動作確認OKです。でも、少し修正を加えたい。
・server.pyのlogging.basicConfigのfilenameパラメータの所は、完全に消すのでなく、コメントアウトしておいてください。
・READMEのdocker runのコマンドの箇所、--rmオプションを付けてくれる?
docker run -d --rm --name mcp-thisweek-container mcp-thisweek

Google Julesの修正したコードを確認してpublish branch。


Pull Requestも作って貰う

Google JulesにPull Requestも作って貰おうとした。 しかし、それは出来ないようだ。

refactor/improve-mcp-thisweekブランチの内容で
Pull Requestを作成することは可能ですか?

Pull Requestのメッセージを作って貰う。
これもなかなか面倒くさいからね。

では、Pull Request用のタイトルと、
Descriptionのメッセージは造れますか?
Refactor: Improve mcp-thisweek usability, add Docker

This pull request introduces a comprehensive refactoring of the `mcp-thisweek` project to improve its usability, maintainability, and deployment options.

**Key Changes:**

*   **English README:** Added a new `README.md` in English and renamed the original Japanese README to `README.ja.md`. Both READMEs now include updated setup instructions.
*   **Simplified Pip/Python Usage:**
    *   `server.py` is now directly executable (e.g., `python server.py`), simplifying the setup for MCP clients.
    *   The recommended pip/Python setup in the READMEs now reflects this direct execution method.
    *   Ensured compatibility with `fastmcp run server.py` for users who prefer that invocation.
*   **Docker Support:**
    *   Added a `Dockerfile` to allow users to easily build and run `mcp-thisweek` as a Docker container.
    *   The container is configured to stay running in the background (`tail -f /dev/null`) and `server.py` is invoked via `docker exec`, as detailed in the READMEs.
    *   The `docker run` command in the READMEs now includes the `--rm` option for automatic container cleanup.
*   **Logging Improvements:**
    *   Modified `server.py` to default logging to `stderr` by commenting out the `filename` parameter in `logging.basicConfig`. This resolves path-related issues encountered with file-based logging in different execution environments.
*   **`.gitignore` Update:** Added common Python and Docker artifacts to the `.gitignore` file.
*   **Iterative Fixes:**
    *   Addressed issues with Python version compatibility for `fastmcp` in the Dockerfile by upgrading the base Python image.
    *   Resolved problems with the Docker container stopping prematurely by changing the `CMD` instruction.
    *   Worked through several iterations to ensure consistent behavior for both direct Python execution and `fastmcp run` methods.

These changes make `mcp-thisweek` more accessible, easier to set up for different user preferences, and provide robust deployment options via Docker. All functionalities (direct Python execution, `fastmcp run`, and Docker) have been confirmed to be working.

5 リリース!!

良さそう。
これでPull Requestを作成して、マージした。
https://github.com/taku-o/mcp-thisweek/pull/1

iPhoneに通知が一切飛んでこなくなった!その問題の解消方法について

急にiPhoneに通知が飛んでこなくなった

突然だが、先日手持ちのiPhoneに各アプリからの通知が一切飛んでこなくなった。
・Slackのような会話アプリからのTO指定も、
・仕事で使っているサーバー管理アプリからの警告アラートも、
・監視カメラが検知した不審者アラートも、
・お弁当注文アプリの届きました通知も、
な〜んにも届かない!

一方で、別で所有しているiPadには通知が届いていた。
アプリが通知を飛ばさなくなったわけではない!
iPhoneの状態が非常に怪しいぞ!


さて、問題はここからなのですが。

この通知が届かない現象への対処方法についてネットで調べたところ、
・電波状況が悪いのでは?
VPNを切ってみて?
・集中モードがONになっていない?
iPhoneの通知の設定、該当アプリの箇所を確認してみて?
と並び、

次に来るのが、
iPhoneのOSの問題。OSバージョンアップを。
・端末が故障している。修理に出して。
となっていたので、


待つのだ。他にも試すべきことがある。
そう思って記事を書くことにしました。
簡単な解決方法を試す前に、iPhoneを修理に出しちゃったら大変ですよね。

そもそもの通知の仕組みの話

そもそも、iPhoneへのpush通知がどのように実現されているか。
アプリによって違いますが、だいたい次のようになるでしょう。

・アプリの初回起動時か、
・アカウント登録時か、
・もしくはログイン時に、
・あるいはアプリで通知機能を有効にした際に、
端末の情報を収集する。
(仮にデバイストークンとでも呼びましょうか)


その収集した通知先に対して、アプリは通知を送信します。

では、なぜ通知が飛んでこなくなった?

iPhoneの各設定を確認して、問題がないことが前提条件ですよ。

私の場合の原因はおそらく
iPhoneの全てのアプリの通知が届かなくなったことから
通知端末の情報(デバイストークン)がなんらかの原因で書き換わってしまった
のだと考えています。


アプリは過去に収集した端末(デバイストークン)に対して通知を送信しようとしているが、
そこが変わってしまったので、
push通知があさっての方向に空振りしているのではないか?

そういう推測ですね。

結局、どうすれば良い?

この問題の解決方法は、
端末情報(デバイストークン)をアプリに取り直させれば良い。
となります。


対処方法はアプリによって異なるのですが、
簡単な方から ・アプリの通知機能を無効有効と切り替える。
・アプリからログアウトしてから、ログインする。
・アプリを消して、再インストールする。
・アプリからログアウト。次にアプリを消す。その後に再インストールする。
となります。


これで端末の情報(デバイストークン)を取り込み直してくれれば解決。
iPhoneに通知が再び届くようになるでしょう。


最後のしつこいやつ
"・アプリからログアウト。次にアプリを消す。その後に再インストールする。"
は、
単純にアプリを消して再インストールしても、
ログイン状態を維持し続けて、
端末情報(デバイストークン)を取り直してくれない頑固なアプリ向けの対応ですね。

おわり

とは言っても、アプリをアンインストールしたくても、
安易にアンインストールできないケースはあると思います。
データのバックアップが大変だったり。


あと、通知を送信するアプリが沢山ありすぎると、
とても全部のiPhoneのアプリにたいして直していられない。


結局、最低限通知が必要ないくつかのアプリに絞って、
通知が届くように修正を行うしかないと思いますね。


(ところで、端末の情報が書き換わってしまった原因とかは・・)
(さぁ?)

ボイスロイドのライセンス昔話 (と、社内プレゼンでのボイスロイドの使用について)

ボイスロイドのライセンスの話をします。
ボイスロイドってのはこういうアプリです。

f:id:taku-o:20210216184348p:plain https://www.ah-soft.com/voiceroid/


会話を機械音声で読み上げた音声ファイルを作るアプリです
Windows用のアプリで、動画に解説を入れるのに使用したりします。
キモいおっさんの声で動画解説を入れなくて済むようになるアプリだよ


入力した通りにメッセージを読み上げてくれるから、
訓練していない素人が生声で解説いれるより、
安定して同じ調子で声を作れて、むしろ生声より楽だったりする。


ボイスロイド動画がYoutubeにアップロードされない時代

その昔々のお話、
Youtubeにボイスロイドを使った動画をアップロードした時、
もし、その動画に広告をつけたなら、そのボイスロイドは商用での利用扱いとする
とされていました。

ボイスロイドの商用ライセンスが必要になる、と。


このボイスロイドの商用ライセンス料はとてもお高い(10万8000円!
こともあり、


そのため、ライセンスをきちんと扱う人は、
また、動画視聴者から指摘されたりもするしで、
Youtubeには、ボイスロイド動画がなかなかアップロードされない時代がありました。


不正な動画転載に悩まされる時代

しかーし、
世の中にはライセンスをまったく気にしない人が居ります。
不正に動画を転載する方々です。


彼らは、もちろん躊躇なく、広告付きで動画をYoutubeに転載してきやがります


不正な転載を潰すために、動画制作者本人が対抗アップロードすることもできますが、


商用ライセンスはお高いので(10万8000円!!)、
Youtubeにアップロードするなら、
広告抜きで動画をアップロードすることになりますよね?


不正転載者が「ガハハ、オメーの動画で食う飯はウメーなー」と稼いでる脇で、
無料で動画をアップロードしてたら、
まっとうに動画を作っている人は「コノヤロー」って思っちゃいますね。
あまりやる気はでません。


だけれども、動画を見る側からすると、
Youtubeに存在しなかった動画がYoutubeにアップロードされるわけで、
それが不正なアップロード動画であっても、
視聴者には喜ばれてしまうんです。


困りましたねー。


ボイスロイドのライセンスが変更された時代

そんな時です。
ボイスロイドのライセンスが変更されました。


Youtubeの広告付き動画は商用扱いとしない!


昔、設定したライセンスの定義が
時代に合わなくなってきたので定義を変えます
みたいな話でした。


こうして広告付きのボイスロイド動画が
Youtubeにアップロードされるようになったのでした。
ちゃんちゃん。


社内プレゼンでボイスロイドを使う場合

さて、社内プレゼンなどで、
ボイスロイドを使いたいこともあるかもしれませんね。


実は、上で述べた、ボイスロイドのライセンスが変更された時に、
ライセンス変更に関わる質疑応答の生放送があり、
その中でライセンスについて色々明言されました。


  • 社内プレゼンでボイスロイドを使う場合、商用ライセンスが必要となる
  • この商用ライセンスは「声」に対するライセンスであり、例えば、立ち絵などでキャラクターを使うなら、また別にキャラクター使用の許諾が必要。

f:id:taku-o:20210216191221p:plain (調子にのって絵を入れると面倒なことになります)


ということで、
社内プレゼンでボイスロイドは実質使えない気がしますね。
(ここで明言されなくても、社内プレゼンでの使用が商用扱いになるのは自明な気もします。
たいていのものはそうでしょ。)


おわり

おわり。

社内プレゼンでボーカロイドの話題を見かけたので、
この記事を書く気になった。
上記が私の歴史の認識である。


ボイスロイド1体で8000円くらいするのに。

1体持ってる人は、どうせ4,5体持ってそうなのに。
(偏見)

  • 結月ゆかり
  • 結月ゆかりプラス
  • 結月ゆかりEX
  • 結月ゆかり2

と同じ声でバージョン違いで複数持ってる人も居そうなのに。

その他にも、AIきりたんとか買ってて、
たくさん貢いでそうなのにー。
(偏見)


ちょっと残念ですね。

update.electronjs.orgを使って、アプリの自動更新機能をお気楽に組み込む

ElectronのautoUpdater

Electronには、アプリの新しいバージョンをダウンロードして
更新する機能が用意されています。
https://www.electronjs.org/docs/api/auto-updater


Electronアプリからこの機能を使うには本来、
最新版アプリの情報を返すアップデートサーバーが必要になるのですが、


GitHubのReleasesに最新アプリバイナリを置いているアプリ
については、
Electron公式がアップデートサーバーを用意してくれているので、
アプリ側の修正だけで、簡単に自動アップデート機能を組み込めるようになっています。


(ここだよ、ここ)


update.electronjs.org

こちらが公式が用意しているサーバーで、
update.electronjs.org (GitHubにリダイレクト)

このサーバーは、次のパターンのURLを叩くと、GitHubの最新リリースの情報を取得して返します。

https://update.electronjs.org/update/${process.platform}/${app.getVersion()}


例えば、GitHubのレポジトリが"taku-o/myukkurivoice"で、
現在実行中のアプリのバージョンが"0.13.6"だとすれば、次のURLになりますね。
このURLをブラウザで開くと、JSON形式で最新リリースのデータが返ってくるのが分かります。
https://update.electronjs.org/taku-o/myukkurivoice/darwin-x64/0.13.6

{"name":"0.13.7","notes":"- \"MYukkuriVoice について\"ダイアログの表示を修正。\r\n  - ビルド時の...


対応している環境

update.electronjs.orgで対応している環境はLinux環境以外です。
Linuxは駄目かー。そっかー、残念ですね。


macOSのアプリについては、アプリにDeveloper ID Applicationの署名が必要。
実行中のアプリ、そして、ダウンロードする更新アプリ、両方に署名が必要です。
(アプリに署名がされていないと、自動アップデートがエラーになる。)

Error: Code signature at URL file:///Users/user/Library/Caches/jp.nanasi.myukkurivoice.ShipIt/update.TdmmDN0/
MYukkuriVoice-darwin-x64/MYukkuriVoice.app/ did not pass validation: コードオブジェクトがまったく署名されていません
Error: Could not get code signature for running application


Catalinaでは、アップル公証(Notarization)も必要かも?
調べていません。
わざわざ公証の実装を外してまで検証しないよ。
面倒だからね。仕方ないね(^_^)/


更新用のアプリをReleasesに置く

更新用のアプリは、zipで固めてGitHubのReleasesに置いておきます。 https://github.com/taku-o/myukkurivoice/releases/tag/0.13.8

1. バージョン番号のReleasesタグを作る(0.13.8 とか)
2. 更新用のアプリをzip形式にしてアップロードする
3. zipファイルのファイル名は、例えば、AppName-darwin-x64.zip のような形式にする


update.electronjs.orgは、Windows環境とMac環境に対応していますが、
いくつかアップロードされているzipファイルのうち、
どのzipファイルが、どの環境用のものかは、ファイル名から判断されています。
なので、zipファイル名には、mac, darwin, win32-x64など対応環境を示す文字を入れておきましょう。

mac
darwin
osx
win32-ia32
win32-x64


Electronアプリへの組み込み(自動的に更新処理)

Electronアプリのコードにアプリ自動更新の機能を組み込みます。


npmでupdate-electron-appを入れておいて、

npm install update-electron-app

アプリ側で、このコードを呼び出す。

require('update-electron-app')()

簡単!


Electronアプリへの組み込み(任意のタイミングで更新)

上の実装は簡単なのですが、しかし、ドキュメントによると、
10分毎に更新をチェックしにいくようです。
https://www.electronjs.org/docs/tutorial/updates


なんてこった(>_<)!
そんなにアプリを頻繁に更新するわけないぞ(>_<)!
1ヶ月に1回更新できたらマシな方だ(>_<)!


そういう場合は、checkForUpdates()を手動で叩いて、
好きな頻度で更新するか、メニューから更新できるようにしたら良いでしょうな。

autoUpdater.checkForUpdates();
import {app, dialog, autoUpdater} from 'electron';

// register events
autoUpdater.on('update-downloaded', (event, releaseNotes, releaseName) => {
  const dialogOptions = {
    type: 'info',
    buttons: ['Restart', 'Later'],
    title: 'Application Update',
    message: process.platform === 'win32' ? releaseNotes : releaseName,
    detail: '新しいアプリのダウンロードが完了しました。アプリを再起動して更新を適用します。',
  };
  dialog.showMessageBox(dialogOptions)
  .then((result) => {
    const btnId: number = result.response;
    if (btnId === 0) {
      autoUpdater.quitAndInstall();
    }
  });
});

autoUpdater.on('error', (err: Error) => {
  const dialogOptions = {
    type: 'error',
    title: 'Application Version Check Error.',
    message: 'アプリのアップデート中にエラーが発生しました。',
    buttons: ['OK'],
    defaultId: 0,
    cancelId: 0,
  };
  dialog.showMessageBox(dialogOptions);
});

// setup feed url
const server = 'https://update.electronjs.org';
const feed = `${server}/taku-o/myukkurivoice/${process.platform}-${process.arch}/${app.getVersion()}`;
autoUpdater.setFeedURL({
  url: feed,
  serverType: 'json',
});

// update
autoUpdater.checkForUpdates();


いったんcheckForUpdates()を実行すると、
アプリの更新処理をストップ出来ないらしく、
実行前にユーザーに確認ダイアログを出しておいた方が良さそうです。


あるいは、今動作しているアプリのバージョンが最新かどうか確認しておいてから、
アプリを更新しても良いか聞いておきましょう。


autoUpdaterが間違ったファイルをダウンロードする(1敗)

ElectronのautoUpdaterは、おおよそ次の順番で処理しています。
Mac環境)

1. 実行中のアプリの署名の確認
2. 実行中のアプリが最新版かどうかチェック
3. 最新アプリのダウンロード
4. zipファイルの解凍
5. 最新アプリの署名の確認
6. アプリの入れ替え


update.electronjs.orgはこんな実装(↓)になっているので、
データファイルなどの名前に"mac"の文字が入っていると、
更新アプリでなく、間違ったファイルをダウンロードすることがありますね。
(1敗)

# https://github.com/electron/update.electronjs.org/blob/ed32d4fedca459fb734a5d898d8573f57b605bd4/index.js#L236

const assetPlatform = fileName => {
  if (/.*(mac|darwin|osx).*\.zip/i.test(fileName)) return 'darwin-x64'
  if (/win32-ia32/.test(fileName)) return 'win32-ia32'
  if (/win32-x64|(\.exe$)/.test(fileName)) return 'win32-x64'
  return false
}


ダウンロードされたzipファイルは、 次のような場所に保存された後、解凍され、
その後、アプリの署名の検証が行われます。

/Users/user/Library/Caches/xx.xx.bundleid.ShipIt/update.QCBggxR


なので、誤ったファイルをダウンロードしていて、 署名エラーが発生している時は、
このディレクトリを監視していれば、問題を追えます。
(エラーになった時、すぐ消されちゃうみたいですけど。)


if (/win32-x64|(\.exe$)/.test(fileName)) return 'win32-x64'

試していませんけれど、このコードを見る限り、
Windows環境はexe形式もいけるみたいですね。
自己解凍形式がサポートされているのかな?


終わり

MacのElectronアプリだったらさ、
自動更新機能を組み込まなくても、Mac App Storeに出せば良いじゃない?


そんなふうに考えていた時期が俺にもありました


どうも駄目なレビュアーに当たった気がします。
審査が通らない。
話が通じない。


そろそろ審査アプリのバンドルIDを変更して、
レビューする人を切り替えようかと考え始めています。
(あくまでも自分は悪くないと決めつけるスタイル)

2020年2月最新のElectronアプリ、アップル公証(Notarization)対応

アップル公証(Notarization)

Macにはアップル公証という、Macの野良アプリを署名する仕組みがあり、
このアップル公証を通しておかないと、 macOS Catalinaではアプリを起動する際、そのアプリの起動がブロックされます。
(起動する術はある、らしい)


少し前、これに対応して、自前のElectronアプリで アップル公証を通していたのですが、
実は、その時は、公証の条件が少し緩くなっていたらしく・・・

アップル、macOS野良アプリの公証要件を一時的に緩和。Catalinaへのスムーズな移行のため https://japanese.engadget.com/2019/09/05/macos-catalina/


またもや、自家製Electronアプリで、 アップル公証が通らなくなってしまったのでありました。
(´・ω・`)


この記事は、Electron製アプリでアップル公証(Notarization)を通す、
その手順についてのお話となります。


Xcodeのバージョン

まず最初に、アップル公証を通すには、新しめのXcodeが必要です。 バージョンが低い場合はXcode 10以降にバージョンを上げておきましょう。
App Storeからインストールできない場合は、Apple Developerからダウンロードしてきて、インストールします。
https://developer.apple.com/jp/


作ったアプリに署名をつける

作ったアプリに署名をつけます。
electron-osx-signというライブラリを使うか、
codesignコマンドで、Electronアプリに署名をつけられます。
https://github.com/electron/electron-osx-sign


おそらく、electron-osx-signを使うか、
electron-builder、electron-packagerのコマンドで、 オプションを追加して署名をつける方が楽でしょう。


が、自分の場合は、
ElectronアプリでDynamicLibraryを使っていたり、
アプリに同梱している別の実行ファイルを叩いていたりしていまして、
アプリの構造が少し独特なので、
codesignコマンドを直に叩いて署名をつけています。


codesign

codesignコマンドでアップル公証用の署名をつける場合は、
次の2つのパラメータをつけて、codesignを実行します。

--options runtime
--timestamp


下のコードは、仮想コードです。

仮想コードですが、
"どのファイルに対して、どのパラメータでcodesignを実行するか"
というのが大事な情報なので、 きっとこの記事を読みに来た人にはこれでもわかるはず・・・
アプリ名とかIDは適当に読み替えてください。

APP='MYukkuriVoice'
identity='Developer ID Application: Foo Bar (99QXXXXXXX)'
PARENT_PLIST='build/darwin.app.plist'
CHILD_PLIST='build/darwin.app-child.plist'

/usr/bin/codesign --options runtime --timestamp -s "${identity}" -f --entitlements "${CHILD_PLIST}"
    `${APP}-darwin-x64/${APP}.app/Contents/Frameworks/Electron Framework.framework/Versions/A/Electron Framework`
    `${APP}-darwin-x64/${APP}.app/Contents/Frameworks/Electron Framework.framework/Versions/A/Libraries/libEGL.dylib`
    `${APP}-darwin-x64/${APP}.app/Contents/Frameworks/Electron Framework.framework/Versions/A/Libraries/libswiftshader_libEGL.dylib`
    `${APP}-darwin-x64/${APP}.app/Contents/Frameworks/Electron Framework.framework/Versions/A/Libraries/libGLESv2.dylib`
    `${APP}-darwin-x64/${APP}.app/Contents/Frameworks/Electron Framework.framework/Versions/A/Libraries/libswiftshader_libGLESv2.dylib`
    `${APP}-darwin-x64/${APP}.app/Contents/Frameworks/Electron Framework.framework/Versions/A/Libraries/libffmpeg.dylib`
    `${APP}-darwin-x64/${APP}.app/Contents/Frameworks/Electron Framework.framework/Versions/A/Resources/crashpad_handler`
    `${APP}-darwin-x64/${APP}.app/Contents/Frameworks/Electron Framework.framework`
    `${APP}-darwin-x64/${APP}.app/Contents/Frameworks/${APP} Helper (GPU).app/Contents/MacOS/${APP} Helper (GPU)`
    `${APP}-darwin-x64/${APP}.app/Contents/Frameworks/${APP} Helper (GPU).app`
    `${APP}-darwin-x64/${APP}.app/Contents/Frameworks/${APP} Helper (Plugin).app/Contents/MacOS/${APP} Helper (Plugin)`
    `${APP}-darwin-x64/${APP}.app/Contents/Frameworks/${APP} Helper (Plugin).app`
    `${APP}-darwin-x64/${APP}.app/Contents/Frameworks/${APP} Helper (Renderer).app/Contents/MacOS/${APP} Helper (Renderer)`
    `${APP}-darwin-x64/${APP}.app/Contents/Frameworks/${APP} Helper (Renderer).app`
    `${APP}-darwin-x64/${APP}.app/Contents/Frameworks/${APP} Helper.app/Contents/MacOS/${APP} Helper`
    `${APP}-darwin-x64/${APP}.app/Contents/Frameworks/${APP} Helper.app`

/usr/bin/codesign --deep --options runtime --timestamp -s "${identity}" -f --entitlements "${CHILD_PLIST}"
    `${APP}-darwin-x64/${APP}.app/Contents/Frameworks/Mantle.framework/Mantle`
    `${APP}-darwin-x64/${APP}.app/Contents/Frameworks/Mantle.framework/Versions/A`
    `${APP}-darwin-x64/${APP}.app/Contents/Frameworks/ReactiveCocoa.framework/ReactiveCocoa`
    `${APP}-darwin-x64/${APP}.app/Contents/Frameworks/ReactiveCocoa.framework/Versions/A`
    `${APP}-darwin-x64/${APP}.app/Contents/Frameworks/Squirrel.framework/Versions/A/Resources/ShipIt`
    `${APP}-darwin-x64/${APP}.app/Contents/Frameworks/Squirrel.framework/Squirrel`
    `${APP}-darwin-x64/${APP}.app/Contents/Frameworks/Squirrel.framework/Versions/A`

# アプリから読み込んでいるDynamicLibrary
# (このアプリ固有)
/usr/bin/codesign --options runtime --timestamp -s "${identity}" -f --entitlements "${CHILD_PLIST}"
    `${APP}-darwin-x64/${APP}.app/Contents/Resources/app.asar.unpacked/vendor/AqKanji2Koe.framework/Versions/A/AqKanji2Koe`
    `${APP}-darwin-x64/${APP}.app/Contents/Resources/app.asar.unpacked/vendor/AqKanji2Koe.framework`
    `${APP}-darwin-x64/${APP}.app/Contents/Resources/app.asar.unpacked/vendor/AqUsrDic.framework/Versions/A/AqUsrDic`
    `${APP}-darwin-x64/${APP}.app/Contents/Resources/app.asar.unpacked/vendor/AqUsrDic.framework`
    `${APP}-darwin-x64/${APP}.app/Contents/Resources/app.asar.unpacked/vendor/AquesTalk10.framework/Versions/A/AquesTalk`
    `${APP}-darwin-x64/${APP}.app/Contents/Resources/app.asar.unpacked/vendor/AquesTalk10.framework`
    `${APP}-darwin-x64/${APP}.app/Contents/Resources/app.asar.unpacked/vendor/AquesTalk2.framework/Versions/A/AquesTalk2`
    `${APP}-darwin-x64/${APP}.app/Contents/Resources/app.asar.unpacked/vendor/AquesTalk2.framework`

# アプリから実行している外部実行ファイル。とそこから読んでいるDynamicLibrary
# (このアプリ固有)
/usr/bin/codesign --options runtime --timestamp -s "${identity}" -f
    `${APP}-darwin-x64/${APP}.app/Contents/Resources/app.asar.unpacked/vendor/maquestalk1-ios`
    `${APP}-darwin-x64/${APP}.app/Contents/Resources/app.asar.unpacked/vendor/secret`
    `${APP}-darwin-x64/${APP}.app/Contents/Resources/app.asar.unpacked/vendor/maquestalk1`
    `${APP}-darwin-x64/${APP}.app/Contents/Resources/app.asar.unpacked/vendor/AquesTalk.framework/Versions/A/AquesTalk`
    `${APP}-darwin-x64/${APP}.app/Contents/Resources/app.asar.unpacked/vendor/AquesTalk.framework`

/usr/bin/codesign --options runtime --timestamp -s "${identity}" -f --entitlements "${CHILD_PLIST}"
    `${APP}-darwin-x64/${APP}.app/Contents/MacOS/${APP}`

/usr/bin/codesign --options runtime --timestamp -s "${identity}" -f --entitlements "${PARENT_PLIST}"
    `${APP}-darwin-x64/${APP}.app`


codesignコマンドで直接、Electronアプリを署名しようとすると、
対象のファイルがとても多くなりますね。
コマンドの実行順序は数カ所ほど、入れ替わると駄目な場所があります。


次の2つのバイナリは、
2020年1月まではcodesignで指定しなくてもアップル公証が通っていたのですが、
今月(2月)になったら、個別にcodesignしておかないとアップル公証が通らなくなってしまっていました。

Electron Framework.framework/Versions/A/Resources/crashpad_handler
Squirrel.framework/Versions/A/Resources/ShipIt


entitlements

codesignコマンドで指定するentitlementsは、 Electronの場合、
com.apple.security.cs.allow-unsigned-executable-memory が必須になります。


それから、このアプリの場合はDynamicLibraryを使っているので、 それとは別に、
com.apple.security.cs.disable-library-validation を追加しています。
アプリによっては、これら以外にも必要となる指定があるでしょう。

<?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.cs.allow-unsigned-executable-memory</key>
    <true/>
    <key>com.apple.security.cs.disable-library-validation</key>
    <true/>
  </dict>
</plist>


古いOSで作った実行ファイル

古いOSでビルドした実行ファイルが含まれていると、アップル公証がエラーになります。 これを解消するには新しく実行ファイルを作り直す必要があり、回避方法は無さそうです。

# このように怒られる
The binary uses an SDK older than the 10.9 SDK.


アプリによっては、この条件のせいで、アップル公証を通したくても通せなくなってしまうこともありそうです。

実行ファイルでなく、古いOSでビルドしたライブラリは、アップル公証でエラーにはなりませんでした。
助かった(>_<)


notarize

codesignで署名したアプリをアップル公証(notarization)に通します。
調べれば分かりますが手順がとてもとてもとても面倒くさい。


electron-notarizeライブラリを使うと、さくっと公証を通して、 完結まで処理してくれるので、
Electronアプリのアップル公証は、基本、これを使うべきだと思いました。
https://github.com/electron/electron-notarize


次のコードはgulpで、notarizeを実行するコードです。
実行結果が返ってくるまでは、毎回毎回少し時間がかかります。
5分、10分くらい。

const gulp = require('gulp');
const notarize = require('electron-notarize').notarize;

gulp.task('_notarize', () => {
    const APP = 'MYukkuriVoice';
    const platform = 'darwin';
    const APP_PATH = `${APP}-${platform}-x64/${APP}.app`;
    const bundleId = 'jp.nanasi.foobar';
    const teamId = '99QXXXXXXX';

    const appleId = DEVELOPER_APPLE_ID;
    const env = process.env;
    const password = env.AC_PASSWORD;

    return notarize({
      appBundleId: bundleId,
      appPath: APP_PATH,
      appleId: appleId,
      appleIdPassword: password,
      ascProvider: teamId,
    });
});


verify notarization

アップル公証を通したら、
本当にアップル公証が通っているか、次のコマンドで確認します。

# アップル公証の実行履歴の表示
xcrun altool --notarization-history 0 -u 'hoge@example.jp' -p 'xxxx-xxxx-xxxx-xxxx'

# 指定のアプリがアップル公証を通っているか
xcrun stapler validate -v MYukkuriVoice-darwin-x64/MYukkuriVoice.app


もちろんこれで確認が取れてもアプリが動かないことがあります。
その場合は、単純にアプリのバグでなければ、

  • アップル公証を通した後にアプリが変更された
    (一度、アンチウィルスソフトにやられました)
  • entitlementsの指定が足りない

のどちらかが怪しそうです。


おわり

終わりでありますよ。


自分はまだmacOS Catalinaに移行していないので、
動作確認が少し面倒です。(´・ω・`)

Github Actionsでいろいろ困ったところと対応方法。主にシェルスクリプトを使うGitHub Actions

GitHub Actions

みなさん、GitHub Actionsで遊んでますか?
私も最近、GitHub Actionsで遊んでいるんですけど、
いくつか困ることもありまして、
この記事は、その時、困ったことの解決メモであります。
(雑な導入)


https://help.github.com/ja/actions/automating-your-workflow-with-github-actions


シェルスクリプト内で、privateなnpmレポジトリをnpm install

プレイベートなnpmレポジトリをnpm installするには、
.npmrc作るのが早い。

Settingsメニューから、Secrets設定を選んで、NPM_AUTH_TOKENを設定。
でもって、こんな感じ。

- run: echo "//npm.pkg.github.com/:_authToken=${{ secrets.NPM_AUTH_TOKEN }}" > ~/.npmrc
- name: npm install
  run: |
    npm install


シェルスクリプト内でgit clone

actionでなくて、シェルスクリプトでgitする。
ローカルで動くビルドスクリプトがあって、
それをGitHub Actionsでもそのまま使いたいケースの対応方法。

パターン1

git cloneする時、httpsでアクセスすれば、SSHの設定は要らない。

git clone git@github.com:taku-o/github-actions-workflow-demo.git
     ↓
git clone https://github.com/taku-o/github-actions-workflow-demo.git

パターン2

SSHのキーを設定しておけば、プライベートなGitHubレポジトリもcloneできる。
シェルスクリプトでいろいろする前に、
ssh-key-actionで、SSHの設定を入れておく。

- name: ssh key
  uses: shimataro/ssh-key-action@v1
  with:
    private-key: ${{ secrets.SSH_KEY }}
    public-key: ${{ secrets.SSH_KEY_PUBLIC }}
    known-hosts: ${{ secrets.SSH_KNOWN_HOSTS }}
- name: git clone
  run: |
    git clone git@github.com:taku-o/github-actions-workflow-demo.git


actions/cacheのlimitが小さくて困る

actions/cacheのキャッシュのlimitが小さい。400MB。
容量足りなくて、大きなアプリだと、一切キャッシュが効かなくなる。


でも、これぐらいならすぐに修正が入るだろう。
そう思っていた私は、issueが建ってから22日経過しているのを見て、
音速でフォークするのであった。

https://github.com/taku-o/github-actions-cache

- name: cache node environment
  id: cache-node
  uses: taku-o/github-actions-cache@v1.0.4
  with:
    path: ~/.npm
    key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
    restore-keys: |
      ${{ runner.os }}-node-


1GBまで増やした。
どうせやるなら、パラメータ外出しにしような
って意見もあるかもしれない。
ダメだわコレ。内部でtgzしてる。 よって単体でファイルサイズが400MB越えて、GitHub Actionsの制限にかかって消されちゃう(´・ω・`)


アクションのフォーク

アクションはGitHub Actions Marketplaceに公開しなくても使える。楽でイイネ。

でも、GitHub ActionsのアクションをForkして、自分のレポジトリに持ってくると、
レポジトリの名前が大げさ(?)な感じになっちゃうね。

アクションをForkする場合は、初手名前変更安定だろうね。

actions/cache
   ↓
taku-o/cache
   ↓
taku-o/github-actions-cache (邪魔にならない感じの名前に)


actions/upload-artifact

actions/upload-artifactはzip形式でファイルをアップロードする。

でも、既にzipファイルを作ってる場合は、それをそのままアップロードして欲しいんだよ、

って少し思ったけど、
mkdir、mvすれば良かっただけだね。
これならまだ許せる。

- name: collect staging built files
  run: |
    mkdir built
    mv release/app.zip built

- name: upload built files
  uses: actions/upload-artifact@master
  with:
    name: built
    path: built


nameは、built.zipとzip付きで指定すると、built.zip.zipが作られるぞ。
気をつけるのだ。

- name: upload built files
  uses: actions/upload-artifact@master
  with:
    name: built.zip
    path: built


gitのbranch名を取る

pushされたGitHubレポジトリのbranchの名前を取る。

GITHUB_REFの中に名前が入っている。
refs/heads/feature/window-sizeのようなフォーマット。

- name: build staging application
  run: |
    npx gulp build:staging --branch=${GITHUB_REF#refs/heads/}


Pull Requestされたコードを取る

Pull Requestされたコードを取る。
Pull Requestされたコードのテストなどで使う。

こちらもGITHUB_REFの中に名前が入っている。
refs/pull/9999/merge

- name: build pr application
  run: |
    npx gulp build:pr --pull_request=${GITHUB_REF#refs/}


実際に、Pull Requestのコードを取り込んで使うには ↓みたいなことするコードを書くことになるかな?

git clone taku-o/github-actions-workflow-demo
cd github-actions-workflow-demo
git pull origin pull/9999/merge


おわり

終わりなのだ。

続・Electron製アプリが Mac App Storeで non-public APIの利用で rejectされる問題。解決しそう

AppStoreのレビュー通ったって

前回
Electron製アプリが Mac App Storeへの申請で non-public APIの利用で rejectされる問題
https://taku-o.hatenablog.jp/entry/2019/11/03/103422


Electronがnon-publicなAPIを利用していたので、
Electron製のアプリがみんなrejectされている問題があったのだけれど、
最新コードでAppStoreのレビューが通ったそうで、解決のメドがたったみたい。
https://github.com/electron/electron/issues/20027


もうちょっとで修正が利用できるようになるのでしょう。
https://github.com/electron/electron/pull/20965


でも、Electron 5以降が対象なんだ

Electronのバージョン4以下は、サポート打ち切られています。
当然修正は用意されない!


Electron 4用のPull Requestも作られたけど、
一瞬でcloseされたよ!!
https://github.com/electron/electron/pull/21003


私が使ってたのは3.1.9だったので、 このままでは修正が用意されないのですね。
Electron 3、Electron 4使っている人は、
Electronのバージョン上げましょうね。


というわけでElectron 5までバージョン上げたよ

というわけで、ここ数日は、
アプリで使っている Electronのバージョンアップに励んでいました。


Electronを追っている人は知っていると思うけど、
Electronはバージョン上げて、すんなり動いたりしないのですよ。


自分の場合、3.1.9 → 5.0.12で、次のような問題があった。
(Electron 6への移行も検討してたので、Electron5用の対応と、Electron6用の対応が混在してるよ)


node-ffi が Node 12 でビルドできない

もっとも古いElectron 5でも、Node 12.0.0です。

# リリースノートから
Upgraded to Chromium 73.0.3683.119, Node.js 12.0.0, and V8 7.3.492.27.

node-ffiというネイティブライブラリを実行できるモジュールがあり、 今までこれを利用していたのですが、
このライブラリはNode 12ではビルドできない。
開発が止まっている。
https://github.com/node-ffi/node-ffi


自分はいろいろ検討した結果、node-ffi-napiに移行しました。
とても良い感じ。
https://github.com/node-ffi-napi/node-ffi-napi


menu item role

Electronのメニュー定義のrole定数が大文字小文字で変わっていたり。
こんな感じであちこち。
Typescriptでコンパイル時にエラーになってた。

# 抜粋
- {role: 'zoomin'},
- {role: 'zoomout'},
- {role: 'resetzoom'},
+ {role: 'zoomIn'},
+ {role: 'zoomOut'},
+ {role: 'resetZoom'}


Spectron

remote-debugging-portを指定しないと、
Spectronのテストコードが動作しなくなった。

# 抜粋
describe('dictWindow', function() {
  this.timeout(10000);

  before(function() {
    this.app = new Application({
      path: 'App-darwin-x64/App.app/Contents/MacOS/App',
      chromeDriverArgs: ['remote-debugging-port=9222'], ←←←←←← これ!
      env: {DEBUG: 1, NODE_ENV: 'test'},
    });
    return this.app.start();
  });


electron-packager

electron-packagerでアプリパッケージを作っているのですが、
Electron 6あたりで、Electronの作りが結構変わっているようですね。
古いバージョンのelectron-packagerを使っている人はアップデートが必要でしょう。
https://github.com/electron/electron-packager


一部ファンクションが非同期になってた

例えば、

dialog.showMessageBox

これが同期から非同期処理になってたり。


そういう変更は、リリースノートの
Breaking Changesの項に置いといて欲しい。


ばっちこい

準備完了だ。
はやくこいこいアップデート。