「Cursorエディター導入」Docker開発環境で快適に使うための工夫

## はじめに

CursorをDockerコンテナにアタッチして開発している方も多いかと思います。

通常は拡張機能を手動でインストールしているケースが多いですが、実務ではDockerイメージを削除・再構築する機会も多く、そのたびに拡張機能を手動で再インストールするのは面倒ですよね。

チームで開発する際に、できるだけエディターの設定も統一したいです。
そこで今回は、実務レベルでチーム開発に対応できるように、Cursorエディターの設定や拡張機能のインストールを自動化してみました。

VSCode を長く使っている方は、プロジェクトごとに「ワークスペース設定」を行っていることが多いかと思います。
プロジェクト直下に .vscode/settings.json を配置することで、ワークスペース単位で設定を上書きすることが可能です。

今回は、Cursor が比較的新しいエディターであり、まだ使い慣れていない方も多いと思いますので、**ユーザー設定を直接編集する方法** をご紹介しています。

## お題

今回、以下の4つの課題を解決することを目的としました

- Cursor エディターの settings.json をチームで共有し、自動で適用できるようにしたい
- Mac ローカル環境に Cursor エディターの拡張機能を一括インストールしたい
- 一部の拡張機能が Marketplace 経由ではインストールできない問題がある
- Docker コンテナ内でも拡張機能を自動でインストールしたい

## 課題


## 事前準備

