Open7

VSCode拡張機能 CodeZoo

まさきちまさきち

vscode-pets

vscode-petsのようなVScodeの拡張機能を作成したい。

https://github.com/MASAKi-cell/codeZoo

【拡張機能の要件】

  • VScode内で猫を育成する。
  • 3段階に成長する
  • 夜は寝る。
  • 夜に起こすと、好感度が下がる
  • 最初はなついていない
  • 餌やり、おもちゃで遊ぶことを複数回繰り返すと好感度が上がりなつく
  • 育成と作業の連動: VS Codeでのタスク(例えばコードのコンパイル、一定時間の作業など)を完了するたびに、リワードを猫に与えられる仕組み。
  • 音楽や効果音: 餌をあげる音や猫の鳴き声、遊ぶときの効果音を追加。癒し効果がアップ。
  • 健康ステータス追加: 猫に「満腹度」「エネルギー」「ストレス」などのステータスを導入。プレイヤーはバランスを取るために餌をあげたり、遊んだり、休ませたりする必要がある。ステータスが低下すると猫が病気になるリスクを追加。


  • タイムイベントを管理: システムの時間に基づき、夜(例えば22時~6時)になると猫が自動的に寝るように設定。
  • UIの作成: WebviewまたはStatusBarを使って猫を表示する
  • ステート管理: データを永続化(例: VS Codeのグローバルステート)し、拡張機能を再起動しても猫の状態が保持されるようにする。
  • デバッグ: 各機能をテストし、挙動が期待通りか確認。


vscodeのGlobal State

https://code.visualstudio.com/api/extension-capabilities/common-capabilities

ExtensionContext.workspaceStateでキー/値のペアを書き込み可能

// on activate
const versionKey = 'shown.version';
context.globalState.setKeysForSync([versionKey]);

// later on show page
const currentVersion = context.extension.packageJSON.version;
const lastVersionShown = context.globalState.get(versionKey);
if (isHigher(currentVersion, lastVersionShown)) {
    context.globalState.update(versionKey, currentVersion);
}


export type CAT_STATUS {
    stage: "kitten" | "young" | "adult"; // 子猫, 若猫, 大人猫
    affection: number; // 好感度
    awake: boolean; // 現在の時間(昼/夜)
}


一定の条件で好感度を設定。

function growCat(cat: Cat): void {
    if (cat.stage === "kitten" && cat.affection >= 10) {
        cat.stage = "young";
    } else if (cat.stage === "young" && cat.affection >= 20) {
        cat.stage = "adult";
    }
}
まさきちまさきち

導入手順

Yeoman と VS Code Extension Generatorジェネレータをインストール

npm install -g yo generator-code
yo code // 拡張機能プロジェクトの生成


インストールされたパッケージを確認

➜  ~ npm -g list
/Users/takahashi_masaki/.nodebrew/node/v16.0.0/lib
├── generator-code@1.11.4
├── npm@7.10.0
├── yarn@1.22.19
└── yo@5.0.0

npx yo code

src/extension.tsを主な開発ファイルとして使用

https://zenn.dev/hiro256ex/articles/20230625_getstartedvscodeextension

https://atmarkit.itmedia.co.jp/ait/articles/1511/30/news030.html


公式ドキュメント

https://code.visualstudio.com/api

コマンド パレット ( ⇧⌘P ) からコマンドデバッグ: デバッグの開始を実行

まさきちまさきち

初期コード

extension.ts
import * as vscode from "vscode";

export function activate(context: vscode.ExtensionContext) {
  console.log('Congratulations, your extension "codezoo" is now active!');

  const disposable = vscode.commands.registerCommand(
    "codezoo.helloWorld",
    () => {
      vscode.window.showInformationMessage("Hello World from CodeZoo!");
    }
  );
  context.subscriptions.push(disposable);
}

export function deactivate() {}


VScode拡張機能公開までの流れ

https://qiita.com/happy663/items/cbd82a0c214a548db857



アニメーションの生成

requestAnimationFrame()

https://ics.media/entry/210414/

まさきちまさきち
  1. ペットの動きをユーザーの操作に連動。
  2. ユーザーのマウス移動に反応
  3. ペットが大きくなる。

WebviewとVS Code APIの間でメッセージ通信
WebviewからVS Codeへのメッセージ送信

