Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
118 changes: 118 additions & 0 deletions .claude/skills/eccube-asset/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
---
name: eccube-asset
description: EC-CUBE 4.4 のフロントエンドアセット(SCSS / JS バンドル)をビルド・改修するときの規約。「スタイルを変えて」「CSSを直して」「scssを編集して」「デザインを調整して」「JSを追加して」「アセットをビルドして」などと言われたとき、または html/template 配下の scss・js/bundle.js・esbuild.config.mjs を作成・編集するときに使用する。生成物(css / min.css / map / html/bundle)は git 管理されているため、ソースだけコミットすると実機に反映されない。
---

# アセットビルド規約(EC-CUBE 4.4)

## 対象

- **ソース**: `html/template/{default,admin}/assets/scss/**/*.scss`(`install` に scss は無い),
`html/template/{default,admin,install}/assets/js/bundle.js`
- **ビルド定義**: `esbuild.config.mjs`, `package.json`
- **生成物(すべて git 管理下)**: `html/template/{default,admin}/assets/css/` の `style.*` / `app.*` / `bootstrap.*`
(`.css` + `.min.css` + 各 `.map`), `html/bundle/{front,admin,install}.bundle.js` / `*.map`, `html/bundle/ace/`
- **`css/` に混在する非生成物**: `install/assets/css/dashboard.css` と `admin/assets/css/tempusdominus-bootstrap-4*.css`
は対応する scss が無い**手管理ファイル**。再ビルドしても更新されないので、`css/` 配下すべてを生成物と扱わない

## 基本ルール

- **生成物は git 管理されている。ソースだけコミットしても実機には反映されない。**
`.scss` を変更したら必ずビルドし、生成された `css/` 配下も同じコミットに含める。
- **生成された `css/` を直接編集しない。** 次のビルドで上書きされて消える。変更は必ず `scss/` 側へ。
ただし上記の**手管理ファイルは例外**で、対応する `scss/` が無いため直接編集する(ビルドでは更新されない)。
- **`.map` も追跡対象**(`style.css.map` 等)。生成物一式をコミットする。
Comment thread
coderabbitai[bot] marked this conversation as resolved.
- ビルドは `npm run build`(= `node esbuild.config.mjs`)。**1 回で SCSS → `.css` / `.min.css` と
JS バンドルまで通る**ので、片方だけ更新されることはない。監視ビルドは `npm start`(`--watch`)。
- **CI は生成物の鮮度を検査しない。** ソースと生成物の不一致は自動検出されないため、
コミット前に自分で `git status` を確認する(下記「実行・確認方法」)。

### 変換パイプライン(`esbuild.config.mjs` の実装)

| 処理 | 入力 | 出力 | 内容 |
|---|---|---|---|
| SCSS | `html/template` 配下を再帰探索して見つけた `_` 始まり**でない** `*.scss` | 同階層の `css/` へ `*.css` + `*.min.css` + 各 `.map` | `sass-embedded` → postcss(`autoprefixer` / `postcss-sort-media-queries`(mobile-first))。1 エントリを expanded と compressed で 2 回ビルドする |
| JS | `html/template/{default,admin,install}/assets/js/bundle.js` | `html/bundle/{front,admin,install}.bundle.js` + `.map` | esbuild(`bundle` / `minify` / `target: es2018` / `sourcemap`)。CommonJS 対応で `global` を `window` に読み替える |
| 画像・フォント | JS / CSS から参照される `.png` `.svg` `.woff2` 等 | バンドルに埋め込み | `dataurl` ローダ(webpack の url-loader 相当) |
| ace エディタ | `node_modules/ace-builds/src-min-noconflict` | `html/bundle/ace/` | **使うものだけの allowlist** をコピー(毎回ディレクトリを作り直す) |

押さえるべき点が 3 つある。

- **SCSS のエントリは自動検出**。`html/template` 配下を歩いて `_` で始まらない `.scss` を全部エントリ扱いする。
つまり**部分ファイルは必ず `_` 始まりにする**。忘れると単独の CSS として出力され、余計な生成物が増える。
- **CSS は JS から `<style>` として注入される**(`esbuild.config.mjs` の `style-inject` プラグイン)。
webpack の style-loader と同じ挙動なので、テンプレート側の読み込み方を変える必要はない。
- **`postcss-sort-media-queries` が `@media` を mobile-first 順に並べ替え・統合する**。
手書きで `css` の末尾に `@media` を足した差分はこの並べ替えを通っていないため一目で判別できる。
レビューで「フルビルドか手書き追記か」を見分けるときはここを見る。