まず、Cursor エディターの settings.json ファイルを作成します。
この設定方法については、以前の記事で紹介していますので、詳しくはこちらをご覧ください：
👉 [【Cursorエディター導入/第一歩】Cursorエディターを触ってみました](https://qiita.com/h-sakamoto/items/90df3ada565cc10e1e99)

```editor_settings.json
{
    "workbench.colorTheme": "Default Dark+",
    "window.newWindowDimensions": "inherit",
    "extensions.verifySignature": false,
    "window.commandCenter": true,
    "emmet.triggerExpansionOnTab": true,
    "search.showLineNumbers": true,
    "files.insertFinalNewline": true,
    "files.trimFinalNewlines": true,
    "files.trimTrailingWhitespace": true,
    "editor.minimap.enabled": true,
    "editor.copyWithSyntaxHighlighting": true,
    "breadcrumbs.enabled": true,
    "editor.tabSize": 2,
    "editor.linkedEditing": true,
    "emmet.variables": {
        "lang": "ja"
    },
    "editor.guides.indentation": true,
    "editor.guides.bracketPairsHorizontal": true,
    "editor.bracketPairColorization.independentColorPoolPerBracketType": true,
    "editor.roundedSelection": true,
    "editor.renderWhitespace": "all",
    "editor.wordWrap": "on",
    "editor.wordWrapColumn": 140,
    "editor.wrappingIndent": "same",
    "editor.tabCompletion": "on",
    "editor.snippetSuggestions": "top",
    "editor.formatOnType": true,
    "editor.formatOnPaste": true,
    "editor.quickSuggestions": {
        "comments": "on",
        "strings": "on"
    },
    "files.autoGuessEncoding": true,
    "editor.fontFamily": "'Fira Code', 'Cica', 'monospace'",
    "editor.fontWeight": "500",
    "editor.fontSize": 13,
    "editor.letterSpacing": -0.4,
    "editor.lineHeight": 1.4,
    "editor.tokenColorCustomizations": {
        "comments": "#5da33c"
    },
    "files.associations": {
        "*.mdc": "markdown"
    },
    "workbench.editorAssociations": {
        "*.mdc": "default"
    },
    "editor.defaultFormatter": "esbenp.prettier-vscode",
    "editor.formatOnSave": true,
    "prettier.printWidth": 140,
    "prettier.singleLine": true,
    "prettier.bracketSpacing": true,
    "prettier.arrowParens": "always",
    "prettier.semi": true,
    "prettier.singleQuote": false,
    "prettier.trailingComma": "es5",
    "prettier.useTabs": false,
    "prettier.tabWidth": 2,
    "prettier.endOfLine": "lf",
    "prettier.bracketSameLine": false,
    "prettier.singleAttributePerLine": false,
    "prettier.jsxSingleQuote": false,
    "prettier.tailwindPreserveWhitespace": true,
    "prettier.plugins": [
        "prettier-plugin-tailwindcss",
        "prettier-plugin-sort-json",
        "@ianvs/prettier-plugin-sort-imports"
    ],
    "prettier.importOrder": [
        "<BUILTIN_MODULES>",
        "<THIRD_PARTY_MODULES>",
        "",
        "^@/(.*)$",
        "^[./]",
        "",
        "<TYPES>",
        "<TYPES>^[./]"
    ],
    "workbench.activityBar.orientation": "vertical",
    "workbench.iconTheme": "vscode-icons",
    "workbench.tree.indent": 16,
    "workbench.tree.renderIndentGuides": "always",
    "workbench.reduceMotion": "on",
    "workbench.colorCustomizations": {
        "editorIndentGuide.background1": "#cb997e",
        "editorIndentGuide.background2": "#ddbea9",
        "editorIndentGuide.background3": "#ffe8d6",
        "editorIndentGuide.background4": "#b7b7a4",
        "editorIndentGuide.background5": "#a5a58d",
        "editorIndentGuide.background6": "#6b705c",
        "editorIndentGuide.activeBackground1": "#06d6a0",
        "editorIndentGuide.activeBackground2": "#06d6a0",
        "editorIndentGuide.activeBackground3": "#06d6a0",
        "editorIndentGuide.activeBackground4": "#06d6a0",
        "editorIndentGuide.activeBackground5": "#06d6a0",
        "editorIndentGuide.activeBackground6": "#06d6a0",
        "tab.activeBackground": "#563b90",
        "tab.activeForeground": "#d3d2d4",
        "statusBar.background": "#53208c"
    },
    "search.exclude": {
        "**/idea": true
    },
    "[python]": {
        "editor.formatOnSave": true,
        "editor.codeActionsOnSave": {
            "source.fixAll": "explicit",
            "source.organizeImports": "explicit"
        },
        "editor.defaultFormatter": "charliermarsh.ruff",
        "editor.suggestSelection": "first",
        "editor.snippetSuggestions": "top",
        "editor.suggest.localityBonus": true,
        "editor.suggest.shareSuggestSelections": true,
        "editor.suggest.preview": true,
        "editor.tabSize": 4
    },
    "[json]": {
        "editor.defaultFormatter": "vscode.json-language-features",
        "editor.formatOnSave": true,
        "editor.formatOnSaveMode": "file",
        "editor.tabSize": 4
    },
    "[markdown]": {
        "editor.defaultFormatter": "vscode.markdown-language-features",
        "files.trimTrailingWhitespace": false,
        "editor.formatOnSave": true,
        "editor.formatOnSaveMode": "file"
    },
    "[html]": {
        "editor.defaultFormatter": "vscode.html-language-features"
    },
    "[javascript, typescript, typescriptreact, json]": {
        "editor.defaultFormatter": "vscode.typescript-language-features",
        "editor.codeActionsOnSave": {
            "source.organizeImports": "explicit"
        },
        "editor.formatOnSave": true,
        "editor.tabSize": 2
    },
    "[terraform]": {
        "editor.codeActionsOnSave": {
            "source.organizeImports": "explicit"
        },
        "editor.defaultFormatter": "hashicorp.terraform",
        "editor.formatOnSave": true,
        "editor.tabSize": 4
    },
    "[terraform-vars]": {
        "editor.codeActionsOnSave": {
            "source.organizeImports": "explicit"
        },
        "editor.defaultFormatter": "hashicorp.terraform",
        "editor.formatOnSave": true,
        "editor.tabSize": 4
    },
    "files.exclude": {
        "**/.idea": true,
        "**/*_cache": true
    },
    "terminal.integrated.fontSize": 15,
    "terminal.integrated.lineHeight": 1.25,
    "terminal.integrated.defaultLocation": "editor",
    "remote.extensionKind": {
        "ms-ceintl.vscode-language-pack-ja": [
            "ui",
            "workspace"
        ],
        "ms-azuretools.vscode-docker": [
            "ui",
            "workspace"
        ],
        "anysphere.pyright": [
            "ui",
            "workspace"
        ],
        "esbenp.prettier-vscode": [
            "ui",
            "workspace"
        ],
        "ms-vscode.makefile-tools": [
            "ui",
            "workspace"
        ],
        "corschenzi.mermaid-graphical-editor": [
            "ui",
            "workspace"
        ]
    }
}
```

拡張機能リスト
```editor_extensions.json
[
    {
        "name": "日本語言語パック",
        "identifier": "ms-ceintl.vscode-language-pack-ja",
        "version": "1.96.2024121109",
        "description": "日本語化するための言語パック"
    },
    {
        "name": "Docker",
        "identifier": "ms-azuretools.vscode-docker",
        "version": "1.29.4",
        "description": "Docker のビルド・起動・管理ツール"
    },
    {
        "name": "Dev Containers",
        "identifier": "ms-vscode-remote.remote-containers",
        "version": "0.394.0",
        "description": "コンテナ内での開発拡張機能"
    },
    {
        "name": "Cursor Pyright",
        "identifier": "anysphere.pyright",
        "version": "1.1.327",
        "description": "Cursor 向けの Pyright 拡張機能"
    },
    {
        "name": "VSCode Icons",
        "identifier": "vscode-icons-team.vscode-icons",
        "version": "12.11.0",
        "description": "VSCodeアイコン"
    },
    {
        "name": "Prettier",
        "identifier": "esbenp.prettier-vscode",
        "version": "11.0.0",
        "description": "Prettierによるコード整形"
    },
    {
        "name": "Tailwind CSS IntelliSense",
        "identifier": "bradlc.vscode-tailwindcss",
        "version": "0.14.15",
        "description": "Tailwind CSS のコード補完"
    },
    {
        "name": "Import Cost",
        "identifier": "wix.vscode-import-cost",
        "version": "3.3.0",
        "description": "インポートコストを表示する"
    },
    {
        "name": "Quokka.js",
        "identifier": "wallabyjs.quokka-vscode",
        "version": "1.0.711",
        "description": "Console の出力をエディタ上で確認する"
    },
    {
        "name": "Flutter Color",
        "identifier": "circlecodesolution.ccs-flutter-color",
        "version": "2.0.0",
        "description": "Flutter の色を管理する"
    },
    {
        "name": "Mermaid Graphical Editor",
        "identifier": "corschenzi.mermaid-graphical-editor",
        "version": "0.5.1",
        "description": "GUI サポート Mermaid の編集ツール"
    },
    {
        "name": "Makefile Tools",
        "identifier": "ms-vscode.makefile-tools",
        "version": "0.12.17",
        "description": "Makefile 開発支援ツール"
    },
    {
        "name": "HashiCorp Terraform",
        "identifier": "hashicorp.terraform",
        "version": "2.34.3",
        "description": "Terraform 拡張機能"
    },
    {
        "name": "Python",
        "identifier": "ms-python.python",
        "version": "2024.12.3",
        "description": "Python 拡張機能（実行・デバッグ・補完など）"
    },
    {
        "name": "Ruff",
        "identifier": "charliermarsh.ruff",
        "version": "2025.22.0",
        "description": "ruff 拡張機能"
    },
    {
        "name": "Cucumber (Gherkin) Full Support",
        "identifier": "alexkrechik.cucumberautocomplete",
        "version": "3.0.5",
        "description": "Gherkin 言語の開発サポート"
    }
]
```

現在のディレクトリと同じ階層に local_vsix という名前のフォルダーを作成してください。

このフォルダーには、Marketplace からダウンロードした .vsix ファイルを配置します。
拡張機能のインストールは基本的に Marketplace 経由が優先されるため、最初は空のフォルダーでも問題ありません。

local_vsix フォルダーに配置する .vsix ファイルのファイル名は、editor_extensions.json に記載されている identifier と同じ名前にしてください。

## Cursorエディターの設定と拡張機能インストールを自動化する

こちらのスクリプトを実行すれば完全に自動化できます

```initialize_cursor.sh
#!/bin/bash

set -euo pipefail

# 定数
EXT_JSON_FILE="editor_extensions.json"
SRC_SETTINGS_FILE="editor_settings.json"
DEST_SETTINGS_FILE="settings.json"
CURSOR_SETTING_PATH="${HOME}/Library/Application Support/Cursor/User"
CURRENT_DIR="$(cd "$(dirname "$0")" && pwd)"
VSIX_DIR="${CURRENT_DIR}/local_vsix"

# Cursor確認
if ! command -v cursor &> /dev/null; then
    echo "❌ Cursorエディターがインストールされていません。"
    exit 1
fi

# settings.jsonのバックアップ & 設定ファイル上書き
if [ -f "${CURSOR_SETTING_PATH}/${DEST_SETTINGS_FILE}" ]; then
    today=$(date +%Y%m%d)
    cp "${CURSOR_SETTING_PATH}/${DEST_SETTINGS_FILE}" "${CURSOR_SETTING_PATH}/${DEST_SETTINGS_FILE}.bak-${today}"
    echo "📦 設定ファイルのバックアップを作成しました（.bak-${today}）"
fi

cp "${CURRENT_DIR}/${SRC_SETTINGS_FILE}" "${CURSOR_SETTING_PATH}/${DEST_SETTINGS_FILE}"
echo "✅ cursor の settings.json を更新しました"

# 拡張機能のインストール
echo "🧩 拡張機能のインストールを開始します..."

# JSONを1行ずつ処理し、identifierとnameを取り出す
identifier=""
name=""
while read -r line; do
  # "identifier": "xxx" を抽出
  if [[ "$line" =~ \"identifier\"\:\ \"([^\"]+)\" ]]; then
    identifier="${BASH_REMATCH[1]}"
  fi

  # "name": "xxx" を抽出
  if [[ "$line" =~ \"name\"\:\ \"([^\"]+)\" ]]; then
    name="${BASH_REMATCH[1]}"
  fi

  # identifier がセットされていればインストール処理へ
  if [[ -n "${identifier:-}" ]]; then
    if cursor --list-extensions 2>/dev/null | grep -q "^${identifier}$"; then
      echo "🔁 ${name}（${identifier}）は既にインストール済みです。スキップします。"
    else
      echo "📦 ${name}（${identifier}）をインストール中..."
      if cursor --install-extension "${identifier}"; then
        echo "✅ ${name}（${identifier}）をインストールしました"
      else
        echo "ℹ️ Marketplaceからのインストールに失敗。ローカルのVSIXを確認します..."

        vsix_path="${VSIX_DIR}/${identifier}.vsix"
        if [[ -f "${vsix_path}" ]]; then
          echo "📦 ${name}（${identifier}）をVSIXでインストール中..."
          if cursor --install-extension "${vsix_path}"; then
            echo "✅ VSIXで ${name}（${identifier}）をインストールしました"
          else
            echo "❌ VSIXによる ${name}（${identifier}）のインストールにも失敗しました"
          fi
        else
          echo "❌ ${name}（${identifier}）のVSIXファイルが見つかりません（期待パス: ${vsix_path}）"
        fi
      fi
    fi
    unset identifier name
  fi
done < "${CURRENT_DIR}/${EXT_JSON_FILE}"
```

これで、Makefile などに以下のような処理を記述すれば、Cursor エディターの設定を快適に行えるようになります。

```Makefile
.PHONY: init-cursor-editor
init-cursor-editor: ## Cursorエディターの設定を初期化
	@./initialize_cursor.sh
```

次回は、Dockerコンテナに拡張機能をインストールする方法をご紹介します。