const vscode = acquireVsCodeApi();
vscode.postMessage({ type: 'move', direction: 'left' });

逆にVS CodeからWebviewへメッセージを送るには、postMessageメソッドを使用

panel.webview.postMessage({ command: 'movePet', direction: 'right' });
まさきちまさきち

Github Actions



CodeQL

CodeQLを使用して、コード内の脆弱性とエラーを特定する。Code Scanning (with CodeQL) は、Public リポジトリであれば無料で利用可能。

https://docs.github.com/ja/code-security/code-scanning/introduction-to-code-scanning/about-code-scanning-with-codeql

name: 'CodeQL Advanced'

on:
  push:
    branches: ['main']
  pull_request:
    branches: ['main']
  schedule:
    - cron: '40 15 * * 5'

jobs:
  analyze:
    name: Analyze (${{ matrix.language }})
    runs-on: ${{ (matrix.language == 'swift' && 'macos-latest') || 'ubuntu-latest' }}
    permissions:
      security-events: write
      packages: read
      actions: read
      contents: read
    strategy:
      fail-fast: false
      matrix:
        include:
          - language: javascript-typescript
            build-mode: none
    steps:
      - name: Checkout repository
        uses: actions/checkout@v4
      - name: Initialize CodeQL
        uses: github/codeql-action/init@v3
        with:
          languages: ${{ matrix.language }}
          build-mode: ${{ matrix.build-mode }}
      - if: matrix.build-mode == 'manual'
        shell: bash
        run: |
          echo 'If you are using a "manual" build mode for one or more of the' \
            'languages you are analyzing, replace this with the commands to build' \
            'your code, for example:'
          echo '  make bootstrap'
          echo '  make release'
          exit 1
      - name: Perform CodeQL Analysis
        uses: github/codeql-action/analyze@v3
        with:
          category: '/language:${{matrix.language}}'

fail-fast

fail-fastは、matrix 内の job が失敗した時のエラー制御を行うためのオプション
false にすると、job の成功・失敗に関わらず matrix 内のすべての job が完了するまで実行する。false でいずれかの job が失敗した場合でも、ワークフロー全体のステータスは失敗となるため、失敗に気づけないということはない。



Dependabot設定

GitHub Dependabotを使ったアップデート運用。Dependabotとは、プロジェクト内の依存関係に含まれる脆弱性やアップデートの有無を検知して自動でプルリクエストを作成したり、アラートを出すことができるサービスのこと。GitHub組み込みの機能。

https://engineering.nifty.co.jp/blog/23423


▪️ 公式ドキュメント
https://docs.github.com/en/code-security/dependabot/working-with-dependabot/dependabot-options-reference

.github/dependabot.ymlに記述を行い、アップデート可能なパッケージがある場合、Dependabotはこの設定を元に自動的でプルリクエストを作成する。

version: 2
updates:
  - package-ecosystem: 'npm'
    directory: '/'
    schedule:
      interval: 'weekly'
      time: '00:00'
    open-pull-requests-limit: 10
    reviewers:
      - MASAKi-cell
    assignees:
      - MASAKi-cell
    commit-message:
      prefix: fix
      prefix-development: chore
      include: scope
    ignore: # バージョンアップの除外設定
      - dependency-name: '*'
        update-types:
          - version-update:semver-patch # パッチバージョンは除外


バージョンアップ戦略はZennに記述されている以下が良さそう

  • major:Dependabot version updatesの対象外とする:majorのアップデートは情報として流れてきて気づくことも多い。本番運用している場合は、気軽にメジャーを上げられない。
  • minor:デフォルトの動作とする(Dependabot version updatesがPRを作成し、自分でマージする):新しい機能が追加されたのであれば、自動マージするのではなく、どのような機能なのか確認した上でマージしたい
  • patch:自動的にマージする:バグFIXであれば、中身を1つ1つ知る重要度が低い。脆弱性に対するパッチであれば、速度を優先できる。パッチバージョンの変更はバグ修正がメインでソフトウェアが壊れるリスクは低いため。

https://zenn.dev/sumiren/articles/ffe6c0bd772718

除外設定を追加した場合は、除外理由をコメントやコミットメッセージに残す。理由が不明な場合は、除外設定自体の削除がしにくいため。

