simple-gitを使ってみたら便利だった

simple-git を使って、TypeScript による自動化事例を記事に致しましたが、TypeScript による cli のフレームワークを主に説明していたり、方向性が微妙なポストだなぁと反省…。
2023.10.24

こんにちは、高崎@アノテーション です。

はじめに

我々が行っている作業におきまして、手動でファイルを更新することや環境を整備することは少なくありません。

以前から手を抜いて少ない工数で効率よく、かつ人為的ミスを削減することを生き甲斐としておりまして、手動でファイル操作をするものがあれば、以前ですと Linux のターミナル上で作業していたこともあり bash シェルで自動化を行う方法はないか、を常に考えていました。

ファイルの変化を見る場合はgitを使ってローカルでリポジトリを組んでみたり、結果の文字列を捏ねて諸々処理する時には、シェルを実装するに当たって欠かすことのできないawksedを駆使して実装しておりましたが、一方で Mac の場合、これらのコマンドは I/F や動きが微妙に代わります。

I/F を合わせるためにgsedgawkをインストールする方法もありますが、全員の環境にこれらが入っているとは限りませんので、本チームで従事するようになると TypeScript を使っての自動化を行うことが増え、今回、gitの代わりに導入した simple-git の使い勝手が良かったので記事にしました。

cli ツールについて

※少し脇道にそれ、恐縮です。
cli ツールの作り方については色々と検索すれば出てくると思いますが、筆者の場合はinquirerを使ってこんな感じで作成しています。

インストール方法

npm init
  -> 色々と入力してセッティング
npm install @types/inquirer
npm install inquirer
npm install esbuild-register
npm install esbuild
他、必要なライブラリをインストール

esbuild、esbuild-register については実行時にnode -r esbuild-register 実行ソース名とするためにインストールしていますが、ts-nodeで実行するもよし、環境に応じて設定ください。

以下、TypeScript の cli の実装例です。

index.ts

import inquirer from 'inquirer';

const cliTasks = [
  '1 : Task No.1',
  '2 : Task No.2',
] as const;

const main = async (): Promise<void> => {
  console.log('This is a gadget tool.');
  const message = 'Please select your task.';

  const { selected } = await inquirer.prompt([
    {
      type: 'list',
      message,
      name: 'selected',
      choices: cliTasks,
    },
  ]);

  switch (selected) {
    case '1 : Task No.1': {
      // タスク No.1 の処理を記載
      break;
    }
    case '2 : Task No.2': {
      // タスク No.2 の処理を記載
      break;
    }
    default: {
      console.log(`Unknown task ${selected}`);
      break;
    }
  }
}

main();

さて、本題

simple-git のインストールは、npm コマンドでインストールします。

npm install simple-git

simple-git の各 API については README を参照にするとよいかと思います。

実装

実装例

例えば、

環境内にてsample.jsonの内容に変化があった時に
何らかの処理をする

みたいなことを行いたければ、下記みたいな形で実装すれば良いでしょう。

import { simpleGit } from 'simple-git';

/**
 * 当該リポジトリ内で引数のファイルが更新されているかをチェックする関数
 */
const getGitStatusFiles = async (checkPath: string): Promise<string[]> => {
  // git status を実行
  const gitStatus = await simpleGit().status();
  const filePathes: string[] = [];
  // 取れた git status からファイルがあるかをチェック
  gitStatus.files.forEach((files) => {
    if (files.path.indexOf(checkPath) !== -1) {
      filePathes.push(files.path);
    }
  })
  return filePathes;
}

/**
 * リポジトリのファイルを検索してあったファイルを処理する関数
 */
const doRepositoryFiles = async (): Promise<void> => {
  const checkFiles = await getGitStatusFiles('sample.json');
  if (checkFiles.length === 0) {
    console.log('json の更新がなかったので終了します。');
    return;
  }

  const response = await Promise.all(checkFiles.map(async (filePath) => {
    /* 何らかの処理 */
  }
}

gitStatus の中身について

上記のサンプルはファイルを探すためのものですが、戻り値を軽くご紹介します。

StatusSummary
 ┣ not_added : リポジトリ内に ignore 指定されておらずに追加され、add されていないファイル一覧
 ┣ conflicted : コンフリクトしているファイル一覧
 ┣ created : 追加され、add もされたファイル一覧
 ┣ deleted : リポジトリ内から rm コマンドで削除されるか git rm --cached で削除されたファイル一覧
 ┣ ignored : 「options」という引数に「[--ignored=matching]」等を付けて有効にした際の ignore ファイル一覧
 ┣ modified : リポジトリ内で変更されたファイル一覧(add される前後に関係なく追加される模様)
 ┣ renamed : リポジトリ内で名前が変更された、と git が判断したファイル一覧
 ┣ files : 変更ファイル一覧
 ┃  ┗ FileStatusSummary
 ┃      ┣ path : 変更対象ファイルパス
 ┃      ┣ index : インデックスのステータス(git status -s 指定すると左側のステータスコード)
 ┃      ┗ working_dir : ワークツリーのステータス(git status -s 指定すると右側のステータスコード)
 ┣ staged : リポジトリ内に載ったファイル一覧
 ┣ ahead : アップストリームブランチより先にあるコミットの数
 ┣ behind : アップストリームブランチより前にあるコミットの数
 ┣ current : 現在のブランチ名(外れた場合は undefined)
 ┣ tracking : アップストリーム先のブランチ名(まだローカルにのみ存在してアップストリームにはない場合は null)
 ┗ detached : ローカル側が切り離されているかを示すステータス

サンプルソースは files から path を拾って変更対象に「simple.json」があるか、を簡単に確認しました。

仮に「新規追加されているものを対象としない」場合であれば、working_dirindexに「?」という文字が入力されるのでフィルタリングする処理を入れる、といった対処が良いかと思います。

また、各コマンドの引数にオプションを指定出来まして、先ほどの例で ignored されたものも含めて確認したい場合は、

  const gitStatus = await simpleGit().status(['--ignored=matching']);

のように git の各コマンドを直感的に呼び出せるような実装が出来ます。

おわりに

今回は simple-git について簡単に紹介しました。

simple-git にはaddcommitpushといったものも一通り揃っているのですが、残念ながらcherry-pickは実装されていなさそうでした。

一つのリポジトリに対して行っているちょっとした作業を自動化したい場合でしたら git + bash の他にも TypeScript による cli から simple-git を導入して行う形も視野に入れて良いかと思います。

アノテーション株式会社について

アノテーション株式会社は、クラスメソッド社のグループ企業として「オペレーション・エクセレンス」を担える企業を目指してチャレンジを続けています。
「らしく働く、らしく生きる」のスローガンを掲げ、様々な背景をもつ多様なメンバーが自由度の高い働き方を通してお客様へサービスを提供し続けてきました。
現在当社では一緒に会社を盛り上げていただけるメンバーを募集中です。
少しでもご興味あれば、アノテーション株式会社WEBサイト をご覧ください。