### source map の sources は相対パスに正規化される

`sass-embedded` は `sources` を `file://` の絶対 URL で返す。**生成物をコミットする運用では
ビルドマシンのパスが焼き込まれ、誰が再ビルドしても map だけが差分化する**ため、
`esbuild.config.mjs` は map ファイルからの相対パス(区切りは POSIX 固定)へ直してから書き出している。
map に絶対パスや `file://` が現れたら、この正規化を通っていない(手で書き換えた等)疑いがある。

## 実装パターン

### スタイルを変える

```bash
# 1. scss を編集(例: 店頭)
# html/template/default/assets/scss/project/_15.1.cart.scss
# 2. ビルド(Docker 環境。ホストに node があれば npm ci && npm run build でもよい)
docker compose -f docker-compose.yml -f docker-compose.dev.yml -f docker-compose.nodejs.yml \
run --rm -T nodejs npm ci
docker compose -f docker-compose.yml -f docker-compose.dev.yml -f docker-compose.nodejs.yml \
run --rm -T nodejs npm run build
# 3. 生成物を含めてコミット
git status # scss と css/*.css *.min.css *.map が両方出ていることを確認
```

新しい部分ファイルを足すときは `_` 始まりの名前にし、エントリ(`style.scss` / `app.scss`)から `@import` する。

### JS を足す

エントリは 3 つ(`front` / `admin` / `install`)で、それぞれ `assets/js/bundle.js` が入口。
新しいスクリプトはこの `bundle.js` から `import` / `require` する。
`esbuild.config.mjs` の `entryPoints` を増やすのは、新しい画面区分を作るときだけ。

### ace エディタのモードを増やす

`esbuild.config.mjs` の `aceFiles` に **allowlist として列挙されているファイルだけ**が
`html/bundle/ace/` へ配置される(既定は `mode-twig` / `mode-css` / `mode-javascript` と worker 3 種ほか)。
新しいモードやテーマを使うテンプレートを追加したら、この配列にも追加する。
**足し忘れはビルドでは検出されず、その画面を開いたときに実行時エラーになる**。

## よくある間違い

- ❌ `.scss` だけ変更してコミットする → ✅ 生成物(`css/*.css` `*.min.css` `*.map`)も同じコミットに含める。含めないと実機のスタイルが変わらない
- ❌ 反映されないので `css/style.css` を直接編集する → ✅ 次のビルドで消える。`scss/` を直して再ビルドする
- ❌ `css/` 配下すべてを生成物と決めつける → ✅ 対応する `.scss` が無いものは手管理ファイルで、再ビルドしても更新されない。`.map` の有無と scss の実在で判別する
- ❌ 「E2E が緑だから生成物は最新」と判断する → ✅ E2E のワークフローは自分で `npm run build` するため、コミット済み生成物が古くても緑になる
- ❌ 部分ファイルを `_` 始まりにしない → ✅ SCSS エントリは自動検出なので、`_` を付けないと単独の CSS として出力され余計な生成物が増える
- ❌ 生成された `css` に手で `@media` を追記する → ✅ `postcss-sort-media-queries` の並べ替えを通らず、次のビルドで消える
- ❌ ace のモードを増やすテンプレートを足したのに `aceFiles` を更新しない → ✅ ビルドは通り、その画面を開いたときだけ壊れる
- ❌ 生成物のパーミッション差分(`.css` は 100755 / `.map` は 100644)に気づかずモードだけ変えてコミットする → ✅ `git diff` でモード変更が出たら戻す
- ❌ ホストとコンテナでビルドを混在させ、sass のバージョン差で無関係な行まで差分が出る → ✅ どちらかに統一する(Docker 推奨)
- ❌ Bootstrap の dist CSS(`node_modules` 由来の生成物)に対する lint 指摘をそのまま直そうとする → ✅ 例えば `:not(:-moz-placeholder)` を「廃止予定で機能しない」とする指摘は誤検知で、Bootstrap は `:-moz-placeholder` と `:placeholder-shown` を意図的に別ルールセットへ分割出力しており実挙動は後者で正常。近傍に両方あるかを grep で確認する。そもそもビルド生成物なので手編集してはいけない

## 実行・確認方法

