手に馴染む開発ツールを揃えて新年を迎える
概要
今年のうちに開発を快適に進めるために、使っているツールを少しでも使いやすくし、今はあまり使っていないツールを手放すようなことをやりました。
いいなと思ったら同じことでなくていいので、ツールの手入れをしてみてほしいです🦧️
やったこと
- シンプルなDotfilesマネージャの作成
- スニペットツールの見直しとコマンドチートシートの作成
シンプルなDotfilesマネージャの作成
詳細はあとでつらつら書きます。
作成したもの
配置したいパス -> 実体ファイルのシンボリックリンクを作成するスクリプトを組みました。
ありがちだと思います。
高機能なものがほしければchezmoiを使うとよいと思いますが、シンプルなもので良かったので簡単なスクリプトを組みました。
操作手順のイメージはだいたいこんな感じ
- GhosttyのConfigファイルをGitのリポジトリで管理する
- Ghosttyのシンボリックリンク情報を記載したファイルを作成する
-
make setupでコマンドを動かす - Ghosttyを選択してシンボリックリンクを作成する
スクリプトは次のように動きます。
=== Dotfiles Setup ===
Select applications to setup:
1) cheaty
2) ghostty
3) git
4) gitleaks
5) zsh
Choose an option (number or 'q' to quit): 2
[Plan] Link to be created:
GhosttyのConfigがあるパス -> /Documents/dokodemo-drawer/drawer/ghostty/config
[Before Execution] Existing symlink found:
今、存在するシンボリックリンクが表示される
Overwrite with Plan? (y/N): y
✓ ghostty config: Linked (GhosttyのConfigがあるパス -> /Documents/dokodemo-drawer/drawer/ghostty/config)
ディレクトリ構造
drawer配下に管理したいconfigファイルやdotfileを設置します。
dokodemo-drawer
├── drawer
│ ├── ghostty
│ │ ├── config // 管理対象のConfigファイル
│ │ └── setup.sh // シンボリックリンク作成関数呼び出し(引数指定)
│ ├── git
│ │ ├── .git_aliases
│ │ └── setup.sh
│ ├── gitleaks
│ │ ├── pre-commit
│ │ └── setup.sh
│ └── zsh
│ ├── .zsh_aliases
│ └── setup.sh
├── Makefile
├── README.md
├── setup.sh // シンボリックリンク作成対象選択
└── symlink.sh //シンボリックリンク作成関数
スクリプト
setup.sh(リポジトリルート)
#!/bin/bash
set -euo pipefail
# リポジトリルートの特定
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
export REPO_ROOT="$SCRIPT_DIR"
# 共通関数の読み込み
source "$SCRIPT_DIR/symlink.sh"
# APPS+=(ghostty git zsh)のように配列に追加するための準備
declare -a APPS=()
if [ ! -d "$REPO_ROOT/drawer" ]; then
# drawerディレクトリが存在しない場合は終了
echo -e "${RED}Error: drawer directory not found${NC}"
exit 1
fi
for app_dir in "$REPO_ROOT/drawer"/*; do
if [ ! -d "$app_dir" ]; then
# ディレクトリでない場合はスキップ
continue
fi
app_name=$(basename "$app_dir")
setup_script="$app_dir/setup.sh"
if [ ! -f "$setup_script" ]; then
# setup.shが存在しない場合はスキップ
echo -e "${YELLOW}Warning: setup.sh not found for $app_name${NC}"
continue
fi
source "$setup_script"
APPS+=("$app_name")
done
# メインメニュー
main() {
echo -e "${BLUE}=== Dotfiles Setup ===${NC}"
echo "Select applications to setup:"
echo
if [ ${#APPS[@]} -eq 0 ]; then
echo -e "${RED}No applications found in drawer/${NC}"
exit 1
fi
PS3="Choose an option (number or 'q' to quit): "
select opt in "${APPS[@]}"; do
# 'q'または'Q'で終了
if [[ "$REPLY" =~ ^[qQ]$ ]]; then
echo "Exiting..."
break
fi
# 選択されたアプリが有効かチェック
valid_opt=false
for app in "${APPS[@]}"; do
if [ "$app" == "$opt" ]; then
valid_opt=true
break
fi
done
if [ "$valid_opt" = false ]; then
echo -e "${RED}Invalid option${NC}"
continue
fi
# setup_<app_name> 関数を動的に呼び出し
func_name="setup_${opt}"
if ! type "$func_name" > /dev/null 2>&1; then
echo -e "${RED}Error: Function $func_name not found${NC}"
continue
fi
$func_name
echo
done
echo -e "${GREEN}Done!${NC}"
}
main
symlink.sh
#!/bin/bash
# Define colors
export GREEN='\033[0;32m'
export YELLOW='\033[1;33m'
export RED='\033[0;31m'
export BLUE='\033[0;34m'
export NC='\033[0m'
# シンボリックリンクを作成
create_symlink() {
local source=$1
local dest=$2
local app_name=$3
# ソースファイルの存在確認
if [ ! -f "$source" ]; then
echo -e "${RED}✗ $app_name: Source file not found at $source${NC}"
return 1
fi
echo
echo -e "${BLUE}[Plan] Link to be created:${NC}"
echo -e "$dest -> $source"
# リンク先が実体ファイルやディレクトリとして存在する場合(シンボリックリンクでない)
if [ -e "$dest" ] && [ ! -L "$dest" ]; then
local type="File"
if [ -d "$dest" ]; then
type="Directory"
fi
echo -e "${YELLOW}$app_name: Skipped. Existing $type found at $dest${NC}"
ls -ldG "$dest"
return 0
fi
# 既にシンボリックリンクが存在する場合
if [ -L "$dest" ]; then
echo
echo -e "${YELLOW}[Before Execution] Existing symlink found:${NC}"
ls -ldG "$dest"
echo
read -p "Overwrite with Plan? (y/N): " -n 1 -r
echo
if [[ ! $REPLY =~ ^[Yy]$ ]]; then
echo -e "${YELLOW}Skipped $app_name${NC}"
return 0
fi
rm "$dest"
fi
# ディレクトリ作成
local parent_dir="$(dirname "$dest")"
if [ ! -d "$parent_dir" ]; then
echo -e "${BLUE}$app_name: Parent directory '$parent_dir' does not exist.${NC}"
read -p "Create it? (y/N): " -n 1 -r
echo
if [[ ! $REPLY =~ ^[Yy]$ ]]; then
echo -e "${YELLOW}Skipped $app_name${NC}"
return 0
fi
mkdir -p "$parent_dir"
fi
# リンク作成
ln -s "$source" "$dest"
echo -e "${GREEN}✓ $app_name: Linked ($dest -> $source)${NC}"
}
setup.sh
#!/bin/bash
setup_ghostty() {
local source="$REPO_ROOT/drawer/ghostty/config"
local dest="$HOME/Library/Application Support/com.mitchellh.ghostty/config"
create_symlink "$source" "$dest" "ghostty config"
}
工夫した点
dotfileはすべてを管理しないようにした
特定のエイリアスなどをGit管理したいだけなので、例えば、.gitconfigであれば.git_aliasesを作成してこれを読み込みます。.gitconfig自体は管理しないようにしました。
[include]
path = ~/.git_aliases
zshも似たようなことをして全体は管理していません。
gitleaksを導入した
gitleaksを導入して、秘匿情報をアップロードしないようにしました。ローカル開発ではbrewを使ってinstallし、Git Hooksを使ってコミットさせないように設定しました。
pre-commitというファイルにコミット前の実行スクリプトを書いて、make setupでgitleaksを選択するだけで、.git/hooks配下にリンクできるようにしました。
setup.sh
#!/bin/bash
set -euo pipefail
setup_gitleaks() {
# Check if gitleaks is installed
if ! command -v gitleaks &> /dev/null; then
echo -e "${YELLOW}Warning: gitleaks is not installed.${NC}"
echo "Please install 'gitleaks' manually to enable pre-commit checks."
return 0
fi
# Link pre-commit hook
create_symlink "$REPO_ROOT/drawer/gitleaks/pre-commit" "$REPO_ROOT/.git/hooks/pre-commit" "gitleaks"
}
pre-commit
#!/bin/bash
# Simply run gitleaks protection on staged files
# If gitleaks is not installed, this will fail and block the commit.
gitleaks protect --staged -v
省略しますが、workflowも作成しました。
シンボリックリンクの作成と更新以外はしないようにした
コアロジックはシンボリックリンクの作成と更新だけです。それ以上をやりたくなったら工数がかかり、他のことをやれなくなるし、年を越してしまうのでやめました。
今のチームになってから、Simple Made EasyやDo One Thing and Do It Wellということをリーダーが何度か口にしていたのでそれをやってみる練習だとも思っています。
スニペットツールの見直しとコマンドチートシートの作成
Clipyの見直し
Clipyには大変お世話になり、これからもMacを導入する人には必ずおすすめすると思います。
しかし、Raycastを使っていることもあり、ClipyとRaycastでスニペットがちらばっている状態でしたので、ClipyをアンインストールしてRaycastに寄せるようにしました。
スニペットはとりあえずで保存をしがちで、使わないものがたくさん放置されている状態でした。
スニペットになんでも保存するのではなく、以下のような選択肢から適切な場所を選択することが重要だと再認識しました。
- dotfilesマネージャ
- パスワードマネージャ
- 辞書ツール
- スニペットツール
例えば、社員番号をスニペットツールで管理していたのですが、辞書ツールに「社員番号」という読みで登録しており、こちらをよく使っていたので自分にとって適切なのはこちらだったのでスニペットツールからは削除しました。
.envファイルも1passwordを使ってれば、こちらから参照できるようになったので、dotfilesマネージャでの管理よりこちらがいいと思っています。
コマンドのチートシートはスニペットツールで管理するべきか
「コマンドのチートシートはコマンドから呼び出せるべきではないか」
それを検証したくて、以下のようなスクリプトをつくりました。
$HOME/bin/cheatyと配置して、aliasで以下のようにchコマンドで呼び出します。
alias ch='print -z "$(cheaty)"'
※Zsh
- カテゴリを選択
- コマンドを選択
- コマンドが入力される
スクリプト
cheaty.sh
#!/bin/zsh
# 1. パス設定 (スクリプトと同じ場所にJSONがある前提)
local SCRIPT_DIR="${${(%):-%N}:A:h}"
local MAIN_FILE="$SCRIPT_DIR/commands.json"
local LOCAL_FILE="$SCRIPT_DIR/commands.local.json"
# 2. メインファイルが無いと始まらないのでチェック
if [ ! -f "$MAIN_FILE" ]; then
echo "Error: $MAIN_FILE not found." >&2
return 1
fi
# 3. 読み込むファイルを決定(ローカルがあればリストに加える)
local targets=("$MAIN_FILE")
[ -f "$LOCAL_FILE" ] && targets+=("$LOCAL_FILE")
# 4. JSONを結合 (後勝ち=上書き)
# 配列 targets を展開して jq に渡すだけ。シンプルです。
local merged_json=$(jq -s 'add' "${targets[@]}")
while true; do
# カテゴリ選択
local category=$(echo "$merged_json" | jq -r 'keys[]' | \
fzf --height 12 --layout=reverse --border --prompt="📂 Category > ")
[ -z "$category" ] && break
# コマンド選択
local cmd=$(echo "$merged_json" | jq -r --arg cat "$category" '.[$cat][]' | \
fzf --height 12 --layout=reverse --border --prompt="📝 Command ($category) > ")
[ -z "$cmd" ] && continue
# 結果を出力して終了
print "$cmd"
break
done
設定ファイル Git管理対象
commands.json
{
"Docker": [
"docker system prune -a",
"docker-compose up -d"
],
"Git": [
"git log --oneline --graph --all",
"git pull origin main",
"git status",
"git reset --soft HEAD~1"
],
"Gitleaks": [
"gitleaks detect --verbose"
],
"Zsh": [
"source ~/.zshrc"
]
}
ローカル専用設定ファイル Git管理しない
commands.local.json
{
"System": [
"brew update && brew upgrade",
"ifconfig | grep \"inet \""
]
}
少しつかってみた感想
使いやすいと思いました。
fzfを使って絞り込めるので、すぐに使いたいものにたどり着けます。
Dockerコンテナに入って操作するときには使えないので、スニペットツールの方が便利だと思いました。
まとめ
Ghosttyをインストールしたのは今年の春で、会社のエンジニアに紹介してもらい、少し触ってみてから放置でした。幽霊部員。設定がシンプルで、AIの補完などがないことが逆によく、シンプルに使えることが魅力的だと思っています。最近は、Ghosttyをメインで使い、他のターミナルエミュレータはアンインストールしました。高機能なものが手に馴染むものとは限らないので、シンプルな機能でも、自分にとって必要な道具を使っていきたいです。
AIを使えばシンプルなものを自作することが容易になったのも嬉しいことです。自分で作るということは車輪の再発明になる可能性もありますが、ツールを作ることはツールをより良く使うための知識になると思いました。扱うツールに対する解像度があがる感じがします。
なによりツールの手入れは楽しいので、年末に限らず定期でやっていこうと思います🔧
NE株式会社のエンジニアを中心に更新していくPublicationです。 NEでは、「コマースに熱狂を。」をパーパスに掲げ、ECやその周辺領域の事業に取り組んでいます。 Homepage: ne-inc.jp/
Discussion