Salesforce CLI のsfdxプラグインを作成する

## はじめに
Salesforce CLIはコマンドラインからSalesforce開発に必要な操作を実行できるツールです。
プラグインで機能が拡張することができます。

Salesforce CLIにはsfdxとsfの実行ファイルが存在し、使用できるプラグインも別になります。
今後はsfに統合されていくので、プラグインを自作するときはsfでプラグインを作成した方が良いです。
ですが、現在sfのプラグインではretireveやdeployをhookすることができません（Spring'23）。
メタデータをretrieveしたときにフォーマットするようなプラグインを作成するときはsfdxのプラグインを作成する必要があります。

## sfdxプラグインの作成（hook）
例として、retrieveをhookして改行コードをLFに自動変換するプラグインを作成していきます。


### 環境
- [Node.js（LTS）](https://nodejs.org/ja)
- [Yarn package manager](https://classic.yarnpkg.com/lang/en/docs/install)
- [Salesforce cli（最新）](https://developer.salesforce.com/ja/tools/sfdxcli)

### 準備
1. 下記のsfdxプラグインのリポジトリをクローンします。
    - [salesforcecli/plugin-metadata-hook-demo](https://github.com/salesforcecli/plugin-metadata-hook-demo)
2. 依存関係をインストールします。
    - `yarn install`
3. 今回はpostretrieveのhookのみ使用するので、package.jsonのoclif.hooksから下記を削除します。
    ```diff_json:package.json
    "hooks": {
    -   "predeploy": "./lib/hooks/predeploy/metadataReplaceDeploy",
        "postretrieve": "./lib/hooks/postretrieve/metadataReplaceRetrieve"
    }
    ```

### 作成
`src/hooks/postretrieve/metadataReplaceRetrieve.ts`を下記の内容に置き換えます。

```typescript:metadataReplaceRetrieve.ts
import { readFileSync, writeFileSync } from 'fs';
import { Command, Hook } from '@oclif/config';
import { FileResponse } from '@salesforce/source-deploy-retrieve';

type HookFunction = (this: Hook.Context, options: HookOptions) => any;

type HookOptions = {
  Command: Command.Class;
  argv: string[];
  commandId: string;
  result?: FileResponse[];
};

type LineBreakCode = '\n' | '\r\n';

const TARGET_TYPES = [
  'ApexClass', 'ApexTrigger', 'ApexPage', 'LightningComponentBundle', 'AuraDefinitionBundle'
];

const DEFAULT_LINE_BREAK_CODE = '\n';

export const hook: HookFunction = async function (options) {
  if (options.result) {
    for (const fileResponse of options.result) {
      const { type, filePath } = fileResponse;
      if (TARGET_TYPES.includes(type) && filePath) {
        console.log(`target: ${type} ${filePath}`);
        const inputData: string = readFileSync(filePath, 'utf-8');
        const outputData: string = convertLineBreaks(inputData, DEFAULT_LINE_BREAK_CODE);
        writeFileSync(filePath, outputData);
      } else {
        console.log(`not target: ${type} ${filePath}`);
      }
    }
  }
};

const convertLineBreaks = (text: string, lineBreakCode: LineBreakCode): string => {
  return text.replace(/\r\n|\r|\n/g, lineBreakCode);
}
```

`sfdx force:source:retrieve`、`sfdx force:source:pull`が実行された時にこのファイルのhookメソッドが実行されます。

postretrieveの`options.result`の型は[FileResponse](https://github.com/forcedotcom/source-deploy-retrieve/blob/main/src/client/types.ts#L66)です。これはhookするイベントによって異なります。詳細は[こちら](https://developer.salesforce.com/docs/atlas.en-us.sfdx_cli_plugins.meta/sfdx_cli_plugins/cli_plugins_customize_hooks_list.htm)。

FileResponseのtypeの種類は[こちら](https://github.com/forcedotcom/source-deploy-retrieve/blob/main/src/registry/metadataRegistry.json)。

## 実行
1. 作成したツールをコンパイルします。
    - `yarn prepack`
2. Salesforce CLIにリンクします。
    - `sfdx plugins:link`
3. Salesforceのプロジェクトフォルダでretrieveすると、改行コードがLFに自動で変換されます。
    - `sfdx force:source:retrieve -m ApexClass`

### 補足
- リンクされているsfdxプラグインを確認する場合は`sfdx plugins`を実行します。
- sfdxプラグインをアンインストールする場合は`sfdx plugins:unlink プラグイン名`を実行します。
- コマンドの初めに`DEBUG=*`をつけて実行するとデバッグがコンソールに表示されます。
    - `DEBUG=* sfdx force:source:retrieve -m ApexClass`

## 参考
- Salesforce CLI
    - [Salesforce CLI 設定ガイド | Salesforce Developers](https://developer.salesforce.com/docs/atlas.ja-jp.sfdx_setup.meta/sfdx_setup/sfdx_setup_intro.htm)
    - [Salesforce CLI Command Reference | Salesforce Developers](https://developer.salesforce.com/docs/atlas.en-us.sfdx_cli_reference.meta/sfdx_cli_reference/cli_reference_top.htm)
    - [Salesforce CLI](https://developer.salesforce.com/ja/tools/sfdxcli)
- sfプラグイン
    - [salesforcecli/cli Wiki](https://github.com/salesforcecli/cli/wiki)
- sfdxプラグイン
    - [Salesforce CLI Plug-In Developer Guide | Salesforce Developers](https://developer.salesforce.com/docs/atlas.en-us.sfdx_cli_plugins.meta/sfdx_cli_plugins/cli_plugins.htm)（廃止・閲覧は可能）
    - [plugin-metadata-hook-demo](https://github.com/salesforcecli/plugin-metadata-hook-demo)