```bash
# ビルド(Docker 環境。AGENTS.md「アセットビルド」の手順)
docker compose -f docker-compose.yml -f docker-compose.dev.yml -f docker-compose.nodejs.yml \
run --rm -T nodejs npm ci
docker compose -f docker-compose.yml -f docker-compose.dev.yml -f docker-compose.nodejs.yml \
run --rm -T nodejs npm run build

# 生成物の取りこぼしが無いか(scss を触ったのに css が出ていなければビルド漏れ)
git status --short html/template html/bundle

# モードだけの差分が混ざっていないか
git diff --summary | grep 'mode change' || echo 'モード変更なし'
```

- 生成物に**想定外の広範囲な差分**が出たときは、ホスト / コンテナのビルド環境差か依存更新を疑う。
意図した変更だけが出ているかを `git diff --stat` で確認してからコミットする。
- 実機での確認は `bin/console cache:clear` 後にブラウザのキャッシュを無効化して表示する。
23 changes: 23 additions & 0 deletions .claude/skills/eccube-contributing/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,29 @@ PR では以下が GitHub Actions で走る。**同じものを手元で先に
- このほか **E2E(`e2e-test.yml`)・プラグインテスト(`plugin-test.yml`)・セキュリティスキャン(zaproxy/vaddy)** が走る。重いので CI に任せてよいが、落ちたら該当ジョブのログを読む。
- **rector は関門になりやすい**(PHP/Symfony/Doctrine の機械的な現代化を強制)。`--dry-run` で出た差分は基本そのまま適用する。

### push 時に走る git フック

CI とは別に、リポジトリの **`.husky/pre-push` が push のたびに rector(全体走査)と `phpstan analyze src/` を実行する**。
変更ファイルに絞った確認だけで済ませていると、ここで初めて落ちる。実行環境は自動判定で、
ec-cube コンテナが起動していれば `docker compose exec`、無ければホストの `vendor/bin` を使う
(`ECCUBE_HOOK_RUNNER=docker|host|skip` で明示指定、`HUSKY=0 git push` でバイパスできる)。
`rector.php` は dev の Symfony コンテナ XML を参照するため、fresh clone や `cache:clear` 直後の
push では**フックが先に `bin/console cache:clear --env=dev` を実行する**(XML が無いと rector が
全ファイル read error で落ちるため)。初回 push が長いのはこれが理由で、異常ではない。

**変更していないファイルでこのフックが落ちたら、まず `vendor/` が `composer.lock` とずれていないか疑う。**
`composer install` で同期すれば直るケースが 2 つある。

- **依存のバージョンずれ**: 古い base のブランチから 4.4 を取り込んだ直後など、`vendor/` が旧バージョンのまま。
例えば Doctrine DBAL 3 系が残っていると、DBAL 4 の API(`createComparator(ComparatorConfig)` 等)を
「引数が多い」と誤検出して rector が差分を出す。**この指摘を機械的に適用すると意図した実装を壊す**。
- **クラスマップの陳腐化**: ファイル移動を含むマージの後、`vendor/composer/autoload_classmap.php` が旧パスを
指し続け、rector が自作ルールを解決できず `Expected an existing class name` で即死する。
`composer dump-autoload`(または `composer install`)で再生成する。

判断の拠り所は **CI が同じゲートで緑かどうか**。CI は毎回クリーンインストールするため、
「CI は緑でローカル pre-push だけ赤」なら環境ずれとみなしてよい。

## よくある間違い

- ❌ `master` や旧バージョンブランチ宛に PR → ✅ base は `4.4`
Expand Down
17 changes: 7 additions & 10 deletions .claude/skills/eccube-phpunit/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -105,17 +105,14 @@ public static function provideStatuses(): array
## よくある間違い

