Google Apps ScriptをGitHub Actionsでclasp pushする際に、"Manifest file has been updated. Do you want to push and overwrite? (y/N) " と出た場合はworkflowsを失敗させる

## はじめに
「[この記事誰得？ 私しか得しないニッチな技術で記事投稿](https://qiita.com/official-events/5d4f04cf2ba0cdbc8821)」を目にしたので、夏の夜のコンビニの灯火にすだく虫のごとく[^1]、ニッチな記事を投稿します。

[^1]:コンビニのライトがLEDになったので、昨今はあまり寄ってこないらしいです。

## 対象読者
記事のタイトルを読んで、なんのことかわかったあなた。

- [clasp](https://github.com/google/clasp)（Command Line Apps Script Projects）とGitHub Actionsを使ってGoogle Apps Scriptを管理しているあなた
  - たとえば、『[GAS(Google Apps Script)の更新をGithub Actionsで自動化してみた](https://qiita.com/yuzinet/items/17be9967b2c660ee8432
)』の記事に書かれているような実装をしている方。
- GitHub Actionsのworkflowは成功しているのに、Google Apps Scriptが反映されていなくて困ったあなた。


## 解決したい問題
`clasp push`を行う際に、ローカルとリモートで`appsscript.json`に齟齬があると対話プロンプトが出る。

<details>
<summary>appsscript.jsonの一例</summary>

```appsscript.json
{
  "timeZone": "Asia/Tokyo",
  "dependencies": {},
  "exceptionLogging": "STACKDRIVER",
  "runtimeVersion": "V8",
  "webapp": {
    "executeAs": "USER_DEPLOYING",
    "access": "ANYONE_ANONYMOUS"
  }
}
```

</details>

```sh
$ clasp push
? Manifest file has been updated. Do you want to push and overwrite? No
```

GitHub Actionsでpushを行っている場合はここで停止する。

結果、`clasp push`はされないが、workflowはエラーとはならず、「成功」となる。そのため、**気づかないうちにGit管理しているコードと実際のGoogle Apps Scriptとに差分が生まれてしまう**。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2722903/b2cddb56-3408-1c2a-7efb-8237fb451029.png)

### 問題が起こるケース
よくあるのが、リモート（Googleのスクリプトエディタ）で**ウェブアプリとしてデプロイ**をしたことで、`appsscript.json`が更新され、齟齬が生じるケース。
あるいは、ライブラリや、`YouTube Data API v3`などのサービスを追加したケース。
すこし前までは、古いスクリプトの`runtimeVersion`をV8にする場合に起きていました。

## 解決策１： `clasp push -f`を使う
Claspのドキュメントのとおり、`clasp push -f`を使うと、`appsscript.json`は上書きされ、対話プロンプトが表示されず、force pushができます。
>-f --force: Forcibly overwrites the remote manifest.
https://github.com/google/clasp#push

GitHub Actionsのstepのコマンドを`clasp push -f`としてあげれば、pushが止まらなくなります。

この方法の問題は、**驚くべきことに`appsscript.json`が上書きされてしまう**ことです！ああ、なんと恐ろしい。（真面目な話、上書きされて困るのは`runtimeVersion`とライブラリでしょうか。）

もう一つ、こちらの方が問題なのですが、**GitHub Actionsのworkflowのステータスが✅successになる**こと。

別にこれでも構わないよ、という方は、フォースと共にあらんことを。

## 解決策2: `clasp push`の出力に応じてエラーを出す
記事タイトルのとおり、workflowをエラーにしたいという場合の解決策です。

流れはシンプル。
- clasp pushの出力を取得
- pushが成功した場合のメッセージかどうかチェック
- 成功でない場合はエラーを出す

`clasp push`の成功メッセージは下記です。

```sh
Pushed <number of pushed files> files. 
```

これをGitHub Actionsのpush部分に実装します。

### Before

```.github/workflows/actions.yml
    - name: deploy
      run: |
        clasp push
```

### After
```.github/workflows/actions.yml
      - name: deploy
        id: deploy
        run: |
          OUTPUT=$(clasp push)
          delimiter="$(openssl rand -hex 8)"
          echo "deploy-output<<${delimiter}" >> "$GITHUB_OUTPUT"
          echo "$OUTPUT" >> "$GITHUB_OUTPUT"
          echo "${delimiter}" >> "$GITHUB_OUTPUT"
        shell: bash
      - name: check for errors
        run: |
          OUTPUT="${{ steps.deploy.outputs.deploy-output }}"
          if ! echo "$OUTPUT" | grep -Pq "Pushed \d+ files"; then
          echo "Deployment failed with unexpected output: $OUTPUT"
          exit 1
          fi
```

### 変更点
#### 出力の受け渡し
stepの出力の受け渡しにはドキュメントに記載のとおり、
- stepへの`id`の追加
- `GITHUB_OUTPUT` の使用

をすることで実現できます。

```sh
echo "{name}={value}" >> "$GITHUB_OUTPUT"
```

アクセスは、`steps.${step_id}.outputs.${name}`となります。

https://docs.github.com/ja/actions/using-workflows/workflow-commands-for-github-actions#setting-an-output-parameter

ここで**claspの出力が複数行**になることに注意が必要で、そのまま、上記の`echo`をしても単一行しか取得できません。

複数行の出力の取得については、[こちらの記事](https://gotohayato.com/content/558/)が日本語でよくまとまっており、[Discussions](https://github.com/orgs/community/discussions/26288)も参考になります。

今回は[こちら](https://github.com/orgs/community/discussions/26288#discussioncomment-3876281)の方法を拝借しました。

:::note warn
GitHub Actionsの出力の操作は、ちょうど移行期にあたるようで、注意してください。

具体的には`save-state`と`set-output`のコマンドが廃止予定です。
（※当初2023年5月をもって廃止の予定が、延期されたようです。）

https://github.blog/changelog/2022-10-11-github-actions-deprecating-save-state-and-set-output-commands/
:::

#### 出力チェックのstepを追加
仕上げとして、

- `${{ steps.deploy.outputs.deploy-output }}`で出力を取得
- 出力に`"Pushed \d+ files`が含まれていなければエラーを出す

という処理を加えています。このあたりはChatGPTさんと相談して作りました。

最後に、更新した`.yml`で`appsscript.json`が上書きされるケースでworkflowがエラーになるかテストします。

無事に（？）エラーになりました。めでたし。（？？）
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2722903/abb18f91-04b5-b243-4e83-4f9af6fa4384.png)

この方法の問題点としては、`clasp push`が成功時に`Pushed n files.`の出力を返さなくなるアップデートがあると壊れること。もっと良い方法がありましたら教えてください。

## おわりに
この世界の片隅の内容の記事を最後までお読みいただきありがとうございます。

記事タイトルの問題で困っていた方には解決の一助として、そうでない方には「GitHub Actionsでの出力による処理の分岐」の例として、それぞれ参考にしていただけたら幸いです。

## 参考文献
### clasp関連
https://github.com/google/clasp

https://qiita.com/yuzinet/items/17be9967b2c660ee8432

### GitHub Actions関連
https://github.blog/changelog/2022-10-11-github-actions-deprecating-save-state-and-set-output-commands/

https://gotohayato.com/content/558/

https://github.com/orgs/community/discussions/26288