github Actionsの自動マージ設定を追加する。dependabotsは依存関係のバージョンアップを検知して、プルリクエストを出すことが仕事であるが、依存関係が多いとレビュー、マージが大変であるため、自動マージするフローを組み込む。github cliでマージするコマンドをワークフローに組み込む。

gh pr merge [<number> | <url> | <branch>] [flags]

https://cli.github.com/manual/gh_pr_merge

▪️ 公式ドキュメント
https://docs.github.com/en/code-security/dependabot/working-with-dependabot/automating-dependabot-with-github-actions#approve-a-pull-request

name: dependabot auto mage
on:
  pull_request:
    branches: ['main']
jobs:
  merge:
    if: ${{ github.actor == 'dependabot[bot]' }} # Dependabotのプルリクエストのみ許可
    runs-on: ubuntu-latest
    permissions:
      contents: write
      pull-requests: write
    env:
      GITHUB_TOKEN: ${{ secrets.ACCESS_TOKEN }} # GitHub CLIのクレデンシャル
    steps:
      - uses: actions/checkout@v4
      - id: meta
        uses: dependabot/fetch-metadata@v2
      - if: ${{ steps.meta.outputs.update-type == 'version-update:semver-patch' }} # patchを自動マージする
        run: |
          gh pr review "${GITHUB_HEAD_REF}" --approve
          gh pr merge "${GITHUB_HEAD_REF}" --merge --auto

dependabotが起動したワークフローでは、 dependabot/fetch-metadataで依存関係のメタデータが取得できる。update-typeなどのバージョンアップの種類が参照可能。patchのみに絞込みを行う。

これ以外に、github actions向けの変更もユーザーに影響がないため自動マージしておく。


tsconfig

https://zenn.dev/chida/articles/bdbcd59c90e2e1



ESLint

https://zenn.dev/noshiro_piko/articles/take-full-advantage-of-typescript-eslint

eslint-config-prettier でコードフォーマットの設定はオフにする。
.eslintrc.json を選ぶメリットはほぼなく、.eslintrc.js または .eslintrc.cjsで記述する。

eslintのFlatConfigについて。。。eslintの設定形式、 v10 では FlatConfig しかサポートしない予定とのこと。
exntendの概念がなくなり、代わりにconfiguration objectと呼ばれる、各設定情報を要素とする配列で格納される。適用するruleが重複した場合は後続のものが適用される。
pluginについてはjavascriptのモジュールとして明示的にimportする必要がある。

これまでは、eslint側がconfig や pluginを独自で設定していた為、パスへの解決を行う必要があった。
ユーザーもlintコマンドを実行しないと、設定したpluginやconfigが有効なものかわからなかった。

latConfig では config や plugin の解決が全て JavaScript のモジュール解決の仕組みに乗りました。ESLint は設定ファイルを評価するだけで依存が全て解決された設定情報を得られるわけです。

FlatConfigは一次元の配列になるため、どれを適用するかは後続のruleだけ確認すればよく、計算が楽。

https://eslint.org/docs/latest/use/configure/configuration-files

https://zenn.dev/cybozu_frontend/articles/about-eslint-flat-config

.mjs拡張子について:CommonJS(CJS) はESM 以前のJSの範囲で実装されており、依存解決するべきところにおいて曖昧さが存在した。ESM は、関数やオブジェクトではなく、新しく「構文(syntax)」を用意することによって、この問題を解決している。
Node.js において、パース時にそのファイルが ESM/CJS どちらなかのかを判別する方法で議論が行われた模様。 → .mjs拡張子を採用。

ブラウザは読み込む前にそのファイルを Script/Module どちらとしてパースするかを判断することができ、.js を維持したまま先に進むことができ、二回パースといったオーバーヘッドもない。

https://blog.jxck.io/entries/2017-08-15/universal-mjs-ecosystem.html



prettier

importの順番を指定する。

https://github.com/IanVS/prettier-plugin-sort-imports

https://zenn.dev/hayato94087/books/9ba8aa1f337dd5/viewer/ia4z46xsztbqk6

 ➜  codeZoo git:(main) npm run format                     
> codezoo@0.0.1 format
> prettier --write src
[error] none is not defined

Prettier が .prettierrc や他の Prettier 設定ファイルで無効な設定 (none) を検出している可能性が高いので、設定ファイルを確認する。