- ❌ `@dataProvider` アノテーション → ✅ `#[DataProvider]` 属性(PHPUnit 11)。
- ❌ `new Client()` など HTTP クライアントの自前生成 → ✅ 親クラスの `$this->client`。
- ❌ URL の文字列直書き(`'/products/list'`)→ ✅ `$this->generateUrl('product_list')`。
- ❌ Entity の手組み → ✅ `createXxx()` フィクスチャヘルパ。
- ❌ 支払方法のデフォルト/再選択テストで `find(1)` 等の ID 前提 → ✅ `Generator::createPayment()` で sort_no・利用条件を明示し `assertSame()` で再選択先を固定(フィクスチャ並び変更で偽陽性になり得る)。
- ❌ ステータス値のハードコーディング(`if ($status == 1)`)→ ✅ 定数(例: `OrderStatus::NEW`)を使う。
- ❌ 回帰テストを追加して、修正を外すと落ちることを確認せずに完了とする → ✅ 修正を 1 つずつ外してどのテストが落ちるか実測する(落ちないテストはゲートにならない)。
- ❌ PHP Warning が出ることを回帰の証拠にする → ✅ `phpunit.xml.dist` に `failOnWarning` が無いため Warning では落ちない。戻り値を assert で直接検証する。
- ❌ HTTP クライアント・URL・Entity を自前で用意する → ✅ 親クラスの `$this->client`、`$this->generateUrl('route_name')`、`createXxx()` フィクスチャヘルパを使う。
- ❌ 型宣言の省略 → ✅ 引数・戻り値に型を付け、PHPStan level 6 を通す。
- ❌ `setUp()` で未宣言のプロパティに代入(`$this->Member = ...`)→ ✅ プロパティを必ず宣言する。PHP 8.2 の動的プロパティ deprecation が `failOnDeprecation`(`phpunit.xml.dist`)で CI red になる。
- ❌ テストのプロパティを非 nullable で宣言(`protected array $Items = [];`)→ ✅ `protected ?array $Items = null;` と nullable にする。`EccubeTestCase::cleanUpProperties()` が tearDown で全プロパティに `null` を代入するため、非 nullable だと `TypeError` で全テストが落ちる(初期値が必要なら `setUp()` で代入する)。
- ❌ HTML パートを持たないメールに `assertEmailHtmlBodyNotContains()` → ✅ `assertNull($Message->getHtmlBody())`。前者は `str_contains(null, …)` の deprecation を出し、かつ「HTML パートが無いので必ず通る」空振りアサーションになる。
- ❌ テストのプロパティを未宣言/非 nullable 宣言 → ✅ `protected ?array $Items = null;` の形にする(未宣言は deprecation、非 nullable は tearDown の `null` 代入で `TypeError`)。
- ❌ ID・ステータス値・フィクスチャの並び順に依存(`find(1)` / `if ($status == 1)`)→ ✅ 定数(`OrderStatus::NEW`)を使い、対象は `Generator::createXxx()` で明示して固定する。
- ❌ 回帰テストを追加して修正を外すと落ちることを確認しない/PHP Warning を回帰の証拠にする → ✅ 修正を 1 つずつ外して落ちるテストを実測する。`failOnWarning` は無いので戻り値を assert で検証する。
- ❌ ローカルだけ 500 になるテストの原因を自分の変更に帰属させる → ✅ `createFormData()` がキーを送らない列で DataMapper が非 nullable setter に `null` を渡す。`catchExceptions(false)` で確認する。
- ❌ HTML パートの無いメールに `assertEmailHtmlBodyNotContains()` → ✅ `assertNull($Message->getHtmlBody())`(前者は deprecation を出し、必ず通る空振り)。
- ❌ 依存ライブラリが投げる例外メッセージを全文(末尾の句点まで)アサート → ✅ 版差で変わらない部分だけを含有判定する。上流はマイナー更新で書式を足すことがあり、lock 更新だけで全マトリクスが落ちる。

## 実行方法

Expand Down
1 change: 1 addition & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -242,6 +242,7 @@ frontmatter の `description` がトリガ条件で、該当レイヤを触る
| カスタマイズ(app/Customize での拡張・上書き・デコレーション) | [`.claude/skills/eccube-customize/SKILL.md`](./.claude/skills/eccube-customize/SKILL.md) | `eccube-customize` |
| CSV 入出力(CsvImport/Export・CSV 定義) | [`.claude/skills/eccube-csv/SKILL.md`](./.claude/skills/eccube-csv/SKILL.md) | `eccube-csv` |
| コンソールコマンド(Symfony Console・バッチ) | [`.claude/skills/eccube-command/SKILL.md`](./.claude/skills/eccube-command/SKILL.md) | `eccube-command` |
| アセットビルド(SCSS / JS バンドル・生成物のコミット) | [`.claude/skills/eccube-asset/SKILL.md`](./.claude/skills/eccube-asset/SKILL.md) | `eccube-asset` |
| 責務分離レビュー(実装直後の自己チェック・全層) | [`.claude/skills/eccube-review-responsibility/SKILL.md`](./.claude/skills/eccube-review-responsibility/SKILL.md) | `eccube-review-responsibility` |

> 規約は必要になった時点で `.claude/skills/eccube-<name>/SKILL.md` を 1 ファイル追加して足す(`.codex`/`.agents` は symlink で自動共有)。
Expand Down
Loading