OmniAICreator/Qiita-1.07M huggingface dataset:
React × TypeScriptで作る天気予報ウィジェット（livedoor 天気互換APIを利用）

# React × TypeScriptで作る天気予報ウィジェット（livedoor 天気互換APIを利用）

本記事では、無料で利用できる [天気予報API（livedoor 天気互換）](https://weather.tsukumijima.net/) を使って、名古屋市の天気予報を表示するウィジェットの作り方を解説します。

## 完成イメージ
以下の情報をウィジェットで表示します。

- 現在の日付・更新時刻
- 今日の天気状態（最高・最低気温、降水確率、風向き、風速など）
- 今日・明日・明後日の3日間の予報
- 天気状態に応じた背景色とアイコン
- 詳細な天気概況文

## 利用する技術スタック
- React + TypeScript
- CSS
- [Lucide React（アイコン）](https://lucide.dev/)

## 実装手順

### 1. APIレスポンスの型定義

APIから返ってくるJSONに合わせて、型定義を行います。

```typescript
interface WeatherForecast {
  date: string;
  dateLabel: string;
  telop: string;
  detail: {
    weather: string | null;
    wind: string | null;
    wave: string | null;
  };
  temperature: {
    min: { celsius: string | null };
    max: { celsius: string | null };
  };
  chanceOfRain: { T00_06: string; T06_12: string; T12_18: string; T18_24: string };
  image: { url: string };
}
```

### 2. 天気状態の判定

APIのテロップ（telop）から天気の状態を判断します。

```typescript
const getWeatherCondition = (telop: string) => {
  if (telop.includes('雷')) return 'thunderstorm';
  if (telop.includes('雨')) return 'rain';
  if (telop.includes('雪') || telop.includes('みぞれ')) return 'snow';
  if (telop.includes('曇')) return 'cloudy';
  if (telop.includes('霧') || telop.includes('霞')) return 'fog';
  return 'clear';
};
```

### 3. 天気データの取得

名古屋市のCity ID「230010」を使用してAPIを叩きます。

```typescript
useEffect(() => {
  fetch('https://weather.tsukumijima.net/api/forecast/city/230010')
    .then(res => res.json())
    .then(data => {/*データ処理*/})
    .catch(() => {/*エラーハンドリング*/});
}, []);
```

### 4. APIからのデータ加工

降水確率は最も高い数値を採用します。

```typescript
const rainChances = [
  forecast.chanceOfRain.T00_06,
  forecast.chanceOfRain.T06_12,
  forecast.chanceOfRain.T12_18,
  forecast.chanceOfRain.T18_24
];
const maxRainChance = rainChances.reduce((max, current) =>
  parseInt(current) > parseInt(max) ? current : max, '0%');
```

### 5. アイコンと背景色の設定

Lucideアイコンを天気状態に応じて使い分けます。

```typescript
const getWeatherIcon = condition => {
  switch (condition) {
    case 'clear': return <Sun />;
    case 'rain': return <CloudRain />;
    // その他の状態
  }
};
```

背景色も状態に応じて変えます。

```css
.weather-clear { background-color: #2c3e50; border-left: 3px solid #ffd700; }
.weather-clear .icon { color: #ffd700; }
```

### 6. ウィジェットのレンダリング

取得した情報を画面に表示します。

```tsx
<div className="widget">
  <h2>天気予報</h2>
  <div>{formattedDate} (更新: {updateTime})</div>
  <div>{/* 今日の天気詳細 */}</div>
  <div>{/* 3日間予報 */}</div>
  <div>{/* 詳細な天気概況文 */}</div>
</div>
```

## 注意点

- 本APIは公式ではないため、仕様変更の可能性があります。
- 風速は詳細文から推測して表示しています。

## 今後の改善案
- 複数都市の切り替え表示
- 週間天気予報機能

## 実行例
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4038659/a46de82b-3dd1-42a5-a265-ea3ce27e2572.png)
※ 今日の天気が--となっていますが、取得時間に起因するもので明日になれば出てくる(はず)。

ReactとTypeScriptを用いて手軽に天気ウィジェットが実装できるので、ぜひ試してみてください！
【Next.js】 Route Handlersでファイルアップロードしたい

## はじめに
- Next.js App Routerの機能である、Route Handlersと標準のfetch APIを使ってファイル送信を実装した
- 調査に苦労したので備忘録としてまとめた
- Next.jsのバージョンは15.2.4。App Routerを使う
※これは、Pages Routerの時はAPI Routesと呼ばれていた機能に当たる

## やりたいこと
- フロントエンドをNext.js、バックエンドをC#で実装し、ファイルの送信を実装する
- ユーザー入力部分からNext.jsのRoute Handlersに向かってリクエストを投げる
→ Route HandlersからC#に向けて再度リクエストを投げる、という流れ
- ブラウザ標準のfetch APIを使用する
- 狙い：Route Handlersを中間APIとして使用することで、セキュリティ強化したい

## 実装方法
- フロントエンドのリクエスト先をRoute Handlersにする
- nextUrlの引数にNext.jsの中間APIを叩くURLを渡すイメージ
- Jsonを送信する場合はContent-typeを指定する

    ```TypeScript:page.tsx
    export async function uploadFile(
      nextUrl: string,
      fileList: FileList | null
    ): Promise<Response> {
      if (!fileList || fileList.length === 0) {
        return Response.error();
      }

      const formData = new FormData();
      for (const file of fileList) {
        formData.append("upfiles", file, file.name);
      }

      // formDataを送信するときはContent-typeを指定しない
      const config: RequestInit = {
        method: "POST",
        body: formData,
      };

      return await fetch(nextUrl, config);;
    }
    ```

- Route Handlersにバックエンドへfetchするように記述する
    - このとき、FormDataで送りたい場合はnew Headers()する（ここがポイント）
    - そうではない場合、フロントエンドのリクエストのHeadersを流用する（ここもポイント）
    - フロントエンド用のリクエストボディを作成するときにCookie等の情報を足す
    ```TypeScript:src/api/backend/[...backendUrl]/route.ts
    export async function POST(request: NextRequest): Promise<Response> {
        const isMultiPartFormData = request.headers.get("content-type")?.includes("multipart/form-data") ?? false;
        const bodyForBackEnd = isMultiPartFormData ? await request.formData() : await request.text();
        // ファイル送信では新たにHeaderを作成し、その他ではそのまま流用する
        const headersForBackEnd = isMultiPartFormData ? new Headers() : request.headers;

        // ここでバックエンドに向けたURLを作成
        const targetUrl = getTargetUrl(request.url);
        const url = new URL(targetUrl, baseUrl).href;
        console.debug("headersForBackend:", headersForBackEnd)

        const config : RequestInit= {
            method: "POST",
            headers: headersForBackEnd,
            body: bodyForBackEnd,
            credentials : 'include' as RequestCredentials,
        }

        // バックエンドに向けてfetchする
        const backendResult = await fetch(url, config);

        // クライアント表示用のResponseをnewして返却
        // ここで足したい情報があれば足す
        const resForClient = new Response(backendResult.body)
        return resForClient;
    }
    ```
## 備考
- Headersをnewする/しない、newするとしたらrequest.headersを渡すか渡さないかによって、UND_ERR_REQ_CONTENT_LENGTH_MISMATCHが発生する
- どうもUndici側のエラーのようで、以前はバージョンアップで解決していた様子
- 今回は最新バージョンのNext.jsを使用しているため、解決方法がわからず、色々試してこうなった
## 参考
- [Route Handlers公式](https://nextjs.org/docs/app/building-your-application/routing/route-handlers)
- [fetchでformDataを送るときはContent-typeを指定してはいけない](https://zenn.dev/kariya_mitsuru/articles/25c9aeb27059e7)
- [本番環境でだけ発生するundiciのエラー](https://zenn.dev/miyaboom0825/scraps/0edebc7939bb43)
NeurIPS2024の量子化論文 (1)

# 概要

この記事では、[NeurIPS2024](https://neurips.cc/Conferences/2024)の量子化論文を紹介します。[^1]

[^1]: 画像や数式は論文から引用しています。


# DuQuant: Distributing Outliers via Dual Transformation Makes Stronger Quantized LLMs

[別の記事](https://qiita.com/kyad/items/8cc8c9d211f946436456)参照


# QTIP: Quantization with Trellises and Incoherence Processing

https://arxiv.org/abs/2406.11235

- 概要：Bitshift+数値加算の遷移に縛ったTrellise QuantizerでLLMをベクトル量子化する方法
- キモ：重みを図2のように量子化する

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2448931/8b5ada7d-cb0e-4df2-ab77-74e426210f53.png)
状態の遷移は、左シフト＋0または1加算 となっています。各「01」などにV次元ベクトルが割当たっています。上のシーケンス$S$を下のシーケンス$\hat{S}$へ量子化しています。

メモリ量を削減するために、Codebookは持つのではなく、計算で求めることを提案しています。Codebookの計算の仕方として、論文ではIMADと3INSTの2つを提案しています。実用上はルックアップテーブルと計算のハイブリッドのCodebookが使われるそうです。

LLMを量子化する際は、Linear層の重みを$T_x\times T_y$ブロックに分割したものをシーケンスと思って量子化を適用します。


# Exploiting LLM Quantization

https://arxiv.org/abs/2405.18137

- 概要：量子化した後に異常動作が発生するように、LLMを攻撃する方法

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2448931/d0817156-bfbb-4a29-b381-3e39b0f8e28a.png)


# ZipCache: Accurate and Efficient KV Cache Quantization with Salient Token Identification

https://arxiv.org/abs/2405.14256

- 概要：KV Cacheの量子化で精度劣化を防ぐ方法
- キモ：
  1. Fig. 2(d)のようにチャネル毎にも量子化パラメータを決める
  2. 重要なトークンを求めるために(8)式の指標を使う。効率的に計算するためにトークンをサンプリングして計算する


# QBB: Quantization with Binary Bases for LLMs

https://openreview.net/forum?id=Kw6MRGFx0R

- 概要：LLMの量子化で、重みを1ビットの行列の一次結合で近似することで量子化する方法
- キモ：基底の1ビット行列と係数$\alpha$はFig. 1の初期値とFig. 2の反復で最適化する
日毎のMAU集計用SQL（GA4）

## 概要

以下2点を満たすためのSQLを検討します。

1. GA4テーブルから日次でMAUを集計して、任意の宛先テーブルを更新する
2. GA4テーブルは最大72時間更新されるため、 過去3日間（N-4〜N-1）のテーブルを取得して宛先テーブルを更新する

※特にアプリのGA4は1日前、2日前のテーブルの欠損が多いため、過去3日分の更新が必要となります。



## テーブル

対象日とその日時点でのMAUを集計します。
例えば2025年4月4日にクエリを実行した場合の返却結果は以下のようになります。

| event_date | mau   | 
| ---------- | ----- | 
| 20250401   | 10000 | 
| 20250402   | 10100 |
| 20250403   | 9000 | 



## SQL

最初にGoogleAnalyticsのテーブルからN-34〜N-1の範囲を取得し、一時テーブルに保存します。
そしてその後のループ処理で、期間を1日ずつずらしてN-1〜N-4の範囲のMAUを集計します。

クエリ結果の日付が宛先テーブルにすでにあればUPDATE、なければINSERTをしています。


```sql
DECLARE
  target_date INT64;
DECLARE
  start_date INT64;
DECLARE
  counter INT64 DEFAULT 0;
DECLARE
  LOOP_PERIOD INT64 DEFAULT 3; -- ループ回数（遡る日数）
DECLARE
  MAU_PERIOD INT64 DEFAULT 30; -- MAUの日数定義
DECLARE
  current_date DATE DEFAULT CURRENT_DATE();

CREATE TEMP TABLE tmp_table AS (
  SELECT
    event_date,
    user_pseudo_id
  FROM
    `projectName.analytics_XXX.events_*`
  WHERE
    -- 取得対象期間
    _table_suffix BETWEEN FORMAT_DATE("%Y%m%d", current_date - MAU_PERIOD - LOOP_PERIOD)
    AND FORMAT_DATE("%Y%m%d", current_date - 1));
    
LOOP
    SET
      counter = counter + 1;
    IF counter > LOOP_PERIOD
        THEN LEAVE;
    END IF;
    
    -- MAU集計対象 終了日
    SET
      target_date = CAST(FORMAT_DATE("%Y%m%d", current_date - counter) AS INT64) ;
    -- MAU集計対象 開始日
    SET
      start_date = CAST(FORMAT_DATE("%Y%m%d", current_date - counter - MAU_PERIOD) AS INT64) ;
    
    MERGE
      `projectName.datasetName.tableName` AS target
    USING
      (
      SELECT
        target_date AS date,
        COUNT(DISTINCT user_pseudo_id) AS mau
      FROM
        tmp_table
      WHERE
        CAST(event_date AS INT64 ) >= start_date
        AND CAST(event_date AS INT64 ) <= target_date ) AS SOURCE
    ON
      target.date = source.date
    WHEN MATCHED
      THEN UPDATE SET date = source.date, mau = source.mau
    WHEN NOT MATCHED
      THEN
        INSERT
          (date,
            mau)
        VALUES
          (source.date, source.mau);
END LOOP;
```

※スケジュールクエリで実行する場合には、バックフィル実行も考慮して
`current_date DATE DEFAULT CURRENT_DATE();`の箇所を
`current_date DATE DEFAULT @run_date;`としておくと良いです。

※MAUを「30日間」と定義していますが、28日や29日とする場合は
`MAU_PERIOD INT64 DEFAULT 30;`の箇所を書き換えてください。


## 参考情報

### GA4テーブルの更新
> 日次エクスポートのテーブル（events_YYYYMMDD）は、その日のイベントがすべて収集された後に作成されます。アナリティクスでは、テーブルの日付から最大72時間日次テーブルが更新されます。
> 
>https://support.google.com/analytics/answer/9358801?hl=ja
XMLを正規表現で抽出してたけど、全部fast-xml-parserでよかった件

## はじめに
この春から✨ピカピカ社会人✨の あなP（[@annap_ms](https://x.com/annap_ms)）です。

いきなりですが、XML（Extensible Markup Language）について、ファイルの設定情報やデータ構造を扱う際に、特定のタグや属性だけを取り出したい場面はよくあります。

何も知らなかった私は、正規表現を使ってタグや属性を抽出するコードを書いていましたが、ネストが深くなったり、属性があったりなかったりと、処理がだんだん面倒に……。

「いや、こんなものに専用ライブラリがないわけないよな？」とふと我に返って調べてみたところ、あっさりと便利なライブラリ `fast-xml-parser` に出会いました。

ちゃんと調べようという戒めの意味も込めて、備忘録として残しておきます。

## 正規表現でXMLを処理していたときの話
例えば、以下のようなシンプルな構造のXMLから、`user` タグの `id` 属性と、  
その中に含まれる `name` タグの中身を抽出したい場合を考えます。

``` XML
<user id="001">
  <name>Alice</name>
</user>
```
このXMLに対して、JavaScriptで正規表現を使って情報を抜き出すコードは次のようになります。
``` js
const xml = `<user id="001"><name>Alice</name></user>`;
const regex = /<user\s+id="(.*?)">.*?<name>(.*?)<\/name><\/user>/s;
const result = regex.exec(xml);

console.log(result[1]); // "001"
console.log(result[2]); // "Alice"
```
このように、シンプルな構造であれば正規表現でも必要な値を取得することができます。

ただし、改行や空白があったり、属性の順序が変わったり、タグの中に別の要素がネストしていたりすると、正規表現だけで正確にマッチさせるのが難しくなります。

また、タグや属性が省略されるケースでは、想定外のデータが取り出されてしまうこともあり、正規表現だけに頼る実装には限界を感じる場面もありました。

## `fast-xml-parser` で同じことをやってみた
`fast-xml-parser` は、XML を JavaScript オブジェクトや JSON に変換できる軽量なライブラリです。  逆に、オブジェクトから XML を生成することもできます。  
バリデーション機能もあり、ESM やブラウザにも対応しています。

https://www.npmjs.com/package/fast-xml-parser

まずはパッケージをインストールしておきます。

```bash
npm install fast-xml-parser
```

先ほのXMLに対して `fast-xml-parser` を使って情報を抽出する場合は、次のようなコードになります。

```js
const { XMLParser } = require("fast-xml-parser");

const xml = `<user id="001"><name>Alice</name></user>`;

const parser = new XMLParser({
  ignoreAttributes: false,
  attributeNamePrefix: "@_",
});

const parsed = parser.parse(xml);

console.log(parsed.user["@_id"]);  // "001"
console.log(parsed.user.name);    // "Alice"
```
このように、`parse` メソッドを使えば XML 全体をオブジェクトに変換できます。  
属性も含めてパースしたい場合は、`ignoreAttributes: false` を指定する必要があります。  
この設定により、属性は `@_` というプレフィックス付きでアクセスできるようになり、  
タグのネスト構造もそのままオブジェクトとして扱うことができます。

正規表現では、タグや属性の存在・順序を細かく気にしながら処理を組み立てる必要がありましたが、`fast-xml-parser` を使えば、そのような細かい制御を意識せずに、シンプルに値を取り出すことができます。

なお、`fast-xml-parser` では他にもさまざまな設定が可能で、  
タグや属性の値を自動で数値や真偽値に変換する `parseTagValue` や `parseAttributeValue`、  
空のタグの扱いを調整する `suppressEmptyNode` など、用途に応じて柔軟にカスタマイズできます。

## さいごに
つい「正規表現でなんとかできそう」と思ってしまいがちな？ XML 処理ですが、構造が複雑になると、思った以上に扱いづらくなっていきます。

今回のように「専用のライブラリがあるのでは？」と立ち止まって調べてみるだけで、ぐっとシンプルに問題が解決できることもあります。

私と同じように、ちょっと遠回りしている方の助けになればうれしいです。
レビューする側で気をつけてきたことの雑記

### はじめに

これまでレビューをする側（レビュアー）としてやってきた工夫を整理します。

ゴチャゴチャになりそうなので、レビューを受ける側（レビューイ）としての工夫は別記事として書きました。良ければそちらもご参照ください。

https://qiita.com/fujithuro/items/a01c4ed3b26f8c0fb365

プログラム等のレビューに限らず、資料作成などにも通じるものとして書いているつもりですが、半ば無意識にプログラミングを前提にしている項目もあるかも。


### レビューする側として

#### コメントにラベルを付ける

コメントの頭に `must` などのラベルを付けることで、温度感やニュアンスを明確に伝えられます。下記の記事を参考にしました。

https://zenn.dev/hacobell_dev/articles/code-review-comment-prefix

使っていたラベルは以下のとおりです。先ほどの記事から引用しています。

| ラベル | 意味 | 意図 |
|--------|------|------|
| Q | 質問 (Question) | 質問。相手は回答が必要 |
| FYI | 参考まで (For your information) | 参考までに共有。アクションは不要（確認事項を残す場合などに使用） |
| NITS | あら捜し (Nitpick) | 重箱の隅をつつく提案。無視しても良い |
| NR | お手すきで (No rush) | 今やらなくて良いが、将来的には解決したい提案。タスク化や修正を検討 |
| IMO | 提案 (In my opinion) | 個人的な見解や、軽微な提案。タスク化や修正を検討 |
| MUST | 必須 (Must) | これを直さないと承認できない。修正を検討 |

##### ラベルのメリット

ラベルの目的は、最初に少し触れた通り、コメントの温度感やニュアンスを伝えることにあります。
これがないと、コメントを受け取った側（レビューイ）はその内容が（レビュアーにとって）どれくらい重要なのかが分からず、**すべてを対応必須と受け取ってしまう**可能性があります。

またレビュアー側としても、「そこまで重要ではないけれど、一応伝えておきたい」という程度の軽いコメントをしたいだけなのに、それが重大な指摘だと受け取られてしまうのは本意ではありません。

コメントにラベルを付けることで、こういったすれ違いを防ぎ、スムーズにやりとりできるようになります。


##### ラベルの注意点

（この項目はレビューを受ける側も気をつけるべき内容を含みます）

ラベルは**あくまでレビューしている側の温度感や認識**です。当たり前ですが、それが絶対的な正解なんてことはありません。

レビューを受ける側、つまりレビューイにも、当然ながら実装者としての意見や見解があるはずです。
仮にレビュアーが`must`でコメントしたものでも、内容に納得できなければ、反論が返ってくることもあるでしょう。
レビュアーとレビューイの立場は対等であり、**コメントもラベルも命令ではない**ので、あくまで意見交換に付与されている温度感でしかないという前提は、お互いに理解しておく必要があります。


#### 設計をひっくり返すようなことを言わない

レビューの場面で、「この設計ではなく、こういう風に変えた方がいいのでは？」のような指摘をしたくなることがあります。もちろん、それが重要なら提案していいのですが、忘れてはいけないのは**レビューの構造を崩すような変更提案は避けるべき**という点です。

どういうことかと言うと、例えば、レビュアーの提案通りに実装者がコードを大幅に書き直した場合、そのコードは事実上レビュアーが設計したものになります。そしてそれがそのままマージされれば、レビュアーの設計が第三者の目を通らないままリポジトリに入ってしまいます。

こうした場合は、設計変更の議論を別の場（チケット、ドキュメント、別PRなど）に切り出すか、あるいは追加で他のレビュアーに加わってもらうなど、「ちゃんと（別の人に）レビューされた設計にする」ことが必要です。


#### 数年後、前提知識を忘れた状態でも理解できる変更かを考える

レビューする時点では、その不具合修正や機能追加の背景をよく知っており、それが前提知識として頭の中にあるので、コードの変更意図を理解しやすい。

数年経って、そういった前提をすっかり忘れた状態で改めて見ても、変更後のコードは理解できそうか？（保守できそうか？）という観点が必要。
この観点がないと、「前提を知らないと読めないコード」がリポジトリに入ってくることになります。


#### 好き嫌いで指摘しない

自分の好き嫌いの域を出ない指摘、つまり客観的なメリットやデメリットを言えない指摘はあまり望ましくありません。

さっきのラベルで言うと`nits`（あら探し）などの優先度低めなラベルをつけて「個人的にはこういう書き方が好みですが、このままでもOKです」みたいなコメントをする（主導権は相手に握らせる）程度ならOK、くらいでしょうか。
[Cursor]@gitがうまく動いてくれないからgit diffで出力して読み込ませた

## はじめに
最近、Cursorを使って作業ブランチの変更点をもとに、プルリクエストのメッセージを生成しようとしたとき、ちょっとしたハマりポイントに遭遇しました。

今回は、Cursorの`@Git`がうまく動作しない場合の対処法と、それに代わる簡単な回避策をご紹介します。

## Cursorで`@Git`が読み込めない？
Cursorには`@Git`という便利な機能があります。これをチャットで呼び出すと、現在の変更差分やコミット履歴などをAIが読み取ってくれるはず…なのですが、

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3680973/51089ce7-e291-4ab9-bcac-055e57eb11bf.png)
```
申し訳ありませんが、コミット内容を確認するためのツールや情報が提供されていないため、
現在のコミット内容を確認することができません。
```
どうやら、うまく動作しないことがあるようです。

## やったこと：git diffで差分ファイルを渡す
そこで、代わりに以下のコマンドを使って、mainブランチとの差分をファイルに書き出すことにしました。

```bash
git diff main..{作業ブランチ名} > branch_changes.diff
```

このコマンドで、現在の作業ブランチとmainブランチとの差分を branch_changes.diff というファイルに保存できます。

その後、このbranch_changes.diffファイルをCursorにファイルとして読み込ませたところ、差分をしっかりと認識してくれました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3680973/e25d7368-e706-4488-a79c-c27edcda7e1a.png)


## プルリクエストのメッセージ生成もOK！
差分ファイルを読み込ませた後に、

この変更内容に基づいて、プルリクエストの説明文を作ってください

といった形でAIに依頼すると、的確な要約と説明文を自動で生成してくれました。とても便利です！

## おまけ：alias登録でさらに簡単に
毎回 `git diff main..{作業ブランチ}` を書くのが面倒な場合は、以下のようにGitエイリアスを設定しておくと時短になります。

```bash
git config --global alias.diff-main '!f() { git diff main..$1 > branch_changes.diff; }; f'
```

使い方：

```bash
git diff-main {作業ブランチ}
```

これで、branch_changes.diffが自動生成されます。


## まとめ
Cursorで`@Git`がうまく使えない場合でも、`git diff`とファイル読み込みを組み合わせることで、ほぼ同じことが実現できます。手間は少しかかりますが、確実に動作する方法として覚えておくと便利です。

以上、CursorでGit差分を読み込ませたいけど@Gitが動かないときの対処法でした。似たような状況に遭遇した方の参考になれば幸いです！

## 参考
👇本当に助かりました！ありがとうございました！

https://qiita.com/tks_00/items/3c924e0ecd1bdc1cf178
住所から都道府県を抽出する

# はじめに
Pythonで正規表現を使用して住所から都道府県を抽出します

# 環境
```terminal
~ % python -V
Python 3.11.9
```

pandasを使用しています
```terminal
~ % pip list | grep pandas
pandas             2.2.3
```

# 正規表現
```
(東京都|北海道|(?:京都|大阪)府|.{2,3}県)
```

# 準備
住所の一覧を用意します
※ChatGPTで適当に作成しています

```text:address.csv
東京都新宿区西新宿2-8-1
北海道札幌市中央区北1条西2丁目
大阪府大阪市北区梅田3丁目1-1
京都府京都市中京区寺町通御池下ル下本能寺前町522
神奈川県横浜市西区みなとみらい2-3-5
愛知県名古屋市中区栄3丁目16-1
福岡県福岡市博多区博多駅中央街1-1
兵庫県神戸市中央区加納町6丁目5-1
宮城県仙台市青葉区中央1丁目1-1
広島県広島市中区基町6-27
```

# コード
```python:main.py
import pandas as pd
import re
pd.set_option('display.unicode.east_asian_width', True)

def extract(address):
    m = re.match('(東京都|北海道|(?:京都|大阪)府|.{2,3}県)', address)
    return m.group()

df = pd.read_csv('address.csv', header=None, names=['address'])
df['pref']=df['address'].apply(extract)
print(df)
```

## 結果
![711f75d7af2da1019e45.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4001795/64ceb797-c8d1-47af-8cf2-89ab41cffcd5.png)
Amazon OpenSearchとは？



**Amazon OpenSearch**は、AWSが提供する**オープンソース検索・分析エンジン**です。大量のデータに対して**全文検索**や**ログ分析、可視化**を行うことができ、リアルタイムでのデータ探索に適しています。もともとは「Elasticsearch」のフォーク（派生）で、OpenSearchプロジェクトとしてAWSが中心となって開発・運用されています。

### 主な特徴
- **フルマネージドサービス**：サーバーのセットアップや管理不要
- **スケーラブル**：大規模データにも対応可能
- **可視化ツール「OpenSearch Dashboards」**付き
- **セキュリティ・認証機能が充実**（IAM連携、VPC内接続など）
- **ログやメトリクスの分析用途でCloudWatchやKinesisと連携可能**

### よく使われるユースケース
- アプリ内検索
- サーバーログの集中管理・分析
- セキュリティイベントのモニタリング
- ビジネスデータの可視化ダッシュボード

---
#0094（2025/04/05） オープン・クローズドの原則（Open/Closed Principle: OCP）

# オープン・クローズドの原則（Open/Closed Principle: OCP）

オープン・クローズドの原則（Open/Closed Principle: OCP）は、ロバート・C・マーチンによって提唱されたSOLID原則の一つで、「ソフトウェアの要素は拡張に対して開いており、変更に対して閉じているべき」という考え方です。この記事では、上級プログラマー向けに、PythonとTypeScriptを用いてOCPが守られている例・守られていない例を具体的に示します。

## オープン・クローズドの原則（OCP）の本質

OCPの本質は、既存コードを変更せずに新しい機能を追加できるように設計することです。これにより、変更による影響範囲を抑え、柔軟な拡張性を確保します。

---

## Pythonによる例

### ❌ OCPが守られていない例

```python
class DiscountCalculator:
    def calculate_discount(self, customer_type, total):
        if customer_type == 'regular':
            return total * 0.1
        elif customer_type == 'premium':
            return total * 0.2
        # 新しい顧客タイプを追加する場合、このクラスを直接修正する必要がある
```

新しい顧客タイプが追加されるたびに、このクラスを修正する必要があるため、変更に弱い構造となっています。

### ✅ OCPが守られている例

```python
from abc import ABC, abstractmethod

class DiscountStrategy(ABC):
    @abstractmethod
    def apply_discount(self, total):
        pass

class RegularDiscount(DiscountStrategy):
    def apply_discount(self, total):
        return total * 0.1

class PremiumDiscount(DiscountStrategy):
    def apply_discount(self, total):
        return total * 0.2

class DiscountCalculator:
    def __init__(self, strategy: DiscountStrategy):
        self.strategy = strategy

    def calculate_discount(self, total):
        return self.strategy.apply_discount(total)
```

新しい顧客タイプが増えた際には新しいクラスを追加するだけで済み、既存コードへの変更が不要です。

---

## TypeScriptによる例

### ❌ OCPが守られていない例

```typescript
class PaymentProcessor {
    processPayment(method: string, amount: number) {
        if (method === 'creditCard') {
            console.log(`Processing credit card payment of ${amount}`);
        } else if (method === 'paypal') {
            console.log(`Processing PayPal payment of ${amount}`);
        }
        // 新しい支払い方法を追加する場合、このクラスを直接修正する必要がある
    }
}
```

支払い方法の追加があった際、このクラスを修正する必要があり、変更の影響を受けやすい設計です。

### ✅ OCPが守られている例

```typescript
interface PaymentMethod {
    pay(amount: number): void;
}

class CreditCardPayment implements PaymentMethod {
    pay(amount: number): void {
        console.log(`Processing credit card payment of ${amount}`);
    }
}

class PayPalPayment implements PaymentMethod {
    pay(amount: number): void {
        console.log(`Processing PayPal payment of ${amount}`);
    }
}

class PaymentProcessor {
    constructor(private paymentMethod: PaymentMethod) {}

    process(amount: number): void {
        this.paymentMethod.pay(amount);
    }
}
```

新しい支払い方法が必要になった場合、新しいクラスを追加するだけで済み、既存の処理に影響を与えません。

---

## オープン・クローズドの原則のopen/closedについて

### Openの意味：「拡張に対して開かれている」
- 新しい機能や振る舞いを追加したい時に、既存のコードを変更せずに新しいコードを追加することで機能拡張できることを指します。

### Openであるべき点（拡張可能な部分）
- 新しいクラスの追加（例：新しい割引方法、新しい決済方法）
- インターフェースを利用した新しい実装クラスの導入

### Closedの意味：「変更に対して閉じている」
- 一度書いたコードを、既存機能を変更することなく安定的に保ち、修正の影響を最小限に抑えることを指します。

### Closedであるべき点（変更されない部分）
- 既存のクラスや関数の内部実装
- すでに動作確認済みの重要なロジック


---

## まとめ

オープン・クローズドの原則を守ることで、ソフトウェアは変更に強く、拡張が容易な構造になります。上級プログラマーとしては、この原則を実践しつつも、過度な抽象化を避け、現実的かつ保守性の高いコード設計を意識することが重要です。
Managed Service for Apache Flinkとは？


近年、IoTやSNS、アプリケーションログなど、リアルタイムで流れ込むデータを活用するニーズが急速に高まっています。そんな中で注目されているのが、**Apache Flink（アパッチ フリンク）**です。AWSではこのFlinkをマネージドサービスとして提供しており、それが**Managed Service for Apache Flink**です。

この記事では、Managed Service for Apache Flinkの概要や特徴、活用シーンについてわかりやすく解説していきます。

---

## ✅ Apache Flinkとは？

Apache Flinkは、**分散型のストリーム処理エンジン**で、リアルタイムに大量のデータを処理するためのオープンソースのフレームワークです。次のような特徴があります：

- ミリ秒単位での**リアルタイム処理**が可能
- 高速でスケーラブルな**分散アーキテクチャ**
- ストリームとバッチ両方の処理に対応（ハイブリッド）
- 複雑なイベント処理（CEP）も可能

---

## ✅ Managed Service for Apache Flinkとは？

AWSの**Managed Service for Apache Flink（旧：Amazon Kinesis Data Analytics）**は、Flinkの機能をAWS上で**マネージドで簡単に使える**サービスです。

### 🎯 主な特徴

| 機能 | 説明 |
|------|------|
| **完全マネージド** | クラスターのプロビジョニングやスケーリング、メンテナンスは不要 |
| **高可用性** | 複数AZ（アベイラビリティゾーン）で自動的に冗長化 |
| **Flinkアプリのデプロイが簡単** | JARファイルやPythonスクリプトをアップロードするだけでOK |
| **統合が容易** | Kinesis Data Streams, Kafka, S3, DynamoDB, Redshift などとシームレスに連携 |
| **オートスケーリング対応** | トラフィックに応じてリソースを自動調整 |

---

## ✅ どんな場面で使われるの？

以下のような**リアルタイムデータ活用**に強力な力を発揮します：

### 📈 IoTデバイスのリアルタイム監視  
センサーデータをリアルタイムで分析し、異常検知やアラートに活用。

### 🔄 アプリログのリアルタイム分析  
Webアプリやモバイルアプリの利用状況をその場で分析。A/BテストやUX改善に貢献。

### 💳 不正取引の検出  
金融取引のデータをリアルタイムで分析し、不審なパターンを検知。

### 🛒 レコメンデーションエンジン  
ユーザーの行動に応じて、即時にパーソナライズされた商品提案を行う。

---

## ✅ 構成イメージ（簡略図）

```
Kinesis / Kafka（入力）
     ↓
Managed Service for Apache Flink（処理）
     ↓
S3 / Redshift / DynamoDB / Lambda（出力）
```

---

## ✅ 料金について

Managed Service for Apache Flinkは**使用したリソースに応じた従量課金制**です。

- **アプリケーションの実行時間**
- **プロビジョンド並列数（Parallelism）**
- **ストレージ使用量（保存されるチェックポイント）**

などに応じて料金が発生します。

---

## ✅ まとめ

Managed Service for Apache Flinkは、リアルタイムデータ処理を手軽に始めたい企業やエンジニアにとって、非常に魅力的なサービスです。従来のバッチ処理だけでなく、ストリーミング処理を活用することで、より迅速な意思決定やサービス改善が可能になります。

AWSの他サービスとも統合しやすく、今後の**データドリブンなシステム開発**には欠かせない存在といえるでしょう。

---
TanStackRouterを用いたルーティングでパスパラメータをコンテキストとして利用する

`/{$groupId}/top`のようにパスパラメータを受け取りコンポーネントを表示するという想定で構築してみます。

## GroupPageTop

```
import { useParams } from "@tanstack/react-router"

const GroupPage = () => {
  const {group } = useParams({strict: false})
  
  return (
    <div>
      <h2>{group} GroupPage</h2>
      <p>Welcome to the {group} top Page!</p>
    </div>
  )
}

export default GroupPage
```

## rooter.tsx

```
import { createRootRoute, createRoute, createRouter, Outlet } from "@tanstack/react-router";
import Home from "./pages/Home";
import GroupPage from "./pages/GroupPage";

const rootRoute = createRootRoute({
  component: () => (
    <div>
      <h1>My App</h1>
      <nav>
        <a href="/">Home</a> |
        <a href="/teamA/top">Team A</a> |
        <a href="/teamB/top">Team B</a>
      </nav>
      <hr />
      <Outlet />
    </div>
  ),
});

const homeRoute = createRoute({ getParentRoute: () => rootRoute, path: "/", component: Home });

const groupRoute = createRoute({
  getParentRoute: () => rootRoute,
  path: "/$group/top",
  component: GroupPage
})

export const router = createRouter(
  {
    routeTree: rootRoute.addChildren(
      [
        homeRoute, 
        groupRoute
      ]
    ) 
  }
);

```

## Home.tsx

```
const Home = () => <div><h2>Home</h2><p>Welcome to the homepage!</p></div>;
export default Home;
```

## 動作確認
`http://localhost:5173/teamA/top`
にアクセスします。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2779836/d95fb8ce-ada1-4bcc-b93a-1c26cfe1632d.png)

OKですね。
パスパラメータからコンテキストを取得できたので、`Jotai`や`Context`に代入しておくことで使いまわせます。
C#でGeminiを使うサンプルコード

# 準備
1. APIキーを作る
2. Microsoft.SemanticKernel.Connectors.Google をNugetする．
プレリリースかもしれないのを注意しよう
# 注意事項
APIキーは直書きしないほうがいいよ
settingやchatServiceにアクセスするときは警告が出るかもしれないので，
```
#pragma warning disable SKEXP0070
#pragma warning restore SKEXP0070
``` 
で囲ってね
# コード
## クラス定義例
```c#
using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.ChatCompletion;
using Microsoft.SemanticKernel.Connectors.Google;
using System.Diagnostics;
public static class Gemini1p5f8b
{
    static string geminiModel = "gemini-1.5-flash-8b"; // コスパ良いよこれ
    static string gemini_api_key = "事前につくってね";
#pragma warning disable SKEXP0070
    public static GoogleAIGeminiChatCompletionService chatService = new GoogleAIGeminiChatCompletionService(modelId: geminiModel, apiKey: gemini_api_key);
    public static GeminiPromptExecutionSettings settings = new()
    {
        MaxTokens = 50,
        Temperature = 0.7
    };
#pragma warning restore SKEXP0070
    public static async Task<ChatMessageContent> Genext(this ChatHistory hist) => await chatService.GetChatMessageContentAsync(hist, settings);
}
```
## 例の使用例
```c#
public static async Task Test()
{
    var chatHistory = new ChatHistory();
    chatHistory.AddUserMessage(new ChatMessageContentItemCollection
    {
         new TextContent("この画像を基に，お話を創作してください．"),
         new ImageContent(File.ReadAllBytes(@"C:\Users\omae\Desktop\test.png"),"image/png")
    });
    var reply = await chatHistory.Genext();
    chatHistory.Add(reply);

    foreach(var hist in chatHistory)
    {
        Debug.Print($"{hist.Role}: {hist.Content}");
    }
}
```
## リトライ処理追加版(おまけ)
```c#
    public static async Task<ChatMessageContent> GenextWithRetry(this ChatHistory hist, TimeSpan timeout)
    {
        const int retryMax = 4;
        int tryCount = 0;
        while (tryCount++ < retryMax)
        {
            using (var cts = new CancellationTokenSource(timeout))
            {
                try
                {
                    ChatMessageContent generated = await Genext(hist).WaitAsync(cts.Token);
                    ResultCheck(generated);
                    return generated;
                }
                catch (OperationCanceledException)
                {
                    Debug.Print("Timeout occurred, retrying...");
                }
                catch (Exception ex)
                {
                    Debug.Print($"Error occurred: {ex.Message}, retrying...");
                }
            }
#pragma warning disable SKEXP0070
            settings.Temperature = Random.Shared.NextSingle() * 0.5f + 0.6f;
#pragma warning restore SKEXP0070
        }
        throw new Exception("自主的リトライ上限に達しました");
        void ResultCheck(ChatMessageContent result)
        {
            if (result.Content == "") throw new Exception("何も生成してません");
        }
    }
```
# 参考にさせて頂いた記事
https://qiita.com/SoundOrion/items/dda1ed56b96cab0db00b
[iOS] SnapKitとThenについて

## ⭐️ はじめに
SnapKitとThenというライブラリーはコードを簡潔に、効率に記述するため使います。
本記事ではこのライブラリーたちの使い方とどんな形に簡潔になるか見てみます。

## 👨‍💻 詳細

### SnapKit

まずは SnapKit について見ていきましょう。

SnapKitを使うためは以下の環境が必要です。

- iOS 12.0+ / Mac OS X 10.13+ / tvOS 10.0+
- Xcode 10.0+
- Swift 4.0+

 SnapKitを使わないと

```Swift
let box = UIView()
box.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(box)

NSLayoutConstraint.activate([
    box.widthAnchor.constraint(equalToConstant: 50),
    box.heightAnchor.constraint(equalToConstant: 50),
    box.centerXAnchor.constraint(equalTo: view.centerXAnchor),
    box.centerYAnchor.constraint(equalTo: view.centerYAnchor)
])
```
こんな感じで記述します。
これだけみたら、良いんじゃないかと思うかもしれないですが、


SnapKitを使うと（SnapKitのGithubにある例文）

```Swift
let box = UIView()
view.addSubview(box)

box.snp.makeConstraints { (make) -> Void in
    make.width.height.equalTo(50)
    make.center.equalTo(self.view)
}
```
こんなに簡潔になります。

上のコードブロックにある、
make はクロージャの引数であり、省略せずに書くこともできますが、Swift では $0 のように Shorthand Argument Names（短縮引数名） を使ってもっと簡潔に記述できます。

```Swift
let box = UIView()
view.addSubview(box)

box.snp.makeConstraints {
    $0.width.height.equalTo(50)
    $0.center.equalTo(self.view)
}
```

最初のコードブロックに比べて確かに簡潔になっているのがわかりますよね？



### Then

次はThenにつしてみてみましょう。

Thenを使うためには以下の環境が必要です。
- Swift 5.0+

Thenを使うと、
```Swift
let label: UILabel = {
  let label = UILabel()
  label.textAlignment = .center
  label.textColor = .black
  label.text = "Hello, World!"
  return label
}()
```
上のコードが
```Swift
let label = UILabel().then {
  $0.textAlignment = .center
  $0.textColor = .black
  $0.text = "Hello, World!"
}
```
こんなに変わります。

見ればまあまあだと思うかもしれないけど、
実際に使って見ると本当にコード記述の効率性も高まり、すっきりとした印象を受けることができます。

## 💬 まとめ 

SnapKitとThenはセットで使用すると非常に便利なライブラリだと思います。
UIKitでUIコードを記述する際には、ぜひ使ってみてください。
ホームディレクトリも管理者権限もないからGit Pullできない😢

みなさんこんにちは．初記事ですので温かい目で見ていただけるとありがたいです．基本ChatGPT先生に教えてもらった内容を状況を合わせてまとめているだけです．めったにない状況だと思いますが同じ状況で困っている方に届けばと思い記事を書きました．

# TL;DR
- SSHが参照するファイルはシステムのユーザー情報(たとえば`/etc/passwd`などに書かれた情報)を元にする
- Gitが呼び出すSSHコマンドは環境変数の`GIT_SSH_COMMAND`や`git config core.sshCommand '...'`のようなコマンドで変更可能

# 状況

みなさんは次のような状況に陥ったことはないですか？
- サーバーなどで管理者権限がない
- なんやかんやでシステムが指定するホームディレクトリがない
- SSHしたいけど，`~/.ssh`に書き込み権限がないから怒られる

はい，私が今さっきまで陥っていた状況です．このような状況下ではどうすれば最低限SSHが実行できるのかについて説明していきます．

以下，なぜそのような状況になったのかですが、ぐちっぽくなってしまったので読み飛ばして大丈夫です．
現在研究室のLinuxサーバーでは先生がホームディレクトリにマウントされている分散ファイルシステムの移行作業を行ってくださっています．そのおかげで現在一時的にホームディレクトリにアクセスできません．研究室のみんなが使用するので管理者権限も与えられておらず，HOMEディレクトリを変更することもできません．
各サーバー上に自由にアクセス可能なディレクトリが用意されているのでそちらで作業は継続可能なのですが，手元のパソコンで書いていたコードをGitHub経由でアップデートしようとしたときに問題が起こりました．
`git pull`しようとするとSSHはどうしても標準の`/home/user/.ssh`を参照しようとしてしまいコマンドが失敗してしまったのです．`$HOME`を変えても頑なに`/home/user/.ssh`以下のファイルを編集，参照しようとします．

# 原因
ChatGPT先生によるとどうやら，SSHコマンドが参照するファイルの位置はシステムが指定するホームディレクトリを元に決められるらしいです．例えば`/etc/passwd`とかでして，UNIXのプログラム上では`getpwuid`関数の結果に基づき決定されるそうです．そのため，`$HOME`を変えてもSSHが参照するフォルダは何も変更されません．

# 準備
ここではGitHubへアクセスするための鍵はすでに用意しているものとします．

# 解決策
とりあえず`git pull`だけしたいのであればSSHの3つのファイルの参照先だけ変えれば大丈夫です．
- `~/.ssh/known_hosts`
- `~/.ssh/config`
- 秘密鍵

自分は新しい擬似的なホームディレクトリ（ここでは`/new/home`としておきます）を作ってその下に`.ssh`を作ってそれぞれのファイルを配置しました．

Gitが呼び出すSSHコマンドは次の2通りで変更可能のようです．
- 環境変数の`GIT_SSH_COMMAND`
- `git config core.sshCommand '...'`のというコマンド

そのため，1つ目の方法を使うなら次のようになります．
```sh
export GIT_SSH_COMMAND='ssh \
  -o UserKnownHostsFile=/new/home/.ssh/known_hosts \
  -F /new/home/.ssh/config \
  -i /new/home/.ssh/github'
```
ただし，`github`はGitHubに登録した鍵の秘密鍵のファイルです．

これでホームディレクトリも管理者権限もなくても`git pull`できるようになりました！
引き続きバリバリ働いていきましょう！
API GatewayのCanaryリリースとは？安全に本番リリースするためのベストプラクティス

本番環境への新機能リリースは、常にリスクを伴います。  
「もしバグがあったら？」「すぐにロールバックできる？」――そんな不安を解消する手段の一つが**Canaryリリース**です。

AWS API GatewayでもこのCanaryリリースが可能で、リスクを最小限に抑えながら新しいバージョンを段階的に展開することができます。

---

## 🐤 Canaryリリースとは？

Canary（カナリア）リリースとは、**新しいバージョンのAPIやコードを全体に反映させるのではなく、まずは一部のユーザーだけに提供するリリース手法**です。

鉄道や鉱山で「カナリア」を使って有毒ガスの検知をしていたように、異常があればすぐに検知してロールバックできるという安全性の高い戦略です。

---

## 🚀 API GatewayにおけるCanaryリリースの仕組み

API Gatewayでは、**ステージごとにCanary設定**を追加することができます。これにより、次のような設定が可能になります：

### 設定できる内容
- **トラフィックの振り分け割合**（例：10%だけ新しいバージョンに）
- **ステージ変数の上書き**
- **X-RayトレースやCloudWatchログの個別記録**

### 典型的なユースケース
- Lambdaとの統合で新しいエイリアス（`prod`, `v2`など）に向けたデプロイ
- 本番ステージにおいて段階的に切り替えるデプロイ戦略の一部として

---

## 🛠️ 設定方法（Lambda統合の例）

1. Lambda関数にエイリアスを作成（例：`v1`と`v2`）
2. API Gatewayの**ステージにCanary設定**を追加
3. トラフィックの10%だけ`v2`にルーティングする

```json
{
  "canarySettings": {
    "percentTraffic": 0.1,
    "stageVariableOverrides": {
      "lambdaAlias": "v2"
    },
    "useStageCache": false
  }
}
```

上記では、Lambdaのエイリアスを切り替えることで、Canary先のロジックを変えることができます。

---

## 🧠 Canaryリリースのメリット

- ✅ **安全なリリース**：万一問題が発生しても影響範囲は限定的
- ✅ **素早いロールバック**：トラフィック割合を0%に戻せば即座に旧バージョンに切り替え可能
- ✅ **段階的な展開**：ユーザーへの影響をモニタリングしながら安心して広げていける

---

## ⚠️ 注意点とベストプラクティス

- **監視を必ず設定する**：CloudWatch LogsやMetrics、X-Rayなどで挙動を把握しましょう
- **新旧のバージョンで互換性を持たせる**：ステージ変数やエイリアスの変更で不整合が出ないよう注意
- **Canary実行期間は明確に**：いつ本番100%に切り替えるのか、計画を立てておく

---

## 📝 まとめ

Canaryリリースは、APIの新しいバージョンを安全に展開するための強力な手段です。  
API GatewayとLambdaを組み合わせることで、設定も簡単に行え、CloudWatchなどを活用すれば運用もスムーズです。

「いきなり本番にデプロイするのは怖い…」という方は、ぜひCanaryリリースを取り入れてみてください！
退職エントリってやつを書いていこうかと思います。 （簡単に）

この記事は退職エントリを記載しており、
関係ない人には関係が無い話となっております。
表の事情だけ書いておこうと思います。
裏事情は色々と出さない方がいいしね。


# なぜ退職するか
25年3月末退職となりますが、
退職理由は——

単にお金の切れ目が縁の切れ目。ですかね。
どこぞの事故起こした会社のコンサルやってた人の言葉だと、
「お金の失敗が縁の切れ目」でしょうか。

給料遅延が主な原因で、
通算3回 → スリーアウト：チェンジ。
というわけです。

元から会社の金銭周りがちょっと歪でもあった気もしますが。
2024年のGWにマータやらかしやがって、成長せんな社長は。
~~遅延におちいった理由は中小が倒産している理由の一つである~~


他の理由は、会社との人間関係ですね。
お互いに信頼・信用ございません事よ。

元自社の人とはもう上司意外とあまりやり取り無かった気もしますね。
そのため、元上司からの電話で、
上司であるメガネ君曰く
「遊んでいる」
だそうです。
流石にどうでもよくなった感有です。

え？あれか？
会社のストラテジーが無いけど、儲かるように自分のスキル向上して単価上げろってことだろ？
そういう意味合いでは遊んでるのかもしれなかったな。
ただし、その上司、何もしないから。
~~怒鳴ったり野郎の行動から、元から信用もしてなかったんだが。もうちょっと頼れる上司で居ていただけたのならねぇーー？~~

そもそも、コロナなかったらもうとっくの昔に辞めてるわよ？

というわけで、色々と割愛するけど、
会社の運営にNOを突き付けるってことで退職を選択です。

# 勤めていた年数

15年以上20年未満

# 退職までの流れ
・24年度3Q に上司に退職の意向を話す。
・2か月放置される (ノД`)・゜・。 いやええねんけど、良くないけど？！
※契約している会社の間に中間会社が1社あるけど、
※そこには上司と退職についての話し合い中というステータスになっていると説明を
※行っていたそうであるが、そんなもんない。
※というか、メガネ君先に辞めちゃったんだけどどうなってんのそれ？
※中間会社の人に行ったら困惑してたぞおい？

・24年度4Qに社長が中間会社の人に辞める話をしに行く。
  → 私が今の所抜けると困るという事で直接やり取りすることになる。
    ※ なお、3Qの間に中間会社の人と私で話し合いはしていたりする。
       だって、対応遅いの知ってるからね。
       11月に元自社からアプローチ有れば見方は変わったかもしれないがそんなもんない。
       （まぁ何も言わない所、人が辞めるのはすでに諦めていたのだろう）

・話し合い進めるわーって社長にメールしたけど返事は無し。
どうでもいいけどさ。
電話してもらえるとでも思っているのだろうか。

で、3末で終わりと。
会社に挨拶行ってないけどまぁいいでしょう。

私の場合は最後は会わずに別れる。
毎回そういう縁ですので。

電話口だが、色々と対応してくださっていた取締役の人とは最後に電話でき、
話が出来たので良しとしたいかと思います。

以上

# 次の仕事は？
契約先 ｜ 中間会社 ｜ 元自社
が
契約先 ｜ 中間会社 ｜ 業務委託（個人事業主）
となります。

『ま、IT系の仕事してるし？フリーランスもやってみるのもいいよねー』
と軽く始めました。
4月に入って書類回りがかなりてんてこ舞いです(ノД`)・゜・。

（後やらないと行けないの、何だよと聞きたいわ本当）

そんなかんじとなり、 裏は誰かに分投げてみるかな

# 最後に

会社が倒産するかは知らんが、
おそらくゾンビ企業で行くのだろうね。
3末までの給料と、退職金が振り込まれたら別に構わないんだけれど
その前に潰れたらどうしようか。

盛大に笑いながら花火でも打ち上げてやろうか？
#24. 最近学んだことのお話

# はじめに
こんにちは、nayaaaaです！
最近はなかなか手を動かして学習する時間が取れなかったのですが、
その分、以前から読んでいた本や記事を通じて得た知識の中で、個人的に面白いと思った内容を、今回は記事としてまとめてみました。

テーマは以下の3つです。

- コンテナにroot権限を与えないことの重要性
- Falcoについて
- Terraformのファイル管理の面白さ

# コンテナにroot権限を与えないことの重要性
一般的にはコンテナビルド時に**root権限**は渡さないことが推奨されています。
その理由としては 「コンテナ内のプロセスが root 権限で実行されていると、万が一コンテナの脆弱性を突かれた際に、ホスト側のシステムまで影響を及ぼす可能性がある」というざっくりした認識でした。
しかし、**実際なぜコンテナホストが乗っ取られるのか？** とか **どうしてroot乗っ取られたらなんでも操作できるのか？** みたいな具体的なところってずっと想像ぐらいでの認識だったんですよね。

最近読んだ本ではこの点について詳しく書かれており、
コンテナ内では、root ユーザーとして動作している場合でも、capability（ケーパビリティ）という機能によって、ある程度の権限制限がかけられているそうです。
ただし、コンテナ内のrootユーザーがコンテナホストにアクセスできる状況にあると、そのユーザーはコンテナホスト側ではcapability（ケーパビリティ）がない、ただのroot 権限で操作できてしまうため、非常に危険とのことです。
Dockerでは明示的にユーザーを指定しない場合、デフォルトでrootユーザーとしてコンテナが実行される仕様になっています。
この状態で何が問題かというと、仮に悪意のある第三者がコンテナにアクセスした場合、

➀不正にコンテナアクセス⇒➁rootユーザーでコンテナホストにアクセス⇒➂コンテナホストを乗っ取り

というような最終的にコンテナホスト側のシステムをroot権限で操作できてしまうリスクがあるという点が危険とのことです。

読んでた本👇　今回の話は第3部に書いてます。

https://www.shoeisha.co.jp/book/detail/9784798182780


特にcapability（ケーパビリティ）の機能について勉強する良い機会になりました。
このようにKubernetesを使用しなくてもコンテナ技術を扱う方であれば読んでみると面白いと思います。

# Falcoについて
Falcoは、Linuxカーネルやコンテナランタイム（Docker、containerdなど）の振る舞いを監視することで、不審な活動を検知して警告することが可能です。機能としてはLinuxカーネルのシステムコールを監視して、ユーザが定義したルールに基づいて異常な挙動にフラグをたてることで警告を発報します。
また、実行中のコンテナ内の異常なプロセスや、コンテナエスケープ（コンテナからコンテナホストに侵入すること）の脆弱性を悪用したり、機密データにアクセスしようとする試みも検知することが可能です。

https://www.designet.co.jp/ossinfo/kubernetes/falco/



https://falco.org/docs/


※似たようなツールで**Tetragon**というのもあるそうですね。


https://gihyo.jp/article/2022/08/kubernetes-cloudnative-topics-01

Kubenetesの技術書を見ていたら必ずと言っていいほどセキュリティ関連ツールでFalcoが記述されており、以前から気になって記事やドキュメントを見ていたのですが、
ルールのカスタマイズも多岐にわたることで扱う難易度やセキュリティ面で考慮することが多く、エンジニア業界でもセキュリティエンジニアが少ないという傾向からも業務に取り入れるというのはとても難しいのかなと思いました。

でも、**名前がかっこいいですよね！！**
子供のころ「ブラッディマンデイ」という漫画原作のドラマが好きで、
話の内容的には天才ハッカーの主人公がそのハッキング能力を駆使してテロリスト集団に立ち向かっていくというお話なのですが、その主人公のコードネームが「ファルコン」なんですよね。
その名前が記憶に残っていて、今回も自然と紐づいて、より興味が湧きましたね。

https://www.tbs.co.jp/tbs-ch/item/d1618/


# Terraformのファイル管理の面白さ

こちらについては、現在まさに勉強中なのですが、Terraform のファイル管理において最近感じるのは、「どのような環境でTerraformを使うか」が非常に重要になってくる、ということです。

1つのファイルにすべてをまとめれば、別ファイルから変数やモジュールを呼び出す必要がなくなるのでシンプルではありますが、可読性が低くなったり、コード変更の影響範囲が大きくなってしまうというデメリットも考えられます。

一方で、ファイルを分割したり、モジュール化を取り入れたりすることで、

- チーム全体での開発スピードが上がる（一斉編集が可能になるため）
- 一斉編集時のコンフリクト（競合）を防げる
- 環境ごとに分けて効率的なデプロイが可能になる
　※各ディレクトリ単位で担当を分け、環境ごとに独立してデプロイできるため、チームでの作業効率が向上します。
- コード変更の影響範囲を機能単位に抑えることができる

といったメリットがあると思います。ただしその分、設計や設定の難易度は多少上がりますし、モジュールや設定がどこから呼び出されているかが把握しづらく、初見の人が構成を理解しにくいという可能性もあると感じています。

こういった構成の選び方には、本当にさまざまなパターンがあり、実際の業務環境や自分のプロジェクトに、どの構成が最適なのかを判断するのが非常に難しいのかなと感じています。
これは、プロダクトに導入する技術を選定する際にも通じる部分があるかなと思います。

# まとめ
最近はこういった知識については、本や記事を通じて何とか補ってはいるのですが、
実際に手を動かして試す機会が減ってしまっているので、今後はもう少し増やしていきたいなと感じています。
それでは、最後まで読んでいただきありがとうございました〜
Claudeデスクトップ ＋ MCP いろいろ勘違いした

## Claudeでファイルの読み書きができる？

私はAIにClaudeを有料プランで利用しています。最早これなくして生きていけない体になってしまい、年払いにしました。
最近、AIでファイルの読み書きができるみたいな話を見聞きするようになりましたが、いまいち何のことかよくわかっていませんでした。
最近になってネットで検索しているうちに、ClaudeでVSCodeの拡張機能Clineみたいなことができるみたいな情報を見つけ、どうやらClaudeデスクトップとMCPというものを利用することで、いろんなことができるということがわかりました。

## いろいろ迷った
ClaudeデスクトップとMCPでできるのですが、説明サイトはいっぱい出てくるし、当初理解できていない状態で読んでも意味がわかりませんでした。一応できるようになりましたが、理解不足や勘違いで右往左往しました。　

 ## 迷ったり勘違いしたところ
 ### MCPはClaudeの拡張機能ではない
 VSCodeのClineと同じように、Claudeに拡張機能を追加する感覚でいました。正確には、いろんな機能ごとのMCPサーバーが起動していて、ClaudeなどのアプリからそのMCPサーバーを利用する形でした。
1. 使いたいMCPサーバーをインストール
1. Claudeデスクトップの設定ファイル（claude_desktop_config.json）に、インストールしたMCPサーバーを使う定義を書く

という手順になります。
利用している分には同じことですが、使えるようになるまでに妙に手間がかかるなと思ってました。一通り動くようになって後から振り返ると、そういうことかと理解が進みました。

 ### Node.jsなどのインストールが必要
 先の拡張機能の勘違いからのつながりですが、MCPサーバーとして起動するためにNode.jsのインストールが必要だったりします。使用するMCPサーバーによってPythonとかも必要になるみたいです。最初、なぜこんなものが必要なのかわけもわからずインストールしてました。

 ### WindowsとMacで使えるものが違う(多分）
私はWindowsを使っていますが、こちらのサイトを参考に設定をしていました。
[Claude DesktopをCline風のAIエージェントにする](https://zenn.dev/nu1/articles/eb8b61e3f10b4c)
 その中にターミナルでコマンド実行できる[iterm-mcp](https://github.com/ferrislucas/iterm-mcp?tab=readme-ov-file#iterm-mcp)というのがあります。設定は正常にできたですが、いざ使用してみるとエラーを出して動きません。
iterm-mcpのGithubの英語で書かれている説明を眺めていると要件に「iTerm2 must be running」 とありました。iTerm2はMacのターミナルなので、このMCPはWindowsでは動かないと思い設定を消しました。
ただ、「On Windows: %APPDATA%/Claude/claude_desktop_config.json」とあり、Windowsに関する説明があるところを見ると動くのかな？よくわかりません。

### 説明通り設定しても動かない
[Brave Search MCP Server](https://github.com/arben-adm/brave-mcp-search)を説明通りに設定してみましたが、Claudeデスクトップの起動時にエラーを起こしてました。そもそもサーバーのインストールからうまく行ってませんでした。
[Claude MCPでWeb検索してPCに結果を保存させる (Windows)](https://zenn.dev/acntechjp/articles/6410534f739c78)の説明の方法で動きました。
インストールしているものが違うのか、Macとの設定方法の違いなのかよくわかりません。同じMCPでも、説明しているサイトによって方法が違っているので、試行錯誤が必要な感じです。

## 最後に
ものわかり悪くて、一通り動くようになって、仕組みとか何のための設定なのかわかるようになりました。
今までコピー＆ペーストでやりとりしていたことが、AIが直接ローカルのリソースにアクセスして仕事してくれます。

今のところ設定したのは以下の2つです。
* Filesystem MCP Server
ローカル端末のファイルやディレクトリの読み書き、移動、検索などができます。
* Brave Search MCP Server
URLを伝えるとClaudeがそのWEBサイトの内容を読んでくれます。記事をAIにコピペしなくてすみます。

「xxx（URL）にアクセスして、公開されているテーブルの情報をCSV形式で、yyy.csvというファイルに出力して」みたいなお願いができます。
他にはDB、Github、Googleドライブやマップなど各リソースを利用できるMCPがあるようです。プルリクエストにある複数のファイルのソースコードのプレビューやってくれるとありがたいなあとか。まだまだ増えていきそうです。
Pythonでfor文の進捗を簡単に表示するには (tqdmの利用)

Pythonでfor文の進捗を簡単に表示するために、tqdmライブラリを使ってみました。
非常に簡単に利用できて(for文にtqdm(～)を加えるだけでOK)、動作も確認OK。

# tqdmを利用して進捗を簡単に表示する

0. 前提
1. ライブラリをインストール
2. サンプルコードと実行例
3. 進捗バーのカスタマイズ

## 1. 前提

下記記載のセットアップが完了しているものとします。

https://qiita.com/dl_from_scratch/items/4110d2e8fca1c7e299cf

https://qiita.com/dl_from_scratch/items/768d2568b71bc029b7ff

## 2. ライブラリをインストール

「# %%」を付ける形で、下記のコードを書いて、Shift+Enterを押して実行すると、インストールが始まります。
```
# %%
!pip install tqdm
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/606924/0aecb07f-6c03-4abd-85c6-a7e20f23e858.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/606924/9d404762-e2f3-416c-87ec-d8e00a87353b.png)

「Interactive-1」のウィンドウが出てきて、インストールが開始され、しばらく待つと、
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/606924/feb87f87-95d0-421b-af2a-afb919bfbb2f.png)

インストールが完了
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/606924/8ff531c5-0ff5-4272-96d6-6fc4155a02a7.png)

## 3. サンプルコードと実行例

tqdmを使うには、`range()`や`list`などのイテラブルを`tqdm()`でラップするだけの変更でOK。
100ステップのループ処理を行い、進捗を表示するコード例は: 
```py
from tqdm import tqdm
import time

for i in tqdm(range(100)):
    time.sleep(0.01)  # 処理の代わりにスリープ
```

実行すると表示される内容の例：
```
 35%|███▌      | 35/100 [00:00<00:01, 89.12it/s]
```
進捗バーには、現在の進行度（パーセント）、完了したステップ数、残り時間の予測、処理速度などが表示されています。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/606924/3204fc57-d1ff-4303-bd7a-45e220e8bfef.png)

## 4. 進捗バーのカスタマイズ

進捗バーに説明文や単位を追加するには、
```py
for i in tqdm(range(100), desc="処理中", unit="ステップ"):
    time.sleep(0.01)
```
進捗バーの左に「処理中」、右に「ステップ」という単位が表示されます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/606924/7388b038-2a22-44a9-bc5b-409249900cf5.png)


# 参照

https://qiita.com/MAsa_min/items/27052f752abe68939855

# 生成AIに聞く

https://chatgpt.com/share/67f0f624-972c-8004-9fae-24647ba56ed1
[Git] fork, clone, pull の違い



## In a nutshell

GitHub における `fork`，`clone`，`pull` の役割と違いを解説する入門記事である．fork は GitHub 上の複製，clone はローカルへの複製，pull はリモートの更新の取得である．初学者が正しい操作手順を理解し，円滑にプロジェクトへ参加するための基礎を提供する．


## はじめに
Gitについて，`fork`，`clone`，`pull`という操作がある．これらはそれぞれ異なる役割と目的を持つが，時々混乱する事があるので，改めて理解する為に，以下に纏めた．



## 予想される読み手
 - Git 及び GitHub について初学者である
 - チームでの開発，プロジェクトを扱う
 - 他人が作成したリポジトリを利用，拡張したい



## fork(フォーク)について

fork とは，他人の GitHub リポジトリを，自分のアカウント上に複製する操作である．これは GitHub 上でのみ行われ，ローカル環境には影響を与えない．主に他人のプロジェクトに対して修正提案(プルリクエスト)を送りたいときに用いられる．

### 例:
オープンソースのライブラリにバグを見つけ，修正したいと考えたとする．その場合，まずリポジトリを fork し，自分の GitHub 上で自由に変更を加える．その後，オリジナルのリポジトリに対してプルリクエストを送ることで，修正を提案できる．

### 特徴:
- GitHub 上の操作のみで完結する
- 親リポジトリとの関連が保たれる
- 他人のリポジトリに直接書き込む権限がなくても作業が可能になる


## 2. clone(クローン)について

clone とは，リモート(GitHub など)上のリポジトリを，ローカル環境(自分のPC)にコピーする操作である．これにより，ソースコードを手元で閲覧・編集・ビルド・実行することが可能になる．

### コマンド例:
```bash
git clone https://github.com/ユーザー名/リポジトリ名.git
```

### 特徴:
- 実際にローカルディレクトリとしてファイルが取得される
- 多くの場合，作業の出発点として最初に行う操作である
- clone した時点では，そのリポジトリの最新状態が取得される


## 3. pull(プル)について

pull とは，リモートリポジトリの最新の変更をローカルリポジトリに取り込む操作である．チーム開発では，他の開発者が変更を加えることが頻繁にあるため，定期的に `git pull` を行うことが推奨される．

### コマンド例:
```bash
git pull origin main
```

### 特徴:
- `git fetch` + `git merge` の操作を一括で行う．`fetch` はリモートの変更をローカルに「取得」するだけで，自動的にはマージされない．`pull` は取得とマージを同時に行う点が異なる．
- 他人が加えた変更を取り込み，ローカルと同期を取るために使われる
- ローカルに変更がある場合は，マージやコンフリクト解消が必要になることもある


## よくある疑問

### fork と clone の違いは何か？

fork は GitHub 上にコピーを作る操作 であり，clone は ローカルPCにコピーを作る操作 である．fork は他人のリポジトリに貢献するための第一歩として使われ，clone は作業を開始するためにローカルにコードを取得するために使われる．

### fork した後に clone は必要か？

必要である．fork は GitHub 上の自分のアカウントにコピーを作成するだけであるため，実際に作業するにはその fork したリポジトリを clone してローカルに落とす必要がある．


## 実際の開発フロー(例)

1. 他人のリポジトリを fork(GitHub 上)
2. 自分の fork 先を clone(ローカルPC)
3. ローカルで作業・変更・コミット
4. GitHub 上の fork に push
5. 元のリポジトリに対して pull request(プルリクエスト)を送る． pull requestとは，自分の変更内容をオリジナルのリポジトリに提案し，レビュー・マージしてもらうための GitHub 上の仕組みである．


## まとめ表

| 用語   | 操作対象         | 主な目的                        | 実行場所         |
|--------|------------------|----------------------------------|------------------|
| fork   | GitHubリポジトリ | 他人のリポジトリをコピー        | GitHub(Web)    |
| clone  | GitHubリポジトリ | ローカルPCにコピー              | ターミナル       |
| pull   | GitHub->ローカル  | GitHub上の最新変更を取り込む    | ターミナル       |





## Summary

- `fork` は他人のリポジトリを GitHub 上に複製し，貢献のための基盤を作る操作である．  
- `clone` はリポジトリをローカル環境に取得し，編集やビルドを可能にする操作である．  
- `pull` はリモートリポジトリの更新をローカルに反映させるために使う．  
- fork -> clone -> 編集 -> push -> pull request が基本フローである．  
- fork と clone の違いは，GitHub 上の複製か，ローカルへの複製かである．  
- チーム開発では pull を定期的に行い，他者の変更と同期することが推奨される．



## References

[Chacon, S., & Straub, B. (2020). Pro Git 日本語版(第2版](https://git-scm.com/book/ja/v2)
[GitHub. (n.d.). GitHub Docs (日本語)](https://docs.github.com/ja)
Cursorでcommit時にアカウントの確認をされたら旧アカウント名が出てきたので削除した

 ## いつものようにCusrorからcommitしたら、ある日突然アカウントの確認をされた。
 
 githubアカウント名を変えたことがあり、過去のアカウント名が表示されていた。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2724581/1495891f-d5d8-4fc9-9d78-4c180e9d7f3e.png)

こういうの気になるので削除したくなり。

Cursor(コードエディタ)の設定をいろいろ調べていたのですが該当箇所見つからず、
コンパネからWindowsの資格情報から削除するのが正解だったよう。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2724581/d27d6af5-d586-4c99-8e96-163238c8f69c.png)

すっきりしたので元の作業に戻ります（こういうので時間を使いすぎなのです、、）

気づかせていただだいた記事（ありがとうございました）
https://qiita.com/kft12/items/261c1be9df5450c2e538
Terrafomeバージョンアップ手順

`Terraform` および `Terraform Provider`のバージョンを更新する必要があったので、手順を残します。

# 更新内容

``` diff_terraform
terraform {
- required_version = "~> 1.2.0"
+ required_version = "~> 1.11.0"
  required_providers {
    aws = {
      source  = "hashicorp/aws"
-     version = "~> 4.19.0"
+     version = "~> 5.94.0"
    }
  }
  backend "s3" {}
}
```

# `Terraform`バージョンを更新

`required_version`を書き換えて、次のコマンドを実行する。

```
% terraform init -reconfigure -backend-config="profile=MY_PROFILE" -backend-config=../../tfvars/STAGE.tfbackend
```

# `Terraform Provider`のバージョンを更新

`required_providers`の`version`を書き換えて、次のコマンドを実行する。

```
terraform init -upgrade -backend-config="profile=MY_PROFILE" -backend-config=../../tfvars/STAGE.tfbackend
```

注意として、`required_providers`が複数存在する場合、バージョンが異なると次のエラーが表示された（１敗）。

```
Error: Failed to query available provider packages

Could not retrieve the list of available versions for provider hashicorp/aws: no available releases match the given constraints ~> 4.19.0, ~> 5.94.0

To see which modules are currently depending on hashicorp/aws and what versions are specified, run the following command:
    terraform providers
```

以上です。
ドメインとは

# ドメインとは
インターネット上でウェブサービスを識別するための名前のこと。
例
`https://nakatani.com`の場合`nakatani.com`がドメイン
# トップレベルドメインとは
ドメイン名の最後の`.com`、`.org`、`.net`などがこれにあたります。

# サブドメインとは
ドメイン名の前に追加される名前のこと。
例
`https://blog.nakatani.com`の`blog`のこと
# DNSサーバーとは
`IPアドレス`と`ドメイン名`を変更する役割をもつサーバーのこと。

# SSL証明書とは
SSL証明書とは、安全にデータを暗号化し、通信を保護するための証明書のこと。
【Firebase】FIREBASE_TOKENを作成して、GitHubActionsで使用する

# はじめに
GitHubActionsでFirebaseのデプロイフローを作成中、FIREBASE_TOKENの設定方法を忘れていました。
今後もスムーズにできるよう備忘録として残します。

# 作成したワークフロー
最後の行に記載の` secrets.FIREBASE_TOKEN `の取得と、設定を行います

```yml:main.yml
name: CiCd

on: push

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout code
        uses: actions/checkout@v2
      - name: Setup Node.js
        uses: actions/setup-node@v2
        with:
          node-version: "20"
      - name: Install dependencies
        run: npm install
      - name: Run build
        run: npm run build
      - name: Archive Production Artifact
        uses: actions/upload-artifact@v4
        with:
          name: dist
          path: dist

  test:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout code
        uses: actions/checkout@v2
      - name: Setup Node.js
        uses: actions/setup-node@v2
        with:
          node-version: "20"
      - name: Install dependencies
        run: npm install
      - name: Run test
        run: npm run test

  deploy:
    runs-on: ubuntu-latest
    needs: build # buildジョブが完了してから実行
    steps:
      - name: Checkout code
        uses: actions/checkout@v2
      - name: Setup Node.js
        uses: actions/setup-node@v2
        with:
          node-version: "20"
      - name: Install dependencies
        run: npm install
      - name: Download Artifact
        uses: actions/download-artifact@v4
        with:
          name: dist
          path: dist
      - name: Deploy to Firebase
        uses: joinflux/firebase-tools@v9.16.0
        with:
          args: deploy --project=new-study-record --only hosting
        env:
          FIREBASE_TOKEN: ${{ secrets.FIREBASE_TOKEN }}


```

# FIREBASE_TOKENの作成
* FIREBASE_TOKENを作成する

```shell
# Firebase CLIにログインする
$ firebase login

# CIサービス用のトークンを生成する
$ firebase login:ci
```

* `firebase login:ci` を実行すると、「コマンドラインは非インタラクティブモードで実行されています」という警告が表示されます。
 表示されたURLをクリックして、ブラウザでの認証に進みます。この画面では、赤枠で囲まれたURLが認証ページへのリンクです

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3993509/50f057c2-ca3b-43a3-bf14-28b9a7572785.png)

* Googleアカウントにログイン、リクエストの許可を行う

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3993509/4224294f-2668-47e5-84a4-167423dbecd4.png)

* トークンが生成されるので、コピーする
**この時、トークンはターミナルに直接表示されます。秘密情報として取り扱ってください。**

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3993509/26089c8f-1696-4986-ac03-96a3c1255334.png)


# GitHubへFIREBASE_TOKENを登録

* GitHubのリポジトリページから
`Settings`→`Secrets and variables`→`Actions`を選択
`New repository secret`押下



![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3993509/3f8ab7b2-056f-48f0-ae09-7373aa800d5b.png)

* FIREBASE_TOKENを登録する
Name:`FIREBASE_TOKEN`
Secret: `コピーしたシークレットキー`

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3993509/e3536205-01a8-4b6b-b65a-20f2ab7d8db2.png)

`Add secret`で追加

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3993509/825f9492-2ab6-43f9-b6fc-c86cd520d9d2.png)

# おわりに
以前も同じ個所で調べて解決しましたが、まとめなかったため、また調べるという作業が発生してしまいました。
こまめに記事にして同じことを繰り返さないようにしたいです。

# 参考
https://github.com/joinflux/firebase-tools