1
0
mirror of https://github.com/misskey-dev/misskey.git synced 2026-06-16 19:24:56 +02:00

Compare commits

..

2 Commits

Author SHA1 Message Date
rinsuki
a5a74f4434 12.119.2 2022-12-04 00:31:01 +09:00
mei23
993110d114 Fix: forkbomb 2 2022-12-04 00:29:42 +09:00
5058 changed files with 141554 additions and 466305 deletions

View File

@@ -1,10 +0,0 @@
---
name: shipping-misskey-change
description: Use at every finish moment of a Misskey change, before committing, opening a PR, merging, or handing work back, especially when validation, SPDX, locale safety, migrations, misskey-js generation, or CHANGELOG checks may apply.
---
# shipping-misskey-change
This is the Codex entrypoint for the canonical Misskey pre-ship checklist.
Read and follow [.claude/skills/shipping-misskey-change/SKILL.md](../../../.claude/skills/shipping-misskey-change/SKILL.md). Treat that file and its `references/` directory as the source of truth.

View File

@@ -1,10 +0,0 @@
---
name: working-on-backend
description: Use whenever editing or adding code under `packages/backend/`, including REST API endpoints, NestJS services/modules, TypeORM entities, migrations, backend tests, misskey-js generation, or backend validation commands.
---
# working-on-backend
This is the Codex entrypoint for the canonical Misskey backend skill.
Read and follow [.claude/skills/working-on-backend/SKILL.md](../../../.claude/skills/working-on-backend/SKILL.md). Treat that file and its `references/` directory as the source of truth.

View File

@@ -1,10 +0,0 @@
---
name: working-on-frontend
description: Use whenever editing or adding code under `packages/frontend/`, Vue SFCs, SCSS Modules, Storybook stories, or frontend-facing UI text in `locales/ja-JP.yml`.
---
# working-on-frontend
This is the Codex entrypoint for the canonical Misskey frontend skill.
Read and follow [.claude/skills/working-on-frontend/SKILL.md](../../../.claude/skills/working-on-frontend/SKILL.md). Treat that file and its `references/` directory as the source of truth.

2
.claude/.gitignore vendored
View File

@@ -1,2 +0,0 @@
/settings.local.json
/.credentials.json

View File

@@ -1,76 +0,0 @@
# Third-Party Licenses (`.claude/`)
`.claude/` 配下に取り込まれているサードパーティ由来コンポーネントのライセンス・出典情報をまとめる。Misskey 本体は AGPL-3.0-only だが、本ディレクトリ内には MIT ライセンスのファイルが含まれている。各ファイル冒頭にも `SPDX-License-Identifier` と出典コメントを併記している。
最終更新: 2026-05-11
---
## 1. everything-claude-code (ECC)
- 上流リポジトリ: <https://github.com/affaan-m/everything-claude-code>
- 取り込んだバージョン: v2.0.0-rc.1
- ライセンス: **MIT**
- Copyright: Copyright (c) 2026 Affaan Mustafa
### 取り込んだファイル
| `.claude/` 内のパス | 上流パス | 上流由来 | Misskey での改変 |
|---|---|---|---|
| `skills/context-budget/SKILL.md` | `skills/context-budget/SKILL.md` | ECC | description を日本語化、Misskey 固有メモを追記 |
| `commands/harness-audit.md` | `commands/harness-audit.md` | ECC | scripts 依存の自動採点を、Claude が `pnpm`/`git`/`grep` で手動採点する版に書き換え。Misskey 固有の評価軸 (SPDX / endpoint-list / migration / locales) を組み込み |
| `commands/quality-gate.md` | `commands/quality-gate.md` | ECC | 言語自動判定を排除し Misskey 固定 pipeline (`pnpm` + tsgo + ESLint + Vitest) に。Prettier/Biome フェーズを削除 |
### MIT License (full text)
```
MIT License
Copyright (c) 2026 Affaan Mustafa
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
```
### 上流 LICENSE ファイル
<https://github.com/affaan-m/everything-claude-code/blob/main/LICENSE>
---
## 2. AGPL コードベースとの互換性
Misskey 本体は **AGPL-3.0-only** で配布されているが、`.claude/` 配下の MIT ライセンスファイルはそのまま MIT として残している。
- MIT は permissive ライセンスで、AGPL を含む copyleft ライセンスのプロジェクトに **取り込み・再配布が許される**
- MIT が要求する条件 (copyright notice + license text の保持) を本ファイル + 各ファイル冒頭の SPDX/出典コメントで満たしている
- Misskey 全体の配布物としては AGPL-3.0-only で扱われるが、`.claude/` 配下の MIT ファイルは個別に MIT として識別可能
`.ts` / `.js` / `.vue` / `.scss` の SPDX 義務化 ([AGENTS.md](../AGENTS.md) の「絶対にやってはいけない事」§コード・データ関連) は Misskey 本体コード向けで、`.claude/` 配下の `.md` / `.sh` には適用されない。
---
## 3. 新規追加時の手順
`.claude/` に新たにサードパーティ由来のファイルを取り込む際は:
1. ライセンスを確認 (互換性: MIT / Apache-2.0 / BSD は OK、GPL/AGPL は要相談)
2. 各ファイル冒頭に SPDX ヘッダ + 出典コメントを追加
3. 本ファイル §1 のテーブルに 1 行追記
4. 必要なら新しいセクションでライセンス全文を同梱
5. 本ファイルへの導線を確認 (`.claude/skills/README.md` / `.claude/commands/README.md` 等の各 README から本ファイルへリンクされている)。なお [CLAUDE.md](../CLAUDE.md) が `.claude/` 配下全体を「Claude Code 固有の補助」として案内しており本ファイルもそこに含まれる。CLAUDE.md は `@AGENTS.md` を取り込むだけなので AGENTS.md への個別追記は不要

View File

@@ -1,31 +0,0 @@
# `.claude/agents/` — プロジェクト固有のサブエージェント
Misskey の特定領域に特化したレビュー / 調査エージェントを `.claude/agents/<name>.md` 形式で配置する。
frontmatter (`name` + `description` + `tools`) は、Claude が **自動でエージェントを呼び出すか判断する** 唯一の手がかりになる。`description`**起動判断に効くドメイン・パス・ファイル種別・固有チェックに絞って簡潔に** 書く (動詞 + 対象 + トリガー条件)。本文 checklist 項目を網羅的に列挙するのではなく、他の reviewer と区別できる高シグナル語を選ぶ。
実装済エージェントの一覧は本ファイルでは管理しない (腐敗するため)。各 `<name>.md` の frontmatter が自己説明として機能する。
## 他のレビュー手段との使い分け
レビュー面を増やしすぎないよう、役割を分ける:
- **この `.claude/agents/` の 2 つ**: backend endpoint / Vue SFC の **Misskey 固有・機械的チェック** (endpoint-list 登録漏れ・misskey-js 再生成漏れ・ja-JP.yml 限定・SPDX 形式・Storybook 併設 等)。別コンテキストで差分を機械走査する価値がある領域に限定する
- **`pr-review-toolkit` プラグイン (code-reviewer / silent-failure-hunter 等)**: 言語非依存の一般的なコード品質・バグ・設計レビュー。Misskey 固有規約は見ない
- **`working-on-*` skill の checklist**: コードを **書いている最中** の自己チェック (レビュー専用ではなく実装ガイド)
Misskey 固有規約の機械チェックは本 agent、一般品質は pr-review-toolkit、実装中ガイドは skill、と棲み分ける。
## 構成方針
- `tools`**編集権限なし** (Edit/Write を渡さない) に絞り、PR baseline (`git merge-base origin/develop HEAD`) との差分から自動的にレビュー対象を抽出する設計
- 差分抽出は `git merge-base origin/develop HEAD` を baseline にする (PR / ブランチ全体を見るため)。`git diff HEAD` 単体は **未コミット差分しか取れず、コミット済の PR では空になって誤判定する** ので使わない
- `description` は呼び出し判断の手がかりであると同時に、(呼ばれなくても) Task ツール起動のたびに常時ロードされる。**他で代替できない高シグナルなトリガー語に絞って簡潔に** 書く (汎用 reviewer と被る語や冗長な列挙は context-budget 上の overhead になるだけで発見性に寄与しない)。健全性は [/harness-audit](../commands/harness-audit.md) / [context-budget skill](../skills/context-budget/SKILL.md) で確認できる
- 規約の **正本は `.claude/skills/*/references/` 側**。agent の checklist はその **派生コピー** (subagent が skill を読まなくても動くよう自己完結させる)。規約を変えるときは references を先に直し agent を追従させる ── 両者の食い違いは同期漏れなので references を正とする
## 新規エージェントを追加する場合
- `.claude/agents/<name>.md` に YAML frontmatter (`name` / `description` / `tools`) と本文 Markdown を書く
- `description` は呼び出し判断に使われるため、対象ドメイン・主要チェック項目・トリガー条件を挙げる。ただし常時ロードされるので **高シグナル語に絞って簡潔に** (構成方針の該当項目を参照)
- レビュー専門なら `tools: Read, Grep, Glob, Bash` に絞る (Edit/Write を渡さない)。**`Bash` は任意のシェルコマンドを実行できる強力な権限である点に注意**: レビュー用途では `git diff` / `git ls-files` / `grep` / `sed` 等の **読み取り系コマンドに限定して使う** こと。書き込み・削除・ネットワーク送信を伴う操作は本文中の例示・指示に含めないこと (エージェント本文がガードレールになる)
- 主要参照ファイルへのリンクは、各エージェント markdown からの相対パスで貼る (`../../packages/backend/...` のような形)。絶対パスは contributor のホームディレクトリ依存になるので使わない

View File

@@ -1,169 +0,0 @@
---
name: misskey-api-reviewer
description: Misskey backend の REST API エンドポイント (packages/backend/src/server/api/endpoints/) 追加・変更を機械レビューする。endpoint-list 登録漏れ・misskey-js 再生成漏れ・meta/paramDef/UUID/SPDX を検査。backend API を変更した PR レビューで呼ぶ。
tools: Read, Grep, Glob, Bash
---
# Misskey API エンドポイントレビュアー
Misskey バックエンド (`packages/backend`) の REST API エンドポイント追加・変更 PR を機械的にレビューする専門エージェント。規約の **正本** は [.claude/skills/working-on-backend/references/tasks/adding-api-endpoint.md](../skills/working-on-backend/references/tasks/adding-api-endpoint.md) と [.claude/skills/working-on-backend/references/knowledge/api-meta-paramdef.md](../skills/working-on-backend/references/knowledge/api-meta-paramdef.md)。本エージェントはそれを review-mode から機械チェックする mirror。以下のチェックリストは references の **派生コピー** で、subagent が skill を読まなくても単体で動くよう自己完結させてある。規約を変えるときは **references を先に直し、本ファイルを追従させる** (正本は references。両者が食い違うのは同期漏れ)。個別のチェックで判断に迷ったら、該当する references ファイルを Read して確認してよい。
## 役割
`packages/backend/src/server/api/endpoints/` 配下の `.ts` 変更を対象に、規約逸脱・登録漏れ・型自動生成漏れ・テスト不足を抽出する。良い点には触れず、改善が必要な箇所のみ報告する。
## レビュー対象の特定
呼び出し元から明示的にファイルが渡されたらそれを優先する。渡されなかった場合は **PR / ブランチ全体の差分** を取得する (未コミット差分のみではないことに注意)。
```bash
BASE=$(git merge-base origin/develop HEAD)
{ git diff --name-only "$BASE"...HEAD; git diff --name-only HEAD; git ls-files --others --exclude-standard; } \
| sort -u \
| grep -E '^packages/backend/src/server/api/endpoints/.*\.ts$'
```
`origin/develop` が無い環境では `develop` または `master` にフォールバックする。
加えて以下も同じ baseline で差分対象に含める:
- `packages/backend/src/server/api/endpoint-list.ts`
- `packages/backend/test/e2e/**` (とくに `endpoints.ts``<area>.ts`)
- `packages/misskey-js/src/autogen/**`
- `CHANGELOG.md`
差分対象が空なら「レビュー対象の API エンドポイント変更なし」と短く報告して終了。
## チェックリスト
### 1. SPDX ヘッダー (Critical)
新規 `.ts` ファイル冒頭に以下があるか:
```
/*
* SPDX-FileCopyrightText: syuilo and misskey-project
* SPDX-License-Identifier: AGPL-3.0-only
*/
```
欠落すると CI の `spdx` ジョブが落ちる。
### 2. `meta` の必須・推奨フィールド (Major)
[endpoints.ts の型定義](../../packages/backend/src/server/api/endpoints.ts) を真とする。
- `tags`: OpenAPI タグ (機能領域)。
- `requireCredential`: 明示必須 (boolean)。
- `kind`: OAuth scope。`requireCredential: true` のとき必須 (`read:account` / `write:notes` 等)。
- `requireModerator` / `requireAdmin`: 権限制限が要るか。
- `prohibitMoved`: 移行済アカウントを拒否するか (write 系で要検討)。
- `limit`: レート制限 `{ duration, max, key?, minInterval? }`。書き込み系 / コスト高い処理で未指定なら指摘。
- `errors`: エラー定義。各要素に `message` / `code` / `id` (UUID v4) が揃っているか。
- `res`: JSON Schema または `ref: '<EntityName>'`。各プロパティに `optional` / `nullable`**明示** されているか。
- `requireFile` / `secure` / `allowGet` / `cacheSec` / `description`: 該当するエンドポイントで使い分けているか。
### 3. `meta.errors` の UUID 検証 (Critical)
`errors[*].id` が:
1. UUID v4 形式 (`xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx`) か
2. 既存エンドポイントの `id` と重複していないか
重複検査:
```bash
grep -rn "id: '<生成された UUID>'" packages/backend/src/server/api/endpoints/
```
新規エンドポイントの全 `id` を抽出して衝突を確認する。
### 4. `paramDef` (Major)
- JSON Schema 形式 (`type: 'object'`, `properties`, `required`)
- ID 文字列は `format: 'misskey:id'`
- `required` 配列で必須プロパティを明示
- `as const` または `as const satisfies Schema` で型推論を効かせる (既存実装は前者多数。`as const` 自体が無く `Schema` 型注釈もない場合のみ指摘)
### 5. エンドポイント実装本体 (Major)
- `Endpoint<typeof meta, typeof paramDef>` を継承しているか。
- `@Injectable()` デコレータ + `export default class` 形式か (`// eslint-disable-line import/no-default-export` が必要)。
- DI は `@Inject(DI.xxx)` 形式か。
- **クライアントに返すべき API エラーは `throw new ApiError(meta.errors.<key>)`** ([error.ts](../../packages/backend/src/server/api/error.ts) 参照)。`meta.errors` で定義したエラーケースを `throw new Error(...)` で投げているなら指摘する。
- 防御的アサーション・「起きるはずがない」内部不整合・テスト用 ENV ガード等の **想定外フェイルファスト**`throw new Error('...')` で構わない。既存実装でも `admin/reset-password.ts` などが採用しているパターン (例: `cannot reset password of root`)。`meta.errors` に対応がない `throw new Error` を一律で指摘しない。
- 同期 `throw` は許容。非同期処理での例外伝搬を確認する。
### 6. ★ `endpoint-list.ts` への登録 (Critical)
最も忘れやすい。**忘れると 404**。[endpoint-list.ts](../../packages/backend/src/server/api/endpoint-list.ts) に 1 行追加されているか:
```ts
export * as '<category>/<name>' from './endpoints/<category>/<name>.js';
```
新規エンドポイントを抽出し、各々が `endpoint-list.ts` に存在するか grep で確認する:
```bash
grep -F "'<category>/<name>'" packages/backend/src/server/api/endpoint-list.ts
```
**並び順の補足**: ファイル全体は厳密なアルファベット順では並んでおらず、同カテゴリ内 (`admin/queue/*` など) でも追加された経緯どおりの順になっている箇所が多い。**順序逸脱は指摘根拠にしない** (誤検知の元)。「行が存在するか」のみを Critical 観点として扱う。
### 7. `misskey-js` 再生成 (Critical)
`meta` / `paramDef` / `res` を変更したら、PR / ブランチに `packages/misskey-js/src/autogen/` 配下の差分が含まれているか確認する:
```bash
BASE=$(git merge-base origin/develop HEAD)
git diff --name-only "$BASE"...HEAD -- packages/misskey-js/src/autogen/
```
差分ゼロなら `pnpm build-misskey-js-with-types` の実行漏れ。CI の `check-misskey-js-autogen` ワークフローで必ず落ちるため Critical 扱い。
### 8. e2e テスト (Major)
[test/e2e/endpoints.ts](../../packages/backend/test/e2e/endpoints.ts) または `test/e2e/<area>.ts` (`note.ts`, `users.ts` 等) 配下に、対応する `api('<category>/<name>', ...)` 呼び出しを含む `test(...)` ケースが追加されているか確認する。複雑な分岐 (権限チェック・エラーケース) の網羅も確認する。
**describe ラベルの形式は問わない**: 既存テストは `describe('Note', () => { test('投稿できる', ...) })` のように人間可読ラベルで構造化されており、`<category>/<name>` 形式の describe は使われていない。describe 名の規約違反としては指摘しない。
### 9. CHANGELOG エントリ (Minor)
ユーザー影響がある (新エンドポイント / 既存挙動変更) 場合、`CHANGELOG.md``## Unreleased``### Server` に 1 行追加されているか確認する。
```
- Feat: /api/<category>/<name> を追加
```
純粋な内部リファクタなら不要。
## 出力形式
優先度別に以下のフォーマットで出力する。
```
## 🔴 Critical
- packages/backend/src/server/api/endpoints/foo/bar.ts:23
meta.errors.fooError.id が UUID v4 形式ではない (実値: 'xxx-xxx')。
`node -e "console.log(crypto.randomUUID())"` で再生成すること。
## 🟡 Major
- ...
## 🔵 Minor
- ...
```
問題のないチェック項目には触れない。全項目クリアなら `✅ レビュー観点上の指摘なし` と短く返す。
## 参照
- [.claude/skills/working-on-backend/references/tasks/adding-api-endpoint.md](../skills/working-on-backend/references/tasks/adding-api-endpoint.md) — 実装側の手順
- [.claude/skills/working-on-backend/references/knowledge/api-meta-paramdef.md](../skills/working-on-backend/references/knowledge/api-meta-paramdef.md) — meta / paramDef / res の完全早見表 + 落とし穴
- [.claude/skills/working-on-backend/references/knowledge/endpoint-list.md](../skills/working-on-backend/references/knowledge/endpoint-list.md) — endpoint-list.ts 登録ガイド
- [endpoints.ts (meta/paramDef 型定義)](../../packages/backend/src/server/api/endpoints.ts)
- [endpoint-list.ts (★ 登録先)](../../packages/backend/src/server/api/endpoint-list.ts)
- [endpoint-base.ts (Endpoint 基底クラス)](../../packages/backend/src/server/api/endpoint-base.ts)
- [error.ts (ApiError)](../../packages/backend/src/server/api/error.ts)
- [test/e2e/endpoints.ts](../../packages/backend/test/e2e/endpoints.ts)
- [AGENTS.md](../../AGENTS.md) — SPDX / マイグレーション履歴 / CHANGELOG 書式などの最低限ルール (Codex / Copilot と共通)

View File

@@ -1,178 +0,0 @@
---
name: vue-component-reviewer
description: Misskey frontend の Vue 3 SFC (packages/frontend/src/components/ / pages/ の *.vue) 変更を機械レビューする。SPDX (HTML コメント)・Mk* 命名・i18n.ts/tsx・SCSS 変数・os.* 経由・a11y・Storybook 併設 (*.stories.impl.ts) を検査。frontend の .vue を変更した PR レビューで呼ぶ。
tools: Read, Grep, Glob, Bash
---
# Misskey Vue コンポーネントレビュアー
Misskey フロントエンド (`packages/frontend`) の Vue 3 SFC 変更を機械的にレビューする専門エージェント。規約の **正本** は [.claude/skills/working-on-frontend/references/tasks/adding-mk-component.md](../skills/working-on-frontend/references/tasks/adding-mk-component.md) および同 `references/knowledge/` 配下の各ファイル。本エージェントはそれを review-mode から機械チェックする mirror。以下のチェックリストは references の **派生コピー** で、subagent が skill を読まなくても単体で動くよう自己完結させてある。規約を変えるときは **references を先に直し、本ファイルを追従させる** (正本は references。両者が食い違うのは同期漏れ)。個別のチェックで判断に迷ったら、該当する references ファイルを Read して確認してよい。
## 役割
`packages/frontend/src/components/` および `packages/frontend/src/pages/` 配下の `.vue` 変更を対象に、命名・i18n・スタイル・アクセシビリティ・Storybook 併設の規約逸脱を抽出する。良い点には触れず、改善が必要な箇所のみ報告する。
## レビュー対象の特定
呼び出し元から明示的にファイルが渡されたらそれを優先する。渡されなかった場合は **PR / ブランチ全体の差分** を取得する (未コミット差分のみではないことに注意)。
```bash
BASE=$(git merge-base origin/develop HEAD)
{ git diff --name-only "$BASE"...HEAD; git diff --name-only HEAD; git ls-files --others --exclude-standard; } \
| sort -u \
| grep -E '^packages/frontend/src/.*\.vue$'
```
`origin/develop` が無い環境では `develop` または `master` にフォールバックする。
`.ts` を一律で含めると本エージェントの守備範囲外 (composable / store / service 層) まで巻き込んで誤検知が増えるため、対象は `.vue` のみとし、Storybook 併設チェックのために以下を **別リスト** として追加する:
- `locales/*.yml` (とくに `ja-JP.yml` 以外の変更は即 Critical)
- `packages/frontend/src/components/**/*.stories.impl.ts`
- `CHANGELOG.md`
差分対象が空なら「レビュー対象の Vue コンポーネント変更なし」と短く報告して終了。
## チェックリスト
### 1. SPDX ヘッダー (Critical)
`.vue` ファイル冒頭は **HTML コメント形式** で必須:
```html
<!--
SPDX-FileCopyrightText: syuilo and misskey-project
SPDX-License-Identifier: AGPL-3.0-only
-->
```
`/* ... */` (TS 形式) は禁止 (CI の `spdx` ジョブはコメント形式ではなく SPDX 文字列の有無のみを検査するため、形式が違っても CI は通るが、規約違反として指摘する)。形式の根拠は references/knowledge 側を参照。
### 2. 命名規約 (Major)
- 共有 / 再利用コンポーネント (`packages/frontend/src/components/` 配下、サブディレクトリ含む) は `Mk` プレフィックス必須 (例: `MkButton.vue`, `global/MkAvatar.vue`, `grid/MkGrid.vue`)。
- ページ固有のものは `pages/` 配下に置き、`Mk` プレフィックスは不要。
**補足:** `<script setup>` SFC は named export を持たないため、「ファイル名と export 名の一致」を機械的に検査することはできない。SFC のデフォルトエクスポートはコンパイラ生成なので、ファイル名規約のみを基準にする。
### 3. `<script>` タグ (Major)
- `<script lang="ts" setup>` または `<script setup lang="ts">` のどちらでもよい (既存コードは多数派が前者だが、後者も `MkThemePreview.vue` 等で使われている)。属性順は指摘しない。`lang="ts"`**無い** ものは指摘する。
- 型ジェネリックが必要なら `generic="T extends ..."` 属性を加える (順序問わず)。
- `defineProps<{ ... }>()` / `defineEmits<{ ... }>()`**type-only** 形式。runtime の object 形式 (`defineProps({ ... })`) は使わない。
- Options API (`export default { data() { ... } }`) は禁止。
### 4. i18n の使い分け (Critical)
- 文字列リテラルの直書き禁止 (テンプレート / JS 両方)。
- 引数なし: `i18n.ts.<path>` (例: `i18n.ts.deleted`)。
- 引数あり: `i18n.tsx.<path>(...)` (関数呼び出し、例: `i18n.tsx.takeOverConfirm({ name })`)。
- 新規 i18n キーは `locales/ja-JP.yml` **のみ** に追加。
- **`locales/ja-JP.yml` 以外の `.yml` 変更があれば即 Critical** (`en-US.yml` 等は Crowdin 自動配信先で、手動編集すると上書き喪失する)。
差分検出:
```bash
BASE=$(git merge-base origin/develop HEAD)
git diff --name-only "$BASE"...HEAD -- 'locales/*.yml' | grep -v 'ja-JP.yml'
```
### 5. スタイル (Major)
- `<style lang="scss" module>` を既定とし、`:class="$style.foo"` で参照する。
- 新規で `<style scoped>` (module なし) は使わない (legacy)。
- **CSS 変数の使用必須** (色・余白・角丸など):
- テーマ色: `var(--MI_THEME-*)` (例: `var(--MI_THEME-panel)`)
- UI 共通: `var(--MI-*)` (例: `var(--MI-radius)`)
- 直接の `#fff` / `rgb(...)` / `rgba(...)` ハードコードは禁止
ハードコード検出:
```bash
BASE=$(git merge-base origin/develop HEAD)
git diff "$BASE"...HEAD -- 'packages/frontend/src/**/*.vue' \
| grep -E '^\+' | grep -E '#[0-9a-fA-F]{3,8}\b|rgba?\('
```
### 6. UI 操作は `os.*` 経由 (Critical)
- 直接の `alert()` / `confirm()` / `window.prompt()` / `window.alert()` は禁止。
- `os.alert` / `os.confirm` / `os.popup` / `os.toast` / `os.popupMenu` / `os.contextMenu` / `os.form` / `os.apiWithDialog` を使う ([os.ts](../../packages/frontend/src/os.ts) 参照)。
検出:
```bash
BASE=$(git merge-base origin/develop HEAD)
git diff "$BASE"...HEAD -- 'packages/frontend/src/**/*.vue' \
| grep -E '^\+' | grep -E '\b(alert|confirm|prompt)\s*\('
```
### 7. アクセシビリティ (Major)
- クリック可能要素は `<button>` か、`role="button"` + `tabindex="0"` + キーボードハンドラ (`@keydown.enter` 等) を実装する。
- 装飾以外の `<div @click>` で a11y 配慮がないものは指摘する。
- フォーム要素には対応する `<label>` または `aria-label` を付ける。
- `:disabled` バインドや `aria-disabled` の整合性を確認する。
### 8. Storybook 併設 (Major)
- 共有 `Mk*` コンポーネントを新規追加した場合、`Mk<Name>.stories.impl.ts` が同階層に併設されているか (サブディレクトリ含む。例: `components/global/MkAvatar.stories.impl.ts`, `components/grid/MkGrid.stories.impl.ts`)。
- **ファイル名は `.stories.impl.ts` 固定** (`.stories.ts` は生成物なので手編集・コミット不可)。
- 既存 [MkButton.stories.impl.ts](../../packages/frontend/src/components/MkButton.stories.impl.ts) を雛形例として参照する。
検出 (新規追加された `Mk*.vue` をサブディレクトリ含めて拾う):
```bash
BASE=$(git merge-base origin/develop HEAD)
git diff --name-only --diff-filter=A "$BASE"...HEAD -- \
'packages/frontend/src/components/**/Mk*.vue' \
| sed 's/\.vue$/.stories.impl.ts/' \
| xargs -I {} sh -c 'test -f {} || echo "missing: {}"'
```
### 9. アイコン (Minor)
- アイコンは Tabler icons クラス (`<i class="ti ti-info-circle">` 等) を使う。
- インライン SVG や別アイコンセットは原則使わない (既存パターンに合わせる)。
### 10. CHANGELOG エントリ (Minor)
ユーザー影響がある変更なら、`CHANGELOG.md``## Unreleased``### Client` に 1 行追加されているか確認する。
```
- Enhance: <component> の <挙動> を改善
- Fix: <component> の <不具合> を修正
```
純粋な内部リファクタなら不要。
## 出力形式
優先度別に以下のフォーマットで出力する。
```
## 🔴 Critical
- packages/frontend/src/components/MkFoo.vue:1
SPDX ヘッダーが HTML コメント形式ではなく TS 形式になっている。
`<!-- ... -->` で書き直すこと。
## 🟡 Major
- ...
## 🔵 Minor
- ...
```
問題のないチェック項目には触れない。全項目クリアなら `✅ レビュー観点上の指摘なし` と短く返す。
## 参照
- [.claude/skills/working-on-frontend/references/tasks/adding-mk-component.md](../skills/working-on-frontend/references/tasks/adding-mk-component.md) — 実装側の手順
- [.claude/skills/working-on-frontend/references/tasks/adding-i18n-key.md](../skills/working-on-frontend/references/tasks/adding-i18n-key.md) — i18n キー追加のルール
- [.claude/skills/working-on-frontend/references/knowledge/component-conventions.md](../skills/working-on-frontend/references/knowledge/component-conventions.md) — SFC 規約・a11y チェックリスト
- [.claude/skills/working-on-frontend/references/knowledge/scss-modules.md](../skills/working-on-frontend/references/knowledge/scss-modules.md) — SCSS Modules / CSS 変数
- [os.ts](../../packages/frontend/src/os.ts) — UI 操作 API
- [MkButton.vue](../../packages/frontend/src/components/MkButton.vue)
- [MkInput.vue](../../packages/frontend/src/components/MkInput.vue) — generic SFC 例
- [MkButton.stories.impl.ts](../../packages/frontend/src/components/MkButton.stories.impl.ts) — Storybook 雛形
- [AGENTS.md](../../AGENTS.md) — SPDX / locales 編集制限 / CHANGELOG 書式などの最低限ルール (Codex / Copilot と共通)

View File

@@ -1,18 +0,0 @@
# `.claude/commands/` — プロジェクト固有のスラッシュコマンド
Misskey 開発で繰り返し使うワークフローを `/command-name` で呼び出せるよう、`.claude/commands/<name>.md` 形式で配置している。
実装済コマンドの一覧は本ファイルでは管理しない (腐敗するため)。各 `<name>.md` の frontmatter (`description`) が自己説明として機能する。
現状残っているのは ECC ([everything-claude-code](https://github.com/affaan-m/everything-claude-code)) 由来の MIT ライセンスコマンドのみで、Misskey 固有のスラッシュコマンドは廃止して `.claude/skills/` 配下のスキルに統合した。MIT 出典は [.claude/THIRD_PARTY_LICENSES.md](../THIRD_PARTY_LICENSES.md) を参照。
## 設計方針
- Misskey 固有のワークフローは原則 `.claude/skills/` に統合する (description で自動索引されるため。コマンドはユーザーが `/name` でタイプしないと起動しない)
- 既存の `superpowers` / `pr-review-toolkit` などのプラグイン提供スラッシュコマンドで足りる場合は新規追加しない
## 新規コマンドを追加する場合 (どうしてもスキルでは表現できない時のみ)
- frontmatter には最低限 `description` を指定する。引数を取るなら `argument-hint`、可能なら `allowed-tools` も指定する (permission prompt を最小化するため)
- 長時間ビルド (2 分超) を伴うコマンドはインライン `` !`<cmd>` `` を使わず、本文で `Bash` ツール呼び出し時の `timeout` を指示する
- 主要参照ファイルへのリンクは、各コマンド markdown からの相対パスで貼る。絶対パスは contributor のホームディレクトリ依存になるので使わない

View File

@@ -1,146 +0,0 @@
---
description: Misskey の .claude/ ハーネス (skills/agents/commands) を 7 カテゴリで採点する確定的な監査。
argument-hint: "[repo|skills|commands|agents]"
---
<!--
SPDX-License-Identifier: MIT
SPDX-FileCopyrightText: 2026 Affaan Mustafa and everything-claude-code contributors
出典 (upstream): https://github.com/affaan-m/everything-claude-code (v2.0.0-rc.1)
upstream path: commands/harness-audit.md
upstream license: MIT — https://github.com/affaan-m/everything-claude-code/blob/main/LICENSE
project-level notice: see .claude/THIRD_PARTY_LICENSES.md (Misskey 内サードパーティ一覧 + MIT 全文)
Imported into Misskey .claude/ on 2026-05-10. The 7-category rubric and output contract are derived from the upstream ECC version (MIT). The runtime layer was substantially reimplemented for Misskey: the upstream relies on scripts/harness-audit.js to mechanically score, while this version asks Claude to score directly with pnpm/git/grep, and adds Misskey-specific evaluation axes (SPDX coverage / endpoint-list 登録漏れ / migration 順序 / ja-JP.yml 整合).
note: 元 ECC 版は scripts/harness-audit.js (専用 Node スクリプト) で機械採点していたが、Misskey は ECC plugin runtime に依存しない方針なので、Claude が直接ファイルを読んで採点する手動運用版に書き換えた。Misskey 固有の重要観点 (SPDX 適用率 / endpoint-list 登録漏れ / migration 順序 / ja-JP.yml 整合) を評価軸として明示的に組み込んでいる。
-->
# /harness-audit — Misskey ハーネス監査
Misskey リポジトリの `.claude/` 構成を 7 カテゴリで採点し、改善優先度を提示する。
## Usage
`/harness-audit [scope]`
- `scope` (任意): `repo` (default) / `skills` / `commands` / `agents`
## 評価カテゴリ (各 0-10)
| # | カテゴリ | 評価軸 |
| --- | --- | --- |
| 1 | Tool Coverage | skill / agent / command の数、欠けているワークフロー段、重複なし |
| 2 | Context Efficiency | frontmatter description の冗長度、SKILL.md の長さ分布、重複情報、CLAUDE.md の肥大化 |
| 3 | Quality Gates | Stop / PreToolUse / PostToolUse hook の整備、`/quality-gate` 等の完了前ゲートの有無、自動 lint/typecheck |
| 4 | Memory Persistence | `.claude/skills/*/SKILL.md``references/` の同期状態を評価。プロジェクト側 `.claude/memory/` は未採用方針 (auto-memory はユーザーホーム側で自動運用) のため、ここを採点起点にせず既定 5/10 から開始する |
| 5 | Eval Coverage | `working-on-backend` / `working-on-frontend` の testing リファレンス (backend-testing.md / frontend-testing.md) の網羅、Misskey 固有の e2e/fed/Storybook/Cypress 適用ガイド |
| 6 | Security Guardrails | SPDX 規約適用、migration 不変性ルール、ja-JP.yml 限定編集ルール、secrets 検出 |
| 7 | Cost Efficiency | enabledPlugins の重複・過剰、context-budget の整備、MCP 過剰登録なし |
## Misskey 固有の確認項目 (採点根拠コマンド)
採点時に以下を実コマンドで確認する。各項目の **属するカテゴリ** は項目内に明記する (#1-#3 は Security Guardrails、#4 は Tool Coverage、#5 は Quality Gates):
```bash
# 1. [Security Guardrails] SPDX 適用率 (新規ファイル想定の汎用チェック)
# - node_modules を prune で除外
# - packages/misskey-js は MIT サブパッケージなので AGPL ヘッダーを持たない (AGENTS.md §1) → 除外
# - built/ なども除外
# 候補にはなお *.config.{ts,js} / *eslint* / *.d.ts のような CI 上 SPDX 対象外
# (.github/workflows/check-spdx-license-id.yml の exclude 参照) も混ざるため、
# 上位に出たファイルが「新規追加した実コード」かどうかは目視判定する。
find packages \
\( -type d \( -name node_modules -o -name built -o -name dist -o -path 'packages/misskey-js' \) -prune \) \
-o -type f \( -name '*.ts' -o -name '*.js' -o -name '*.vue' -o -name '*.scss' \) -print \
| xargs -r grep -L 'SPDX-License-Identifier: AGPL-3.0-only' | head -20
# → 上位に新規実コードが無ければ満点
# 2. [Security Guardrails] ja-JP.yml 以外の locales が直近で手動編集されていないか
# --pretty=format: でコミットヘッダ行を抑止し、ファイル名行のみを残してから grep する。
# Crowdin の自動同期 commit でも他言語 yml は更新されるため、出力が 0 行になることは少ない。
# 出力があった場合は、author / commit message を確認し Crowdin 由来か手動編集かを判定する:
# git log --since='30 days ago' --pretty=format:'%h %an %s' -- locales/<file>.yml
git log --since='30 days ago' --pretty=format: --name-only -- 'locales/*.yml' \
| grep -v '^$' | grep -v 'ja-JP.yml' | sort -u
# → 出力が無い、または全て Crowdin 由来 commit なら満点
# 3. [Security Guardrails] migration の pending DDL 検査 (TypeORM schema builder)
pnpm --filter backend check-migrations
# → 0 errors (= "All migrations are clean.") なら満点
# 4. [Tool Coverage] endpoint-list.ts 登録漏れ (新規 endpoint がリストにない場合)
# endpoints/ は再帰構造 (notes/create.ts, admin/announcements/create.ts 等) で 400+ ファイルあるため、
# endpoint-list.ts も `export * as '<category>/<name>' from './endpoints/<category>/<name>.js';` 形式で
# 1 ファイル 1 行登録される。両者の行数を「再帰 .ts 数」と「export * as 行数」で比較する。
# e2e / 単体テストは endpoint ではないので *.test.ts を除外する。
endpoint_files=$(find packages/backend/src/server/api/endpoints -type f -name '*.ts' ! -name '*.test.ts' | wc -l)
list_entries=$(grep -cE "^export \* as " packages/backend/src/server/api/endpoint-list.ts)
echo "endpoints (recursive): $endpoint_files / endpoint-list.ts entries: $list_entries"
# 差分が 0 なら満点。差分が出たら、登録漏れの具体特定:
comm -23 \
<(find packages/backend/src/server/api/endpoints -type f -name '*.ts' ! -name '*.test.ts' \
| sed -E 's|.*/endpoints/||;s|\.ts$||' | sort -u) \
<(grep -oE "^export \* as '[^']+'" packages/backend/src/server/api/endpoint-list.ts \
| sed -E "s/^export \* as '([^']+)'/\1/" | sort -u)
# 出力された行が登録漏れの endpoint。0 行なら満点。
# 5. [Quality Gates] console.log の混入
grep -rn 'console\.\(log\|debug\)' packages/backend/src packages/frontend/src 2>/dev/null \
| grep -v 'node_modules\|test\|.spec\.\|.test\.' | wc -l
# → 0 が理想
```
## 出力契約
以下を返す:
1. `overall_score` / `max_score` (repo は 70 点満点)
2. カテゴリごとのスコア + 具体的な根拠
3. 失敗チェック項目と該当ファイルパス
4. Top 3 改善アクション
5. 次に適用を推奨する skill / 手順
## サンプル出力
```text
Harness Audit (repo): 55/70
Tool Coverage: 9/10 (skills 5, agents 2, commands 5 — 偏りなし)
Context Efficiency: 8/10 (description 平均 3-5 行、肥大なし)
Quality Gates: 5/10 (Stop hook 共有設定に未登録 / `/quality-gate` あり)
Memory Persistence: 5/10 (プロジェクト側 memory/ 未採用方針 = 既定値)
Eval Coverage: 7/10 (backend/frontend testing リファレンス網羅、Storybook 一部抜け)
Security Guardrails: 10/10 (SPDX 100%, locales OK, migrations clean)
Cost Efficiency: 8/10 (context-budget 導入済 / MCP 0)
Failed Checks:
- packages/frontend/src/.../X.vue で SPDX 欠落 (Security Guardrails)
- console.log が backend に 3 件 (Quality Gates)
- 共有 Stop hook なし (Quality Gates) — 各 contributor が `.claude/settings.local.json` で opt-in する方針なら減点しなくて良い
Top 3 Actions:
1) [Security Guardrails] SPDX 欠落 1 ファイルを修正:
packages/frontend/src/.../X.vue
2) [Quality Gates] backend の console.log 3 件を logger に置換。
git grep "console\.log" packages/backend/src
3) [Cost Efficiency] enabledPlugins から未使用のものを外す。
`.claude/settings.json` の `enabledPlugins` と実プロジェクト利用状況を照合。
Suggested next skills to apply:
- /quality-gate で完了前に lint + unit test を回す
- context-budget で plugin 由来の overhead を確認
```
## 採点の信頼性
- 確定的: 同じ commit / 同じ `.claude/` 構成なら同じスコア
- ヒューリスティクス: 「description の冗長度」のような主観項目は同一基準で機械的に判定
- スクリプト不要: `pnpm``git``grep`/`find` 等の標準ツールのみ
## 参考: ECC オリジナルとの差分
- ECC 版は `node scripts/harness-audit.js` を直叩きする運用で、ECC リポジトリ全体に閉じた採点だった。
- Misskey 版は **Misskey の規約 (SPDX/migration/locales/endpoint-list)** を Security 採点に組み込み、`pnpm` ベースの実コマンドで根拠を取る方式に再設計。
- 結果として ECC への依存はゼロ。

View File

@@ -1,123 +0,0 @@
---
description: Misskey の lint / typecheck / 高速テストを順に実行して品質ゲートを通すコマンド。完了前の軽量検証用。
argument-hint: "[repo|backend|frontend|<path/to/file.ts>]"
---
<!--
SPDX-License-Identifier: MIT
SPDX-FileCopyrightText: 2026 Affaan Mustafa and everything-claude-code contributors
出典 (upstream): https://github.com/affaan-m/everything-claude-code (v2.0.0-rc.1)
upstream path: commands/quality-gate.md
upstream license: MIT — https://github.com/affaan-m/everything-claude-code/blob/main/LICENSE
project-level notice: see .claude/THIRD_PARTY_LICENSES.md (Misskey 内サードパーティ一覧 + MIT 全文)
Imported into Misskey .claude/ on 2026-05-10. Pipeline 概念 (lint → typecheck → test) は upstream ECC 版から借用 (MIT)。実コマンド層は Misskey の pnpm + tsgo + ESLint + Vitest に固定し、formatter (Prettier/Biome) フェーズは削除した。
note: 元 ECC 版は言語自動判定 + format/lint/type のジェネリック版だったが、Misskey 専用に pnpm + tsgo + ESLint + Vitest の組み合わせに固定。重い test:e2e / test:fed は含まない (CI 側で実行される)。
-->
# /quality-gate — Misskey 軽量品質ゲート
`/quality-gate [scope]`
完了前の **軽量** 品質チェック。重い E2E / 連合テスト (test:e2e / test:fed / Cypress) は CI 側で実行されるため、本コマンドには含めない。
## Scope
- `repo` (default) — 全パッケージ
- `backend``packages/backend` のみ
- `frontend``packages/frontend` のみ
- `path/to/file.ts` — 単一ファイルへの ESLint --fix のみ
## Pipeline
### Repo scope (全部)
各パッケージの `lint` スクリプト実体は `pnpm typecheck && pnpm eslint` ([packages/backend/package.json](../../packages/backend/package.json), [packages/frontend/package.json](../../packages/frontend/package.json)) で、ルートの `pnpm lint``pnpm --no-bail -r lint` (= 全パッケージで lint を `--no-bail` で実行)。**typecheck は lint に含まれている**ため、通常はこの 2 コマンドで十分:
```bash
# 1. Lint (= typecheck + ESLint、全パッケージ。--no-bail で最初の失敗で止まらず全結果を集める)
pnpm lint
# 2. Unit test (高速、e2e は含まない)
pnpm --filter backend test
pnpm --filter frontend test
```
#### 詳細を分けて見たい時のみ (optional)
lint がまとめて失敗していて typecheck の結果だけ単独で見たい場合は、以下を個別に回す。**通常は不要** (lint の出力を読めば足りる):
```bash
pnpm --filter backend typecheck # tsgo 単体
pnpm --filter frontend typecheck # vue-tsc 単体 (Vue SFC の型を見るため)
```
### Backend scope
`pnpm --filter backend lint` は内部で `pnpm typecheck && pnpm eslint` を実行する ([packages/backend/package.json](../../packages/backend/package.json)) ので、`lint` を回せば typecheck も終わる。軽量ゲートでは typecheck の二重実行を避けるため `lint` + `test` のみ:
```bash
pnpm --filter backend lint
pnpm --filter backend test
```
`tsgo` の出力を単独で見たい時のみ optional で `pnpm --filter backend typecheck` を別途回す。
### Frontend scope
`pnpm --filter frontend lint` も内部で `pnpm typecheck && pnpm eslint` を実行する ([packages/frontend/package.json](../../packages/frontend/package.json)) ため、軽量ゲートでは Backend 同様に `lint` + `test` のみ:
```bash
pnpm --filter frontend lint
pnpm --filter frontend test
```
`vue-tsc` の出力を単独で見たい時のみ optional で `pnpm --filter frontend typecheck` を別途回す。
### Single file scope
```bash
pnpm exec eslint --fix <path>
```
## Output
実行したフェーズの pass/fail と件数を集計する。標準パイプラインは `pnpm lint` (typecheck 内包) と unit test のみなので、デフォルトの出力は以下のようになる:
```text
Quality Gate (repo):
Lint: PASS (0 errors, 2 warnings)
Backend ut: PASS (412/412)
Frontend ut: PASS (87/87)
→ 完了前の軽量チェック OK。重い e2e / 連合テストは CI 側で実行される。
```
`#### 詳細を分けて見たい時のみ (optional)` で個別 typecheck (`pnpm --filter backend typecheck` / `pnpm --filter frontend typecheck`) も回した場合のみ、その結果を追加行として表示する:
```text
Quality Gate (repo):
Lint: PASS (0 errors, 2 warnings)
Backend tc: PASS (0 errors) # optional 実行時のみ
Frontend tc: PASS (0 errors) # optional 実行時のみ
Backend ut: PASS (412/412)
Frontend ut: PASS (87/87)
```
失敗時は最初に落ちたフェーズで停止して詳細を見せる。
## 関連 skill / コマンド
- [`shipping-misskey-change` スキル](../skills/shipping-misskey-change/SKILL.md) — commit / PR 直前の最終チェックリスト (misskey-js 再生成 / SPDX / CHANGELOG 等)
- [`shipping-misskey-change/references/tasks/regenerate-misskey-js.md`](../skills/shipping-misskey-change/references/tasks/regenerate-misskey-js.md) — API 変更時の `pnpm build-misskey-js-with-types` 実行手順
- [.github/copilot-instructions.md §Validation コマンド](../../.github/copilot-instructions.md) — pnpm コマンド一覧 (Copilot / Codex 向けに再掲)
## 元 ECC 版との差分
- ジェネリックな言語自動判定を排除し、Misskey 固定 pipeline に。
- formatter フェーズなし (Misskey は ESLint --fix のみ採用)。
- e2e / federation / Cypress は重いため除外し CI 側に委譲。

View File

@@ -1,18 +0,0 @@
{
"enabledPlugins": {
"frontend-design@claude-plugins-official": true,
"superpowers@claude-plugins-official": true,
"context7@claude-plugins-official": true,
"code-review@claude-plugins-official": true,
"code-simplifier@claude-plugins-official": true,
"github@claude-plugins-official": true,
"skill-creator@claude-plugins-official": true,
"feature-dev@claude-plugins-official": true,
"claude-md-management@claude-plugins-official": true,
"typescript-lsp@claude-plugins-official": true,
"security-guidance@claude-plugins-official": true,
"pr-review-toolkit@claude-plugins-official": true,
"claude-code-setup@claude-plugins-official": true,
"playwright@claude-plugins-official": true
}
}

View File

@@ -1,32 +0,0 @@
# `.claude/skills/` — プロジェクト固有のカスタムスキル
Misskey 固有の繰り返しタスクを Claude にスムーズに実行させるための **カスタムスキル**`.claude/skills/<name>/SKILL.md` 形式で配置する。
frontmatter (`name` + `description`) は、Claude が **自動でスキルを呼び出すか判断する** 唯一の手がかりになる。`description` には用途を具体的かつ網羅的に書き、pushy なトリガー語 (例: "Use whenever ...", "Must be consulted before any ...") で発見されやすくする。
実装済スキルの一覧は本ファイルでは管理しない (腐敗するため)。各サブディレクトリの `SKILL.md` の frontmatter が自己説明として機能する。
## 構成方針
Anthropic 公式の [Agent Skills ベストプラクティス](https://platform.claude.com/docs/ja/agents-and-tools/agent-skills/best-practices) に従い、以下の構造を採用する:
- **SKILL.md 本体は 500 行以下** (理想は 30-80 行の索引)
- 詳細は `references/tasks/` (手順) と `references/knowledge/` (規約・背景知識) に分離 (progressive disclosure)
- リンクは原則 **references への 1 段リンク** に留める (例外: 他 skill / agent への導線は可)
- ファイルシステム上の references は読まれるまでゼロコンテキストコスト
ECC (everything-claude-code) 由来の MIT スキルが含まれる場合は、ファイル冒頭の SPDX ヘッダー + [.claude/THIRD_PARTY_LICENSES.md](../THIRD_PARTY_LICENSES.md) §1 に出典を記載する。
## 新規スキルを追加する場合
- `.claude/skills/<name>/SKILL.md` に YAML frontmatter (`name` + `description`) と本文 Markdown を書く
- description は **三人称の "Use when ..." 形式** で、主要キーワード網羅。pushy なトリガー語 ("Must be consulted before ...") を入れる
- `disable-model-invocation: true` は付けない (auto-invoke させたいため)
- 主要参照ファイルへのリンクは、各 markdown ファイルからの相対パスで貼る (`../../../../packages/backend/...` のような形)。絶対パスは contributor のホームディレクトリ依存になるので使わない
- 詳細を分ける場合は `references/tasks/` (手順) / `references/knowledge/` (知識) の二分に従う
- スキル作成は `/skill-creator` (公式の skill-creator スキル) のガイドを経由するのが推奨
## 関連
- 各スキルの description で自動索引される設計のため、実装済スキルの手書き索引 (一覧表) は本ファイルにも `AGENTS.md` にも持たない方針 (手書き索引は腐敗するため、frontmatter の description を唯一の索引とする)
- スキルそのものの健全性検査は [/harness-audit](../commands/harness-audit.md) で採点できる

View File

@@ -1,148 +0,0 @@
---
name: context-budget
description: Claude Code セッションのコンテキスト窓消費を agents/skills/MCP/rules/CLAUDE.md ごとに見える化し、肥大化と冗長コンポーネントを検出して節約候補を提示する。"コンテキスト消費を見せて"、"context budget"、"context audit"、"トークン内訳"、"これ以上 MCP 入る?" 等の発話で起動する。
---
<!--
SPDX-License-Identifier: MIT
SPDX-FileCopyrightText: 2026 Affaan Mustafa and everything-claude-code contributors
出典 (upstream): https://github.com/affaan-m/everything-claude-code (v2.0.0-rc.1)
upstream path: skills/context-budget/SKILL.md
upstream origin frontmatter: ECC
upstream license: MIT — https://github.com/affaan-m/everything-claude-code/blob/main/LICENSE
project-level notice: see .claude/THIRD_PARTY_LICENSES.md (Misskey 内サードパーティ一覧 + MIT 全文)
Imported into Misskey .claude/ on 2026-05-10 as a standalone copy (no dependency on the ECC plugin runtime). description was rewritten in Japanese and a "Misskey 固有メモ" section was appended; body content remains MIT-licensed.
note: Misskey の skills/agents 数は少ないので、MCP / CLAUDE.md / プラグイン由来の overhead が支配的になりやすい点に留意。
-->
# Context Budget
セッション内に読み込まれるコンポーネント (agents / skills / rules / MCP servers / CLAUDE.md) の token overhead を分析し、空き context を回復する具体策を提示する。
## 使う場面
- セッションが重い・出力品質が落ちてきた感覚がある
- 直近で skills / agents / MCP server を多数追加した
- 残りの context headroom を知りたい
- 追加コンポーネントを入れる前に空きを確認したい
- 「context-budget」「token 内訳」等のキーワードでユーザーが明示的に要請した時 (Misskey リポジトリにはこの名前のスラッシュコマンドは登録していない — 本 skill は名前 / description マッチで auto-invoke される想定。実装済の slash command 一覧は [.claude/commands/](../../commands/) を参照)
## 仕組み
### Phase 1: Inventory
各コンポーネントを走査して token を推定する。
**Agents** (`.claude/agents/*.md`)
- 行数とトークン数 (`words × 1.3`) を計算
- frontmatter `description` の長さを抽出
- フラグ: 200 行超 (重い)、description 30 word 超 (frontmatter 肥大)
**Skills** (`.claude/skills/*/SKILL.md`)
- SKILL.md ごとに token を計算
- フラグ: 400 行超
- `.agents/skills/` 等の重複コピーは除外
**Rules** (リポジトリルートの `AGENTS.md` + `.claude/` から `@-import` されるファイル)
- ファイル単位で token 計算
- フラグ: 100 行超
- 同一言語モジュール内の内容重複を検出
**MCP Servers** (`.mcp.json` または有効 MCP 設定)
- server 数と総 tool 数
- schema overhead をツールあたり ~500 token で見積もる
- フラグ: 20 tool 超のサーバー、`gh` / `git` / `npm` 等の CLI を単純ラップしただけのサーバー
**CLAUDE.md** (project + user-level)
- ファイルごとに token を計算
- フラグ: 合計 300 行超
### Phase 2: Classify
| バケット | 判定基準 | 行動 |
|--------------------|-------------------------------------------------------------|-----------------------------------|
| **Always needed** | CLAUDE.md から参照されている / 有効コマンドの裏 / 現プロジェクトと一致 | 維持 |
| **Sometimes needed** | ドメイン依存 (例: 言語パターン)、CLAUDE.md 参照なし | オンデマンド有効化を検討 |
| **Rarely needed** | コマンド参照なし、内容重複、明確な用途なし | 削除または lazy-load |
### Phase 3: Detect Issues
- **Bloated agent description** — frontmatter description が 30 word 超だと、Task ツール起動のたびに毎回ロードされる
- **Heavy agents** — 200 行超は Task ツールの context を毎回膨らませる
- **Redundant components** — agent ロジックを重複する skill、CLAUDE.md と重複する rule
- **MCP over-subscription** — 10 server 超、または CLI 代用可能なサーバー
- **CLAUDE.md bloat** — 冗長説明、古いセクション、rule に移すべき指示
### Phase 4: Report
```
Context Budget Report
═══════════════════════════════════════
Total estimated overhead: ~XX,XXX tokens
Context model: <現在モデル名> (<window>K window) ← 例: Claude Opus 4.7 (1M), Claude Sonnet (200K)
Effective available context: ~XXX,XXX tokens (XX%)
Component Breakdown:
┌─────────────────┬────────┬───────────┐
│ Component │ Count │ Tokens │
├─────────────────┼────────┼───────────┤
│ Agents │ N │ ~X,XXX │
│ Skills │ N │ ~X,XXX │
│ Rules │ N │ ~X,XXX │
│ MCP tools │ N │ ~XX,XXX │
│ CLAUDE.md │ N │ ~X,XXX │
└─────────────────┴────────┴───────────┘
WARNING: Issues Found (N):
[token 節約量の降順]
Top 3 Optimizations:
1. [action] → save ~X,XXX tokens
2. [action] → save ~X,XXX tokens
3. [action] → save ~X,XXX tokens
Potential savings: ~XX,XXX tokens (XX% of current overhead)
```
verbose mode ではさらにファイルごとの token 内訳、最重ファイルの行単位ブレークダウン、重複行の対比、MCP tool 一覧 + tool ごとの schema サイズ推定を出す。
## 例
**基本監査**
```
User: コンテキスト消費を見せて
Skill: 16 agents (12,400 tokens), 28 skills (6,200), 87 MCP tools (43,500), 2 CLAUDE.md (1,200)
Flags: 重い agent 3 個、CLI 代用可能な MCP 3 個
Top saving: MCP 3 個削除 → -27,500 tokens (overhead の 47% 削減)
```
**Verbose**
```
User: トークン内訳をファイル単位で
Skill: 上記レポートに加えて、planner.md (213 lines, 1,840 tokens) のような
per-file 行内訳、MCP tool ごとのサイズ、rule の重複行を side-by-side で表示
```
**追加前チェック**
```
User: MCP server を 5 個追加したいが、空きある?
Skill: 現状 33% → 5 server (≈ 50 tools) 追加で +25,000 tokens → 45% に到達
推奨: CLI 代用可能な server 2 個を先に外して 40% 以下を維持
```
## ベストプラクティス
- **トークン推定**: prose は `words × 1.3`、code 主体は `chars / 4`
- **MCP は最大のレバー**: tool あたり ~500 token、30-tool server ひとつで全 skill より大きい
- **agent description は常時ロード**: 呼ばれない agent でも description は毎 Task 投入
- **verbose は debug 用**: 普段は使わない
- **変更後は監査**: agent/skill/MCP 追加直後に走らせて creep を早期発見
## Misskey 固有メモ
- Misskey は MCP server をプロジェクトで明示登録していないため (`.mcp.json` 不在)、現状 overhead の支配項は CLAUDE.md と公式プラグイン群の skills / agents description である。
- ECC プラグインがユーザースコープで `installed_plugins.json` に存在するため、プロジェクトで `enabledPlugins` に追加していなくても system reminder に 200+ skill が現れる。これらは description が短いので個別 overhead は小さいが、合計値の確認に本 skill を使う。

View File

@@ -1,33 +0,0 @@
---
name: shipping-misskey-change
description: Use at every "finish" moment of a Misskey change — immediately before committing, opening a PR, merging, or handing the work back to the user even without a commit. Runs the final pre-ship checklist — `pnpm lint`, misskey-js regeneration (`pnpm build-misskey-js-with-types`) when backend API changed, `pnpm --filter backend check-migrations` when entities or migrations changed, SPDX header verification on new files, locale safety check (no edits to non-`ja-JP` locale yml files), and `CHANGELOG.md` Unreleased entry for user-visible changes. Must be consulted as the last step of every change — including uncommitted handoffs — to avoid CI failures and lost translations. This is NOT waived by having already invoked brainstorming, writing-plans, or any other upstream skill — invoke this regardless of what preceded it.
---
# shipping-misskey-change
Misskey の変更の **finish 局面** (commit / PR / merge する直前、またはコミットせずユーザーに作業を返す直前) に必ず走らせる最終チェックリスト。
CI で落ちやすい / レビュアーから指摘されやすいポイントを 1 箇所に集めている。後で references を辿る余裕を作らないため、チェックリストは SKILL.md 本体に直書きする。
**他スキル実行後も免除されない。** `brainstorming` / `writing-plans` / その他アップストリームスキルを先に呼んでいても、作業を返す直前・commit 直前のタイミングでこのスキルを呼ぶこと。
## 最終チェックリスト
このリストを TodoWrite に展開して 1 項目ずつ確認すること。**該当しない項目は飛ばして良いが、判断は明示する**。
- [ ] lint が通る — ECC 由来の [/quality-gate](../../commands/quality-gate.md) コマンドで lint (typecheck + eslint) + 高速テストをまとめて回すのが基本。lint だけ単発で確認したいなら `pnpm lint` 直接でもよい
- [ ] backend で `meta` / `paramDef` / `res` を変更した → `pnpm build-misskey-js-with-types` を実行して `packages/misskey-js/src/autogen/` の差分も commit に含めた → 詳細手順は [references/tasks/regenerate-misskey-js.md](references/tasks/regenerate-misskey-js.md)
- [ ] エンティティ (`packages/backend/src/models/*.ts``@Column` / `@Entity` / `@Index`) を変更した → `pnpm --filter backend check-migrations` が pending DDL 0 件で通る
- [ ] migration ファイルを追加した → `up()``down()` の両方を実装した / 既存のマージ済 migration は一切触っていない
- [ ] 新規 `.ts` / `.js` / `.cjs` / `.mjs` / `.vue` / `.scss` / `.html` ファイルを追加した → SPDX ヘッダーを付けた (`.vue` / `.html` は HTML コメント形式、その他は TS コメント形式)
- [ ] `locales/` を編集した → **`ja-JP.yml` だけ** を変更しており、他言語 yml の diff は出ていない (`git diff --name-only develop -- 'locales/*.yml' | grep -v '^locales/ja-JP\.yml$'` が空)
- [ ] ユーザーから見える変更 (機能追加 / 既存挙動変更) → `CHANGELOG.md``## Unreleased` 直下の該当サブセクション (General / Client / Server) に 1 行追記した → 詳細書式は [references/tasks/changelog-update.md](references/tasks/changelog-update.md)
- [ ] backend API endpoint を追加・変更した → [misskey-api-reviewer](../../agents/misskey-api-reviewer.md) agent を Task で起動して機械レビューする (endpoint-list 登録漏れ / misskey-js 再生成漏れ / meta・UUID / SPDX。lint や CI では拾いにくい 404・登録漏れの最終関門なので、該当する変更があれば飛ばさない)
- [ ] frontend の `.vue` を追加・変更した → [vue-component-reviewer](../../agents/vue-component-reviewer.md) agent を Task で起動して機械レビューする (SPDX 形式 / 命名 / i18n / SCSS 変数 / os.* / a11y / Storybook 併設)
- [ ] (任意) `.claude/` ハーネス自体の健全性を確認したい → ECC 由来の [/harness-audit](../../commands/harness-audit.md) コマンドを実行
## 何のためのスキルか
これは「**作業中に何を作るか**」を決めるスキルではなく、「**作り終わった後に CI を通す**」スキル。`working-on-backend` / `working-on-frontend` から始まった作業の **出口** として機能する。
該当する変更がある場合は各 references/tasks/ を Read して詳細手順を踏むこと。`pnpm lint` だけは references を読まずに直接走らせて良い (`/quality-gate` でまとめて回せる)。

View File

@@ -1,61 +0,0 @@
# CHANGELOG.md の Unreleased セクションに 1 行追記する
ユーザー影響のある変更 (機能追加・修正・改善) は `CHANGELOG.md` の冒頭 `## Unreleased` セクションに 1 行追加する。リファクタリング等の内部変更は不要。
## セクション構造
`## Unreleased` 配下に **3 つのサブセクション** が用意されている:
- `### General` — 共通 / 横断的な変更
- `### Client``packages/frontend`
- `### Server``packages/backend`
## エントリ書式
該当サブセクションに `- <Prefix>: <概要>` の形式で追加。Prefix は先頭大文字。
```text
- Enhance: ノートの詳細表示での公開範囲の表示を改善
- Fix: 通知が約10秒遅延する問題を修正
- Feat: 新機能の追加
```
| Prefix | 用途 |
|---|---|
| `Feat:` | 新機能の追加 |
| `Enhance:` | 既存機能の改善 |
| `Fix:` | バグ修正 |
| `Note:` | 機能変更ではないが利用者に知らせたい事項 (設定の初期化・config 項目の追加・非互換な挙動変更など) |
`Note:` は Feat / Enhance / Fix のような変更そのものではなく、「アップデート後に利用者が知っておくべき注意」を伝えるためのもの (例: `- Note: アップデート後、サウンドに関する設定が初期化されます`)。該当サブセクション内に `- Note: ...` として置く。リリースによっては `## <version>` 直下に `### Note` 専用サブセクションを設ける形もある (既存履歴に両パターンあり)。新規追加時は近傍の既存エントリの書き方に合わせる。
## 触ってはいけない範囲
- `## Unreleased` **以外** のセクション (過去リリース) は変更しない
- `## Unreleased` の見出しと 3 つの空サブセクション骨格自体は維持する (リリーススクリプトが期待する構造)
## 作業手順 (手で書く場合)
1. `CHANGELOG.md` を開いて `## Unreleased` セクションを探す
2. 対象サブセクション (`### General` / `### Client` / `### Server`) の状態を確認
- **空 (placeholder のみ)**: 見出し直下に `-` 単独行のみがある → これを `- Feat: ...` 等で **置換**
- **既存エントリあり**: `- Enhance: ...` / `- Fix: ...` 等の行が 1 つ以上ある → 既存エントリ群の **末尾****追記**
3. 順序入れ替えはしない (差分レビューしやすさのため)
4. `git diff CHANGELOG.md` で 1 行のみ追加されていることを確認
## 例
| 引数イメージ | 結果 |
|---|---|
| server, `Fix: 通知が遅延する問題を修正` | `### Server` 末尾に `- Fix: 通知が遅延する問題を修正` を追記 |
| client, `Enhance: ノートの表示を改善` | `### Client` 末尾に `- Enhance: ノートの表示を改善` を追記 |
| general, `Feat: 新機能の追加` | `### General` の placeholder `-``- Feat: 新機能の追加` で置換 |
## コミットメッセージ書式との違い
CHANGELOG とコミットメッセージは **書式が異なる**:
- CHANGELOG: `- Enhance: ノートの表示を改善` (先頭大文字の英語 Prefix + コロン + 日本語本文)
- コミットメッセージ: `enhance(frontend): improve note display` (小文字 + スコープ + コロン + 英語本文。詳細は [CONTRIBUTING.md](../../../../../CONTRIBUTING.md))
両方を 1 つの PR で更新するときに混同しないこと。

View File

@@ -1,78 +0,0 @@
# misskey-js の自動生成型を再生成する
backend の API endpoint やスキーマ (`meta` / `paramDef` / `res`) を変更した後、`packages/misskey-js/src/autogen/` の自動生成型を最新化するための手順。
**忘れると CI の `check-misskey-js-autogen` で必ず落ちる**。最頻ミスのひとつ。
## いつ実行するか
以下のいずれかに該当する変更を加えたとき:
- 新規エンドポイント追加 (`packages/backend/src/server/api/endpoints/<category>/<name>.ts`)
- 既存エンドポイントの `meta` (errors / res / kind / requireCredential 等) を変更
- 既存エンドポイントの `paramDef` (入力 schema) を変更
- packed entity (`packages/backend/src/models/json-schema/*.ts`) を変更
実質「`packages/backend/src/server/api/` 配下を触ったら必ず」と考えてよい。
## 実行コマンド
```bash
# リポジトリルートから実行する
pnpm build-misskey-js-with-types
```
内部で以下が一括実行される:
1. backend ビルド (`pnpm --filter backend build`)
2. OpenAPI spec 生成 (`packages/backend/built/api.json`)
3. misskey-js 用 schema 生成 (`packages/misskey-js/generator/api.json`)
4. misskey-js の TypeScript 型再生成 (`packages/misskey-js/src/autogen/{types,entities,endpoint,models,apiClientJSDoc}.ts`)
5. misskey-js ビルド + API extractor
実行時間は 1-3 分程度。タイムアウト警告が出る場合は `--timeout=600000` 相当の長めの設定を使う。
## 実行後の確認
```bash
# 何が変わったかを軽く確認
git status --short -- packages/misskey-js/
git diff --stat -- packages/misskey-js/src/autogen/
# 内容を見たい場合
git diff -- packages/misskey-js/src/autogen/
```
## 差分のパターン
- **差分なし** → backend の変更は misskey-js の公開型に影響していない (内部リファクタなど)。追加コミット不要
- **差分あり** → `packages/misskey-js/src/autogen/` 配下のファイルを **必ず commit に含める**
```bash
git add packages/misskey-js/src/autogen/
```
`api.json` の差分が大きい場合は、API endpoint 側の `meta` / `paramDef` / `res` 定義が想定通りか確認する。
## 注意
- このコマンドは **backend 編集後の確認** が目的。backend を変更していないのに走らせるとビルドキャッシュ次第で no-op になる
- 実行中は `packages/backend/built/` や `packages/misskey-js/built/` などの中間生成物が更新されるが、これらは `.gitignore` 対象
- 生成物以外 (`packages/misskey-js/src/` のうち `autogen/` 以外) に予期せぬ差分が出た場合は、ローカルの編集が混入している可能性があるため、一旦中止して原因を調査する
- `packages/misskey-js/` 配下は **MIT ライセンスのサブパッケージ** なので、`autogen/` ファイルには AGPL の SPDX ヘッダーを付けない / 不要
## CI で落ちた場合のメッセージ例
```
CI: check-misskey-js-autogen
> Please regenerate misskey-js by running:
> pnpm build-misskey-js-with-types
> and commit the changes under packages/misskey-js/src/autogen/.
```
ローカルでもう一度上記コマンドを実行 → 差分を commit → push し直す。
## 関連
- API endpoint 追加の全手順 → [working-on-backend/references/tasks/adding-api-endpoint.md](../../../working-on-backend/references/tasks/adding-api-endpoint.md)
- `meta` / `paramDef` / `res` の規約 → [working-on-backend/references/knowledge/api-meta-paramdef.md](../../../working-on-backend/references/knowledge/api-meta-paramdef.md)

View File

@@ -1,35 +0,0 @@
---
name: working-on-backend
description: Use whenever editing or adding code under `packages/backend/` — including REST API endpoints, NestJS services/modules, TypeORM entities, migrations, and backend tests. Covers NestJS DI patterns, TypeORM entity conventions, endpoint-list registration, meta/paramDef/res, misskey-js regeneration, migration up/down rules, and the `.config/test.yml` prerequisite. Must be consulted before any backend change to avoid CI failures and production incidents. This is NOT waived by having already invoked brainstorming, writing-plans, or any other upstream skill — invoke this at implementation time regardless of what preceded it.
---
# working-on-backend
`packages/backend/` (Misskey サーバー本体) を編集するとき、最初に参照するスキル。NestJS / TypeORM / API endpoint / migration / backend テストの **手順****背景知識** をまとめている。
SKILL.md 本体は references への索引だけ。具体的な手順や規約は該当ファイルを Read すること (progressive disclosure)。
**他スキル実行後も免除されない。** `brainstorming` / `writing-plans` / その他アップストリームスキルを先に呼んでいても、`packages/backend/` に触れる実装フェーズに入る時点でこのスキルを呼ぶこと。
## 作業別ワークフロー (tasks)
タスク単位の完結したチェックリスト + チェックポイント。新しい何かを足すときに開く。
- 新規 REST API endpoint を追加する → [references/tasks/adding-api-endpoint.md](references/tasks/adding-api-endpoint.md)
- DB migration を作成する (TypeORM CLI / 手書きどちらも) → [references/tasks/creating-migration.md](references/tasks/creating-migration.md)
## 共通知識 (knowledge)
タスクに紐付かない参照リファレンス。複数のタスクから引かれる規約・背景説明。
- NestJS DI / module 登録 / `@Injectable` パターン → [references/knowledge/nestjs-di.md](references/knowledge/nestjs-di.md)
- TypeORM entity / `@Column` / `@Index` パターン (難ケース込み) → [references/knowledge/typeorm-patterns.md](references/knowledge/typeorm-patterns.md)
- API endpoint の `meta` / `paramDef` / `res` 完全早見表 + 落とし穴集 → [references/knowledge/api-meta-paramdef.md](references/knowledge/api-meta-paramdef.md)
- `endpoint-list.ts` への登録方法 (★ 漏れると 404) → [references/knowledge/endpoint-list.md](references/knowledge/endpoint-list.md)
- backend テストの前提 (`.config/test.yml`) と書き方 / e2e ヘルパー一覧 → [references/knowledge/backend-testing.md](references/knowledge/backend-testing.md)
## 必ず最後に通る場所
backend の変更を commit / PR にする前に、必ず [shipping-misskey-change](../shipping-misskey-change/SKILL.md) の最終チェックリストに従う。`pnpm lint` / misskey-js 再生成 / `check-migrations` / SPDX / CHANGELOG をまとめて確認する。
API endpoint を追加・変更したなら、その出口で [misskey-api-reviewer](../../agents/misskey-api-reviewer.md) agent (この skill の規約を review-mode から機械チェックする専門 reviewer) を Task で起動すると、endpoint-list 登録漏れや misskey-js 再生成漏れを取りこぼしにくい。

View File

@@ -1,368 +0,0 @@
# API endpoint の meta / paramDef / res 完全早見表
[`IEndpointMeta`](../../../../../packages/backend/src/server/api/endpoints.ts) の全フィールドと AJV `paramDef` の実用パターン、それと PR レビューで頻発する落とし穴を 1 つにまとめたページ。新規 / 既存 endpoint 編集時に開く。
## 目次
- [全フィールド一覧](#全フィールド一覧)
- [権限制限フィールドの使い分け](#権限制限フィールドの使い分け)
- [`kind` の値](#kind-の値)
- [`errors` の書き方](#errors-の書き方)
- [`res` の書き方](#res-の書き方)
- [`paramDef` (AJV) 実用パターン](#paramdef-ajv-実用パターン)
- [OpenAPI への反映マップ](#openapi-への反映マップ)
- [落とし穴](#落とし穴)
## 全フィールド一覧
[endpoints.ts](../../../../../packages/backend/src/server/api/endpoints.ts) の `IEndpointMetaBase` 型より。
| フィールド | 型 | デフォルト | 用途 |
|---|---|---|---|
| `stability` | `'deprecated' \| 'experimental' \| 'stable'` | (未指定) | 安定度のヒント。`'deprecated'` を付けた API は新規利用を避ける |
| `tags` | `ReadonlyArray<string>` | — | OpenAPI タグ。実質 `tags[0]` のみが反映される |
| `errors` | `Record<key, { message, code, id }>` | — | クライアントに返す業務エラー定義。各 `id` は UUID v4 で一意 |
| `res` | `Schema` (`@/misc/json-schema.js`) | — | レスポンス JSON Schema。`ref: 'Note'` のような packed entity 参照も可 |
| `requireCredential` | `boolean` | `false` | 認証必須か。`true` のとき `kind` を必ず設定する |
| `requireModerator` | `boolean` | `false` | isModerator ロール必須。`true` のとき `kind` 必須 |
| `requireAdmin` | `boolean` | `false` | isAdministrator ロール必須。`true` のとき `kind` 必須 |
| `requiredRolePolicy` | `KeyOf<'RolePolicies'>` | (未指定) | 特定のロールポリシー (例: `'canCreateChannel'`) を満たすロールを要求 |
| `prohibitMoved` | `boolean` | `false` | アカウント移行済ユーザーを拒否 (主に write 系で検討) |
| `limit` | `{ key?, duration?, max?, minInterval? }` | なし | レート制限。`duration``max` はセットで設定する |
| `requireFile` | `boolean` | `false` | multipart/form-data でファイル添付必須。`true` だと `exec``file` 引数が確実に渡る |
| `secure` | `boolean` | `false` | サードパーティアプリからは利用不可。OpenAPI に "Internal Endpoint" 表記が出る |
| `kind` | `(typeof permissions)[number]` | — | OAuth スコープ。`'read:account'` / `'write:notes'` 等。型は require* 系と相互排他制約あり ([endpoints.ts](../../../../../packages/backend/src/server/api/endpoints.ts) の型ユニオン定義) |
| `description` | `string` | — | OpenAPI の operation description に入る |
| `allowGet` | `boolean` | `false` | GET メソッドを許可するか (デフォルトは POST のみ)。冪等な read 系で有用 |
| `cacheSec` | `number` | — | 正常応答に `Cache-Control: public, max-age=<秒>` を付与 |
## 権限制限フィールドの使い分け
[endpoints.ts](../../../../../packages/backend/src/server/api/endpoints.ts) で型ユニオンとして表現されており、組み合わせに制約がある:
| ケース | `requireCredential` | `requireModerator` | `requireAdmin` | `kind` |
|---|---|---|---|---|
| 認証不要 | `false` または省略 | (省略) | (省略) | 不要 |
| 一般ユーザー認証必須 | `true` | (省略) | (省略) | **必須** (`'read:account'` 等) |
| モデレーター以上必須 | (省略) | `true` | (省略) | **必須** (例: `'read:admin:show-user'`) |
| 管理者必須 | (省略) | (省略) | `true` | **必須** (例: `'write:admin:emoji'`) |
| Misskey 本体専用 (`secure: true`) | 任意 | 任意 | 任意 | **不要** (型 union で除外) |
**`secure: true` の例外**: [endpoints.ts](../../../../../packages/backend/src/server/api/endpoints.ts) の `secure: true` union variant は他の require* と独立しており、`kind` を要求しない。実例: [auth/accept.ts](../../../../../packages/backend/src/server/api/endpoints/auth/accept.ts) (`secure: true + requireCredential: true``kind` なし)、[i/export-user-lists.ts](../../../../../packages/backend/src/server/api/endpoints/i/export-user-lists.ts) も同様。サードパーティアプリから叩けないので OAuth scope の必要がない。
加えて以下も使える:
- **`requiredRolePolicy: 'canCreateChannel'`** — 特定のロールポリシーが許可されているユーザーだけに絞る。**`requireCredential: true` 必須**: [ApiCallService.ts](../../../../../packages/backend/src/server/api/ApiCallService.ts) が `requiredRolePolicy` 分岐で `user!.id` を非null前提アクセスするため、匿名許可と組み合わせると TypeError で 500 になる。匿名も許したいなら、`meta` ではなく実行時に `RoleService.getUserPolicies(me ? me.id : null)` で判定する ([endpoints/notes/global-timeline.ts](../../../../../packages/backend/src/server/api/endpoints/notes/global-timeline.ts) のパターン)。ポリシーの一覧は [`RolePolicies`](../../../../../packages/backend/src/core/RoleService.ts) を参照
- **`secure: true`** — Misskey 本体フロントエンドからしか叩けないようにする (OAuth トークンで叩けなくなる)。上記の通り `kind` は不要
## `kind` の値
完全な一覧は [`packages/misskey-js/src/consts.ts`](../../../../../packages/misskey-js/src/consts.ts) の `permissions` 配列。代表例:
| パターン | 例 |
|---|---|
| 一般 read | `'read:account'`, `'read:notifications'`, `'read:drive'`, `'read:reactions'` |
| 一般 write | `'write:account'`, `'write:notes'`, `'write:reactions'`, `'write:drive'` |
| Admin read | `'read:admin:meta'`, `'read:admin:server-info'`, `'read:admin:show-user'`, `'read:admin:user-ips'` |
| Admin write | `'write:admin:reset-password'`, `'write:admin:suspend-user'`, `'write:admin:emoji'`, `'write:admin:roles'` |
新しい操作領域を追加する場合は `consts.ts``permissions` 配列にも追加する必要がある。
## `errors` の書き方
```ts
errors: {
noSuchNote: { // ← キーは camelCase
message: 'No such note.', // ← 英語ハードコード (バックエンドに i18n 機構なし)
code: 'NO_SUCH_NOTE', // ← code は SCREAMING_SNAKE_CASE
id: '17a0e0fa-3f3e-4f3e-9f3e-3f3e3f3e3f3e', // ← UUID v4。リポジトリ内で一意
httpStatusCode: 404, // ← オプション。HTTP ステータスを上書き
kind: 'client', // ← オプション。'client' (デフォルト) / 'server' / 'permission'
},
},
```
`httpStatusCode``kind` は [error.ts](../../../../../packages/backend/src/server/api/error.ts) の型 `E` 経由で受け付けられる。指定しないとデフォルト挙動 (クライアントエラーは 400 系) になる。
命名規則 (既存実装で一貫):
- キー: `camelCase` (`noSuchNote`, `cannotReRenote`, `alreadyBlocking`, `youHaveBeenBlocked`)
- `code`: `SCREAMING_SNAKE_CASE` (`'NO_SUCH_NOTE'`, `'CANNOT_RENOTE_TO_A_PURE_RENOTE'`)
- 接頭辞パターン: `NO_SUCH_*` / `CANNOT_*` / `ALREADY_*` / `TOO_MANY_*` / `INVALID_*` / `*_REQUIRED`
`throw new ApiError(meta.errors.noSuchNote, { reason: '詳細情報' })` の第 2 引数は `info` に入り、レスポンス JSON の `error.info` として返却される。
## `res` の書き方
JSON Schema または packed entity への参照:
```ts
// 単純なオブジェクト
res: {
type: 'object',
optional: false, nullable: false,
properties: {
count: { type: 'integer' },
},
},
// packed entity 参照
res: {
type: 'object',
optional: false, nullable: false,
ref: 'Note', // ← packages/backend/src/models/json-schema/*.ts の定義名
},
// 配列
res: {
type: 'array',
optional: false, nullable: false,
items: {
type: 'object',
optional: false, nullable: false,
ref: 'Note',
},
},
```
各プロパティに `optional: false, nullable: false`**必ず明示する**。省略すると schema が緩くなり、生成される misskey-js 型も曖昧になる。
## `paramDef` (AJV) 実用パターン
`paramDef` は AJV (`new Ajv({ useDefaults: true })`) でコンパイルされた JSON Schema 7 互換のスキーマ。詳細は [endpoint-base.ts](../../../../../packages/backend/src/server/api/endpoint-base.ts) の AJV 初期化を参照。
### カスタム format
**`format: 'misskey:id'`** だけが Misskey 独自 ([endpoint-base.ts](../../../../../packages/backend/src/server/api/endpoint-base.ts) の `addFormat`):
```ts
ajv.addFormat('misskey:id', /^[a-zA-Z0-9]+$/);
```
その他 (`'date-time'`, `'email'`, `'url'` 等) は JSON Schema 標準。AJV はデフォルトでは format 検証を行わないが、Misskey の AJV 設定ではフォーマット名はバリデーションエラーを出さず通過する程度の動作になっている (ID パターンのみ実際に正規表現検証される)。
### 基本パターン
```ts
export const paramDef = {
type: 'object',
properties: {
noteId: { type: 'string', format: 'misskey:id' }, // 必須 ID
text: { type: 'string', minLength: 1, maxLength: 500 }, // 文字長制約
count: { type: 'integer', minimum: 0, maximum: 100, default: 10 },
isPublic: { type: 'boolean', default: false },
visibility: { type: 'string', enum: ['public', 'home', 'followers', 'specified'] },
},
required: ['noteId'],
} as const;
```
`as const` を必ず付ける。これで `SchemaType<typeof paramDef>` が型推論される。
### ページネーション (sinceId / untilId / limit)
[notes/timeline.ts](../../../../../packages/backend/src/server/api/endpoints/notes/timeline.ts):
```ts
properties: {
limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 },
sinceId: { type: 'string', format: 'misskey:id' },
untilId: { type: 'string', format: 'misskey:id' },
sinceDate: { type: 'integer' },
untilDate: { type: 'integer' },
},
```
`QueryService.makePaginationQuery(qb, ps.sinceId, ps.untilId, ps.sinceDate, ps.untilDate)` で TypeORM クエリビルダに反映する。
### 配列とアイテム制約
```ts
properties: {
// 一意・最小1・最大100 個のID リスト
noteIds: {
type: 'array',
uniqueItems: true,
minItems: 1,
maxItems: 100,
items: { type: 'string', format: 'misskey:id' },
},
},
```
実例: [notes/show-partial-bulk.ts](../../../../../packages/backend/src/server/api/endpoints/notes/show-partial-bulk.ts) (`noteIds`), [notes/drafts/create.ts](../../../../../packages/backend/src/server/api/endpoints/notes/drafts/create.ts) (`fileIds` / `visibleUserIds``uniqueItems` 付き)
### `oneOf` / `anyOf` (排他的選択)
複数のリクエストパラメータ形態を許す場合:
```ts
properties: {
userId: { type: 'string', format: 'misskey:id' },
username: { type: 'string' },
host: { type: 'string', nullable: true },
},
anyOf: [
{ required: ['userId'] },
{ required: ['username'] },
],
```
`res` 側でも `oneOf` を使ってバリアントレスポンスを表現できる ([ap/show.ts](../../../../../packages/backend/src/server/api/endpoints/ap/show.ts) の `res`):
```ts
res: {
optional: false, nullable: false,
oneOf: [
{ type: 'object', properties: { type: { enum: ['User'] }, object: { ref: 'UserDetailedNotMe' } } },
{ type: 'object', properties: { type: { enum: ['Note'] }, object: { ref: 'Note' } } },
],
},
```
### `additionalProperties` (動的キー)
固定の `properties` ではなく「任意のキー → 値の型」を表すとき:
```ts
data: {
type: 'object',
additionalProperties: {
anyOf: [{ type: 'number' }],
},
},
```
実例: [retention.ts](../../../../../packages/backend/src/server/api/endpoints/retention.ts), [admin/get-table-stats.ts](../../../../../packages/backend/src/server/api/endpoints/admin/get-table-stats.ts)
`type: 'object', additionalProperties: true` だと「任意の中身を受け入れる」(検証なし) になる。
### `default` (値補完)
AJV を `useDefaults: true` で構築しているため、`default` を書くとリクエストに値が無い場合に自動で埋まる:
```ts
properties: {
includeMyRenotes: { type: 'boolean', default: true },
},
```
クライアントの省略を吸収できるため、後方互換変更で重宝する。
### nullable プロパティ
```ts
properties: {
parentId: { type: 'string', format: 'misskey:id', nullable: true },
},
```
`nullable: true` を付けると `null` を明示的に受け付ける。
## OpenAPI への反映マップ
[gen-spec.ts](../../../../../packages/backend/src/server/api/openapi/gen-spec.ts) より:
| meta フィールド | OpenAPI への反映 |
|---|---|
| `description` | operation description (先頭) |
| `secure: true` | description に "**Internal Endpoint**: ..." の警告 |
| `requireCredential: true` | description に "**Credential required**: *Yes*" + `security: [bearerAuth]` |
| `kind` | description に "**Permission**: *<kind>*" |
| `tags[0]` | operation tag (実質 1 個目のみ) |
| `requireFile: true` | requestBody が `multipart/form-data` になり `file: { type: 'string', format: 'binary' }` が追加される |
| `errors` | examples (operation の `responses` 配下) |
| `res` | response body schema |
| `limit` | `429 Too many requests` レスポンスが `responses` に追加される |
| `allowGet` | 同一 path に `get` operation が追加される (POST と両方が生える) |
**OpenAPI に反映されない (内部のみ)**: `requireModerator` / `requireAdmin` / `requiredRolePolicy` / `prohibitMoved` / `cacheSec` / `stability`
## 落とし穴
PR レビューで頻発するミスを「**症状 → 原因 → 修正**」で集めた。
### 1. エンドポイントが 404 になる
- **症状**: 開発サーバーで叩くと `{"error": {"code": "UNKNOWN_API_ENDPOINT", ...}}` (GET の catch-all 経由)、または素の 404 (POST など)
- **原因**: [endpoint-list.ts](../../../../../packages/backend/src/server/api/endpoint-list.ts) への登録漏れ。エンドポイントは glob 自動収集されない
- **修正**: → [knowledge/endpoint-list.md](endpoint-list.md)
### 2. CI `check-misskey-js-autogen` で落ちる
- **症状**: PR に `Please regenerate misskey-js` のコメント
- **原因**: `meta` / `paramDef` / `res` を変えたのに misskey-js の自動生成物を再生成していない
- **修正**: → [shipping-misskey-change/references/tasks/regenerate-misskey-js.md](../../../shipping-misskey-change/references/tasks/regenerate-misskey-js.md)
### 3. CI `spdx` ジョブで落ちる
- **症状**: `SPDX header missing` のメッセージ
- **原因**: 新規 `.ts` ファイルに SPDX ヘッダーが無い
- **修正**: ファイル冒頭に SPDX を貼る。注: `packages/misskey-js/` 配下は MIT 別ライセンスなので SPDX 不要
### 4. クライアントが 500 + error 型不在 を受け取る
- **症状**: フロントエンド側で `result.error.code` を分岐したいが、misskey-js の型に出てこない。レスポンスは 500
- **原因**: `meta.errors` に列挙していないエラーを `throw new ApiError({...})` または `throw new Error(...)` した
- **修正**: 業務エラーは必ず `meta.errors` に登録してから `throw new ApiError(meta.errors.<key>)`
- **逆方向の罠**: 「想定外バグまで全部 `ApiError` で包む」のもダメ。`endpoints/notes/create.ts``catch` 節末尾の `throw err;` が手本
### 5. `me.id` で `Cannot read properties of null`
- **症状**: 認証なしリクエストで TypeError
- **原因**: `requireCredential: false` のとき `me``MiLocalUser | null` なのに null チェックなしで `me.id` を使った
- **修正**: null チェックを入れるか、認証必須なら `requireCredential: true` に変更
### 6. UUID が他エンドポイントと衝突
- **症状**: `errors.id` を再利用してしまうと misskey-js 側で型が混線
- **原因**: UUID をハードコードして再利用
- **修正**: 衝突確認
```bash
grep -r "id: '<生成した UUID>'" packages/backend/src/server/api/endpoints/
```
新規生成は `node -e "console.log(crypto.randomUUID())"`
### 7. `paramDef` に `policies` を書く
- **症状**: 「`gtlAvailable: true` を payload で渡してください」のような不自然な API になっている / クライアントが指定したらバイパスできる
- **原因**: ロールポリシーは **動的に取得するもの**
- **修正**: paramDef からは外し、`exec` 内で `RoleService.getUserPolicies(me?.id)` を呼んで判定する
### 8. エラーメッセージを日本語で書く
- **症状**: `message: 'ノートが見つかりません'` のような日本語が i18n されずクライアントに渡る
- **原因**: バックエンドに i18n 機構が無い
- **修正**: `message` は英語ハードコードに統一。フロントエンドは `error.id` (UUID) または `error.code` をキーに自前で localize する
### 9. `as const` を忘れる
- **症状**: `Endpoint<typeof meta, typeof paramDef>` の型推論が壊れて `ps` の型が `any` になる
- **修正**: `export const meta = { ... } as const;` と `export const paramDef = { ... } as const;` を必ず付ける
### 10. `requireCredential: true` なのに `kind` を書き忘れる
- **症状**: TypeScript の型エラー (`Property 'kind' is missing`)
- **原因**: [endpoints.ts](../../../../../packages/backend/src/server/api/endpoints.ts) のユニオン制約で `kind` が型レベルで必須
- **修正**: 適切な OAuth スコープを `kind` に設定する
- **例外**: `secure: true` (Misskey 本体専用) のエンドポイントは [endpoints.ts](../../../../../packages/backend/src/server/api/endpoints.ts) の別 union variant 扱いで `kind` 不要
### 11. `requireFile: true` の cleanup を呼び忘れて一時ファイルが残る
- **症状**: アップロード後にエンドポイントが正常終了/例外終了しても OS の一時ディレクトリにファイルが残り続け、ディスクが埋まる
- **原因**: [endpoint-base.ts](../../../../../packages/backend/src/server/api/endpoint-base.ts) が `cleanup` を自動で呼ぶのは **AJV バリデーション失敗時のみ**
- **修正**: `try { ... } finally { cleanup!(); }` で囲む ([drive/files/create.ts](../../../../../packages/backend/src/server/api/endpoints/drive/files/create.ts) の `finally { cleanup!(); }` が手本)
### 12. `requiredRolePolicy` だけで匿名許可してしまう
- **症状**: API を匿名で叩くと 500 + `TypeError: Cannot read properties of null (reading 'id')`
- **原因**: [ApiCallService.ts](../../../../../packages/backend/src/server/api/ApiCallService.ts) が `requiredRolePolicy` ありのエンドポイントで `user!.id` を非null前提でアクセス
- **修正**: 静的に必須ポリシーを宣言するなら `requireCredential: true` と必ず併用する。匿名ユーザーにも違うポリシーセットを適用したいなら、実行時に `RoleService.getUserPolicies(me ? me.id : null)` で判定 ([notes/global-timeline.ts](../../../../../packages/backend/src/server/api/endpoints/notes/global-timeline.ts) パターン)
### 13. e2e テストが起動しない
- **症状**: `pnpm --filter backend test:e2e` 実行直後にこける / DB 接続エラー
- **原因**: `.config/test.yml` が無い
- **修正**: → [knowledge/backend-testing.md §前提](backend-testing.md)

View File

@@ -1,209 +0,0 @@
# Backend テストの前提と書き方
Misskey backend のテスト構成、`.config/test.yml` の前提、e2e テストのヘルパー関数集を 1 つにまとめたページ。
## 目次
- [前提: `.config/test.yml`](#前提-configtestyml)
- [テスト種別と実行コマンド](#テスト種別と実行コマンド)
- [e2e テストの配置](#e2e-テストの配置)
- [共通 setup](#共通-setup)
- [`api()` ヘルパー](#api-ヘルパー)
- [`signup()` / `post()` / `uploadFile()` 等](#signup--post--uploadfile-等)
- [ローカル DB / Redis](#ローカル-db--redis)
## 前提: `.config/test.yml`
backend のテストスクリプト (`test` / `test:e2e` / `test:fed`) はすべて内部で `cross-env NODE_ENV=test pnpm compile-config` を実行し、`.config/test.yml` を読み込む ([packages/backend/package.json](../../../../../packages/backend/package.json), [packages/backend/scripts/compile_config.js](../../../../../packages/backend/scripts/compile_config.js))。**未作成だとテスト自体が起動しない**。
未作成なら以下を 1 回だけ手動コピーする (どちらでも可):
```bash
ncp .github/misskey/test.yml .config/test.yml
# または
cp .github/misskey/test.yml .config/test.yml
```
補足:
- ルートの `pnpm start:test` (Cypress 用にテストサーバーを起動するコマンド) を使う経路では実行時に `ncp` で自動コピーされる ([package.json](../../../../../package.json))。それ以外で backend テストを直接走らせる時は上記の手動コピーが必要
- すでに `.config/test.yml` があれば各テストスクリプトの内部 `compile-config` で十分なので、追加で `pnpm --filter backend compile-config` を叩く必要はない
- `pnpm start:test` は backend e2e テスト (`pnpm --filter backend test:e2e`) の前提ではない (ポート競合の元になるため使わないこと)
## テスト種別と実行コマンド
| 種別 | 設定ファイル | 実行コマンド |
| --- | --- | --- |
| Unit | `packages/backend/vitest.config.unit.ts` | `pnpm --filter backend test` |
| E2E (HTTP / DB) | `packages/backend/vitest.config.e2e.ts` | `pnpm --filter backend test:e2e` |
| Federation | `packages/backend/vitest.config.fed.ts` | `pnpm --filter backend test:fed` |
- 配置: `packages/backend/test/` 配下
- カバレッジ: `pnpm --filter backend test-and-coverage`
## e2e テストの配置
`packages/backend/test/e2e/` の現状ファイル例:
```
note.ts ノート関連 (作成・renote・visibility・添付ファイル等)
users.ts ユーザー関連
timelines.ts タイムライン
drive.ts ドライブ (アップロード/ダウンロード)
clips.ts クリップ
oauth.ts OAuth フロー
streaming.ts WebSocket
api.ts API レイヤ全般 (認証・レート制限など)
api-visibility.ts 公開範囲チェック
endpoints.ts 上記カテゴリに収まらない雑多なもの
2fa.ts 2FA
block.ts / mute.ts / antennas.ts / clips.ts / move.ts / nodeinfo.ts / ...
```
**`admin.ts` は存在しない**。admin 系エンドポイントの e2e は `api.ts` (API レイヤ挙動として) または `endpoints.ts` (雑多枠) に置くのが現実的。
### 判断ルール
1. 自分の追加するエンドポイントが既存カテゴリファイル (`note.ts`, `users.ts` 等) に所属するなら、そこに `describe('...', () => { test(...) })` を追加
2. どのカテゴリにも収まらないなら `endpoints.ts` に追加
3. テストケースが多くなり (>200 行)、独立性が高い場合のみ新ファイル化
`describe` のラベル名は **人間可読** で OK (`describe('Note', ...)`, `describe('管理者操作', ...)` のような形式)。`<category>/<name>` 形式である必要はない。
## 共通 setup
`packages/backend/test/setup.e2e.ts` (vitest の `setupFiles`) が各テストファイル共通の `beforeAll` (テスト DB 初期化 + 環境リセット) を登録する。テストサーバーの起動/停止は別途 vitest の `globalSetup` (`test-server/entry.ts``setup()` / `teardown()`) が担う。各テストファイルでは自前の `beforeAll` でユーザーを用意する:
```ts
import { describe, test, beforeAll, afterAll } from 'vitest';
import * as assert from 'node:assert';
import { api, signup, post, role, uploadFile } from '../utils.js';
import type { UserToken } from '../utils.js';
describe('機能名', () => {
let alice: UserToken;
beforeAll(async () => {
alice = await signup({ username: 'alice' });
});
test('正常系', async () => {
const res = await api('<category>/<name>', { /* params */ }, alice);
assert.strictEqual(res.status, 200);
});
});
```
## `api()` ヘルパー
[test/utils.ts](../../../../../packages/backend/test/utils.ts) の `api()`:
```ts
const res = await api('<category>/<name>', params, me?);
// res.status : HTTP ステータス (200 / 400 / 401 / 403 / 500 等)
// res.headers : Headers
// res.body : レスポンス JSON (型は misskey.Endpoints から自動推論)
```
`me?` を省略すると未認証リクエスト。`me` を渡すとそのユーザーの token で叩く。
### エラーレスポンスの検証
```ts
test('存在しないノートで怒られる', async () => {
const res = await api('notes/show', { noteId: '0000000000000000' }, alice);
assert.strictEqual(res.status, 400);
assert.strictEqual(castAsError(res.body as any).error.code, 'NO_SUCH_NOTE');
});
```
`castAsError(...).error.code``meta.errors.<key>.code` を検証できる ([test/utils.ts](../../../../../packages/backend/test/utils.ts) の `castAsError`)。
## `signup()` / `post()` / `uploadFile()` 等
### `signup()` — テストユーザー作成
```ts
const alice = await signup({ username: 'alice' }); // 既定パスワード 'test'
const bob = await signup({ username: 'bob', password: 'secret123' });
```
戻り値はサインアップレスポンス (token を含む) で、`api()` の第 3 引数にそのまま渡せる。
### `post()` — ノート投稿
```ts
const note = await post(alice, { text: 'hello' });
// 戻り値は misskey.entities.Note
```
複雑な公開範囲・添付ファイル付きでも `post(alice, { text: ..., visibility: 'specified', visibleUserIds: [...], fileIds: [...] })` のように渡せる。
### `uploadFile()` — ドライブにファイルアップロード
```ts
const file = await uploadFile(alice); // resources/192.jpg をアップロード
const file2 = await uploadFile(alice, { path: '192.png' }); // resources/192.png
const file3 = await uploadFile(alice, { blob: new Blob([...]) }); // 任意 Blob
// file.body.id を fileIds に渡せる
```
### `role()` — ロール作成 + アサイン
[test/utils.ts](../../../../../packages/backend/test/utils.ts) の `role()`:
```ts
const myRole = await role(adminUser, { name: 'tester' }, { canCreateChannel: { useDefault: false, priority: 0, value: true } });
// admin/roles/create を叩く。policies 引数で個別ポリシーを上書き可能
```
モデレーター・管理者ロールが要るテストは事前に `signup({ ... })` + `role(...)` で作る。
### `createAppToken()` — アプリ scope 付きトークン
```ts
const token = await createAppToken(alice, ['write:notes', 'read:account']);
// token は文字列。api() の me.token として使うか、{ token, bearer: true } で渡せば Bearer Auth で叩く
```
OAuth scope (`kind`) のテストに使う。
### その他のヘルパー
[test/utils.ts](../../../../../packages/backend/test/utils.ts) には以下も用意されている:
- `userList()` — ユーザーリスト作成
- `page()` / `play()` — Page / Flash 作成
- `clip()` / `galleryPost()` / `channel()` — 各種リソース作成
- `react()` — リアクション
- `simpleGet()` — fetch ラッパ (raw HTTP)
- `testPaginationConsistency()` — ページネーション挙動の網羅検証
- `sendEnvUpdateRequest()` / `sendEnvResetRequest()` — テスト用環境変数の更新
- `connectStream()` / `waitFire()` — WebSocket (Streaming API)
詳細はソースを直接参照。
### 既存テスト例
- [test/e2e/note.ts](../../../../../packages/backend/test/e2e/note.ts) — `describe('Note', ...)` で多数の `test(...)` を並べる伝統的なスタイル
- [test/e2e/endpoints.ts](../../../../../packages/backend/test/e2e/endpoints.ts) — カテゴリ不問の雑多なエンドポイント
- [test/e2e/api.ts](../../../../../packages/backend/test/e2e/api.ts) — API レイヤ (認証・レート制限) の挙動
## ローカル DB / Redis
backend の **テスト****開発** では用途別に別の compose ファイルを使う。ポートが異なるので混同すると接続できない。
| 用途 | compose ファイル | host ポート (db / redis) |
| --- | --- | --- |
| テスト (`test` / `test:e2e` / `test:fed`) | [packages/backend/test/compose.yml](../../../../../packages/backend/test/compose.yml) | `54312` / `56312` ([.github/misskey/test.yml](../../../../../.github/misskey/test.yml) のポート設定と一致) |
| 開発 (`pnpm dev` 等) | `compose.local-db.yml` (リポジトリルート) | `5432` / `6379` |
```bash
# テスト用 DB / Redis (テスト時はこちら)
docker compose -f packages/backend/test/compose.yml up -d
# 開発用 DB / Redis (Misskey 本体は起動せず postgres / redis / meilisearch だけ立てる)
docker compose -f compose.local-db.yml up -d
```
`compose.local-db.yml` は開発向け (標準ポート `5432` / `6379`) で、テスト用 DB (`test-misskey` / ポート `54312` / `56312`) とは別物。CI (`.github/workflows/test-backend.yml`) は docker compose ではなく GitHub Actions の `services:` で同じテスト用ポートの postgres / redis コンテナを立ててから走る。

View File

@@ -1,50 +0,0 @@
# `endpoint-list.ts` への登録
新規 API endpoint を追加する際の **最大の落とし穴**。エンドポイントは glob 自動収集されないため、ここへの 1 行追加を忘れると 404 になる。
## なぜ必要か
[`packages/backend/src/server/api/EndpointsModule.ts`](../../../../../packages/backend/src/server/api/EndpointsModule.ts) が [`endpoint-list.ts`](../../../../../packages/backend/src/server/api/endpoint-list.ts) の全エクスポートを `Object.entries()` で反復し、NestJS provider (`provide: 'ep:<path>'`) を生成している。**このリストが API ルーティングの単一の真実** で、ここに無いものは存在しないものとして扱われる。
## 登録方法
[endpoint-list.ts](../../../../../packages/backend/src/server/api/endpoint-list.ts) の **同カテゴリ内** に 1 行追加する:
```ts
export * as '<category>/<name>' from './endpoints/<category>/<name>.js';
```
`<category>` は機能領域 (`notes`, `users`, `admin/announcements` 等)、`<name>` はエンドポイント名 (`create`, `show`, `delete` 等)。両方ともケバブケース / スラッシュ区切りで、ファイルシステムのパス構造と一致する。
例: `endpoints/notes/create.ts` を追加するなら:
```ts
export * as 'notes/create' from './endpoints/notes/create.js';
```
## 並び順
**並び順は厳密ではない**。同じディレクトリ (例: `admin/queue/*`) の中でも、アルファベット順ではなく追加された経緯どおりの順になっている箇所が多い。
- **新規追加**: 同カテゴリ内の末尾に追加すれば OK
- **既存近傍**: 同カテゴリ内の関連エンドポイントの近くに置く判断もあり
- **過度に整理しない**: 既存の並びを全部 sort し直すような PR は不要 (review コストだけ増える)
## 登録確認
ファイルを追加した後、grep で 1 行存在することを確認する:
```bash
grep -F "'<category>/<name>'" packages/backend/src/server/api/endpoint-list.ts
```
ヒットしなければ登録漏れ。
## 既存例 (登録漏れに気づくための grep 例)
`endpoint-list.ts` の冒頭コメントに「このリストが API ルーティングの単一の真実」という旨が記載されている。新規開発時はこのファイルを開いてカテゴリ単位の構造を把握してから新規 endpoint ファイルを書くのが効率的。
## 関連
- 新規 endpoint 追加の全手順 → [tasks/adding-api-endpoint.md](../tasks/adding-api-endpoint.md)
- NestJS DI / module 構造 → [nestjs-di.md](nestjs-di.md)

View File

@@ -1,97 +0,0 @@
# NestJS DI / module 登録パターン
Misskey の backend は NestJS 11 + Fastify 5 + TypeORM 1 (PostgreSQL) + Redis の構成。DI コンテナと Repository パターンが軸。
## アーキテクチャ
- **DI コンテナ**: NestJS の `@Injectable()` サービス + Repository (TypeORM) パターン
- **DI トークン**: [`@/di-symbols.js`](../../../../../packages/backend/src/di-symbols.ts) の `DI` から `@Inject(DI.xxx)` で注入
- **ビルド**: `rolldown -c``built/` にバンドル。型チェックは `tsgo`
## エンドポイント内での DI
API endpoint は `Endpoint<typeof meta, typeof paramDef>` を extends するクラスとして書く。`@Injectable()` を付けてコンストラクタで Repository / Service を `@Inject(DI.xxx)` で注入する。
```ts
import { Inject, Injectable } from '@nestjs/common';
import type { NotesRepository } from '@/models/_.js';
import { Endpoint } from '@/server/api/endpoint-base.js';
import { DI } from '@/di-symbols.js';
@Injectable()
export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
constructor(
@Inject(DI.notesRepository)
private notesRepository: NotesRepository,
// 他にも RoleService, UserEntityService, GlobalEventService 等を必要なだけ inject
) {
super(meta, paramDef, async (ps, me) => {
// this.notesRepository.findOneBy(...) のように使う
});
}
}
```
`// eslint-disable-line import/no-default-export` は Endpoint のお約束 (NestJS が default export を要求する一方で、ESLint ルールでは制約されているため)。
## 主要 DI トークン
`@/di-symbols.js` から提供される。代表例:
| トークン | 型 | 用途 |
|---|---|---|
| `DI.notesRepository` | `NotesRepository` | notes テーブルの TypeORM Repository |
| `DI.usersRepository` | `UsersRepository` | users テーブル |
| `DI.driveFilesRepository` | `DriveFilesRepository` | drive_file テーブル |
| `DI.config` | `Config` | アプリ設定 |
| `DI.redis` | `Redis` | Redis クライアント |
| `DI.db` | `DataSource` | TypeORM DataSource (raw SQL を打ちたい時) |
Service 系 (例: `NoteCreateService`, `RoleService`, `UserEntityService`) は **トークン経由ではなく型をそのまま inject** する:
```ts
constructor(
private roleService: RoleService,
private userEntityService: UserEntityService,
) {}
```
## Service クラスの書き方
Service は `@Injectable()` を付け、必要な依存をコンストラクタで宣言する。NestJS の module (`packages/backend/src/core/CoreModule.ts` 等) に provider として登録される必要がある。
```ts
@Injectable()
export class MyService {
constructor(
@Inject(DI.notesRepository)
private notesRepository: NotesRepository,
private roleService: RoleService,
) {}
async doSomething(noteId: string) {
const note = await this.notesRepository.findOneBy({ id: noteId });
// ...
}
}
```
新規 Service を追加する場合は **module 側の `providers` 配列にも追加** する必要がある。既存 Service が `CoreModule` に登録されているか確認するのが手っ取り早い。
## Module 構造
主要 module は以下:
- **CoreModule** (`src/core/CoreModule.ts`) — Service 群を集約
- **EndpointsModule** (`src/server/api/EndpointsModule.ts`) — endpoint-list.ts を `Object.entries()` で反復して NestJS provider (`provide: 'ep:<path>'`) を自動生成
- **GlobalModule** (`src/GlobalModule.ts`) — Repository / Config / Redis / DataSource など低レベル依存
- **QueueModule** (`src/core/QueueModule.ts`) — BullMQ ジョブキュー
新規 endpoint 追加時に module への明示的な登録は不要 ([knowledge/endpoint-list.md](endpoint-list.md) 参照)。新規 Service 追加時は CoreModule (または該当 module) に provider 登録が必要。
## 既存例 (DI / 例外処理が綺麗な参考実装)
- [endpoints/notes/create.ts](../../../../../packages/backend/src/server/api/endpoints/notes/create.ts) — Service を型注入 (`NoteEntityService` / `NoteCreateService`) + `meta.errors` + `try/catch` で業務エラー変換 + 末尾 `throw err;` の二段構え
- [endpoints/i/pin.ts](../../../../../packages/backend/src/server/api/endpoints/i/pin.ts) — `.catch(err => { ... throw err; })` で同様にエラー変換
- [endpoints/notes/global-timeline.ts](../../../../../packages/backend/src/server/api/endpoints/notes/global-timeline.ts) — `RoleService.getUserPolicies()` で動的ポリシー判定

View File

@@ -1,160 +0,0 @@
# TypeORM / migration パターン
Misskey backend は TypeORM 1 + PostgreSQL。エンティティ定義と migration の関係、そして migration で踏みうる難ケースをまとめる。
## モデル / Repository
- エンティティ: `packages/backend/src/models/<Name>.ts` (`@Entity` + `@Column`)
- DI 経由で注入される Repository を経由してアクセス (`@Inject(DI.notesRepository)` 等) → [nestjs-di.md](nestjs-di.md)
エンティティ側の `@Column` / `@Entity` / `@Index` 変更は migration の DDL と整合させる必要がある。`pnpm --filter backend check-migrations` がエンティティと migration の不一致を検出する ([scripts/check_migrations_clean.js](../../../../../packages/backend/scripts/check_migrations_clean.js))。
## migration ファイルの構造
各ファイル `packages/backend/migration/{unixMs}-{descriptive-name}.js` は ESM JS。最小形:
```js
/*
* SPDX-FileCopyrightText: syuilo and misskey-project
* SPDX-License-Identifier: AGPL-3.0-only
*/
export class PascalCaseName1234567890123 {
name = 'PascalCaseName1234567890123'
async up(queryRunner) {
await queryRunner.query(`...`);
}
async down(queryRunner) {
await queryRunner.query(`...`); // up の完全な巻き戻し
}
}
```
詳細手順は [tasks/creating-migration.md](../tasks/creating-migration.md) を参照。**マージ済 migration の編集は絶対禁止**。
## CONCURRENTLY (CREATE INDEX CONCURRENTLY) の扱い
大規模テーブルへの `CREATE INDEX` は本番で長時間ロックする恐れがある。`CONCURRENTLY` で発行するときは migration class に **「この migration は transaction を張らない」と指示する** 必要がある。PostgreSQL は `CREATE INDEX CONCURRENTLY` を transaction 内で実行できないため。
参照実装: [migration/1745378064470-composite-note-index.js](../../../../../packages/backend/migration/1745378064470-composite-note-index.js)
```js
const isConcurrentIndexMigrationEnabled = process.env.MISSKEY_MIGRATION_CREATE_INDEX_CONCURRENTLY === '1';
export class CompositeNoteIndex1745378064470 {
name = 'CompositeNoteIndex1745378064470';
transaction = isConcurrentIndexMigrationEnabled ? false : undefined;
async up(queryRunner) {
const concurrently = isConcurrentIndexMigrationEnabled;
if (concurrently) {
// CREATE INDEX CONCURRENTLY ...
} else {
// CREATE INDEX ...
}
}
async down(queryRunner) {
// 同様に環境変数で分岐
}
}
```
要点:
- **`transaction = isConcurrentIndexMigrationEnabled ? false : undefined;`** が必須。これがないと `CREATE INDEX CONCURRENTLY` が transaction 内で実行されて `ERROR: CREATE INDEX CONCURRENTLY cannot run inside a transaction block` で失敗
- 環境変数 `MISSKEY_MIGRATION_CREATE_INDEX_CONCURRENTLY=1` がデフォルト OFF。OFF のときは普通の `CREATE INDEX` (transaction 内) で動く必要がある。`up`/`down` 双方を環境変数で分岐させる
- `ormconfig.js``migrationsTransactionMode`**環境変数で切り替わる**: `MISSKEY_MIGRATION_CREATE_INDEX_CONCURRENTLY=1` のときだけ `'each'`、未設定時は `'all'` (全 migration を 1 つの transaction でラップ) ([ormconfig.js](../../../../../packages/backend/ormconfig.js) の `migrationsTransactionMode`)。普段は `'all'` 前提
## migration 難ケース集
`migration:generate` / 手書きどちらでも踏み外しやすいパターンを「**なぜ危険か → up の形 → down 戦略 → 参照実装**」でまとめる。
共通の鉄則: `down()``up()`**完全な巻き戻し**。下記ケースは「単純な逆 SQL では戻らない」ものが多い。
### 1. NOT NULL 列の追加
**なぜ危険か**: 既存行があるテーブルに `NOT NULL` 列を `DEFAULT` 無しで足すと、既存行を埋められず `ALTER TABLE` が失敗する。
- **既定値で良い場合** — `DEFAULT` を付ければ 1 文で済む。これが最も多い
```js
// up
await queryRunner.query(`ALTER TABLE "note_draft" ADD "isActuallyScheduled" boolean NOT NULL DEFAULT false`);
// down
await queryRunner.query(`ALTER TABLE "note_draft" DROP COLUMN "isActuallyScheduled"`);
```
参照: [migration/1758677617888-scheduled-post.js](../../../../../packages/backend/migration/1758677617888-scheduled-post.js)
- **行ごとに計算した値で埋めたい / 既定値を後で外したい場合** — 3 段に分ける: ①nullable で追加 → ②`UPDATE` でバックフィル (ケース 3 参照) → ③`ALTER COLUMN ... SET NOT NULL`。`down` は `DROP COLUMN` で良い。巨大テーブルでは ② の `UPDATE` と ③ の `SET NOT NULL` (全行スキャン) が長時間ロックし得る点に注意
**補足:** エンティティ側で `@Column({ default: ... })` を付けると `migration:generate` が `DEFAULT` 付き DDL を出す。アプリ実行時に常に値を入れるので DB 既定値が不要なら、生成後に `DEFAULT` 句だけ手で外す判断もある (既存 migration には両スタイルある)。
### 2. enum 型の値の追加・変更
**なぜ危険か**: PostgreSQL の enum は **値を削除できない** (`ALTER TYPE ... DROP VALUE` は存在しない) ため、`ADD VALUE` した変更を素直に巻き戻せない。さらに Misskey はデフォルトで migration 全体を 1 トランザクションにまとめる (`migrationsTransactionMode: 'all'`) ので、`ADD VALUE` で足した値を同一トランザクション内で使う処理もエラーになる。そこで TypeORM `migration:generate` は **「旧型を rename → 新型を CREATE → 列を新型へ ALTER (USING キャスト) → 旧型を DROP」** という巻き戻し可能な手順を出す。手書きでもこの形に従うこと。
```js
// up: 値 'app' を追加する例 (新値を含む型へ載せ替える)
await queryRunner.query(`ALTER TYPE "public"."notification_type_enum" RENAME TO "notification_type_enum_old"`);
await queryRunner.query(`CREATE TYPE "public"."notification_type_enum" AS ENUM('follow', 'mention', /* ... */ 'app')`);
await queryRunner.query(`ALTER TABLE "notification" ALTER COLUMN "type" TYPE "public"."notification_type_enum" USING "type"::"text"::"public"."notification_type_enum"`);
await queryRunner.query(`DROP TYPE "public"."notification_type_enum_old"`);
```
```js
// down: 新値を含まない旧い値集合へ同じ手順で戻す
await queryRunner.query(`ALTER TYPE "public"."notification_type_enum" RENAME TO "notification_type_enum_old"`);
await queryRunner.query(`CREATE TYPE "public"."notification_type_enum" AS ENUM('follow', 'mention', /* ... 'app' を除く ... */)`);
await queryRunner.query(`ALTER TABLE "notification" ALTER COLUMN "type" TYPE "public"."notification_type_enum" USING "type"::"text"::"public"."notification_type_enum"`);
await queryRunner.query(`DROP TYPE "public"."notification_type_enum_old"`);
```
要点: ①列がデフォルトを持つ場合は ALTER 前に `DROP DEFAULT`、ALTER 後に `SET DEFAULT` を挟む。②配列列 (`mutingNotificationTypes` 等) は `TYPE "..."[] USING "col"::"text"::"..."[]` と配列キャストにする。③**`down` の落とし穴**: 削除する値を既存行が使っていると `USING` キャストが「該当 enum に存在しない」で失敗する。新値を追加しただけの直後の巻き戻しは安全だが、運用後に使われた値を消す巻き戻しは本質的に危うい — その場合は down で先に `UPDATE ... SET "type" = '<代替値>' WHERE "type" = '<消す値>'` で退避してからキャストする。
参照: [migration/1674118260469-achievement.js](../../../../../packages/backend/migration/1674118260469-achievement.js) (rename/recreate の完全な up/down)。型の新規作成は [migration/1580276619901-v12-10.js](../../../../../packages/backend/migration/1580276619901-v12-10.js)。
### 3. データ移行 (UPDATE バックフィル)
**なぜ危険か**: migration 内の `UPDATE` は本番の全行を触る可能性がある。大量行では長時間ロック・トランザクション肥大を招く。
- 既定値を入れるだけなら `UPDATE ... WHERE col IS NULL` で冪等に書く。複数回流れても安全な形にする
- 巨大テーブルの全行更新は避けるのが基本。どうしても必要なら CONCURRENTLY 同様にバッチ分割や別運用を検討し、PR で相談する
- `down` で元値に戻せないデータ移行 (情報が失われる変換) は、`down` に戻せない旨をコメントで明示し、最低限スキーマだけは巻き戻す
```js
// up: nullable 追加 → バックフィル → NOT NULL 化
await queryRunner.query(`ALTER TABLE "user_profile" ADD "github" boolean`);
await queryRunner.query(`UPDATE "user_profile" SET "github" = FALSE WHERE "github" IS NULL`);
await queryRunner.query(`ALTER TABLE "user_profile" ALTER COLUMN "github" SET NOT NULL`);
```
### 4. JSONB / 配列列のデフォルト
**なぜ危険か**: 既定値リテラルの書式を誤ると `migration:generate` の出力とズレてスタイル不一致になる。実績ある書式に揃える。
```js
await queryRunner.query(`ALTER TABLE "user_profile" ADD "room" jsonb NOT NULL DEFAULT '{}'`); // オブジェクト
await queryRunner.query(`ALTER TABLE "bubble_game_record" ADD "logs" jsonb NOT NULL DEFAULT '[]'`); // 配列(JSON)
await queryRunner.query(`ALTER TABLE "meta" ADD "pinnedUsers" character varying(256) array NOT NULL DEFAULT '{}'::varchar[]`); // PG 配列型
```
参照: [migration/1565634203341-room.js](../../../../../packages/backend/migration/1565634203341-room.js), [migration/1704959805077-bubble-game-record.js](../../../../../packages/backend/migration/1704959805077-bubble-game-record.js), [migration/1557476068003-PinnedUsers.js](../../../../../packages/backend/migration/1557476068003-PinnedUsers.js)。`down` はいずれも `DROP COLUMN`。
### 5. 安全な DROP と COMMENT
- **DROP の冪等性**: 状況により対象が無いことがある DROP は `IF EXISTS` を付ける (`DROP INDEX IF EXISTS "..."`)。ただし `migration:generate` は通常 `IF EXISTS` を付けない素の DDL を出すので、手で足すのは「条件付きで存在する」と分かっている時だけにする (無闇に付けると本来検出すべき不整合を隠す)
- **COMMENT ON COLUMN**: Misskey は denormalize した列に `'[Denormalized]'` コメントを付ける慣習がある。エンティティの `@Column({ comment: '[Denormalized]' })` に対応して `migration:generate` が `COMMENT ON COLUMN` を出す。`up` で付与したら `down` でも対称に書く
```js
await queryRunner.query(`COMMENT ON COLUMN "note"."renoteChannelId" IS '[Denormalized]'`);
```
参照: [migration/1761569941833-add-channel-muting.js](../../../../../packages/backend/migration/1761569941833-add-channel-muting.js)
### 6. 列リネーム
`migration:generate` はエンティティのプロパティ名変更を **「DROP 旧列 + ADD 新列」** と解釈しがちで、これだと **データが消える**。意図がリネームなら生成 SQL を捨て、手書きで `ALTER TABLE "t" RENAME COLUMN "old" TO "new"` (down は逆) に直す。生成結果を鵜呑みにしないこと。

View File

@@ -1,291 +0,0 @@
# 新規 REST API endpoint を追加する
`packages/backend/src/server/api/endpoints/<category>/<name>.ts` に新規エンドポイントを追加するための手順。**配線フェーズの `endpoint-list.ts` 登録を忘れると 404** になるので、まずそこを念頭に置く。
## 最重要事実 (見落とすと CI / 本番が壊れる)
1. **エンドポイントは glob 自動収集されない**。[endpoint-list.ts](../../../../../packages/backend/src/server/api/endpoint-list.ts) への 1 行追加が必須 → [knowledge/endpoint-list.md](../knowledge/endpoint-list.md)
2. **`meta` / `paramDef` / `res` を変えたら misskey-js 再生成が必須**。`pnpm build-misskey-js-with-types` を忘れると CI の `check-misskey-js-autogen` で必ず落ちる
3. **`meta.errors` の各 `id` は UUID v4 で、リポジトリ内で一意**。`crypto.randomUUID()` で生成し、`grep -r "id: '<UUID>'" packages/backend/src/server/api/endpoints/` で衝突確認
## ワークフロー全体図
```
1. 設計 : エンドポイントの種類を決める (read/write × 認証要否 × 権限)
2. 実装 : meta / paramDef / クラス本体を書く (SPDX ヘッダー付き)
3. 配線 : endpoint-list.ts に登録 (★ 忘れると 404)
4. 検証 : e2e テスト + lint + misskey-js 再生成
5. 仕上げ : CHANGELOG エントリ (shipping-misskey-change で確認)
```
---
## 1. 設計フェーズ — どのテンプレートをベースにするか
まず作るエンドポイントの性質を確定させる。**既存実装をテンプレートとしてコピペ起点にするのが最短路**。
| 性質 | ベースにする既存実装 |
|---|---|
| 認証不要・パラメータなし・小さなレスポンス | [endpoints/ping.ts](../../../../../packages/backend/src/server/api/endpoints/ping.ts) |
| 認証必須・DI で Repository / Service を注入・errors あり | [endpoints/notes/create.ts](../../../../../packages/backend/src/server/api/endpoints/notes/create.ts) |
| ページネーション (sinceId/untilId/limit) | [endpoints/notes/timeline.ts](../../../../../packages/backend/src/server/api/endpoints/notes/timeline.ts) |
| ロールポリシー (動的) ベースのアクセス制御 | [endpoints/notes/global-timeline.ts](../../../../../packages/backend/src/server/api/endpoints/notes/global-timeline.ts) — `RoleService.getUserPolicies()` を使う |
| ファイル添付 (`requireFile: true`) | [endpoints/drive/files/create.ts](../../../../../packages/backend/src/server/api/endpoints/drive/files/create.ts) |
| moderator / admin 専用 | [endpoints/admin/suspend-user.ts](../../../../../packages/backend/src/server/api/endpoints/admin/suspend-user.ts) (moderator), [endpoints/admin/roles/create.ts](../../../../../packages/backend/src/server/api/endpoints/admin/roles/create.ts) (admin) |
`<category>` は機能領域 (例: `notes`, `users`, `admin/announcements`)。ディレクトリは既存に倣う。
---
## 2. 実装フェーズ
### 2.1 SPDX ヘッダー (必須)
新規 `.ts` ファイル冒頭に必ず付ける (欠落すると CI の `spdx` ジョブで失敗):
```ts
/*
* SPDX-FileCopyrightText: syuilo and misskey-project
* SPDX-License-Identifier: AGPL-3.0-only
*/
```
**注:** `packages/misskey-js/src/autogen/` 配下にも diff が出るが、**misskey-js は MIT ライセンス** で別管理 (`packages/misskey-js/package.json:license` = MIT) なので SPDX ヘッダーは付けない / 不要。
### 2.2 最小テンプレート (認証不要 read 系)
```ts
/*
* SPDX-FileCopyrightText: syuilo and misskey-project
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { Injectable } from '@nestjs/common';
import { Endpoint } from '@/server/api/endpoint-base.js';
export const meta = {
tags: ['<tag>'],
requireCredential: false,
res: {
type: 'object',
optional: false, nullable: false,
properties: {
// ...
},
},
} as const;
export const paramDef = {
type: 'object',
properties: {},
required: [],
} as const;
@Injectable()
export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
constructor(
) {
super(meta, paramDef, async (ps, me) => {
// 実装。me は MiLocalUser | null (requireCredential: false のため null チェック必須)
});
}
}
```
### 2.3 DI / errors / limit を含むテンプレート
```ts
import { Inject, Injectable } from '@nestjs/common';
import type { NotesRepository } from '@/models/_.js';
import { Endpoint } from '@/server/api/endpoint-base.js';
import { ApiError } from '@/server/api/error.js';
import { DI } from '@/di-symbols.js';
export const meta = {
tags: ['notes'],
requireCredential: true, // 認証必須 → kind 必須 (例外: secure: true な内部 API は kind 不要)
kind: 'write:notes', // OAuth scope (一覧は packages/misskey-js/src/consts.ts の `permissions`)
prohibitMoved: false, // 移行済アカウントを拒否するか
limit: {
duration: 1000 * 60 * 60, // 1 時間
max: 300,
},
errors: {
noSuchNote: { // ← キーは camelCase
message: 'No such note.', // ← 英語ハードコード (バックエンドに i18n 機構なし)
code: 'NO_SUCH_NOTE', // ← code は SCREAMING_SNAKE_CASE
id: '17a0e0fa-3f3e-4f3e-9f3e-3f3e3f3e3f3e', // ← crypto.randomUUID() で生成し衝突確認
},
},
res: {
type: 'object',
optional: false, nullable: false,
ref: 'Note', // packed entity を参照する場合
},
} as const;
export const paramDef = {
type: 'object',
properties: {
noteId: { type: 'string', format: 'misskey:id' },
},
required: ['noteId'],
} as const;
@Injectable()
export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
constructor(
@Inject(DI.notesRepository)
private notesRepository: NotesRepository,
) {
super(meta, paramDef, async (ps, me) => {
// requireCredential: true なので me は MiLocalUser (null になり得ない)
const note = await this.notesRepository.findOneBy({ id: ps.noteId });
if (note == null) throw new ApiError(meta.errors.noSuchNote);
// 実装
});
}
}
```
DI / module 登録の詳細は [knowledge/nestjs-di.md](../knowledge/nestjs-di.md) を参照。
### 2.4 `exec` 関数のフルシグネチャ
`super(meta, paramDef, cb)``cb` が受け取る引数は 7 つある ([endpoint-base.ts](../../../../../packages/backend/src/server/api/endpoint-base.ts) の `Executor` 型):
```ts
async (ps, me, token, file, cleanup, ip, headers) => { ... }
```
| 引数 | 型 | 用途 |
|---|---|---|
| `ps` | `SchemaType<typeof paramDef>` | AJV 検証済の入力 |
| `me` | `MiLocalUser` (requireCredential: true) / `MiLocalUser \| null` (false) | ローカルユーザー。`requireCredential: false` のとき必ず null チェック |
| `token` | `MiAccessToken \| null` | OAuth トークン (アプリ識別が要るとき) |
| `file` | `{ name, path } \| undefined` | `requireFile: true` のときのみ確実に渡る。エンドポイント基底クラスが既に null チェック済 |
| `cleanup` | `() => any \| undefined` | アップロードされた一時ファイルを削除するコールバック。**基底クラスが自動で呼ぶのは AJV バリデーション失敗時だけ**。正常終了や endpoint 内例外時は **呼ばれない** ので、`try { ... } finally { cleanup!(); }` で必ず呼ぶ責務がある ([drive/files/create.ts](../../../../../packages/backend/src/server/api/endpoints/drive/files/create.ts) の `finally { cleanup!(); }` が手本) |
| `ip` | `string \| null \| undefined` | クライアント IP |
| `headers` | `Record<string, string> \| null \| undefined` | リクエストヘッダ |
ほとんどのエンドポイントは `(ps, me)` だけで十分。`token` / `ip` / `headers` まで使うのは admin / debug / auth 系のごく一部。
### 2.5 meta / paramDef の規約
頻出 5 件 (`tags` / `requireCredential` / `kind` / `limit` / `errors`) の使い方や全フィールド一覧、`requiredRolePolicy` / `secure` / `cacheSec` / `allowGet` 等、それと `paramDef` の AJV 実用パターンは → [knowledge/api-meta-paramdef.md](../knowledge/api-meta-paramdef.md)。
### 2.6 エラー throw のバランス
**クライアントに返すべき業務エラー** は必ず `meta.errors` に列挙して `throw new ApiError(meta.errors.<key>)` する。これを守らないと misskey-js 側の型に出ず、レスポンスも 500 になる。第 2 引数で追加情報を渡せる:
```ts
throw new ApiError(meta.errors.invalidParam, { reason: 'too short' });
```
一方で **想定外の例外 (DB 不整合 / 下層 service の bug / 防御的アサーション)**`throw new Error('...')` のままで構わない。すべての例外を `ApiError` で包むと、未知のバグが client error として隠蔽されてしまう。`endpoints/notes/create.ts``catch` 節末尾の `throw err;` がこの二段構えの典型。
---
## 3. 配線フェーズ — endpoint-list.ts に登録 ★必須
[endpoint-list.ts](../../../../../packages/backend/src/server/api/endpoint-list.ts) の **同カテゴリ内** に 1 行追加する:
```ts
export * as '<category>/<name>' from './endpoints/<category>/<name>.js';
```
詳細・落とし穴は [knowledge/endpoint-list.md](../knowledge/endpoint-list.md) を参照。**ここへの登録漏れ = 404**。
---
## 4. 検証フェーズ
### 4.1 e2e テスト
[packages/backend/test/e2e/](../../../../../packages/backend/test/e2e/) の構造は **機能カテゴリごとのファイル分け** (`note.ts` / `users.ts` / `timelines.ts` / `drive.ts` / `clips.ts` / `oauth.ts` 等)。
- 既存のカテゴリファイルがあるなら、そこに `describe('<人間可読ラベル>', () => { test('正常系', ...) })` で追加
- どのファイルにも合わないなら `test/e2e/endpoints.ts` に追加
- `describe` 名は **人間可読 OK**
最小例 (詳細なヘルパー一覧は → [knowledge/backend-testing.md](../knowledge/backend-testing.md)):
```ts
import { describe, test } from 'vitest';
import * as assert from 'node:assert';
import { api, signup } from '../utils.js';
describe('<人間可読ラベル>', () => {
test('正常系', async () => {
const alice = await signup({ username: 'alice' });
const res = await api('<category>/<name>', { /* params */ }, alice);
assert.strictEqual(res.status, 200);
});
});
```
実行 (前提: `.config/test.yml` — [knowledge/backend-testing.md](../knowledge/backend-testing.md) §前提 参照):
```bash
pnpm --filter backend test:e2e
```
### 4.2 lint / typecheck
```bash
# 個別ファイルを高速にチェック
pnpm exec eslint --fix packages/backend/src/server/api/endpoints/<category>/<name>.ts
pnpm --filter backend typecheck # tsgo --noEmit (backend のみ)
# 一括 (PR 提出前)
pnpm --filter backend lint
```
### 4.3 misskey-js 再生成 (★必須)
`meta` / `paramDef` / `res` を変えたら必ず:
```bash
pnpm build-misskey-js-with-types
```
PR に `packages/misskey-js/src/autogen/` 配下の差分が含まれていないと CI の `check-misskey-js-autogen` で必ず落ちる (最頻ミス)。詳細手順は [shipping-misskey-change/references/tasks/regenerate-misskey-js.md](../../../shipping-misskey-change/references/tasks/regenerate-misskey-js.md)。
---
## 5. 仕上げフェーズ — CHANGELOG
ユーザー影響がある (新機能 / 既存挙動変更) なら `CHANGELOG.md``## Unreleased``### Server` に 1 行追加する。詳細は [shipping-misskey-change スキル](../../../shipping-misskey-change/SKILL.md) に従う。
---
## 落とし穴サマリ (PR で頻発するミス)
詳細な症状 → 原因 → 修正 のフォーマット → **[knowledge/api-meta-paramdef.md](../knowledge/api-meta-paramdef.md) §落とし穴**
- **404 になる** → `endpoint-list.ts` 登録漏れ
- **CI `check-misskey-js-autogen` で落ちる** → `pnpm build-misskey-js-with-types` 忘れ
- **CI `spdx` で落ちる** → SPDX ヘッダー欠落
- **クライアントが 500 と error 型不在を受け取る** → `meta.errors` 列挙なしに `throw new ApiError(...)` した
- **`me.id` で TypeError** → `requireCredential: false` で null チェックを忘れた
- **UUID 重複** → 衝突確認グレップを忘れた
- **一時ファイルが残る** → `requireFile: true``cleanup!()``finally` で呼び忘れた
- **`requiredRolePolicy` で匿名アクセスが 500 になる** → `ApiCallService``user!.id` を非null前提で参照するため `requireCredential: true` 必須
---
## 参照ファイル
### コードベース
- [endpoints.ts (meta/paramDef 型定義)](../../../../../packages/backend/src/server/api/endpoints.ts)
- [endpoint-base.ts (Endpoint 基底クラス)](../../../../../packages/backend/src/server/api/endpoint-base.ts)
- [endpoint-list.ts (★ ここに登録)](../../../../../packages/backend/src/server/api/endpoint-list.ts)
- [error.ts (ApiError)](../../../../../packages/backend/src/server/api/error.ts)
- [endpoints/ping.ts (最小例)](../../../../../packages/backend/src/server/api/endpoints/ping.ts)
- [endpoints/notes/create.ts (DI + errors の典型)](../../../../../packages/backend/src/server/api/endpoints/notes/create.ts)
- [endpoints/notes/global-timeline.ts (policies 動的チェック)](../../../../../packages/backend/src/server/api/endpoints/notes/global-timeline.ts)
- [test/e2e/endpoints.ts (テスト例)](../../../../../packages/backend/test/e2e/endpoints.ts)
- [test/utils.ts (api/signup/post 等のヘルパー)](../../../../../packages/backend/test/utils.ts)
- [scripts/generate_api_json.js (misskey-js 生成元)](../../../../../packages/backend/scripts/generate_api_json.js)

View File

@@ -1,180 +0,0 @@
# DB migration を作成する
`packages/backend/migration/` に新規 TypeORM マイグレーションを追加するための手順。
## 大前提 (絶対 NG)
- **既にマージ済み (develop / master) のマイグレーションファイルを編集しない** ([AGENTS.md](../../../../../AGENTS.md))。本番履歴の改変は深刻なデータ不整合を引き起こす。スキーマ変更は **常に新しいタイムスタンプで新規ファイル** を作る
- ファイル名のタイムスタンプ部分を後から書き換えない (順序が壊れる)
- マージ済 migration の `up()` / `down()` 本文も触らない (たとえ "明らかなバグ" であっても、新しい migration で打ち消すこと)
---
## どの方式を使うか決める
| 状況 | 方式 |
|---|---|
| エンティティ (`packages/backend/src/models/*.ts`) を `@Column` / `@Index` / `@Entity` 等で先に変更し、差分から自動生成したい | `typeorm migration:generate` (本ファイルの "A. 差分から自動生成") |
| 手書き SQL / データ移行 / `CREATE INDEX CONCURRENTLY` など、エンティティ差分では表現できない変更 | `typeorm migration:create` で空雛形を作る (本ファイルの "B. 空雛形を作る") |
迷ったら **まずエンティティを変更 → `migration:generate`** が原則。既存 migration (`packages/backend/migration/*.js`) のほぼすべてが `queryRunner.query(\`SQL...\`)` の raw SQL なので、CLI 出力でも手書きでもスタイルは揃う。
---
## 共通: クラス命名規則
- ファイル名: `packages/backend/migration/{unixMs}-{descriptive-name}.js` (拡張子 `.js`)
- ファイル名の `descriptive-name` 部分は既存履歴で混在 (PascalCase / camelCase / kebab-case)、変更を表す単一英語名なら良い
- **クラス名は PascalCase + 13 桁タイムスタンプ** (例: `class BirthdayIndex1767169026317`)
- **`name` プロパティもクラス名と同一文字列** にする (`name = 'BirthdayIndex1767169026317'`)
```js
/*
* SPDX-FileCopyrightText: syuilo and misskey-project
* SPDX-License-Identifier: AGPL-3.0-only
*/
export class PascalCaseName1234567890123 {
name = 'PascalCaseName1234567890123'
async up(queryRunner) {
// 前進マイグレーション
}
async down(queryRunner) {
// up を完全に巻き戻す
}
}
```
---
## A. エンティティ差分から自動生成
```bash
# リポジトリルートから実行してよい。--filter backend exec が cwd を packages/backend に移すので、
# 出力パス migration/<PascalName> と -d ormconfig.js は packages/backend/ 基準で解決される
pnpm --filter backend exec typeorm migration:generate -d ormconfig.js -o --esm migration/<PascalName>
```
**CONTRIBUTING.md との違い**: CONTRIBUTING.md は `pnpm dlx typeorm ...` を案内しているが、`dlx` はパッケージを一時ダウンロードするため、バージョンが backend の依存関係と揃わない可能性がある。`pnpm --filter backend exec typeorm` はワークスペースにインストール済みの typeorm を使うため **こちらを推奨**。
**`-o --esm` について**: `-o` (`--outputJs`) は「TS ではなく JS を出力する」オプション、`--esm` は「ESM 形式 (`export class ...`) で出力する」オプション。Misskey の既存 migration はすべて ESM JS であるため **両方が必須**。`--esm` を省略すると CommonJS 形式の JS が生成されスタイルが揃わない。
### 事前準備 (一括スクリプト)
`migration:generate` には backend ビルド + ローカル DB が必要。一括で揃えるスクリプトを同梱している (node 製。pure Windows でも動く)。リポジトリルートから:
```bash
node .claude/skills/working-on-backend/scripts/prepare-generate.mjs
```
スクリプトがやること:
- `pnpm build-pre` → `built/meta.json` を生成 (`loadConfig()` が要求)
- `pnpm --filter backend compile-config` → `built/.config.json` を生成 (`ormconfig.js` の `loadConfig()` が要求するのはこれ。ソースの `.config/default.yml` はその入力なので、無ければ `.config/example.yml` から作っておく)
- `pnpm --filter backend build` → エンティティを `built/` に反映 (CLI は `built/` を読む)
- `docker compose -f compose.local-db.yml up -d --wait db` → ローカル DB (postgres) を起動。`--wait` は Docker Compose v2.1.1 (2021-11) 以降が必要 (v2 の `docker compose` 前提。EOL の `docker-compose` v1 は対象外)
`migration:create` (空雛形) しか使わないなら DB もビルドも不要なので、このスクリプトは不要。
---
## B. 空雛形を作る (手書き SQL / データ移行用)
```bash
pnpm --filter backend exec typeorm migration:create -o --esm migration/<PascalName>
```
ローカル DB の起動とビルドは不要。空の `up` / `down` だけが生成される。
**注意:** `-o --esm` を **必ず付ける**。これが無いと `<UnixMs>-<PascalName>.ts` (CommonJS / TS 出力) が生成されるが、Misskey の `ormconfig.js` は `migration/*.js` だけを読み、既存の他 migration も全て `export class ... { async up(queryRunner) {...} }` の ESM JS 形式なので、後で手作業で変換が必要になる。`-o --esm` を付ければそのまま `.js` ESM で出る。
ただし `migration:create` の雛形は **`name = '...'` プロパティを出力しない**ので、後段の SPDX 付与に加えて `name = '<PascalName><ms>'` を手で足し、`up`/`down` を埋める必要がある。雛形冒頭の `@typedef` / `@implements MigrationInterface` JSDoc は既存ファイルに無いので消して house style に揃える。
### B の補助: 引数だけで全部を済ませたい場合
引数で `<PascalCaseName>` を渡すだけで「空雛形生成 + SPDX 付与 + check-migrations 実行」までやる薄いラッパー (旧 `.claude/commands/migrate-new.md` 由来) は廃止された。同等の流れを手で踏みたい場合、上記の `typeorm migration:create` + SPDX 付与 + `name` プロパティ追加 + `check-migrations` の順で実行する。
---
## SPDX ヘッダー付与
CLI 出力には SPDX ヘッダーが含まれない。**必ず冒頭に追加する** (CI の `spdx` ジョブが失敗するため)。
```js
/*
* SPDX-FileCopyrightText: syuilo and misskey-project
* SPDX-License-Identifier: AGPL-3.0-only
*/
```
---
## up / down の整合確認
- `up()` の各ステートメントに対し、`down()` で完全に巻き戻せること
- 列追加 (`ADD COLUMN`) ↔ 列削除 (`DROP COLUMN`)、テーブル作成 ↔ テーブル削除、FK 追加 ↔ FK 削除、インデックス作成 ↔ インデックス削除 を必ずペアで書く
- `down()` を空のまま残さない。本番ロールバック時に詰む
**単純な逆 SQL では戻らない難ケース** (enum 値の追加・変更 / NOT NULL 列追加 / データ移行 UPDATE / JSONB・配列デフォルト / 列リネーム / 安全な DROP・COMMENT) は [knowledge/typeorm-patterns.md §migration 難ケース](../knowledge/typeorm-patterns.md) を必ず参照。特に **enum 変更** と **列リネーム** は `migration:generate` の出力をそのまま使うと巻き戻せない / データが消えるので要注意。
### インデックス追加時 (CREATE INDEX CONCURRENTLY)
大規模テーブルへの `CREATE INDEX` は本番で長時間ロックする恐れがある。`CONCURRENTLY` で発行するときは migration class に `transaction = false` 等の対応が必要。詳細は [knowledge/typeorm-patterns.md §CONCURRENTLY](../knowledge/typeorm-patterns.md) を参照。
参照実装: [packages/backend/migration/1745378064470-composite-note-index.js](../../../../../packages/backend/migration/1745378064470-composite-note-index.js)。
---
## 検証
ルートから実行:
```bash
# 未反映の差分が無いか (新規 migration が生成すべき DDL を取り逃していないか)
pnpm --filter backend check-migrations
# ローカル DB に適用
pnpm migrate
# ロールバック (down が壊れていないか)
pnpm revert
# 再適用 (順方向にもう一度通す)
pnpm migrate
```
`check-migrations` の実体は [scripts/check_migrations_clean.js](../../../../../packages/backend/scripts/check_migrations_clean.js)。TypeORM の `dataSource.driver.createSchemaBuilder().log()` で pending DDL を取得し、`upQueries` / `downQueries` のいずれかが残っていれば非ゼロ終了する。**順序検査ではなく**「エンティティと migration が同期しているか」の検査。
---
## 既存ファイル参照テンプレ
新規ファイルを書くときは、変更パターンが近い既存ファイルを **必ずひとつ開いて並べて書く**。スタイルが激しくズレた PR は差し戻されやすい。
| パターン | 参照ファイル |
|---|---|
| インデックス追加 + 関数定義 | [migration/1767169026317-birthday-index.js](../../../../../packages/backend/migration/1767169026317-birthday-index.js) |
| 列追加のみ | [migration/1766652173085-add-category-to-avatar-decorations.js](../../../../../packages/backend/migration/1766652173085-add-category-to-avatar-decorations.js) |
| テーブル新規作成 + FK | [migration/1761569941833-add-channel-muting.js](../../../../../packages/backend/migration/1761569941833-add-channel-muting.js) |
---
## CHANGELOG (ユーザー影響がある場合)
スキーマ変更がユーザーに見える挙動を生む場合のみ、`CHANGELOG.md` に追記する。内部リファクタや純粋なインデックス追加は不要。詳細は [shipping-misskey-change スキル](../../../shipping-misskey-change/SKILL.md) で確認。
---
## 提出前セルフレビューチェックリスト
完了前に以下を上から確認する (各項目を TodoWrite 化してよい):
- [ ] **新規タイムスタンプ**で作成し、既にマージ済みの migration ファイルは一切編集していない (大前提)
- [ ] ファイル冒頭に **SPDX ヘッダー**がある
- [ ] `export class <PascalName><ms>` と `name = '<PascalName><ms>'` の **文字列が完全一致** している (PascalCase + 13 桁タイムスタンプ)
- [ ] `up()` の各文に対応する巻き戻しが `down()` にあり、**`down()` が空でない** (難ケースは [knowledge/typeorm-patterns.md](../knowledge/typeorm-patterns.md) を確認済み)
- [ ] `pnpm --filter backend check-migrations` が **0 件 (pending DDL なし)** で通る
- [ ] (可能なら) `pnpm migrate` → `pnpm revert` → `pnpm migrate` が通る
- [ ] ユーザーに見える変更なら CHANGELOG 追記 → [shipping-misskey-change](../../../shipping-misskey-change/SKILL.md)

View File

@@ -1,66 +0,0 @@
/*
* typeorm migration:generate の前準備をまとめて実行する (冪等・クロスプラットフォーム)。
* リポジトリルートから実行: node .claude/skills/working-on-backend/scripts/prepare-generate.mjs
*
* generate はエンティティのビルド出力 (built/)、コンパイル済み設定 (built/.config.json)、
* 稼働中の DB を必要とする。手で 5 段並べると取りこぼすのでここに集約する。
* migration:create (空雛形) しか使わないなら DB もビルドも不要なのでこのスクリプトは不要。
*
* Node で書いているのは pure Windows (bash の無い環境) でも動かすため。node はこのリポジトリの
* ランタイムなので必ず存在し、build-pre.mjs / compile_config.js と同じ流儀に揃う。
*/
import { execSync } from 'node:child_process';
import { existsSync } from 'node:fs';
import { fileURLToPath } from 'node:url';
import { dirname, resolve } from 'node:path';
// このファイルの 4 つ上が repo root
const root = resolve(dirname(fileURLToPath(import.meta.url)), '../../../..');
process.chdir(root);
function step(msg) { console.log(`\n==> ${msg}`); }
function run(cmd) { console.log(`$ ${cmd}`); execSync(cmd, { stdio: 'inherit' }); }
function fail(msg) { console.error(`ERROR: ${msg}`); process.exit(1); }
step('1/5 設定ファイルの確認');
if (!existsSync('.config/default.yml')) {
fail([
'.config/default.yml が存在しません。',
' .config/example.yml を .config/default.yml にコピーしてから再実行してください:',
' Unix系: cp .config/example.yml .config/default.yml',
' PowerShell: Copy-Item .config/example.yml .config/default.yml',
' コピー後、db.user / pass / db を .config/docker.env と一致させてください',
' (example.yml の既定値は docker.env の例と一致するので、独自 DB を使わなければそのままで可)。',
].join('\n'));
}
// compose.local-db.yml の db サービスは .config/docker.env を env_file に要求する
if (!existsSync('.config/docker.env')) {
fail([
'.config/docker.env が存在しません (compose.local-db.yml の db が要求)。',
' 例 (.config/default.yml の db.user / db.pass / db.db と一致させる):',
' POSTGRES_USER=example-misskey-user',
' POSTGRES_PASSWORD=example-misskey-pass',
' POSTGRES_DB=misskey',
].join('\n'));
}
console.log('OK: .config/default.yml と .config/docker.env あり');
step('2/5 built/meta.json の生成 (build-pre)');
run('pnpm build-pre');
step('3/5 設定のコンパイル (compile-config -> built/.config.json)');
run('pnpm --filter backend compile-config');
step('4/5 backend のビルド (エンティティを built/ へ反映)');
run('pnpm --filter backend build');
step('5/5 ローカル DB の起動 (postgres のみ・healthcheck 完了まで待機)');
// migration:generate が必要とするのは postgres だけ。db サービスに絞れば meilisearch.env 等が無くても動く。
// --wait は compose の pg_isready healthcheck 完了まで待つ。直後の migration:generate が
// DB 未起動で失敗しないために必須。--wait は Docker Compose v2.1.1 (2021-11) で導入されており、
// このリポジトリが前提とする v2 の `docker compose` なら標準で使える (EOL の `docker-compose` v1 は対象外)。
run('docker compose -f compose.local-db.yml up -d --wait db');
console.log('\n準備完了。次を実行できます:');
console.log(' pnpm --filter backend exec typeorm migration:generate -d ormconfig.js -o --esm migration/<PascalName>');

View File

@@ -1,36 +0,0 @@
---
name: working-on-frontend
description: Use whenever editing or adding code under `packages/frontend/`, or editing `locales/ja-JP.yml` for frontend-facing UI text — including Vue 3 SFCs (`Mk*` components), i18n keys (`i18n.ts.<key>` / `i18n.tsx.<key>()`), SCSS Modules, theme/CSS variables, `os.*` UI helpers, and Storybook stories. Covers SPDX (HTML comment form), `<script setup lang="ts">` conventions, type-only defineProps, `ja-JP.yml`-only locale editing (other locale yml files are Crowdin-managed and must not be edited), and accessibility. Must be consulted before any frontend or UI-locale change to avoid CI failures, lost translations, and reviewer pushback. This is NOT waived by having already invoked brainstorming, writing-plans, or any other upstream skill — invoke this at implementation time regardless of what preceded it.
---
# working-on-frontend
`packages/frontend/` (Misskey Web クライアント) を編集するとき、最初に参照するスキル。Vue 3 SFC / SCSS Modules / i18n / `os.*` / Storybook / アクセシビリティの **手順****背景知識** をまとめている。
SKILL.md 本体は references への索引だけ。具体的な手順や規約は該当ファイルを Read すること (progressive disclosure)。
**他スキル実行後も免除されない。** `brainstorming` / `writing-plans` / その他アップストリームスキルを先に呼んでいても、`packages/frontend/` に触れる実装フェーズに入る時点でこのスキルを呼ぶこと。
## 作業別ワークフロー (tasks)
タスク単位の完結したチェックリスト。新しい何かを足すときに開く。
- 新規 / 既存 `Mk*` Vue コンポーネントを追加・改修する → [references/tasks/adding-mk-component.md](references/tasks/adding-mk-component.md)
- i18n キーを追加・改修する (`locales/ja-JP.yml` 編集) → [references/tasks/adding-i18n-key.md](references/tasks/adding-i18n-key.md)
## 共通知識 (knowledge)
タスクに紐付かない参照リファレンス。SFC を **編集する** 場面 (新規追加でなくても) で踏みうる規約。
- `<script setup>` / type-only `defineProps` / `defineEmits` / generic SFC / v-model 連動など SFC 規約 → [references/knowledge/component-conventions.md](references/knowledge/component-conventions.md)
- `i18n.ts.<key>` / `i18n.tsx.<key>(...)` の使い分け / HTML タグ埋め込み / 動的キー切替 / 既存キーのリネーム手順 → [references/knowledge/i18n-usage.md](references/knowledge/i18n-usage.md)
- SCSS Modules / `--MI_THEME-*` `--MI-*` CSS 変数 / グローバル utility class (`_button` 等) → [references/knowledge/scss-modules.md](references/knowledge/scss-modules.md)
- `os.alert` / `os.confirm` / `os.popup` 等 UI ヘルパー (ブラウザ標準 `alert()` 直呼びは禁止) → [references/knowledge/os-api.md](references/knowledge/os-api.md)
- `*.stories.impl.ts` 併設規則 + 複数 story / argTypes / layout / action パターン → [references/knowledge/storybook.md](references/knowledge/storybook.md)
- frontend Vitest / Cypress E2E の書き方と前提 → [references/knowledge/frontend-testing.md](references/knowledge/frontend-testing.md)
## 必ず最後に通る場所
frontend の変更を commit / PR にする前に、必ず [shipping-misskey-change](../shipping-misskey-change/SKILL.md) の最終チェックリストに従う。`pnpm lint` / SPDX / `ja-JP.yml` のみ編集確認 / CHANGELOG をまとめて確認する。
`.vue` を追加・変更したなら、その出口で [vue-component-reviewer](../../agents/vue-component-reviewer.md) agent (この skill の規約を review-mode から機械チェックする専門 reviewer) を Task で起動すると、SPDX 形式・命名・i18n・SCSS 変数・a11y・Storybook 併設の逸脱を取りこぼしにくい。

View File

@@ -1,357 +0,0 @@
# Vue SFC 規約・テンプレート集 + a11y チェックリスト
Misskey の Vue 3 SFC 規約と、新規 `Mk*` コンポーネント / 既存コンポーネント編集時のテンプレート / アクセシビリティ要件をまとめたページ。
## 目次
- [SFC スタイルの基本](#sfc-スタイルの基本)
- [`<script>` / `<style>` 規約](#script--style-規約)
- [テンプレート集](#テンプレート集)
- [simple (`<slot>` + 単純 props)](#simple-slot--単純-props)
- [generic + 2 ブロック script](#generic--2-ブロック-script)
- [`defineModel` で v-model 連動](#definemodel-で-v-model-連動)
- [emit + 名前付き slot で外部から動作を差し込む](#emit--名前付き-slot-で外部から動作を差し込む)
- [a11y チェックリスト](#a11y-チェックリスト)
## SFC スタイルの基本
Composition API + `<script setup lang="ts">` を基本とする (Options API は新規導入しない)。型宣言や module スコープのユーティリティを置きたい時は、setup ブロックと **併用** する形で追加の `<script lang="ts">` ブロックを置いて構わない (例: [MkInput.vue](../../../../../packages/frontend/src/components/MkInput.vue) は `SupportedTypes` 型を別ブロックで宣言してから setup を書いている)。SCSS は **CSS Modules** で書き、`<style lang="scss" module>` を使う。
```vue
<!--
SPDX-FileCopyrightText: syuilo and misskey-project
SPDX-License-Identifier: AGPL-3.0-only
-->
<template>
<div :class="$style.root">
<!-- ... -->
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue';
// ...
</script>
<style lang="scss" module>
.root {
/* ... */
}
</style>
```
## `<script>` / `<style>` 規約
| 項目 | 規約 | 新規不可 |
|---|---|---|
| `<script>` 開始タグ | `<script lang="ts" setup>` または `<script setup lang="ts">` (順序不問) | `<script>` (lang 無し) / Options API (`export default { data() {...} }`) |
| Props 定義 | `defineProps<{ ... }>()` (type-only) | runtime object 形式 `defineProps({ name: { type: String } })` |
| Emits 定義 | `defineEmits<{ (ev: 'click'): void }>()` (type-only) | runtime array 形式 `defineEmits(['click'])` |
| 型ジェネリック | `<script setup lang="ts" generic="T extends ...">` 属性で渡す。複雑な型宣言が必要なら **2 ブロック構成** ([generic パターン](#generic--2-ブロック-script)) | — |
| `<style>` 開始タグ | `<style lang="scss" module>`、参照は `:class="$style.foo"` | `<style scoped>` (module なし) は新規不可 (legacy 混在) |
| CSS 値 | `var(--MI_THEME-...)` (テーマ) / `var(--MI-...)` (UI 共通定数) を使う | `#fff` / `rgb(...)` / `rgba(...)` のハードコード ([scss-modules.md](scss-modules.md)) |
| グローバル class | `_button` / `_panel` / `_selectable` / `_buttonPrimary` 等の global utility class を活用 | — |
| アイコン | Tabler icons クラス `<i class="ti ti-info-circle">` | インライン SVG / 別アイコンセット |
## テンプレート集
### simple (`<slot>` + 単純 props)
下記は `<slot>` + props + `withDefaults` の典型パターンを示す**合成例** (特定ファイルの写しではない)。実在する単純コンポーネントの例は [MkInfo.vue](../../../../../packages/frontend/src/components/MkInfo.vue) 等を参照。
```vue
<!--
SPDX-FileCopyrightText: syuilo and misskey-project
SPDX-License-Identifier: AGPL-3.0-only
-->
<template>
<div :class="[$style.root, { [$style.warn]: variant === 'warn' }]" class="_selectable">
<i v-if="variant === 'warn'" class="ti ti-alert-triangle" :class="$style.icon"></i>
<i v-else class="ti ti-info-circle" :class="$style.icon"></i>
<div><slot></slot></div>
</div>
</template>
<script lang="ts" setup>
const props = withDefaults(defineProps<{
variant?: 'info' | 'warn';
}>(), {
variant: 'info',
});
</script>
<style lang="scss" module>
.root {
display: flex;
align-items: center;
gap: 4px;
padding: 12px 14px;
font-size: 90%;
background: var(--MI_THEME-infoBg);
color: var(--MI_THEME-infoFg);
border-radius: var(--MI-radius);
&.warn {
background: var(--MI_THEME-infoWarnBg);
color: var(--MI_THEME-infoWarnFg);
}
}
.icon {
margin-right: 4px;
}
</style>
```
ポイント:
- デフォルト値が必要なら `withDefaults(defineProps<{...}>(), { ... })` を使う (type-only のまま既定値を渡せる)
- `_selectable` は本文選択を許可する global utility class ([scss-modules.md](scss-modules.md) 参照)
- `<i class="ti ti-...">` は Tabler icons。`v-if` 切り替えで variant 別アイコンを出すのは多用パターン
### generic + 2 ブロック script
参考: [MkInput.vue](../../../../../packages/frontend/src/components/MkInput.vue)
型ジェネリックを取りつつ、その型計算や `type` エイリアス宣言を setup ブロックの中に書きたくない場合は、**型宣言用 `<script lang="ts">` と setup 用 `<script lang="ts" setup>` を 2 つ並べる** 構成にできる。
```vue
<!--
SPDX-FileCopyrightText: syuilo and misskey-project
SPDX-License-Identifier: AGPL-3.0-only
-->
<template>
<div :class="$style.root">
<button
v-for="item in items"
:key="String(item.value)"
class="_button"
:class="[$style.item, { [$style.active]: item.value === modelValue }]"
@click="select(item.value)"
>
{{ item.label }}
</button>
</div>
</template>
<script lang="ts">
// module scope: 型 / 定数 / 純関数のみ。setup の中から見える。
export type ChoiceItem<T> = {
value: T;
label: string;
};
</script>
<script lang="ts" setup generic="T extends string | number">
const props = defineProps<{
modelValue: T;
items: ChoiceItem<T>[];
}>();
const emit = defineEmits<{
(ev: 'update:modelValue', value: T): void;
}>();
function select(value: T) {
emit('update:modelValue', value);
}
</script>
```
ポイント:
- `generic="T extends string | number"` の制約を付けることで、`v-model` で渡された型が `string` / `number` 系に限定される
- 2 ブロック構成にする理由は **setup ブロック内では `export type` が書けない** から
- `MkSelect.vue` のような複雑な型エクスポートをするコンポーネントで多用される
### `defineModel` で v-model 連動
参考: [MkSelect.vue](../../../../../packages/frontend/src/components/MkSelect.vue), [MkRadios.vue](../../../../../packages/frontend/src/components/MkRadios.vue)
`defineModel` を使うと `props.modelValue` + `emit('update:modelValue', v)` の 2 行が 1 行に圧縮できる。
```vue
<template>
<label :class="[$style.root, { [$style.disabled]: disabled }]">
<input
v-model="checked"
type="checkbox"
:class="$style.input"
:disabled="disabled"
>
<span :class="$style.label"><slot></slot></span>
</label>
</template>
<script lang="ts" setup>
const checked = defineModel<boolean>({ required: true });
const props = defineProps<{
disabled?: boolean;
}>();
</script>
```
ポイント:
- `defineModel<boolean>()`**自動で `props.modelValue` と `emit('update:modelValue', v)` を生成** する。返り値は `Ref` なので `checked.value = ...` で書き換えると emit される
- `defineModel('foo')` のように引数を渡すと `v-model:foo` (`props.foo` + `emit('update:foo', v)`) の連動が作れる
- 新規ファイルの v-model 連動は原則として `defineModel` を使う (`props.modelValue` + `emit` の手書きは既存コードに残るのみ)
### emit + 名前付き slot で外部から動作を差し込む
下記は emit + 名前付き slot の典型パターンを示す**合成例** (特定ファイルの写しではない)。クリック時の処理を呼び出し元に委ねるパターン (確認 UI など)。なお [MkButton.vue](../../../../../packages/frontend/src/components/MkButton.vue) 自体は `(ev: 'click', payload: PointerEvent)` のみを emit する単機能ボタンで、この合成例とは構造が異なる。
```vue
<template>
<div :class="$style.root" class="_panel">
<div :class="$style.header">
<slot name="header">{{ i18n.ts.confirm }}</slot>
</div>
<div :class="$style.body">
<slot></slot>
</div>
<div :class="$style.footer">
<button class="_button" :class="$style.cancel" @click="emit('cancel')">
{{ i18n.ts.cancel }}
</button>
<button class="_button _buttonPrimary" :class="$style.ok" @click="emit('ok')">
{{ i18n.ts.ok }}
</button>
</div>
</div>
</template>
<script lang="ts" setup>
import { i18n } from '@/i18n.js';
const emit = defineEmits<{
(ev: 'ok'): void;
(ev: 'cancel'): void;
}>();
</script>
```
ポイント:
- 名前付き slot (`<slot name="header">`) と無名 slot (`<slot></slot>`) は両方使ってよい
- `_panel` / `_button` / `_buttonPrimary` は global utility class なので、自前で同じスタイルを書かない
- `emit('ok')` 等の単純 emit は中継するだけにし、`os.confirm` などの実際の確認 UI 起動は呼び出し元の責務にする (テスト・差し替えしやすくするため)
## a11y チェックリスト
Misskey の PR レビューで頻繁に出る a11y 指摘をまとめた。新規 / 既存コンポーネントを編集する時は以下を満たす。
### クリック可能要素
#### 第一選択: `<button class="_button">`
```vue
<button class="_button" :class="$style.action" :disabled="disabled" @click="onClick">
{{ i18n.ts.save }}
</button>
```
- `_button` global class はボタンの装飾を除去するリセット (背景/枠線なし + `cursor: pointer` + disabled cursor)。focus ring や ripple は**付かない** — ripple 付きのボタンが要るなら `MkButton.vue` コンポーネントを使う
- `<button>` はデフォルトで `tabindex` / Enter / Space / `aria-disabled` の挙動とブラウザ標準のフォーカスリングを持つので、追加の ARIA を書かなくてよい
- form の中で意図せず submit させたくない場合は `type="button"` を明示する (省略時は `type="submit"` 扱い)
#### やむを得ず `<div @click>` を使う場合
装飾やレイアウト都合で `<button>` が使えないときは、**4 点セット** を必ず揃える。
```vue
<div
role="button"
tabindex="0"
:aria-disabled="disabled"
:class="$style.fakeButton"
@click="onClick"
@keydown.enter="onClick"
@keydown.space.prevent="onClick"
>
<slot></slot>
</div>
```
| 属性 / ハンドラ | なぜ必要か |
|---|---|
| `role="button"` | スクリーンリーダーにボタンとして読ませる |
| `tabindex="0"` | キーボードでフォーカス可能にする |
| `@keydown.enter` | Enter で発火 (本物の `<button>` の挙動を再現) |
| `@keydown.space.prevent` | Space で発火 + ページスクロール防止 |
| `:aria-disabled` | disabled スタイルだけでなく状態も伝える |
`@keydown.enter` を忘れて click だけ付けるのが最頻出ミス。
#### `<a>` をボタン代わりに使うのは原則禁止
URL に飛ばない `<a href="#" @click.prevent>` は a11y / SEO 両面で良くない。リンクなら `<MkA>` ([MkA.vue](../../../../../packages/frontend/src/components/global/MkA.vue))、アクションなら `<button>` を使う。
### フォーム要素
#### `<label>` 接続
```vue
<!-- for / id -->
<label :for="id">{{ i18n.ts.username }}</label>
<input :id="id" v-model="username" type="text">
<!-- ラップする (id 不要) -->
<label>
{{ i18n.ts.username }}
<input v-model="username" type="text">
</label>
```
label を slot で受け取る共通コンポーネント ([MkInput.vue](../../../../../packages/frontend/src/components/MkInput.vue), [MkSwitch.vue](../../../../../packages/frontend/src/components/MkSwitch.vue)) を使うとこの規約は自然に守れる。
#### `aria-label` で代替
slot や label を見せたくない (アイコンのみのボタンなど) 場合は `aria-label`:
```vue
<button class="_button" :aria-label="i18n.ts.close" @click="emit('close')">
<i class="ti ti-x"></i>
</button>
```
`aria-label` の値も i18n 経由にする (英語直書きは禁止)。
**実情:** 現状コードベースでは `aria-label` の使用例自体が乏しい (アイコンの hover ヒントには `:title="i18n.ts..."` が使われるが、`title` は tooltip でありスクリーンリーダー向けラベルの代替にはならない)。このため aria-label は確立した慣習というより a11y 上の推奨ベストプラクティスとして書いている。新規でアイコンのみのボタンを足すなら付けるのが望ましい。
### `:disabled` と `aria-disabled` の整合
- 本物の `<button :disabled>` ならブラウザが click を抑止するが、`<div role="button">` は止めてくれない。`aria-disabled` を付けるだけでなく、**ハンドラ側でも早期 return** する:
```ts
function onClick() {
if (props.disabled) return; // ← これが無いと disabled でも発火する
// ...
}
```
### キーボード操作
- Tab で全ての操作可能要素にたどり着けること (`tabindex="-1"` を不用意に付けない)
- モーダル / popup を開いたら focus trap を考える ([MkModal.vue](../../../../../packages/frontend/src/components/MkModal.vue) のような既存コンポーネントは内部で対応している)
- リスト中の項目は矢印キー操作も考慮する。Space / Enter で開く・確定する UI は `MkSelect.vue``@keydown.space.enter`(メニューを開く) パターンを参考にする
### 既存実装の参考
| パターン | 既存コンポーネント |
|---|---|
| 標準的なボタン | [MkButton.vue](../../../../../packages/frontend/src/components/MkButton.vue) |
| カスタム UI でも a11y を満たす | [MkSwitch.vue](../../../../../packages/frontend/src/components/MkSwitch.vue) |
| input + label slot | [MkInput.vue](../../../../../packages/frontend/src/components/MkInput.vue) |
| キーボード操作対応の選択 UI | [MkSelect.vue](../../../../../packages/frontend/src/components/MkSelect.vue) |
### ありがちな PR レビュー指摘
- `<div @click>` に role / tabindex / keydown が無い
- アイコンだけのボタンに `aria-label` が無い (Tabler icon 自体には意味情報が無い)
- `disabled` スタイルだけ付けて `aria-disabled` / ハンドラ抑止が無い
- フォーカスリング (`:focus-visible` / `outline`) を `outline: none` で消したまま放置

View File

@@ -1,60 +0,0 @@
# Frontend テスト (Vitest / Cypress)
Misskey frontend のテスト構成。
## Vitest (unit)
```bash
pnpm --filter frontend test # 1 回実行
pnpm --filter frontend test-and-coverage # カバレッジ付き
```
### 配置
- 主な配置: `packages/frontend/test/*.test.ts` (例: `i18n.test.ts`, `theme.test.ts`, `is-birthday.test.ts`)
- ビルドツール周りなど対象コードと隣接させた方が分かりやすいテストは、コードと同じディレクトリに `*.test.ts` として置く (例: [packages/frontend/lib/rollup-plugin-unwind-css-module-class-name.test.ts](../../../../../packages/frontend/lib/rollup-plugin-unwind-css-module-class-name.test.ts))
- 共有コンポーネント (`MkX.vue`) のユニットテストは現状少なく、`*.spec.ts` / `__tests__/` 形式は採用していない (Storybook + Cypress でカバー)
## Cypress E2E
Cypress は **起動済みのテストサーバー** に対して走るため、unit より前提が多い。[.github/workflows/test-frontend.yml](../../../../../.github/workflows/test-frontend.yml) の `e2e` ジョブと同じ手順をローカルで踏む:
```bash
# 1. テスト用 DB / Redis を起動 (テスト用ポート。開発用の compose.local-db.yml ではない)
docker compose -f packages/backend/test/compose.yml up -d
# 2. テスト設定を配置 (未作成なら。例示なので、cpコマンドは環境にあったコマンドに適宜読み替えること)
cp .github/misskey/test.yml .config/test.yml
# 3. 全体ビルド
pnpm build
# 4. テストサーバー起動 + Cypress 実行 (いずれもルートから)
pnpm e2e # 内部で pnpm start:test を起動し http://localhost:61812 を待って Cypress run
pnpm cy:open # 対話的に開く (サーバーは別途 pnpm start:test で起動しておく)
```
- 設定: ルート [cypress.config.ts](../../../../../cypress.config.ts)
- テスト本体は [cypress/](../../../../../cypress/) 配下
新規 frontend 機能の E2E は Cypress に書くのが基本。ただし対象は主要 UI フロー (login / post / drive etc) に限定し、細かい単位テストは Vitest または Storybook で代替する慣習。
## Storybook (視覚確認 + Chromatic 視覚回帰)
詳細は → [storybook.md](storybook.md)。
```bash
pnpm --filter frontend storybook-dev # http://localhost:6006
pnpm --filter frontend build-storybook # 静的ビルド
```
各コンポーネント横に `*.stories.impl.ts` を併設する慣習 (例: `MkButton.stories.impl.ts`)。Chromatic (`pnpm --filter frontend chromatic`) で視覚回帰チェック。
## ローカル DB / Redis
frontend のテスト種別で DB / Redis の要否が違う:
- **Vitest (unit)** — DB 不要。ロジック / コンポーネント単体のテストで backend に繋がない (CI の `vitest` ジョブにも `services:` は無い)
- **Cypress (E2E)** — テストサーバー (`pnpm start:test`) 経由で backend に繋ぐため DB / Redis が必要。**テスト用ポートの [packages/backend/test/compose.yml](../../../../../packages/backend/test/compose.yml)** を使う (上記 Cypress E2E の手順を参照)
開発用の `compose.local-db.yml` (db `5432` / redis `6379`) は **テストには使わない**。テスト用の `packages/backend/test/compose.yml` (`54312` / `56312`) とはポートが異なり、混同すると接続できない。

View File

@@ -1,412 +0,0 @@
# i18n 使い分け / Crowdin 安全策 / トラブルシュート
`i18n.ts` / `i18n.tsx` の使い分け、Crowdin との同期メカニズム、頻発する型エラー / 実行時警告の対処を 1 箇所にまとめたページ。
## 目次
- [基本: ts と tsx の使い分け](#基本-ts-と-tsx-の使い分け)
- [実装パターン](#実装パターン)
- [Crowdin 安全策 (既存キーのリネーム / 復旧)](#crowdin-安全策-既存キーのリネーム--復旧)
- [トラブルシュート](#トラブルシュート)
- [制約と補足](#制約と補足)
## 基本: ts と tsx の使い分け
文言は **必ず** [i18n.ts](../../../../../packages/frontend/src/i18n.ts) 経由で参照する。引数の有無で **使う変数名そのものが変わる**。間違えると、非パラメータキーを `i18n.tsx` で呼ぶ場合は型エラーになるが、パラメータキーを `i18n.ts` で参照する場合は型エラーにならず `{name}` 等が未展開のまま画面に出る (後述のトラブルシュート参照)。
- 引数なし → `i18n.ts.<key>` (プロパティアクセス)
```ts
os.toast(i18n.ts.removed);
```
- 引数あり → `i18n.tsx.<key>(...)` (関数呼び出し)
```ts
os.alert({ type: 'info', text: i18n.tsx.unfollowConfirm({ name: user.username }) });
```
YAML 側に `{name}` 形式のプレースホルダが含まれているキーは **`i18n.tsx`** からしか呼べない。誤って `i18n.ts.unfollowConfirm` と書くと値がフォーマット前の関数になってそのまま表示される。
- **既存キーの再利用が第一**。新キー追加が必要に見えても、まず `locales/ja-JP.yml` を grep して `deleteAreYouSure({ x })` のような汎用キー (`x` プレースホルダ) が転用可能でないか確認する。新キー追加は [tasks/adding-i18n-key.md](../tasks/adding-i18n-key.md)。他言語ファイルは Crowdin の自動配信先なので絶対に手で触らない
```vue
<script lang="ts" setup>
import { i18n } from '@/i18n.js';
import * as os from '@/os.js';
const props = defineProps<{ name: string }>();
async function onDelete() {
const { canceled } = await os.confirm({
type: 'warning',
text: i18n.tsx.driveFileDeleteConfirm({ name: props.name }), // 引数あり
});
if (canceled) return;
os.toast(i18n.ts.removed); // 引数なし
}
</script>
```
| 用途 | 書き方 |
|---|---|
| 単純文字列 | `i18n.ts.save` |
| ネスト | `i18n.ts._settings.general` |
| パラメータ付き (1 個) | `i18n.tsx.unfollowConfirm({ name })` |
| パラメータ付き (複数) | `i18n.tsx.monthAndDay({ month, day })` |
| Vue テンプレート内 | `{{ i18n.ts.save }}` / `{{ i18n.tsx.unfollowConfirm({ name }) }}` |
## 実装パターン
### HTML タグ埋め込み
ja-JP.yml の値に `<b>` / `<br>` / `<strong>` を含めて、表示側で v-html や `<Mfm>` で描画するパターンが多用されている。
```yaml
# locales/ja-JP.yml
poweredByMisskeyDescription: "{name}は、オープンソースのプラットフォーム<b>Misskey</b>のサーバーのひとつです。"
# locales/ja-JP.yml (改行 + br)
driveAboutTip: "ドライブでは、過去に...<br>\nートに添付する際に再利用したり...<br>\n<b>ファイルを削除すると...</b><br>\n..."
```
参照側:
```vue
<div v-html="i18n.tsx.poweredByMisskeyDescription({ name: 'Misskey' })" />
```
注意:
- HTML を含むキー値は **必ずダブルクォート** で囲む (YAML パース失敗回避)
- `v-html` 越しの XSS リスクが無いことを必ず確認する。パラメータ側にユーザー入力をそのまま渡すと事故る。安全な静的文字列か、別途エスケープ済の値だけにする
### リアクティブ参照 + 動的キー切替
時間経過などで翻訳キー自体を切り替えたい場合の慣習。`computed` でラップし、ブラケット記法で翻訳キーを動的に選ぶ。
出典: [packages/frontend/src/components/MkPoll.vue](../../../../../packages/frontend/src/components/MkPoll.vue) の `_poll` 動的キー
```ts
const timer = computed(() => i18n.tsx._poll[
remaining.value >= 86400 ? 'remainingDays' :
remaining.value >= 3600 ? 'remainingHours' :
remaining.value >= 60 ? 'remainingMinutes' : 'remainingSeconds'
]({
s: Math.floor(remaining.value % 60),
m: Math.floor(remaining.value / 60) % 60,
h: Math.floor(remaining.value / 3600) % 24,
d: Math.floor(remaining.value / 86400),
}));
```
対応する yml (各キーで実際に使うプレースホルダは違って良い):
```yaml
_poll:
remainingDays: "終了まであと{d}日{h}時間" # {d} {h}
remainingHours: "終了まであと{h}時間{m}分" # {h} {m}
remainingMinutes: "終了まであと{m}分{s}秒" # {m} {s}
remainingSeconds: "終了まであと{s}秒" # {s}
```
ポイント:
- 各キーで使うプレースホルダは **バラバラで構わない**
- **呼び出し側で候補キー全体に必要な全パラメータの superset を 1 つの引数オブジェクトで渡す**。各キーの内部実装は受け取ったオブジェクトから自分が必要なものだけ拾う
### 識別子として無効なキー名 (ブラケット記法)
キー名が数字始まりや予約語の場合、ドット記法ではアクセスできずブラケット記法を使う。
出典: [packages/frontend/src/components/MkSignin.totp.vue](../../../../../packages/frontend/src/components/MkSignin.totp.vue)
```vue
<div :class="$style.totpDescription">{{ i18n.ts['2fa'] }}</div>
```
新規キー追加時は **lowerCamelCase を守れば不要**。
### ネスト + パラメータ複合
```vue
{{ i18n.tsx._uploader.maxFileSizeIsX({ x: maxSize + 'MB' }) }}
{{ i18n.tsx._auth.shareAccess({ name: appName }) }}
```
### `tsx` の引数に `ts` を埋め込む
別の翻訳済み文字列をパラメータとして渡せる。
出典: [packages/frontend/src/components/MkSignupDialog.rules.vue](../../../../../packages/frontend/src/components/MkSignupDialog.rules.vue)
```ts
i18n.tsx.iHaveReadXCarefullyAndAgree({ x: i18n.ts.serverRules })
```
### 三項演算子で ts / tsx を切り替え
パラメータ有無で出し分け。
```vue
{{ name ? i18n.tsx._auth.shareAccess({ name }) : i18n.ts._auth.shareAccessAsk }}
```
## Crowdin 安全策 (既存キーのリネーム / 復旧)
ja-JP.yml 以外の locales/*.yml は **Crowdin の自動配信先**。手動編集や source 側の不用意な操作で他言語の翻訳資産が失われる。
### 同期メカニズム
[crowdin.yml](../../../../../crowdin.yml):
```yaml
files:
- source: /locales/ja-JP.yml
translation: /locales/%locale%.yml
update_option: update_as_unapproved
```
- `ja-JP.yml` = **source**。これだけが翻訳元
- `en-US.yml` / `fr-FR.yml` ほか `ja-JP.yml` 以外の全 locale = **translation**。Crowdin が自動 PR で更新する
- 翻訳済みキーの **source 文字列が変わると** `update_as_unapproved` 設定により翻訳が "unapproved" 状態に戻る (= レビュー再要求)
- **キー名自体が変わる** と Crowdin は別キー扱いし、旧キーの翻訳は孤立 → 同期で削除される
根拠: [locales/README.md](../../../../../locales/README.md) "DO NOT edit locale files except `ja-JP.yml`."
### 既存キーをリネームしたい時 (3 段階)
単純な「旧キー削除 → 新キー追加」を 1 PR で行うと、すべての言語の旧キー翻訳が失われる。以下のように分割する。
#### Step 1: 新キー追加 (PR A)
旧キーを残したまま、新キー (同等の意味の日本語) を ja-JP.yml に追加する。
```yaml
# 旧キー (まだ残す)
_settings:
theme: "テーマ"
# 新キー (追加)
appearance: "外観"
```
参照箇所も新キーに移行 (frontend の全 grep + 置換)。
#### Step 2: マージ → Crowdin 翻訳が来るのを待つ
Crowdin の自動 PR で他言語にも `appearance` が追加され、翻訳が入る。`update_option: update_as_unapproved` のため、初回は unapproved 状態。プロジェクト管理者が approve するまで本番には載らない (フォールバックで日本語が出る)。
通常は数日〜数週間。急ぐ場合は Crowdin プロジェクト管理者に依頼。
#### Step 3: 旧キー削除 (PR B)
新キーの翻訳が十分埋まった後、別 PR で旧キー (`theme`) を ja-JP.yml から削除。次の Crowdin 同期で他言語からも消える。
### 単純リネームをやってしまったら
```bash
# git diff で他言語 yml が変更されていないか必ず確認 (出力が空なら OK)
git diff --name-only develop -- 'locales/*.yml' | grep -v '^locales/ja-JP\.yml$'
```
`grep -v 'ja-JP.yml'` を diff 本文に当てる書き方は、ja-JP.yml 単体の変更でも追加行 (`+`) が素通りして必ず非空になるため使わない。**ファイル名にだけ grep を当てる** こと。
- **他言語 yml が変更されていたら即 revert**:
```bash
git restore --source=develop -- locales/en-US.yml locales/<lang>.yml
```
- ja-JP.yml だけで旧キー削除 + 新キー追加してしまった場合は、PR を分割するか、上記 3 段階に組み直す。**マージ前なら間に合う**
### ja-JP.yml 以外を触ってしまったら
```bash
# 最も安全な復旧: develop 側の中身に戻す
git restore --source=develop -- locales/en-US.yml
# あるいは特定 path だけステージから外し作業ツリーごと戻す
git checkout HEAD -- locales/zh-CN.yml
```
PR 化前なら何度でもやり直せる。**マージしてしまうと Crowdin 側との整合性が崩れて手動回復が必要** になるので、PR レビュー段階で必ず `locales/*.yml` (ja-JP 以外) の diff がゼロであることを確認する。
### CHANGELOG 記載の判定
| 変更内容 | CHANGELOG 記載 |
|---|---|
| 新規画面追加と一緒に新キー追加 | 必要 (`### Client` に Feat/Enhance) |
| 既存文言の改善 (誤字脱字以外) | 必要 (`### Client` に Enhance) |
| 誤字脱字・微妙な言い回し修正 | 不要 |
| キーのリネーム (UI 変化なし) | 不要 |
| キー削除 (画面から消える) | 必要 (`### Client` に Feat / 機能削除) |
書き方は [shipping-misskey-change スキル](../../../shipping-misskey-change/SKILL.md) を参照。
## トラブルシュート
i18n 周辺で踏みやすい失敗とその対処。エラー文字列で grep してたどり着けるよう整理。
### 型エラー: `Property '<key>' does not exist on type 'Locale'`
**症状**:
```
packages/frontend/src/components/MkXxx.vue
> i18n.ts.newKey
Property 'newKey' does not exist on type 'Locale'.
```
**原因**: ja-JP.yml にキーは追加したが、`packages/i18n` の型生成 (`autogen/locale.ts`) が再生成されていない。
**対処**:
- `pnpm dev` を起動中なら、`packages/i18n` の watch (`nodemon ... tsx ./build.ts --watch`) が自動再生成するので、yml 保存後に typecheck をやり直す
- 一回だけ手動再生成したいなら: `pnpm --filter i18n generate` (実体は `tsx scripts/generateLocaleInterface.ts`)
- 検出経路: `pnpm --filter frontend lint`
実装根拠: [packages/i18n/scripts/generateLocaleInterface.ts](../../../../../packages/i18n/scripts/generateLocaleInterface.ts) (パラメータ抽出の正規表現 `/\{(\w+)\}/g`)。
### 型エラー: ts/tsx の取り違え
**症状 A** (パラメータ無しキーを tsx で呼ぶ):
```
i18n.tsx.save({...})
> Property 'save' does not exist on type 'Tsx<Locale>'.
```
**症状 B** (パラメータ付きキーを ts で参照、関数化されたまま使う):
```vue
{{ i18n.ts.unfollowConfirm }}
<!-- 画面に "{name}のフォローを解除しますか?" が {name} 未置換のまま出る -->
```
**原因**: `Tsx<T>` 型 ([packages/frontend-shared/js/i18n.ts](../../../../../packages/frontend-shared/js/i18n.ts)) は `ParameterizedString<P>` を持つキーだけを関数として公開する。
**対処**: パラメータ有無は yml の `{...}` 記法で決まる。
| yml の値 | ts | tsx |
|---|---|---|
| `"保存"` | `i18n.ts.save` ✅ | (キー存在せず) ❌ |
| `"{name}のフォローを解除しますか?"` | `i18n.ts.unfollowConfirm` → `{name}` 未置換の文字列のまま ❌ | `i18n.tsx.unfollowConfirm({ name })` ✅ |
### 実行時警告: `Unexpected locale key: <key>`
**症状**: 開発モードのコンソールに出る。
**原因**: dev mode の Proxy が ja-JP.yml に存在しないキーへのアクセスを検知 ([packages/frontend-shared/js/i18n.ts](../../../../../packages/frontend-shared/js/i18n.ts) の dev 用 Proxy)。
**対処**: ja-JP.yml に該当キーを追加するか、参照側のタイポを直す。
### 実行時警告: `Missing locale parameters: <param> at <key>`
**症状**: dev mode コンソール。
**原因**:
- yml 側 `{name}` に対し、呼び出し側で `{ user: ... }` のように **キー名が違う**
- あるいは引数オブジェクトに値が含まれていない
実装根拠: [packages/frontend-shared/js/i18n.ts](../../../../../packages/frontend-shared/js/i18n.ts) (`Object.hasOwn(arg, expressions[i])` チェック)。
**対処**: yml と呼び出し側でパラメータ名を一致させる。yml 側のキー名を変更したら、呼び出し側 (frontend 全体) を grep で揃える。
### YAML パース失敗
**症状**: `pnpm --filter i18n generate` 実行時に `YAMLException: ...`、または `pnpm dev` の watch ログにエラー。
**原因**: 値に YAML の特殊文字 (`<` `>` `:` `'` `&` `*` `|` `>` `#`) を含むのに **クォートしていない**。
**対処**: 値全体を `"..."` (ダブルクォート) で囲む。
```yaml
# OK: HTML タグを含む
poweredByMisskeyDescription: "{name}は、...プラットフォーム<b>Misskey</b>のサーバーのひとつです。"
# OK: コロン・シングルクォート・角括弧を含む URL 説明
objectStorageBaseUrlDesc: "参照に使用するURL。CDNやProxyを使用している場合はそのURL、S3: 'https://<bucket>.s3.amazonaws.com'、GCS等: 'https://storage.googleapis.com/<bucket>'。"
# OK: 改行をリテラルで埋め込む
driveAboutTip: "ドライブでは、過去にアップロードしたファイルの...<br>\nートに添付する際に..."
```
YAML の block scalar (`|` / `>`) も使えるが、HTML タグ + プレースホルダ混在では **ダブルクォート + `\n` エスケープ** の方が安定する。
### キー名衝突: `_lang_` を上書きしてしまう
**症状**: 各言語ファイルの先頭にある `_lang_` (例: ja-JP は `"日本語"`) を別用途で使おうとして上書き。
**原因**: `_lang_` は **言語自身の表記** に予約されている ([packages/i18n/src/autogen/locale.ts](../../../../../packages/i18n/src/autogen/locale.ts) の先頭キー)。
**対処**: 新規キーは別名にする。
### frontend で diff を当てても変わらない
**症状**: ja-JP.yml を変更したが画面に反映されない。
**原因**:
- `pnpm dev` ではなく `pnpm --filter frontend watch` だけ起動していて、`packages/i18n` の watch が走っていない
- もしくは frontend へ配信される生成物 (`built/_frontend_dist_/locales/*.json`) がブラウザ側でキャッシュされている
**対処**: ルートの `pnpm dev` を起動する (frontend + backend + i18n watch が全部立ち上がる)。それでも反映しないならブラウザのキャッシュをクリア、または `pnpm --filter i18n build` を手動実行。
## 制約と補足
### ICU MessageFormat 非対応
[packages/i18n/scripts/generateLocaleInterface.ts](../../../../../packages/i18n/scripts/generateLocaleInterface.ts) の正規表現は `/\{(\w+)\}/g`。つまり受け付けるのは **`{paramName}` 形式の単純置換のみ**。
```yaml
# NG: ICU plural — そのまま画面に文字列として出るだけ
items: "{count, plural, one {1個} other {{count}個}}"
# NG: ICU select
gender: "{gender, select, male {彼} female {彼女} other {その人}}"
```
代替戦略:
#### 1. 件数別にキーを分ける
```yaml
# OK
withNFiles: "{n}個のファイル"
withOneFile: "1個のファイル"
```
```ts
const text = files.length === 1
? i18n.ts.withOneFile
: i18n.tsx.withNFiles({ n: files.length });
```
#### 2. 切替パターン (動的キー)
時間経過のような連続的な分岐は MkPoll のパターン ([上記「リアクティブ参照」](#リアクティブ参照--動的キー切替)) を採用。
### 予約キー `_lang_`
各 yml ファイルの **トップレベル先頭** に置かれ、その言語自身の表記名を持つ。
```yaml
# locales/ja-JP.yml (トップレベル先頭)
_lang_: "日本語"
```
UI の言語切替プルダウンなどで参照される。**新規キーには使わない**。
### Storybook での挙動
Storybook 環境はバンドラが別物なので、本番の i18n パッケージをそのままは使わない。代わりに [packages/frontend/.storybook/preload-locale.ts](../../../../../packages/frontend/.storybook/preload-locale.ts) がビルド時に **ja-JP の locale だけを JSON にダンプして同居 `locale.ts` を生成** する。
つまり Storybook では:
- **ja-JP の文字列だけが見える** (他言語の検証はできない)
- ja-JP.yml にキーを追加した直後に Storybook を起動しても、`preload-locale.ts` 実行前なら反映されない。Storybook を再起動するか、`packages/i18n` を一度 build する
- stories からの呼び方は通常通り: `i18n.tsx._dialog.charactersBelow({ current: 0, min: 2 })`
### backend での i18n 直接参照は基本無し
i18n は frontend (および一部の SSR されるエラーページ) でのみ使われる。`packages/backend` 配下から `import { i18n }` するパターンは原則無く、API エラー文言は別ルート (`ApiError` の i18n 化されていないメッセージ + frontend 側で翻訳) で扱う。
### 改行の扱い
ダブルクォート値の中で `\n` は実際の改行になる。block scalar (`|`) でも可だが、HTML タグやプレースホルダ混在では扱いづらい。慣習はダブルクォート + `\n`。
Vue 側で表示時に `white-space: pre-wrap` などを当てる必要あり。

View File

@@ -1,96 +0,0 @@
# `os.*` UI ヘルパー
[`packages/frontend/src/os.ts`](../../../../../packages/frontend/src/os.ts) で公開されている UI 操作 API の一覧。**ブラウザ標準の `window.alert()` / `window.confirm()` / `window.prompt()` を直接呼ばない**。これらは Misskey のテーマ / アクセシビリティ / モーダルレイヤと整合しないため。
## 主要 API
| 関数 | 用途 |
|---|---|
| `os.alert({ type?, title?, text? })` | 単方向アラート (全フィールド任意) |
| `os.confirm({ type, title?, text? })` | yes/no 確認 (`type` 必須、`{ canceled }` を返す) |
| `os.toast(message)` | 一時通知 |
| `os.popup(component, props, handlers)` | 任意コンポーネントの非同期ポップアップ |
| `os.popupMenu(items, anchor?)` | コンテキストメニュー |
| `os.contextMenu(items, ev)` | 右クリックメニュー |
| `os.form(title, fields)` | フォームダイアログ |
| `os.apiWithDialog(endpoint, data)` | API 呼出し + エラー時ダイアログ表示 |
| `os.success()` / `os.waiting()` | 成功 / ローディング表示 |
## 使用例
### `os.alert` (単方向通知)
```ts
await os.alert({
type: 'info',
text: i18n.ts.savedSuccessfully,
});
```
`type``'info'` / `'warning'` / `'error'` / `'question'` / `'success'` / `'waiting'`
### `os.confirm` (yes/no 確認)
```ts
const { canceled } = await os.confirm({
type: 'warning',
text: i18n.ts._notes.deleteConfirm,
});
if (canceled) return;
// 削除処理
```
`canceled === true` のとき何もしない、というパターンが頻出。
### `os.toast` (一時通知)
```ts
os.toast(i18n.ts.deleted);
```
成功通知などの軽い fire-and-forget なフィードバック。
### `os.popup` (任意コンポーネント)
```ts
const { dispose } = os.popup(MkUserSelectDialog, {
includeSelf: false,
}, {
ok: (user) => {
// ...
dispose();
},
cancel: () => {
dispose();
},
});
```
カスタムダイアログを開く場合は、コンポーネント (props / emits) を `os.popup` で起動する。`dispose()` で閉じる。
### `os.apiWithDialog` (API + 自動エラーダイアログ)
```ts
const result = await os.apiWithDialog('notes/create', {
text: 'hello',
});
// 成功時: result は API レスポンス
// 失敗時: 自動でエラーダイアログを表示。ただし promise 自体は reject されるので、await するなら try/catch が必要
```
通常の `misskeyApi(...)` だと自前でエラーダイアログ表示が必要だが、`apiWithDialog` は失敗時に自動で `os.alert({ type: 'error', ... })` を表示してくれる。ただし返す promise は元の `misskeyApi(...)` と同一で **reject される** ([os.ts](../../../../../packages/frontend/src/os.ts) で `return promise`)。`await` する場合は依然 try/catch が要る (ダイアログ表示後に後続処理を止めたいだけなら catch して握りつぶす)。
## なぜブラウザ標準 UI を使わないか
- `window.alert()` は Misskey のテーマ (ダークモード / カスタムテーマ) に追従しない
- `window.confirm()` はキーボード操作・focus trap・i18n のいずれも Misskey の規約と整合しない
- `window.prompt()` の入力 UI も同じ
- ブラウザ依存の表示揺れ (Firefox / Safari / Chrome で見た目が違う)
- vue-component-reviewer から指摘される
代わりに `os.alert` / `os.confirm` / `os.form` / `os.popup` を使う。
## 参照ファイル
- [packages/frontend/src/os.ts](../../../../../packages/frontend/src/os.ts) — 全 API の実装
- 既存のダイアログ系コンポーネント: `MkDialog.vue` (alert / confirm はこれを再利用)、`MkFormDialog.vue`

View File

@@ -1,135 +0,0 @@
# SCSS Modules / CSS 変数 / utility class
Misskey の SCSS 規約。`<style lang="scss" module>` の書き方、`--MI_THEME-*` / `--MI-*` CSS 変数の使い分け、グローバル utility class の一覧をまとめる。
## CSS 変数の使い分け
Misskey のテーマシステムは 2 系統の CSS 変数で構成される。新規のスタイルは **必ず変数経由** にする。直接の `#fff` / `rgb()` / `rgba()` ハードコードは vue-component-reviewer から Major 指摘される。
### `--MI_THEME-*` (テーマ依存)
ユーザーが選んだテーマ (light / dark / 個別テーマ) で変わる色。`packages/frontend-shared/themes/_dark.json5` などで定義。
| 変数 | 用途 |
|---|---|
| `--MI_THEME-bg` | ページ背景 |
| `--MI_THEME-panel` | カード / パネル背景 |
| `--MI_THEME-panelHighlight` | 強調表示パネル |
| `--MI_THEME-fg` | 本文文字色 |
| `--MI_THEME-fgHighlighted` | 強調文字色 |
| `--MI_THEME-fgOnPanel` | パネル上の文字 |
| `--MI_THEME-fgOnAccent` | accent 色背景上の文字 (≒白系) |
| `--MI_THEME-accent` | プライマリアクセント (リンク、active state) |
| `--MI_THEME-accentedBg` | accent 系の薄背景 |
| `--MI_THEME-divider` | 罫線 |
| `--MI_THEME-error` | エラー色 |
| `--MI_THEME-warn` / `--MI_THEME-infoWarnBg` / `--MI_THEME-infoWarnFg` | 警告系 |
| `--MI_THEME-infoBg` / `--MI_THEME-infoFg` | 情報系 |
| `--MI_THEME-buttonBg` / `--MI_THEME-buttonHoverBg` | ボタン背景 |
| `--MI_THEME-inputBorder` / `--MI_THEME-inputBorderHover` | フォーム枠 |
| `--MI_THEME-focus` | フォーカスリング色 |
| `--MI_THEME-link` | リンク色 |
| `--MI_THEME-mention` / `--MI_THEME-hashtag` | メンション / ハッシュタグ |
全部の一覧が必要なら `packages/frontend-shared/themes/_light.json5` を読むのが早い (JSON5 で全キーが揃っている)。
### `--MI-*` (UI 共通定数、テーマ非依存)
| 変数 | 用途 |
|---|---|
| `--MI-radius` | 標準角丸 (`12px`) |
| `--MI-margin` | 標準余白 (大、`16px` / モバイルでは `10px`) |
| `--MI-marginHalf` | 標準余白の半分 |
| `--MI-modalBgFilter` | モーダル背景 (backdrop) のフィルタ |
`var(--MI-radius)` を使うとアプリ全体で角丸の大きさが揃う。`border-radius: 12px;` のように直書きすると、後から角丸を変える要件が来たときに全件直すことになる。
### ハードコードの例外
色は基本ハードコード禁止だが、以下のケースは正当化される:
- `transparent` / `currentColor` / `none` などの CSS キーワード
- alpha だけ動的に変えたい → `color-mix(in srgb, var(--MI_THEME-fg) 50%, transparent)` のように合成する
- アイコンサイズ等、CSS 変数化されていない数値定数 (`font-size: 14px;` 等は OK)
## グローバル utility class
`packages/frontend/src/style.scss` に定義されたグローバル class。`<style module>` 内のクラスと **併用** する (`:class="[$style.root, '_button']"` ではなく、HTML の `class="_button"` 属性で直接書く)。
下表は **よく使う代表例** で網羅ではない (class は随時増減するため、この一覧は腐りやすい)。手元の class が実在するか / 実装を確認したいときは正本の [packages/frontend/src/style.scss](../../../../../packages/frontend/src/style.scss) を直接見る (`grep -nE '^\._' packages/frontend/src/style.scss` で定義済み class を列挙できる)。
| class | 意味 |
|---|---|
| `_button` | クリック可能な無装飾ベース (`appearance:none` + `cursor:pointer` + disabled cursor のリセットのみ。focus ring や ripple は**含まない** — ripple が要るなら `MkButton.vue` を使う)。`<button>` または `<a>` に付ける |
| `_buttonPrimary` | `_button` + accent 色背景 (確定アクション) |
| `_buttonGradate` | `_button` + グラデーション背景 |
| `_panel` | カード / パネル枠 (背景 + 角丸 + `overflow:clip`。shadow は含まない) |
| `_selectable` | テキスト選択許可 (Misskey はデフォルトで本文以外の選択を抑止しているため) |
| `_selectableAtomic` | 子要素まとめて 1 単位で選択 |
| `_noSelect` | テキスト選択禁止 |
| `_nowrap` | `white-space: nowrap;` |
| `_help` | accent 色 + `cursor: help` (ヘルプアイコン用) |
| `_textButton` | accent 色のテキストボタン (hover で下線) |
| `_link` | テキストリンク強調 |
| `_gaps` | 縦並び flex (`display: flex; flex-direction: column; gap: var(--MI-margin);`) |
| `_gaps_m` / `_gaps_s` | 同じく縦並び flex で gap 固定 (`21px` / `10px`) |
| `_margin` | 標準 margin (= `--MI-margin`) |
| `_shadow` | 標準シャドウ (`box-shadow`) |
| `_popup` | popup / dropdown 用 (背景 + 角丸 + `contain`。shadow は含まない) |
| `_acrylic` | 半透明 + backdrop blur (アクリル風) |
使い方:
```vue
<template>
<button class="_button _buttonPrimary" :class="$style.action" @click="onClick">
{{ i18n.ts.save }}
</button>
</template>
<style lang="scss" module>
.action {
padding: 8px 24px;
/* 背景色や focus ring は _buttonPrimary が持つので書かない */
}
</style>
```
## `<style lang="scss" module>` の特殊記法
### `:global(...)` で module スコープから出る
`<style lang="scss" module>` 内に書いたクラス名はビルド時にハッシュ化されて他コンポーネントから参照できなくなる。これを意図的に外したい (子コンポーネント側の特定クラスや外部ライブラリのクラスにスタイルを当てたい) 場合のみ `:global(...)` を使う:
```scss
.root {
:global(.someThirdPartyClass) {
color: var(--MI_THEME-fg);
}
}
```
通常はほぼ使わない。
### `:deep(...)` で子コンポーネント内部を狙う
```scss
.root :deep(.child-internal-class) {
color: var(--MI_THEME-accent);
}
```
これも頻用しない (子コンポーネントを直接修正する方が望ましい)。
## 命名
- module class は **camelCase** が慣習 (`root` / `inputCore` / `headerText`)
- BEM 風の `block__element--modifier` は使わない (CSS Modules でハッシュ化されるので名前衝突を心配する必要が無い)
- 状態 modifier は `&.active` / `&.disabled` のようにネストする
## ありがちなレビュー指摘
- `#fff` / `#000` / `rgba(0, 0, 0, 0.5)` のハードコード → `var(--MI_THEME-fg)` / `var(--MI_THEME-bg)` / `color-mix(...)` 等に置き換える
- `<style scoped>` で書いている (module ではない) → `<style lang="scss" module>` に直し、`:class="$style.foo"` で参照する
- 自前で `border-radius: 8px; padding: 14px;` を書いている → `_panel` global class 使えば不要
- 自前で button styling を書いている → `_button` global class を base に乗せる

View File

@@ -1,191 +0,0 @@
# Storybook (`*.stories.impl.ts`) 規約
共有 `Mk*` コンポーネントには `Mk<Name>.stories.impl.ts`**同階層** に併設するのが慣習。
## 配置と命名
- **ファイル名は `.stories.impl.ts` 固定** (`.stories.ts``packages/frontend/.storybook/generate.tsx` による生成物で手編集・コミット不可)
- 同階層に置く (`components/MkButton.stories.impl.ts``components/global/MkAvatar.stories.impl.ts` 等)
- 先頭に TS コメント形式の SPDX ヘッダーが必要
## 基本: 単一 story (Default のみ)
シンプルなコンポーネントならこれで十分。(以下の `MkColoredTag` は説明用の**架空のコンポーネント名**。実在しない。実物のパターンは `MkButton.stories.impl.ts` を参照。)
```ts
/*
* SPDX-FileCopyrightText: syuilo and misskey-project
* SPDX-License-Identifier: AGPL-3.0-only
*/
/* eslint-disable @typescript-eslint/explicit-function-return-type */
/* eslint-disable import/no-default-export */
import type { StoryObj } from '@storybook/vue3';
import MkColoredTag from './MkColoredTag.vue';
export const Default = {
render(args) {
return {
components: { MkColoredTag },
setup() {
return { args };
},
template: '<MkColoredTag v-bind="args">タグ</MkColoredTag>',
};
},
args: {
variant: 'info',
},
parameters: {
layout: 'centered',
},
} satisfies StoryObj<typeof MkColoredTag>;
```
ポイント:
- 上 2 つの `eslint-disable` は Storybook のお作法で必須 (render の関数が return type を明示しないため / `default export` ではないため)
- `satisfies StoryObj<typeof MkColoredTag>` が無いと `args` の型補完が効かなくなる
## 複数 story (variant 別)
参考: [MkButton.stories.impl.ts](../../../../../packages/frontend/src/components/MkButton.stories.impl.ts)
variant / size / 状態などのバリエーションがあるなら、`Default` を base にして spread で派生させると簡潔。
```ts
export const Default = {
render(args) {
return {
components: { MkColoredTag },
setup() {
return { args };
},
template: '<MkColoredTag v-bind="args">タグ</MkColoredTag>',
};
},
args: {
variant: 'info',
},
parameters: {
layout: 'centered',
},
} satisfies StoryObj<typeof MkColoredTag>;
export const Warn = {
...Default,
args: { ...Default.args, variant: 'warn' },
} satisfies StoryObj<typeof MkColoredTag>;
export const Danger = {
...Default,
args: { ...Default.args, variant: 'danger' },
} satisfies StoryObj<typeof MkColoredTag>;
export const Disabled = {
...Default,
args: { ...Default.args, disabled: true },
} satisfies StoryObj<typeof MkColoredTag>;
```
## イベントを可視化する (`action()`)
クリック等の emit を Storybook の Actions panel で見たい場合、`storybook/actions``action()` を使う。
```ts
import { action } from 'storybook/actions';
// ...
export const Default = {
render(args) {
return {
components: { MkColoredTag },
setup() {
return { args };
},
computed: {
props() {
return { ...this.args };
},
events() {
return {
click: action('click'),
close: action('close'),
};
},
},
template: '<MkColoredTag v-bind="props" v-on="events">タグ</MkColoredTag>',
};
},
args: {},
parameters: { layout: 'centered' },
} satisfies StoryObj<typeof MkColoredTag>;
```
`MkButton.stories.impl.ts` がこのパターン。
## `argTypes` で controls を細かく制御
string union を radio に / number を range に変えるとレビューが楽になる。(標準の Storybook 機能。現状リポジトリ内の `.stories.impl.ts` では実際には使われていないので必須ではない。)
```ts
export const Default = {
render(args) { /* ... */ },
args: { variant: 'info' },
argTypes: {
variant: {
control: 'inline-radio',
options: ['info', 'warn', 'danger'],
},
disabled: {
control: 'boolean',
},
},
parameters: { layout: 'centered' },
} satisfies StoryObj<typeof MkColoredTag>;
```
## `parameters.layout` の使い分け
| 値 | 使い所 |
|---|---|
| `'centered'` | 単体表示 (ボタン、タグ、アイコン等の小さい部品) |
| `'fullscreen'` | ページ単位、もしくはパネル全体を見せたい時 |
| `'padded'` (デフォルト) | 周囲に余白が欲しい中サイズ部品 |
`layout` を変えるだけで Storybook 上の見え方が大きく変わる。レイアウト依存のコンポーネント (sticky header 等) なら `'fullscreen'` を選ぶ。
## slot の中身を可変にする
`args` に slot 用文字列フィールドを足し、template で `{{ args.label }}` のように展開する。
```ts
export const Default = {
render(args) {
return {
components: { MkColoredTag },
setup() {
return { args };
},
template: '<MkColoredTag v-bind="args">{{ args.label }}</MkColoredTag>',
};
},
args: {
label: 'タグ',
variant: 'info',
},
parameters: { layout: 'centered' },
} satisfies StoryObj<typeof MkColoredTag>;
```
ただし `label` を component の props にしてしまうのは禁物 (slot で受け取る方針なら slot のままにする)。Storybook 上だけで使う表示用文字列として扱う。
## 確認方法
```bash
pnpm --filter frontend storybook-dev # http://localhost:6006
pnpm --filter frontend build-storybook # 静的ビルド
```
新規コンポーネントの stories が Sidebar に出ない場合、多くは [generate.tsx](../../../../../packages/frontend/.storybook/generate.tsx) の生成対象 **allowlist** に入っていないため。`src/{components,pages,...}/**/*.vue` の全体 glob はコメントアウトされており、対象は `globSync('src/components/global/Mk*.vue')` / `globSync('src/components/Mk[B-E]*.vue')` などの**明示列挙**になっている。`.stories.impl.ts` を併設しただけでは自動では出ないことがあるので、対象外なら generate.tsx に 1 行追加する。加えて、ファイル名 (`.stories.impl.ts`) と SPDX ヘッダー以降に構文エラーが無いかも確認する。
Chromatic (`pnpm --filter frontend chromatic`) で視覚回帰チェックも行われる。

View File

@@ -1,124 +0,0 @@
# i18n キーを追加・改修する
UI 文言の追加・変更を行う際の手順。**手動編集して良いのは `locales/ja-JP.yml` のみ**。
## 大前提 (絶対 NG)
- **`locales/<lang>.yml` (ja-JP.yml 以外) の編集は禁止**。これらは Crowdin の自動配信先で、手動編集すると次の同期で上書き喪失する ([locales/README.md](../../../../../locales/README.md), [crowdin.yml](../../../../../crowdin.yml))
- 文字列リテラルを SFC に直書きしない (`<span>こんにちは</span>` 等)。必ず `i18n.ts.<key>` を経由する
- 既存キーの破壊的リネームは Crowdin 翻訳資産を失わせる。**追加 → 移行 → 旧キー削除** の 3 段階に分割する。詳細手順と誤編集の復旧は [knowledge/i18n-usage.md §Crowdin 安全策](../knowledge/i18n-usage.md)
## ステップ 1: ja-JP.yml にキーを追加
[locales/ja-JP.yml](../../../../../locales/ja-JP.yml) を編集する。YAML の階層構造を維持し、関連するセクションに配置する:
```yaml
# トップレベル単純キー
save: "保存"
# ネストしたカテゴリ (アンダースコア接頭辞は内部カテゴリ)
_settings:
general: "全般"
appearance: "外観"
# パラメータ付き (単純なプレースホルダ置換)
# 受け付けるのは {name} 形式のみ。ICU MessageFormat (plural/select) は非対応
greeting: "こんにちは、{name}さん"
```
### 命名のお作法
- 単純キー: lowerCamelCase (例: `saveChanges`, `confirmDelete`)
- カテゴリ: アンダースコア接頭辞 (例: `_settings`, `_abuseUserReport`)
- 既存セクション内に追加する場合は **周辺の既存配置・意味グループに合わせる** (例えば `_settings` は機能ブロック順に並んでおりアルファベット順ではない)。新セクション全体を末尾に追加するのは可
- **HTML タグ (`<b>` `<br>` `<strong>` 等) や `:` `'` `&` を含む値は必ずダブルクォートで囲む** (未クォートだと YAML パース失敗)
**詳細:** ICU 非対応の代替戦略・予約キー `_lang_`・Storybook での挙動は → [knowledge/i18n-usage.md §制約と補足](../knowledge/i18n-usage.md)
## ステップ 2: 型定義の自動再生成
`packages/i18n/build.ts``ja-JP.yml` を解析し、TypeScript インターフェースを [packages/i18n/src/autogen/locale.ts](../../../../../packages/i18n/src/autogen/locale.ts) に出力する。
### 自動 (推奨)
`pnpm dev` 実行中なら、`packages/i18n` の watch スクリプト (`nodemon ... tsx ./build.ts --watch`) が yml の変更を検知して自動再生成する。
### 手動
```bash
pnpm --filter i18n generate
```
実体は `tsx scripts/generateLocaleInterface.ts`
### 失敗パターン
これを実行せずに frontend 側で `i18n.ts.<newKey>` を参照すると、`Locale` インターフェースに追加されていないため typecheck で `Property '<newKey>' does not exist on type 'Locale'` というエラーになる (`pnpm --filter frontend lint` で発覚)。型エラー・実行時警告 (`Unexpected locale key`, `Missing locale parameters`) と対処は → [knowledge/i18n-usage.md §トラブルシュート](../knowledge/i18n-usage.md)。
## ステップ 3: frontend での参照
```ts
import { i18n } from '@/i18n.js';
```
| 用途 | 書き方 |
|---|---|
| 単純文字列 | `i18n.ts.save` |
| ネスト | `i18n.ts._settings.general` |
| パラメータ付き | `i18n.tsx.greeting({ name: userName })` |
| Vue テンプレート内 | `{{ i18n.ts.save }}` / `{{ i18n.tsx.greeting({ name }) }}` |
`i18n.ts` は型付き文字列、`i18n.tsx``{name}` プレースホルダを埋め込む関数 (パラメータ付きキーのみ存在。ICU MessageFormat ではなく単純な文字列置換)。
**詳細:** HTML タグ埋め込み・computed によるリアクティブ参照・動的キー切替・ブラケット記法 (`i18n.ts['2fa']`) などの実装パターンは → [knowledge/i18n-usage.md §実装パターン](../knowledge/i18n-usage.md)
## ステップ 4: 検証
```bash
# i18n の型再生成 → typecheck + eslint (lint は generate を呼ばないので順番が必須)
pnpm --filter i18n generate
pnpm --filter i18n lint
# frontend で新キー参照箇所の型チェック
pnpm --filter frontend lint
# 他言語 yml に diff が出ていないことを確認 (出力が空であれば OK)
git diff --name-only develop -- 'locales/*.yml' | grep -v '^locales/ja-JP\.yml$'
```
**注意:** `grep -v 'ja-JP.yml'`**diff 本文** に当てると ja-JP.yml 単体の変更でも `+追加行` が素通りして必ず非空になる。`--name-only` でファイル名だけに絞ってから完全一致で除外するのが正しい。
ユーザー影響のある UI 変更を伴う場合は [shipping-misskey-change スキル](../../../shipping-misskey-change/SKILL.md) で CHANGELOG エントリの判定をする。
## 例: 「ノートを削除しますか?」確認ダイアログを追加する
1. `locales/ja-JP.yml`:
```yaml
_notes:
deleteConfirm: "このノートを削除しますか?"
```
2. `pnpm --filter i18n generate` (または `pnpm dev` で watch 中)
3. SFC:
```vue
<script setup lang="ts">
import { i18n } from '@/i18n.js';
import * as os from '@/os.js';
async function onDelete() {
const { canceled } = await os.confirm({
type: 'warning',
text: i18n.ts._notes.deleteConfirm,
});
if (canceled) return;
// 削除処理
}
</script>
```
## 参照ファイル
- [locales/README.md (★ 編集ポリシー根拠)](../../../../../locales/README.md)
- [locales/ja-JP.yml](../../../../../locales/ja-JP.yml)
- [packages/i18n/build.ts](../../../../../packages/i18n/build.ts)
- [packages/i18n/src/autogen/locale.ts (生成物)](../../../../../packages/i18n/src/autogen/locale.ts)
- [packages/frontend/src/i18n.ts](../../../../../packages/frontend/src/i18n.ts)

View File

@@ -1,196 +0,0 @@
# 新規 / 既存 `Mk*` Vue コンポーネントを追加・改修する
`packages/frontend/src/components/` 配下に新規の共有 Vue 3 SFC を追加する、または既存コンポーネントを大きく改修する時の手順。同じ規約をレビュー側からチェックする agent が [.claude/agents/vue-component-reviewer.md](../../../../agents/vue-component-reviewer.md)。
## 大前提 (事故直結 / Critical)
1. **SPDX ヘッダー**`.vue` は HTML コメント形式 `<!-- ... -->``.stories.impl.ts` は TS コメント形式 `/* ... */`。欠落すると CI (`spdx` ジョブ) が落ちる
2. **`Mk` プレフィックス必須** — 共有コンポーネントは `MkButton.vue` / `global/MkAvatar.vue` のように `Mk` で始める。ページ固有 UI は `Mk` を付けず `pages/` 側に置く
3. **`locales/ja-JP.yml` のみ編集可** — i18n キー追加時に他言語 (`en-US.yml` 等) を手で触ってはいけない。Crowdin の自動配信で上書きされて失われる。詳細は [tasks/adding-i18n-key.md](adding-i18n-key.md) を参照
4. **文字列リテラルの直書き禁止** — テンプレート / JS どちらでも、ユーザーに見せる文言は必ず `i18n.ts.<key>``i18n.tsx.<key>(...)` 経由 → [knowledge/i18n-usage.md](../knowledge/i18n-usage.md)
5. **ブラウザ標準 UI を直接呼ばない**`alert()` / `confirm()` / `window.prompt()` は禁止、必ず `os.alert` / `os.confirm` / `os.popup` 経由 → [knowledge/os-api.md](../knowledge/os-api.md)
## ファイル配置
| 配置先 | 用途 | 命名 |
|---|---|---|
| `packages/frontend/src/components/Mk<Name>.vue` | 通常の共有 UI コンポーネント | `Mk<Name>.vue` |
| `packages/frontend/src/components/global/Mk<Name>.vue` | `components/index.ts` で Vue グローバルコンポーネント登録 (`app.component`) され、import 無しで全テンプレートから使える基本部品 (`MkA` / `MkAvatar` / `MkAcct` 等) | `Mk<Name>.vue` (サブディレクトリ内でも `Mk` prefix 必須) |
| `packages/frontend/src/components/grid/Mk<Name>.vue` | テーブル/グリッド系の部品セット | 同上 |
| `packages/frontend/src/pages/<Name>.vue` | 単一ページ専用の UI (再利用しない) | `Mk` prefix **不要** |
迷ったら「他の `Mk*.vue` から import される可能性があるか?」で判定する。Yes なら `components/`、No なら `pages/`
ストーリーが必要 (= ほぼ常に必要) なら、同階層に `Mk<Name>.stories.impl.ts` も作る → [knowledge/storybook.md](../knowledge/storybook.md)。
## SPDX ヘッダー
### `.vue` ファイル (HTML コメント)
```html
<!--
SPDX-FileCopyrightText: syuilo and misskey-project
SPDX-License-Identifier: AGPL-3.0-only
-->
```
`/* ... */` (TS / JS 形式) は **使わない**。既存の `.vue` ファイルがすべて HTML コメント形式を採用しており、SFC 先頭として自然な形式に統一するため。
### `.stories.impl.ts` ファイル (TS コメント)
```ts
/*
* SPDX-FileCopyrightText: syuilo and misskey-project
* SPDX-License-Identifier: AGPL-3.0-only
*/
```
## 最小テンプレート
シンプルな表示コンポーネントの最小形を示す**合成例** (特定ファイルの写しではない)。実在する単純コンポーネントの例は [MkInfo.vue](../../../../../packages/frontend/src/components/MkInfo.vue) 等を参照:
```vue
<!--
SPDX-FileCopyrightText: syuilo and misskey-project
SPDX-License-Identifier: AGPL-3.0-only
-->
<template>
<div :class="[$style.root, $style[`variant_${variant}`]]">
<slot></slot>
<button
v-if="closable"
class="_button"
:class="$style.close"
:aria-label="i18n.ts.close"
@click="emit('close')"
>
<i class="ti ti-x"></i>
</button>
</div>
</template>
<script lang="ts" setup>
import { i18n } from '@/i18n.js';
const props = withDefaults(defineProps<{
variant?: 'info' | 'warn' | 'danger';
closable?: boolean;
}>(), {
variant: 'info',
});
const emit = defineEmits<{
(ev: 'close'): void;
}>();
</script>
<style lang="scss" module>
.root {
display: flex;
align-items: center;
gap: 8px;
padding: 12px 14px;
border-radius: var(--MI-radius);
}
.variant_info {
background: var(--MI_THEME-infoBg);
color: var(--MI_THEME-infoFg);
}
.variant_warn {
background: var(--MI_THEME-infoWarnBg);
color: var(--MI_THEME-infoWarnFg);
}
.variant_danger {
background: var(--MI_THEME-error);
color: var(--MI_THEME-fgOnAccent);
}
.close {
margin-left: auto;
}
</style>
```
より複雑なケース (型ジェネリック / 2 ブロック script / `v-model` 連動 / 名前付き slot) は → [knowledge/component-conventions.md §テンプレート集](../knowledge/component-conventions.md)。
## `<script>` / `<style>` 規約サマリ
| 項目 | 規約 | 新規不可 |
|---|---|---|
| `<script>` 開始タグ | `<script lang="ts" setup>` または `<script setup lang="ts">` | `<script>` (lang 無し) / Options API |
| Props 定義 | `defineProps<{ ... }>()` (type-only) | runtime object 形式 |
| Emits 定義 | `defineEmits<{ (ev: 'click'): void }>()` (type-only) | runtime array 形式 |
| `<style>` 開始タグ | `<style lang="scss" module>`、参照は `:class="$style.foo"` | `<style scoped>` (module なし) |
| CSS 値 | `var(--MI_THEME-...)` / `var(--MI-...)` | `#fff` / `rgb(...)` のハードコード |
| グローバル class | `_button` / `_panel` / `_selectable` 等を活用 | — |
| アイコン | Tabler icons クラス `<i class="ti ti-info-circle">` | インライン SVG / 別アイコンセット |
詳細・テンプレート集は → [knowledge/component-conventions.md](../knowledge/component-conventions.md) / [knowledge/scss-modules.md](../knowledge/scss-modules.md)。
## i18n の使い分け
引数なし → `i18n.ts.<key>` / 引数あり → `i18n.tsx.<key>(...)`。詳細は → [knowledge/i18n-usage.md](../knowledge/i18n-usage.md)。
新キー追加が必要なら → [tasks/adding-i18n-key.md](adding-i18n-key.md)。
## `os.*` ヘルパー
`os.alert` / `os.confirm` / `os.popup` / `os.toast` / `os.popupMenu` 等。詳細は → [knowledge/os-api.md](../knowledge/os-api.md)。
## アクセシビリティ最低ライン
1. **クリック可能要素は `<button class="_button">` を第一選択**。やむを得ず `<div @click>` なら `role="button"` + `tabindex="0"` + `@keydown.enter` / `@keydown.space.prevent` の 4 点セット必須
2. **フォーム要素 (`<input>` / `<select>` / `<textarea>`) は `<label>` 接続もしくは `aria-label`**
3. **`:disabled` バインドと `aria-disabled` を一致**させる。ハンドラ側でも早期 return
4. **キーボードのみで完結**できるか確認 (Tab で focus 移動できる / Enter で確定できる)
5. ARIA 属性は最小限
詳細チェックリストと既存例 (`MkButton.vue` / `MkSwitch.vue`) は → [knowledge/component-conventions.md §a11y](../knowledge/component-conventions.md)。
## Storybook 併設
共有 `Mk*` コンポーネントには `Mk<Name>.stories.impl.ts`**同階層** に併設する (サブディレクトリ含む)。詳細は → [knowledge/storybook.md](../knowledge/storybook.md)。
## 検証フロー
```bash
# 型チェック (vue-tsc)
pnpm --filter frontend typecheck
# ESLint (規約全体)
pnpm --filter frontend eslint
# 単一ファイルに ESLint --fix
pnpm exec eslint --fix packages/frontend/src/components/Mk<Name>.vue
# Storybook で目視確認
pnpm --filter frontend storybook-dev # localhost:6006
# Vitest unit test (component spec があれば)
pnpm --filter frontend test
```
## CHANGELOG エントリ
ユーザーから見える変更 (新規コンポーネントが新しい UI として露出する、既存 UI の挙動を変える) なら、`CHANGELOG.md` に追記する。判定方法と書式は [shipping-misskey-change スキル](../../../shipping-misskey-change/SKILL.md) で確認。
## 既存コンポーネントとの整合性
- 似た用途の既存 `Mk*` を 1-2 個読んで、props 命名 (`primary` / `danger` / `small` 等の形容詞、`onClose` ではなく `emit('close')` 等) を揃える
- グローバル utility class (`_button` / `_panel` / `_selectable` / `_gaps_m`) を使えば独自スタイルを書かずに済む → [knowledge/scss-modules.md](../knowledge/scss-modules.md)
- 大きな機能なら Storybook で各バリエーション (variant / size / disabled / loading) を網羅する
## 参照コード
- [MkInfo.vue](../../../../../packages/frontend/src/components/MkInfo.vue) — simple SFC 例
- [MkButton.vue](../../../../../packages/frontend/src/components/MkButton.vue) — 汎用ボタン (a11y / `_button` global class)
- [MkInput.vue](../../../../../packages/frontend/src/components/MkInput.vue) — generic + 2 ブロック script 例
- [MkSelect.vue](../../../../../packages/frontend/src/components/MkSelect.vue) — `defineModel` + 名前付き slot 例
- [MkSwitch.vue](../../../../../packages/frontend/src/components/MkSwitch.vue) — a11y 込みカスタム UI
- [MkButton.stories.impl.ts](../../../../../packages/frontend/src/components/MkButton.stories.impl.ts) — 複数 story Storybook 雛形
- [packages/frontend/src/os.ts](../../../../../packages/frontend/src/os.ts) — UI 操作 API 一覧
- [packages/frontend/src/i18n.ts](../../../../../packages/frontend/src/i18n.ts) — `i18n.ts` / `i18n.tsx` 実装

View File

@@ -1,226 +0,0 @@
#━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
# Misskey configuration
#━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
# ┌────────────────────────┐
#───┘ Initial Setup Password └─────────────────────────────────────────────────────
# Password to initiate setting up admin account.
# It will not be used after the initial setup is complete.
#
# Be sure to change this when you set up Misskey via the Internet.
#
# The provider of the service who sets up Misskey on behalf of the customer should
# set this value to something unique when generating the Misskey config file,
# and provide it to the customer.
setupPassword: example_password_please_change_this_or_you_will_get_hacked
# ┌─────┐
#───┘ URL └─────────────────────────────────────────────────────
# Final accessible URL seen by a user.
url: 'http://misskey.local'
# ONCE YOU HAVE STARTED THE INSTANCE, DO NOT CHANGE THE
# URL SETTINGS AFTER THAT!
# ┌───────────────────────┐
#───┘ Port and TLS settings └───────────────────────────────────
#
# Misskey requires a reverse proxy to support HTTPS connections.
#
# +----- https://example.tld/ ------------+
# +------+ |+-------------+ +----------------+|
# | User | ---> || Proxy (443) | ---> | Misskey (3000) ||
# +------+ |+-------------+ +----------------+|
# +---------------------------------------+
#
# You need to set up a reverse proxy. (e.g. nginx)
# An encrypted connection with HTTPS is highly recommended
# because tokens may be transferred in GET requests.
# The port that your Misskey server should listen on.
port: 61812
# ┌──────────────────────────┐
#───┘ PostgreSQL configuration └────────────────────────────────
db:
host: db
port: 5432
# Database name
db: misskey
# Auth
user: postgres
pass: postgres
# Whether disable Caching queries
#disableCache: true
# Extra Connection options
#extra:
# ssl: true
dbReplications: false
# You can configure any number of replicas here
#dbSlaves:
# -
# host:
# port:
# db:
# user:
# pass:
# -
# host:
# port:
# db:
# user:
# pass:
# ┌─────────────────────┐
#───┘ Redis configuration └─────────────────────────────────────
redis:
host: redis
port: 6379
#family: 0 # 0=Both, 4=IPv4, 6=IPv6
#pass: example-pass
#prefix: example-prefix
#db: 1
#redisForPubsub:
# host: redis
# port: 6379
# #family: 0 # 0=Both, 4=IPv4, 6=IPv6
# #pass: example-pass
# #prefix: example-prefix
# #db: 1
#redisForJobQueue:
# host: redis
# port: 6379
# #family: 0 # 0=Both, 4=IPv4, 6=IPv6
# #pass: example-pass
# #prefix: example-prefix
# #db: 1
#redisForTimelines:
# host: redis
# port: 6379
# #family: 0 # 0=Both, 4=IPv4, 6=IPv6
# #pass: example-pass
# #prefix: example-prefix
# #db: 1
#redisForReactions:
# host: redis
# port: 6379
# #family: 0 # 0=Both, 4=IPv4, 6=IPv6
# #pass: example-pass
# #prefix: example-prefix
# #db: 1
# ┌───────────────────────────┐
#───┘ MeiliSearch configuration └─────────────────────────────
#meilisearch:
# host: meilisearch
# port: 7700
# apiKey: ''
# ssl: true
# index: ''
# ┌───────────────┐
#───┘ ID generation └───────────────────────────────────────────
# You can select the ID generation method.
# You don't usually need to change this setting, but you can
# change it according to your preferences.
# Available methods:
# aid ... Short, Millisecond accuracy
# aidx ... Millisecond accuracy
# meid ... Similar to ObjectID, Millisecond accuracy
# ulid ... Millisecond accuracy
# objectid ... This is left for backward compatibility
# ONCE YOU HAVE STARTED THE INSTANCE, DO NOT CHANGE THE
# ID SETTINGS AFTER THAT!
id: 'aidx'
# ┌────────────────┐
#───┘ Error tracking └──────────────────────────────────────────
# Sentry is available for error tracking.
# See the Sentry documentation for more details on options.
#sentryForBackend:
# enableNodeProfiling: true
# options:
# dsn: 'https://examplePublicKey@o0.ingest.sentry.io/0'
#sentryForFrontend:
# vueIntegration:
# tracingOptions:
# trackComponents: true
# browserTracingIntegration:
# replayIntegration:
# options:
# dsn: 'https://examplePublicKey@o0.ingest.sentry.io/0'
# ┌─────────────────────┐
#───┘ Other configuration └─────────────────────────────────────
# Whether disable HSTS
#disableHsts: true
# Number of worker processes
#clusterLimit: 1
# Number of threads of extra thread pool for CPU-intensive tasks (per worker)
#threadPoolSize: 1
# Job concurrency per worker
# deliverJobConcurrency: 128
# inboxJobConcurrency: 16
# Job rate limiter
# deliverJobPerSec: 128
# inboxJobPerSec: 32
# Job attempts
# deliverJobMaxAttempts: 12
# inboxJobMaxAttempts: 8
# IP address family used for outgoing request (ipv4, ipv6 or dual)
#outgoingAddressFamily: ipv4
# Proxy for HTTP/HTTPS
#proxy: http://127.0.0.1:3128
proxyBypassHosts:
- api.deepl.com
- api-free.deepl.com
- www.recaptcha.net
- hcaptcha.com
- challenges.cloudflare.com
# Proxy for SMTP/SMTPS
#proxySmtp: http://127.0.0.1:3128 # use HTTP/1.1 CONNECT
#proxySmtp: socks4://127.0.0.1:1080 # use SOCKS4
#proxySmtp: socks5://127.0.0.1:1080 # use SOCKS5
# Media Proxy
#mediaProxy: https://example.com/proxy
allowedPrivateNetworks: [
'127.0.0.1/32'
]
# Upload or download file size limits (bytes)
#maxFileSize: 262144000

View File

@@ -1,11 +1,4 @@
# misskey settings
# MISSKEY_URL=https://example.tld/
# db settings
POSTGRES_PASSWORD=example-misskey-pass
# DATABASE_PASSWORD=${POSTGRES_PASSWORD}
POSTGRES_USER=example-misskey-user
# DATABASE_USER=${POSTGRES_USER}
POSTGRES_DB=misskey
# DATABASE_DB=${POSTGRES_DB}
DATABASE_URL="postgres://${POSTGRES_USER}:${POSTGRES_PASSWORD}@db:5432/${POSTGRES_DB}"

View File

@@ -1,251 +0,0 @@
#━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
# Misskey configuration
#━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
# ┌─────┐
#───┘ URL └─────────────────────────────────────────────────────
# Final accessible URL seen by a user.
# You can set url from an environment variable instead.
url: https://example.tld/
# ONCE YOU HAVE STARTED THE INSTANCE, DO NOT CHANGE THE
# URL SETTINGS AFTER THAT!
# ┌───────────────────────┐
#───┘ Port and TLS settings └───────────────────────────────────
#
# Misskey requires a reverse proxy to support HTTPS connections.
#
# +----- https://example.tld/ ------------+
# +------+ |+-------------+ +----------------+|
# | User | ---> || Proxy (443) | ---> | Misskey (3000) ||
# +------+ |+-------------+ +----------------+|
# +---------------------------------------+
#
# You need to set up a reverse proxy. (e.g. nginx)
# An encrypted connection with HTTPS is highly recommended
# because tokens may be transferred in GET requests.
# The port that your Misskey server should listen on.
port: 3000
# ┌──────────────────────────┐
#───┘ PostgreSQL configuration └────────────────────────────────
db:
host: db
port: 5432
# Database name
# You can set db from an environment variable instead.
db: misskey
# Auth
# You can set user and pass from environment variables instead.
user: example-misskey-user
pass: example-misskey-pass
# Whether disable Caching queries
#disableCache: true
# Extra Connection options
#extra:
# ssl: true
dbReplications: false
# You can configure any number of replicas here
#dbSlaves:
# -
# host:
# port:
# db:
# user:
# pass:
# -
# host:
# port:
# db:
# user:
# pass:
# ┌─────────────────────┐
#───┘ Redis configuration └─────────────────────────────────────
redis:
host: redis
port: 6379
#family: 0 # 0=Both, 4=IPv4, 6=IPv6
#pass: example-pass
#prefix: example-prefix
#db: 1
#redisForPubsub:
# host: redis
# port: 6379
# #family: 0 # 0=Both, 4=IPv4, 6=IPv6
# #pass: example-pass
# #prefix: example-prefix
# #db: 1
#redisForJobQueue:
# host: redis
# port: 6379
# #family: 0 # 0=Both, 4=IPv4, 6=IPv6
# #pass: example-pass
# #prefix: example-prefix
# #db: 1
#redisForTimelines:
# host: redis
# port: 6379
# #family: 0 # 0=Both, 4=IPv4, 6=IPv6
# #pass: example-pass
# #prefix: example-prefix
# #db: 1
#redisForReactions:
# host: redis
# port: 6379
# #family: 0 # 0=Both, 4=IPv4, 6=IPv6
# #pass: example-pass
# #prefix: example-prefix
# #db: 1
# ┌───────────────────────────────┐
#───┘ Fulltext search configuration └─────────────────────────────
# These are the setting items for the full-text search provider.
fulltextSearch:
# You can select the ID generation method.
# - sqlLike (default)
# Use SQL-like search.
# This is a standard feature of PostgreSQL, so no special extensions are required.
# - sqlPgroonga
# Use pgroonga.
# You need to install pgroonga and configure it as a PostgreSQL extension.
# In addition to the above, you need to create a pgroonga index on the text column of the note table.
# see: https://pgroonga.github.io/tutorial/
# - meilisearch
# Use Meilisearch.
# You need to install Meilisearch and configure.
provider: sqlLike
# For Meilisearch settings.
# If you select "meilisearch" for "fulltextSearch.provider", it must be set.
# You can set scope to local (default value) or global
# (include notes from remote).
#meilisearch:
# host: meilisearch
# port: 7700
# apiKey: ''
# ssl: true
# index: ''
# scope: local
# ┌───────────────┐
#───┘ ID generation └───────────────────────────────────────────
# You can select the ID generation method.
# You don't usually need to change this setting, but you can
# change it according to your preferences.
# Available methods:
# aid ... Short, Millisecond accuracy
# aidx ... Millisecond accuracy
# meid ... Similar to ObjectID, Millisecond accuracy
# ulid ... Millisecond accuracy
# objectid ... This is left for backward compatibility
# ONCE YOU HAVE STARTED THE INSTANCE, DO NOT CHANGE THE
# ID SETTINGS AFTER THAT!
id: 'aidx'
# ┌────────────────┐
#───┘ Error tracking └──────────────────────────────────────────
# Sentry is available for error tracking.
# See the Sentry documentation for more details on options.
#sentryForBackend:
# enableNodeProfiling: true
# options:
# dsn: 'https://examplePublicKey@o0.ingest.sentry.io/0'
#sentryForFrontend:
# vueIntegration:
# tracingOptions:
# trackComponents: true
# browserTracingIntegration:
# replayIntegration:
# options:
# dsn: 'https://examplePublicKey@o0.ingest.sentry.io/0'
# ┌─────────────────────┐
#───┘ Other configuration └─────────────────────────────────────
# Whether disable HSTS
#disableHsts: true
# Number of worker processes
#clusterLimit: 1
# Number of threads of extra thread pool for CPU-intensive tasks (per worker)
#threadPoolSize: 1
# Job concurrency per worker
# deliverJobConcurrency: 128
# inboxJobConcurrency: 16
# Job rate limiter
# deliverJobPerSec: 128
# inboxJobPerSec: 32
# Job attempts
# deliverJobMaxAttempts: 12
# inboxJobMaxAttempts: 8
# IP address family used for outgoing request (ipv4, ipv6 or dual)
#outgoingAddressFamily: ipv4
# Proxy for HTTP/HTTPS
#proxy: http://127.0.0.1:3128
proxyBypassHosts:
- api.deepl.com
- api-free.deepl.com
- www.recaptcha.net
- hcaptcha.com
- challenges.cloudflare.com
# Proxy for SMTP/SMTPS
#proxySmtp: http://127.0.0.1:3128 # use HTTP/1.1 CONNECT
#proxySmtp: socks4://127.0.0.1:1080 # use SOCKS4
#proxySmtp: socks5://127.0.0.1:1080 # use SOCKS5
# Media Proxy
#mediaProxy: https://example.com/proxy
# For security reasons, uploading attachments from the intranet is prohibited,
# but exceptions can be made from the following settings. Default value is "undefined".
# Read changelog to learn more (Improvements of 12.90.0 (2021/09/04)).
#allowedPrivateNetworks: [
# '127.0.0.1/32'
#]
# Upload or download file size limits (bytes)
#maxFileSize: 262144000
# Log settings
# logging:
# sql:
# # Outputs query parameters during SQL execution to the log.
# # default: false
# enableQueryParamLogging: false
# # Disable query truncation. If set to true, the full text of the query will be output to the log.
# # default: false
# disableQueryTruncation: false

View File

@@ -2,77 +2,6 @@
# Misskey configuration
#━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
# ┌──────────────────────────────┐
#───┘ a boring but important thing └────────────────────────────
#
# First of all, let me tell you a story that may possibly be
# boring to you and possibly important to you.
#
# Misskey is licensed under the AGPLv3 license. This license is
# known to be often misunderstood. Please read the following
# instructions carefully and select the appropriate option so
# that you do not negligently cause a license violation.
#
# --------
# Option 1: If you host Misskey AS-IS (without any changes to
# the source code. forks are not included).
#
# Step 1: Congratulations! You don't need to do anything.
# --------
# Option 2: If you have made changes to the source code (forks
# are included) and publish a Git repository of source
# code. There should be no access restrictions on
# this repository. Strictly speaking, it doesn't have
# to be a Git repository, but you'll probably use Git!
#
# Step 1: Build and run the Misskey server first.
# Step 2: Open <https://your.misskey.example/admin/settings> in
# your browser with the administrator account.
# Step 3: Enter the URL of your Git repository in the
# "Repository URL" field.
# --------
# Option 3: If neither of the above applies to you.
# (In this case, the source code should be published
# on the Misskey interface. IT IS NOT ENOUGH TO
# DISCLOSE THE SOURCE CODE WHEN A USER REQUESTS IT BY
# E-MAIL OR OTHER MEANS. If you are not satisfied
# with this, it is recommended that you read the
# license again carefully. Anyway, enabling this
# option will automatically generate and publish a
# tarball at build time, protecting you from
# inadvertent license violations. (There is no legal
# guarantee, of course.) The tarball will generated
# from the root directory of your codebase. So it is
# also recommended to check <built/tarball> directory
# once after building and before activating the server
# to avoid ACCIDENTAL LEAKING OF SENSITIVE INFORMATION.
# To prevent certain files from being included in the
# tarball, add a glob pattern after line 15 in
# <scripts/tarball.mjs>. DO NOT FORGET TO BUILD AFTER
# ENABLING THIS OPTION!)
#
# Step 1: Uncomment the following line.
#
# publishTarballInsteadOfProvideRepositoryUrl: true
# ┌────────────────────────┐
#───┘ Initial Setup Password └─────────────────────────────────────────────────────
# Password to initiate setting up admin account.
# It will not be used after the initial setup is complete.
#
# Be sure to change this when you set up Misskey via the Internet.
#
# The provider of the service who sets up Misskey on behalf of the customer should
# set this value to something unique when generating the Misskey config file,
# and provide it to the customer.
#
# setupPassword: example_password_please_change_this_or_you_will_get_hacked
# ┌─────┐
#───┘ URL └─────────────────────────────────────────────────────
@@ -101,58 +30,6 @@ url: https://example.tld/
# The port that your Misskey server should listen on.
port: 3000
# You can also use UNIX domain socket.
# socket: /path/to/misskey.sock
# chmodSocket: '777'
# Proxy trust settings
#
# Specifies the IP addresses that Misskey will use as trusted
# reverse proxies (e.g., nginx, Cloudflare). This affects how
# Misskey determines the source IP for each request and is used
# for important rate limiting and security features. If the value
# is not set correctly, Misskey may use the IP address of the
# reverse proxy instead of the actual source IP, which may lead to
# unintended rate limiting or security vulnerabilities.
# By default, the loopback network and private network address
# ranges shown below are trusted.
# If you are using a single reverse proxy and it is on the same
# machine or the same private network as Misskey, it is unlikely you
# need to change this setting, and the default setting is fine.
# Also, if you are using multiple reverse proxy servers and they are
# all on the same private network as Misskey, the default setting
# is fine.
# However, if you are using a reverse proxy server that accesses
# Misskey web servers and streaming servers via public IP addresses
# (for example, Cloudflare), you must set this variable.
# When changing this setting, you can use one of the following values:
#
# - true: Trust all proxies
# - false: Do not trust any proxies
# - IP address, IP address range, or array of them: Trust hops that
# match the specified criteria.
# - Integer: Trust the nth hop from the front-facing proxy server as
# the client.
# For more information on how to configure this setting, please refer
# to the Fastify documentation:
# https://fastify.dev/docs/latest/Reference/Server/#trustproxy
#
# Note that if this variable is set, it overrides the default range,
# so if you have both an external reverse proxy and a proxy on the
# local host, you must include both IPs (or IP ranges).
#
#trustProxy:
# - '10.0.0.0/8'
# - '172.16.0.0/12'
# - '192.168.0.0/16'
# - '127.0.0.1/32'
# - '::1/128'
# - 'fc00::/7'
# # Example: If you are using some external reverse proxies like CDNs,
# # you may need to add the CDN IP ranges here.
# # If you're using Cloudflare, you can find IP Ranges at:
# # https://www.cloudflare.com/ips/
# ┌──────────────────────────┐
#───┘ PostgreSQL configuration └────────────────────────────────
@@ -174,23 +51,6 @@ db:
#extra:
# ssl: true
dbReplications: false
# You can configure any number of replicas here
#dbSlaves:
# -
# host:
# port:
# db:
# user:
# pass:
# -
# host:
# port:
# db:
# user:
# pass:
# ┌─────────────────────┐
#───┘ Redis configuration └─────────────────────────────────────
@@ -201,80 +61,16 @@ redis:
#pass: example-pass
#prefix: example-prefix
#db: 1
# You can specify more ioredis options...
#username: example-username
#redisForPubsub:
# ┌─────────────────────────────┐
#───┘ Elasticsearch configuration └─────────────────────────────
#elasticsearch:
# host: localhost
# port: 6379
# #family: 0 # 0=Both, 4=IPv4, 6=IPv6
# #pass: example-pass
# #prefix: example-prefix
# #db: 1
# # You can specify more ioredis options...
# #username: example-username
#redisForJobQueue:
# host: localhost
# port: 6379
# #family: 0 # 0=Both, 4=IPv4, 6=IPv6
# #pass: example-pass
# #prefix: example-prefix
# #db: 1
# # You can specify more ioredis options...
# #username: example-username
#redisForTimelines:
# host: localhost
# port: 6379
# #family: 0 # 0=Both, 4=IPv4, 6=IPv6
# #pass: example-pass
# #prefix: example-prefix
# #db: 1
# # You can specify more ioredis options...
# #username: example-username
#redisForReactions:
# host: localhost
# port: 6379
# #family: 0 # 0=Both, 4=IPv4, 6=IPv6
# #pass: example-pass
# #prefix: example-prefix
# #db: 1
# # You can specify more ioredis options...
# #username: example-username
# ┌───────────────────────────────┐
#───┘ Fulltext search configuration └─────────────────────────────
# These are the setting items for the full-text search provider.
fulltextSearch:
# You can select the ID generation method.
# - sqlLike (default)
# Use SQL-like search.
# This is a standard feature of PostgreSQL, so no special extensions are required.
# - sqlPgroonga
# Use pgroonga.
# You need to install pgroonga and configure it as a PostgreSQL extension.
# In addition to the above, you need to create a pgroonga index on the text column of the note table.
# see: https://pgroonga.github.io/tutorial/
# - meilisearch
# Use Meilisearch.
# You need to install Meilisearch and configure.
provider: sqlLike
# For Meilisearch settings.
# If you select "meilisearch" for "fulltextSearch.provider", it must be set.
# You can set scope to local (default value) or global
# (include notes from remote).
#meilisearch:
# host: localhost
# port: 7700
# apiKey: ''
# ssl: true
# index: ''
# scope: local
# port: 9200
# ssl: false
# user:
# pass:
# ┌───────────────┐
#───┘ ID generation └───────────────────────────────────────────
@@ -285,7 +81,6 @@ fulltextSearch:
# Available methods:
# aid ... Short, Millisecond accuracy
# aidx ... Millisecond accuracy
# meid ... Similar to ObjectID, Millisecond accuracy
# ulid ... Millisecond accuracy
# objectid ... This is left for backward compatibility
@@ -293,27 +88,7 @@ fulltextSearch:
# ONCE YOU HAVE STARTED THE INSTANCE, DO NOT CHANGE THE
# ID SETTINGS AFTER THAT!
id: 'aidx'
# ┌────────────────┐
#───┘ Error tracking └──────────────────────────────────────────
# Sentry is available for error tracking.
# See the Sentry documentation for more details on options.
#sentryForBackend:
# enableNodeProfiling: true
# options:
# dsn: 'https://examplePublicKey@o0.ingest.sentry.io/0'
#sentryForFrontend:
# vueIntegration:
# tracingOptions:
# trackComponents: true
# browserTracingIntegration:
# replayIntegration:
# options:
# dsn: 'https://examplePublicKey@o0.ingest.sentry.io/0'
id: 'aid'
# ┌─────────────────────┐
#───┘ Other configuration └─────────────────────────────────────
@@ -321,47 +96,36 @@ id: 'aidx'
# Whether disable HSTS
#disableHsts: true
# Enable internal IP-based rate limiting (default: true)
# To configure them in reverse proxy instead, set this to false.
#enableIpRateLimit: true
# Number of worker processes
#clusterLimit: 1
# Number of threads of extra thread pool for CPU-intensive tasks (per worker)
#threadPoolSize: 1
# Job concurrency per worker
#deliverJobConcurrency: 128
#inboxJobConcurrency: 16
#relationshipJobConcurrency: 16
# What's relationshipJob?:
# Follow, unfollow, block and unblock(ings) while following-imports, etc. or account migrations.
# deliverJobConcurrency: 128
# inboxJobConcurrency: 16
# Job rate limiter
#deliverJobPerSec: 128
#inboxJobPerSec: 32
#relationshipJobPerSec: 64
# deliverJobPerSec: 128
# inboxJobPerSec: 16
# Job attempts
#deliverJobMaxAttempts: 12
#inboxJobMaxAttempts: 8
# Local address used for outgoing requests
#outgoingAddress: 127.0.0.1
# deliverJobMaxAttempts: 12
# inboxJobMaxAttempts: 8
# IP address family used for outgoing request (ipv4, ipv6 or dual)
#outgoingAddressFamily: ipv4
# Syslog option
#syslog:
# host: localhost
# port: 514
# Proxy for HTTP/HTTPS
#proxy: http://127.0.0.1:3128
proxyBypassHosts:
- api.deepl.com
- api-free.deepl.com
- www.recaptcha.net
- hcaptcha.com
- challenges.cloudflare.com
#proxyBypassHosts: [
# 'example.com',
# '192.0.2.8'
#]
# Proxy for SMTP/SMTPS
#proxySmtp: http://127.0.0.1:3128 # use HTTP/1.1 CONNECT
@@ -369,36 +133,17 @@ proxyBypassHosts:
#proxySmtp: socks5://127.0.0.1:1080 # use SOCKS5
# Media Proxy
# Reference Implementation: https://github.com/misskey-dev/media-proxy
# * Deliver a common cache between instances
# * Perform image compression (on a different server resource than the main process)
#mediaProxy: https://example.com/proxy
# Movie Thumbnail Generation URL
# There is no reference implementation.
# For example, Misskey will point to the following URL:
# https://example.com/thumbnail.webp?thumbnail=1&url=https%3A%2F%2Fstorage.example.com%2Fpath%2Fto%2Fvideo.mp4
#videoThumbnailGenerator: https://example.com
# Proxy remote files (default: false)
#proxyRemoteFiles: true
# Sign to ActivityPub GET request (default: false)
#signToActivityPubGet: true
# For security reasons, uploading attachments from the intranet is prohibited,
# but exceptions can be made from the following settings. Default value is "undefined".
# Read changelog to learn more (Improvements of 12.90.0 (2021/09/04)).
#allowedPrivateNetworks: [
# '127.0.0.1/32'
#]
# Upload or download file size limits (bytes)
#maxFileSize: 262144000
# PID File of master process
#pidFile: /tmp/misskey.pid
# Log settings
# logging:
# sql:
# # Outputs query parameters during SQL execution to the log.
# # default: false
# enableQueryParamLogging: false
# # Disable query truncation. If set to true, the full text of the query will be output to the log.
# # default: false
# disableQueryTruncation: false

View File

@@ -1 +0,0 @@
FROM mcr.microsoft.com/devcontainers/javascript-node:4.0.3-24-trixie

View File

@@ -1,53 +0,0 @@
services:
app:
build:
context: .
dockerfile: Dockerfile
volumes:
- ../:/workspace:cached
- node_modules:/workspace/node_modules
command: sleep infinity
networks:
- internal_network
- external_network
redis:
restart: unless-stopped
image: redis:7-alpine
networks:
- internal_network
volumes:
- redis-data:/data
healthcheck:
test: "redis-cli ping"
interval: 5s
retries: 20
db:
restart: unless-stopped
image: postgres:18-alpine
networks:
- internal_network
environment:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
POSTGRES_DB: misskey
volumes:
- postgres-data:/var/lib/postgresql
healthcheck:
test: "pg_isready -U $$POSTGRES_USER -d $$POSTGRES_DB"
interval: 5s
retries: 20
volumes:
postgres-data:
redis-data:
node_modules:
networks:
internal_network:
internal: true
external_network:

View File

@@ -1,27 +0,0 @@
{
"name": "Misskey",
"dockerComposeFile": "compose.yml",
"service": "app",
"workspaceFolder": "/workspace",
"features": {
"ghcr.io/devcontainers/features/node:1": {
"version": "22.15.0"
},
"ghcr.io/devcontainers-extra/features/pnpm:2": {
"version": "10.10.0"
}
},
"forwardPorts": [3000],
"postCreateCommand": "/bin/bash .devcontainer/init.sh",
"customizations": {
"vscode": {
"extensions": [
"editorconfig.editorconfig",
"dbaeumer.vscode-eslint",
"Vue.volar",
"dbaeumer.vscode-eslint",
"mrmlnc.vscode-json5"
]
}
}
}

View File

@@ -1,213 +0,0 @@
#━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
# Misskey configuration
#━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
# ┌─────┐
#───┘ URL └─────────────────────────────────────────────────────
# Final accessible URL seen by a user.
url: http://127.0.0.1:3000/
# ONCE YOU HAVE STARTED THE INSTANCE, DO NOT CHANGE THE
# URL SETTINGS AFTER THAT!
# ┌───────────────────────┐
#───┘ Port and TLS settings └───────────────────────────────────
#
# Misskey requires a reverse proxy to support HTTPS connections.
#
# +----- https://example.tld/ ------------+
# +------+ |+-------------+ +----------------+|
# | User | ---> || Proxy (443) | ---> | Misskey (3000) ||
# +------+ |+-------------+ +----------------+|
# +---------------------------------------+
#
# You need to set up a reverse proxy. (e.g. nginx)
# An encrypted connection with HTTPS is highly recommended
# because tokens may be transferred in GET requests.
# The port that your Misskey server should listen on.
port: 3000
# ┌──────────────────────────┐
#───┘ PostgreSQL configuration └────────────────────────────────
db:
host: db
port: 5432
# Database name
db: misskey
# Auth
user: postgres
pass: postgres
# Whether disable Caching queries
#disableCache: true
# Extra Connection options
#extra:
# ssl: true
dbReplications: false
# You can configure any number of replicas here
#dbSlaves:
# -
# host:
# port:
# db:
# user:
# pass:
# -
# host:
# port:
# db:
# user:
# pass:
# ┌─────────────────────┐
#───┘ Redis configuration └─────────────────────────────────────
redis:
host: redis
port: 6379
#family: 0 # 0=Both, 4=IPv4, 6=IPv6
#pass: example-pass
#prefix: example-prefix
#db: 1
#redisForPubsub:
# host: redis
# port: 6379
# #family: 0 # 0=Both, 4=IPv4, 6=IPv6
# #pass: example-pass
# #prefix: example-prefix
# #db: 1
#redisForJobQueue:
# host: redis
# port: 6379
# #family: 0 # 0=Both, 4=IPv4, 6=IPv6
# #pass: example-pass
# #prefix: example-prefix
# #db: 1
#redisForTimelines:
# host: redis
# port: 6379
# #family: 0 # 0=Both, 4=IPv4, 6=IPv6
# #pass: example-pass
# #prefix: example-prefix
# #db: 1
#redisForReactions:
# host: redis
# port: 6379
# #family: 0 # 0=Both, 4=IPv4, 6=IPv6
# #pass: example-pass
# #prefix: example-prefix
# #db: 1
# ┌───────────────────────────┐
#───┘ MeiliSearch configuration └─────────────────────────────
#meilisearch:
# host: meilisearch
# port: 7700
# apiKey: ''
# ssl: true
# index: ''
# ┌───────────────┐
#───┘ ID generation └───────────────────────────────────────────
# You can select the ID generation method.
# You don't usually need to change this setting, but you can
# change it according to your preferences.
# Available methods:
# aid ... Short, Millisecond accuracy
# aidx ... Millisecond accuracy
# meid ... Similar to ObjectID, Millisecond accuracy
# ulid ... Millisecond accuracy
# objectid ... This is left for backward compatibility
# ONCE YOU HAVE STARTED THE INSTANCE, DO NOT CHANGE THE
# ID SETTINGS AFTER THAT!
id: 'aidx'
# ┌────────────────┐
#───┘ Error tracking └──────────────────────────────────────────
# Sentry is available for error tracking.
# See the Sentry documentation for more details on options.
#sentryForBackend:
# enableNodeProfiling: true
# options:
# dsn: 'https://examplePublicKey@o0.ingest.sentry.io/0'
#sentryForFrontend:
# vueIntegration:
# tracingOptions:
# trackComponents: true
# browserTracingIntegration:
# replayIntegration:
# options:
# dsn: 'https://examplePublicKey@o0.ingest.sentry.io/0'
# ┌─────────────────────┐
#───┘ Other configuration └─────────────────────────────────────
# Whether disable HSTS
#disableHsts: true
# Number of worker processes
#clusterLimit: 1
# Number of threads of extra thread pool for CPU-intensive tasks (per worker)
#threadPoolSize: 1
# Job concurrency per worker
# deliverJobConcurrency: 128
# inboxJobConcurrency: 16
# Job rate limiter
# deliverJobPerSec: 128
# inboxJobPerSec: 32
# Job attempts
# deliverJobMaxAttempts: 12
# inboxJobMaxAttempts: 8
# IP address family used for outgoing request (ipv4, ipv6 or dual)
#outgoingAddressFamily: ipv4
# Proxy for HTTP/HTTPS
#proxy: http://127.0.0.1:3128
proxyBypassHosts:
- api.deepl.com
- api-free.deepl.com
- www.recaptcha.net
- hcaptcha.com
- challenges.cloudflare.com
# Proxy for SMTP/SMTPS
#proxySmtp: http://127.0.0.1:3128 # use HTTP/1.1 CONNECT
#proxySmtp: socks4://127.0.0.1:1080 # use SOCKS4
#proxySmtp: socks5://127.0.0.1:1080 # use SOCKS5
# Media Proxy
#mediaProxy: https://example.com/proxy
allowedPrivateNetworks: [
'127.0.0.1/32'
]
# Upload or download file size limits (bytes)
#maxFileSize: 262144000

View File

@@ -1,15 +0,0 @@
#!/bin/bash
set -xe
sudo chown node node_modules
sudo apt-get update
sudo apt-get -y install libgtk2.0-0 libgtk-3-0 libgbm-dev libnotify-dev libnss3 libxss1 libasound2 libxtst6 xauth xvfb
git config --global --add safe.directory /workspace
git submodule update --init
pnpm config set store-dir /home/node/.local/share/pnpm/store
pnpm install --frozen-lockfile
cp .devcontainer/devcontainer.yml .config/default.yml
pnpm build
pnpm migrate
pnpm exec cypress install

View File

@@ -6,26 +6,10 @@
Dockerfile
build/
built/
src-js/
db/
.devcontainer/compose.yml
docker-compose.yml
elasticsearch/
node_modules/
packages/*/node_modules
redis/
files/
fluent-emojis/
.pnp.*
# .yarn関連
.yarn/*
!.yarn/patches
!.yarn/plugins
!.yarn/releases
!.yarn/sdks
!.yarn/versions
.pnpm-store
.idea/
packages/*/.vscode/
packages/backend/test/compose.yml
misskey-assets/

View File

@@ -1,3 +0,0 @@
DKL-DI-0005
DKL-DI-0006
DKL-LI-0003

View File

@@ -5,15 +5,6 @@ indent_style = tab
indent_size = 2
charset = utf-8
insert_final_newline = true
end_of_line = lf
trim_trailing_whitespace = true
[*.md]
trim_trailing_whitespace = false
[*.{yml,yaml}]
[*.yml]
indent_style = space
[packages/backend/migration/*.js]
indent_style = space
indent_size = 4

1
.gitattributes vendored
View File

@@ -5,4 +5,3 @@
*.glb -diff -text
*.blend -diff -text
*.afdesign -diff -text
* text=auto eol=lf

3
.github/FUNDING.yml vendored Normal file
View File

@@ -0,0 +1,3 @@
# These are supported funding model platforms
patreon: syuilo

42
.github/ISSUE_TEMPLATE/01_bug-report.md vendored Normal file
View File

@@ -0,0 +1,42 @@
---
name: 🐛 Bug Report
about: Create a report to help us improve
title: ''
labels: ⚠bug?
assignees: ''
---
<!--
Thanks for reporting!
First, in order to avoid duplicate Issues, please search to see if the problem you found has already been reported.
-->
## 💡 Summary
<!-- Tell us what the bug is -->
## 🥰 Expected Behavior
<!--- Tell us what should happen -->
## 🤬 Actual Behavior
<!--
Tell us what happens instead of the expected behavior.
Please include errors from the developer console and/or server log files if you have access to them.
-->
## 📝 Steps to Reproduce
1.
2.
3.
## 📌 Environment
<!-- Tell us where on the platform it happens -->
Misskey version:
Your OS:
Your browser:

View File

@@ -1,97 +0,0 @@
name: 🐛 Bug Report
description: Create a report to help us improve
labels: ["⚠bug?"]
body:
- type: markdown
attributes:
value: |
Thanks for reporting!
First, in order to avoid duplicate Issues, please search to see if the problem you found has already been reported.
Also, If you are NOT owner/admin of server, PLEASE DONT REPORT SERVER SPECIFIC ISSUES TO HERE! (e.g. feature XXX is not working in misskey.example) Please try with another misskey servers, and if your issue is only reproducible with specific server, contact your server's owner/admin first.
- type: textarea
attributes:
label: 💡 Summary
description: Tell us what the bug is
validations:
required: true
- type: textarea
attributes:
label: 🥰 Expected Behavior
description: Tell us what should happen
validations:
required: true
- type: textarea
attributes:
label: 🤬 Actual Behavior
description: |
Tell us what happens instead of the expected behavior.
Please include errors from the developer console and/or server log files if you have access to them.
validations:
required: true
- type: textarea
attributes:
label: 📝 Steps to Reproduce
placeholder: |
1.
2.
3.
validations:
required: false
- type: textarea
attributes:
label: 💻 Frontend Environment
description: |
Tell us where on the platform it happens
DO NOT WRITE "latest". Please provide the specific version.
Examples:
* Model and OS of the device(s): MacBook Pro (14inch, 2021), macOS Ventura 13.4
* Browser: Chrome 113.0.5672.126
* Server URL: misskey.example.com
* Misskey: 2026.x.x
value: |
* Model and OS of the device(s):
* Browser:
* Server URL:
* Misskey:
render: markdown
validations:
required: false
- type: textarea
attributes:
label: 🛰 Backend Environment (for server admin)
description: |
Tell us where on the platform it happens
DO NOT WRITE "latest". Please provide the specific version.
If you are using a managed service, put that after the version.
Examples:
* Installation Method or Hosting Service: docker compose, k8s/docker, systemd, "Misskey install shell script", development environment
* Misskey: 2026.x.x
* Node: 20.x.x
* PostgreSQL: 18.x.x
* Redis: 7.x.x
* OS and Architecture: Ubuntu 24.04.2 LTS aarch64
value: |
* Installation Method or Hosting Service:
* Misskey:
* Node:
* PostgreSQL:
* Redis:
* OS and Architecture:
render: markdown
validations:
required: false
- type: checkboxes
attributes:
label: Do you want to address this bug yourself?
options:
- label: Yes, I will patch the bug myself and send a pull request

View File

@@ -0,0 +1,12 @@
---
name: ✨ Feature Request
about: Suggest an idea for this project
title: ''
labels: ✨Feature
assignees: ''
---
## Summary
<!-- Tell us what the suggestion is -->

View File

@@ -1,22 +0,0 @@
name: ✨ Feature Request
description: Suggest an idea for this project
labels: ["✨Feature"]
body:
- type: textarea
attributes:
label: Summary
description: Tell us what the suggestion is
validations:
required: true
- type: textarea
attributes:
label: Purpose
description: Describe the specific problem or need you think this feature will solve, and who it will help.
validations:
required: true
- type: checkboxes
attributes:
label: Do you want to implement this feature yourself?
options:
- label: Yes, I will implement this by myself and send a pull request

View File

@@ -1,8 +1,7 @@
contact_links:
- name: 👪 Misskey Forum
url: https://forum.misskey.io/
about: Ask questions and share knowledge
- name: 💬 Misskey official Discord
url: https://discord.gg/Wp8gVStHW3
about: Chat freely about Misskey
# 仮
- name: 💬 Start discussion
url: https://github.com/misskey-dev/misskey/discussions
about: The official forum to join conversation and ask question

17
.github/PULL_REQUEST_TEMPLATE.md vendored Normal file
View File

@@ -0,0 +1,17 @@
<!-- お読みください / README
PRありがとうございます PRを作成する前に、コントリビューションガイドをご確認ください:
Thank you for your PR! Before creating a PR, please check the contribution guide:
https://github.com/misskey-dev/misskey/blob/develop/CONTRIBUTING.md
-->
# What
<!-- このPRで何をしたのか どう変わるのか? -->
<!-- What did you do with this PR? How will it change things? -->
# Why
<!-- なぜそうするのか? どういう意図なのか? 何が困っているのか? -->
<!-- Why do you do it? What are your intentions? What is the problem? -->
# Additional info (optional)
<!-- テスト観点など -->
<!-- Test perspective, etc -->

View File

@@ -1,23 +0,0 @@
<!-- お読みください / README
PRありがとうございます PRを作成する前に、コントリビューションガイドをご確認ください:
Thank you for your PR! Before creating a PR, please check the contribution guide:
https://github.com/misskey-dev/misskey/blob/develop/CONTRIBUTING.md
-->
## What
<!-- このPRで何をしたのか どう変わるのか? -->
<!-- What did you do with this PR? How will it change things? -->
## Why
<!-- なぜそうするのか? どういう意図なのか? 何が困っているのか? -->
<!-- Why do you do it? What are your intentions? What is the problem? -->
## Additional info (optional)
<!-- テスト観点など -->
<!-- Test perspective, etc -->
## Checklist
- [ ] Read the [contribution guide](https://github.com/misskey-dev/misskey/blob/develop/CONTRIBUTING.md)
- [ ] Test working in a local environment
- [ ] (If needed) Update CHANGELOG.md
- [ ] (If possible) Add tests

View File

@@ -1,23 +0,0 @@
<!-- お読みください / README
PRありがとうございます PRを作成する前に、コントリビューションガイドをご確認ください:
Thank you for your PR! Before creating a PR, please check the contribution guide:
https://github.com/misskey-dev/misskey/blob/develop/CONTRIBUTING.md
-->
## What
<!-- このPRで何をしたのか どう変わるのか? -->
<!-- What did you do with this PR? How will it change things? -->
## Why
<!-- なぜそうするのか? どういう意図なのか? 何が困っているのか? -->
<!-- Why do you do it? What are your intentions? What is the problem? -->
## Additional info (optional)
<!-- テスト観点など -->
<!-- Test perspective, etc -->
## Checklist
- [ ] Read the [contribution guide](https://github.com/misskey-dev/misskey/blob/develop/CONTRIBUTING.md)
- [ ] Test working in a local environment
- [ ] (If needed) Update CHANGELOG.md
- [ ] (If possible) Add tests

View File

@@ -1,20 +0,0 @@
## Summary
This is a release PR.
For more information on the release instructions, please see:
https://github.com/misskey-dev/misskey/blob/develop/CONTRIBUTING.md#release
## For reviewers
- CHANGELOGに抜け漏れは無いか
- バージョンの上げ方は適切か
- 他にこのリリースに含めなければならない変更は無いか
- 全体的な変更内容を俯瞰し問題は無いか
- レビューされていないコミットがある場合は、それが問題ないか
- 最終的な動作確認を行い問題は無いか
などを確認し、リリースする準備が整っていると思われる場合は approve してください。
## Checklist
- [ ] package.jsonのバージョンが正しく更新されている
- [ ] CHANGELOGが過不足無く更新されている
- [ ] CIが全て通っている

View File

@@ -1,80 +0,0 @@
# Copilot Instructions for Misskey
このファイルは GitHub Copilot の repository-wide instructions として使われる。Copilot code review では `AGENTS.md` が読まれない環境があるため、レビューや軽微な実装判断に必要な規約はこのファイル単体で満たすこと。
リポジトリは Misskey の pnpm workspace モノレポ。主要な実装は `packages/backend` (NestJS / TypeORM) と `packages/frontend` (Vue 3) にある。より詳しいガイドはリポジトリルートの `AGENTS.md` を参照してよいが、このファイルの要件を省略してそちらへの参照だけで済ませないこと。
## 絶対にやってはいけない事
違反すると CI 失敗 / 本番事故 になる。
### コード・データ関連
- **SPDX ヘッダー必須**: AGPL-3.0-only 管轄かつ SPDX CI 対象ディレクトリに新規 `.ts` / `.js` / `.cjs` / `.mjs` / `.scss` / `.vue` / `.html` ファイルを追加する場合は冒頭に必ず付ける。詳細な対象判定は `.github/workflows/check-spdx-license-id.yml` を参照。
```text
/*
* SPDX-FileCopyrightText: syuilo and misskey-project
* SPDX-License-Identifier: AGPL-3.0-only
*/
```
新規 `.vue` / `.html` ファイルは HTML コメント形式で:
```text
<!--
SPDX-FileCopyrightText: syuilo and misskey-project
SPDX-License-Identifier: AGPL-3.0-only
-->
```
`packages/misskey-js` は MIT ライセンスのサブパッケージなので、この AGPL ヘッダーを一律に付けない (サブパッケージ固有の `package.json` / `LICENSE` / 既存ファイルのヘッダーに従う)。
- **`locales/ja-JP.yml` 以外の locale YAML を編集しない**。他言語ファイル (`en-US.yml` など `ja-JP.yml` 以外すべて) は Crowdin の自動配信先で、手動編集すると次の同期で上書き喪失する。
- **マージ済 migration を編集しない**。`packages/backend/migration/{timestamp}-*.js` のうち既に `develop` / `master` に入ったものは絶対に変更しない。スキーマ変更が必要なら新しい timestamp で新規ファイルを追加し、`up()` と `down()` の両方を実装する。
- **secrets / 認証情報をリポジトリにコミットしない** (`.config/*.yml` の本番値、`.env` ファイル、API token、private key 等)。
### Git / リポジトリ操作
- `git push --force` / `--force-with-lease` を `main` / `develop` / `master` にしない
- `git commit --no-verify` で hook をスキップしない
- マージ済 / プッシュ済コミットを `git commit --amend` で書き換えない
- 他人のブランチを `git reset --hard` / `git branch -D` で破壊しない
- `git config` をユーザーに無断で書き換えない (特に `user.name` / `user.email` / `commit.gpgsign`)
### Issue / PR / 外部送信
- ユーザーの明示指示なしに PR を merge / close / force-push しない
- ユーザーの明示指示なしに external service (GitHub comments / Slack / メール 等) へ送信しない
## 変更を出す前の最低チェック
1. `pnpm lint` が通る (typecheck + eslint, 全パッケージ)
2. backend で `meta` / `paramDef` / `res` を変更した → `pnpm build-misskey-js-with-types` を実行し `packages/misskey-js/src/autogen/` の差分も commit に含めた
3. entity / migration を変更した → `pnpm --filter backend check-migrations` が pending DDL 0 件で通る / 新規 migration は `up()` と `down()` 両方実装済
4. 新規 `.ts` / `.js` / `.cjs` / `.mjs` / `.vue` / `.scss` / `.html` ファイルを追加した → SPDX ヘッダーを付けた
5. ユーザー影響のある変更 → `CHANGELOG.md` の `## Unreleased` 配下の該当サブセクション (`### General` / `### Client` / `### Server`) に `- <Feat|Enhance|Fix>: <概要>` を 1 行追記
6. `locales/` を編集した場合、`git diff --name-only develop -- 'locales/*.yml' | grep -v '^locales/ja-JP\.yml$'` が空 (ja-JP.yml 以外に差分が無い) ことを確認
## Validation コマンド
- 全体ビルド: `pnpm build`
- 全体 lint / typecheck: `pnpm lint`
- Backend unit test: `pnpm --filter backend test`
- Backend e2e test: `pnpm --filter backend test:e2e`
- Backend federation test: `pnpm --filter backend test:fed`
- Frontend test: `pnpm --filter frontend test`
- Migration 差分検査: `pnpm --filter backend check-migrations`
- `misskey-js` 再生成 (API 変更後必須): `pnpm build-misskey-js-with-types`
**注意:** backend テスト (`test` / `test:e2e` / `test:fed`) 実行前に `.config/test.yml` が必要。未作成の場合は `ncp .github/misskey/test.yml .config/test.yml` (または `cp .github/misskey/test.yml .config/test.yml`) を実行してから走らせる。各テストスクリプトが内部で `cross-env NODE_ENV=test pnpm compile-config` を呼ぶため、コピー済みであれば追加の compile-config は不要。
変更範囲に応じて最も近いコマンドから優先して検証し、必要なら全体コマンドに広げること。
## Editing hints
- Backend の API / migration / TypeORM 変更は `packages/backend` を見る
- Frontend の Vue コンポーネントやページ変更は `packages/frontend` を見る
- `AGENTS.md` 内の相対リンクはリポジトリルート起点で解決する想定
**補足:** `AGENTS.md` はより詳細な正典 (Codex / Claude Code が読み込む)。Copilot code review ではこのファイルが主な入口になる。両方が読まれる環境では `AGENTS.md` を補助情報として使ってよい。

View File

@@ -5,38 +5,18 @@
version: 2
updates:
- package-ecosystem: github-actions
directory: "/"
schedule:
interval: daily
open-pull-requests-limit: 0
# Add only the root, not each workspace item
# https://github.com/dependabot/dependabot-core/issues/4993#issuecomment-1289133027
- package-ecosystem: npm
directory: "/"
schedule:
interval: daily
open-pull-requests-limit: 0
# List dependencies required to be updated together, sharing the same version numbers.
# Those who simply have the common owner (e.g. @fastify) don't need to be listed.
groups:
aws-sdk:
patterns:
- "@aws-sdk/*"
nestjs:
patterns:
- "@nestjs/*"
slacc:
patterns:
- "slacc-*"
storybook:
patterns:
- "storybook*"
- "@storybook/*"
typescript-eslint:
patterns:
- "@typescript-eslint/*"
tensorflow:
patterns:
- "@tensorflow/*"
- package-ecosystem: npm
directory: "/packages/backend"
schedule:
interval: daily
open-pull-requests-limit: 0
- package-ecosystem: npm
directory: "/packages/client"
schedule:
interval: daily
open-pull-requests-limit: 0

40
.github/labeler.yml vendored
View File

@@ -1,34 +1,12 @@
'packages/backend':
- any:
- changed-files:
- any-glob-to-any-file: ['packages/backend/**/*']
'Server':
- packages/backend/**/*
'packages/backend:test':
- any:
- changed-files:
- any-glob-to-any-file: ['packages/backend/test/**/*', 'packages/backend/test-federation/**/*']
'🖥Client':
- packages/client/**/*
'packages/frontend':
- any:
- changed-files:
- any-glob-to-any-file: ['packages/frontend/**/*']
'🧪Test':
- cypress/**/*
- packages/backend/test/**/*
'packages/frontend:test':
- any:
- changed-files:
- any-glob-to-any-file: ['cypress/**/*']
'packages/sw':
- any:
- changed-files:
- any-glob-to-any-file: ['packages/sw/**/*']
'packages/misskey-js':
- any:
- changed-files:
- any-glob-to-any-file: ['packages/misskey-js/**/*']
'packages/misskey-js:test':
- any:
- changed-files:
- any-glob-to-any-file: ['packages/misskey-js/test/**/*', 'packages/misskey-js/test-d/**/*']
'‼️ wrong locales':
- any: ['locales/*.yml', '!locales/ja-JP.yml']

View File

@@ -1 +0,0 @@
22.15.0

View File

@@ -1,19 +1,15 @@
url: 'http://misskey.local'
setupPassword: example_password_please_change_this_or_you_will_get_hacked
# ローカルでテストするときにポートを被らないようにするためデフォルトのものとは変える(以下同じ)
port: 61812
db:
host: 127.0.0.1
host: localhost
port: 54312
db: test-misskey
user: postgres
pass: ''
redis:
host: 127.0.0.1
host: localhost
port: 56312
id: aidx
proxyRemoteFiles: true
id: aid

View File

@@ -1,24 +0,0 @@
<!-- お読みください / README
PRありがとうございます PRを作成する前に、コントリビューションガイドをご確認ください:
Thank you for your PR! Before creating a PR, please check the contribution guide:
https://github.com/misskey-dev/misskey/blob/develop/CONTRIBUTING.md
-->
## What
<!-- このPRで何をしたのか どう変わるのか? -->
<!-- What did you do with this PR? How will it change things? -->
## Why
<!-- なぜそうするのか? どういう意図なのか? 何が困っているのか? -->
<!-- Why do you do it? What are your intentions? What is the problem? -->
## Additional info (optional)
<!-- テスト観点など -->
<!-- Test perspective, etc -->
## Checklist
- [ ] Read the [contribution guide](https://github.com/misskey-dev/misskey/blob/develop/CONTRIBUTING.md)
- [ ] Test working in a local environment
- [ ] (If needed) Add story of storybook
- [ ] (If needed) Update CHANGELOG.md
- [ ] (If possible) Add tests

View File

@@ -1,44 +0,0 @@
name: API report (misskey.js)
on:
push:
paths:
- packages/misskey-js/**
- .github/workflows/api-misskey-js.yml
pull_request:
paths:
- packages/misskey-js/**
- .github/workflows/api-misskey-js.yml
jobs:
report:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v6.0.2
- name: Setup pnpm
uses: pnpm/action-setup@v6.0.3
- name: Setup Node.js
uses: actions/setup-node@v6.4.0
with:
node-version-file: '.node-version'
cache: 'pnpm'
- name: Install dependencies
run: pnpm i --frozen-lockfile
- name: Build
run: pnpm --filter misskey-js build
- name: Check files
run: ls packages/misskey-js/built
- name: API report
run: pnpm --filter misskey-js api-prod
- name: Show report
if: always()
run: cat packages/misskey-js/temp/misskey-js.api.md

View File

@@ -1,43 +0,0 @@
name: Check the description in CHANGELOG.md
on:
pull_request:
branches:
- master
- develop
jobs:
check-changelog:
runs-on: ubuntu-latest
steps:
- name: Checkout head
uses: actions/checkout@v6.0.2
- name: Setup Node.js
uses: actions/setup-node@v6.4.0
with:
node-version-file: '.node-version'
- name: Checkout base
run: |
mkdir _base
cp -r .git _base/.git
cd _base
git fetch --depth 1 origin ${{ github.base_ref }}
git checkout origin/${{ github.base_ref }} CHANGELOG.md
- name: Copy to Checker directory for CHANGELOG-base.md
run: cp _base/CHANGELOG.md scripts/changelog-checker/CHANGELOG-base.md
- name: Copy to Checker directory for CHANGELOG-head.md
run: cp CHANGELOG.md scripts/changelog-checker/CHANGELOG-head.md
- name: diff
continue-on-error: true
run: diff -u CHANGELOG-base.md CHANGELOG-head.md
working-directory: scripts/changelog-checker
- name: Setup Checker
run: npm install
working-directory: scripts/changelog-checker
- name: Run Checker
run: npm run run
working-directory: scripts/changelog-checker

View File

@@ -1,139 +0,0 @@
name: Check Misskey JS autogen
on:
pull_request_target:
branches:
- master
- develop
- improve-misskey-js-autogen-check
paths:
- packages/backend/**
jobs:
# pull_request_target safety: permissions: read-all, and there are no secrets used in this job
generate-misskey-js:
runs-on: ubuntu-latest
permissions:
contents: read
if: ${{ github.event.pull_request.mergeable == null || github.event.pull_request.mergeable == true }}
steps:
- name: checkout
uses: actions/checkout@v6.0.2
with:
submodules: true
persist-credentials: false
ref: refs/pull/${{ github.event.pull_request.number }}/merge
- name: setup pnpm
uses: pnpm/action-setup@v6
- name: setup node
id: setup-node
uses: actions/setup-node@v6.4.0
with:
node-version-file: '.node-version'
cache: pnpm
- name: install dependencies
run: pnpm i --frozen-lockfile
# generate api.json
- name: Copy Config
run: cp .config/example.yml .config/default.yml
- name: Build
run: pnpm build
- name: Generate API JSON
run: pnpm --filter backend generate-api-json
# build misskey js
- name: Build misskey-js
run: |-
cp packages/backend/built/api.json packages/misskey-js/generator/api.json
pnpm run --filter misskey-js-type-generator generate
# packages/misskey-js/generator/built/autogen
- name: Upload Generated
uses: actions/upload-artifact@v7
with:
name: generated-misskey-js
path: packages/misskey-js/generator/built/autogen
# pull_request_target safety: permissions: read-all, and no user codes are executed
get-actual-misskey-js:
runs-on: ubuntu-latest
permissions:
contents: read
if: ${{ github.event.pull_request.mergeable == null || github.event.pull_request.mergeable == true }}
steps:
- name: checkout
uses: actions/checkout@v6.0.2
with:
submodules: true
persist-credentials: false
ref: refs/pull/${{ github.event.pull_request.number }}/merge
- name: Upload From Merged
uses: actions/upload-artifact@v7
with:
name: actual-misskey-js
path: packages/misskey-js/src/autogen
# pull_request_target safety: nothing is cloned from repository
comment-misskey-js-autogen:
runs-on: ubuntu-latest
needs: [generate-misskey-js, get-actual-misskey-js]
permissions:
pull-requests: write
steps:
- name: download generated-misskey-js
uses: actions/download-artifact@v8
with:
name: generated-misskey-js
path: misskey-js-generated
- name: download actual-misskey-js
uses: actions/download-artifact@v8
with:
name: actual-misskey-js
path: misskey-js-actual
- name: check misskey-js changes
id: check-changes
run: |
diff -r -u --label=generated --label=on-tree ./misskey-js-generated ./misskey-js-actual > misskey-js.diff || true
if [ -s misskey-js.diff ]; then
echo "changes=true" >> $GITHUB_OUTPUT
else
echo "changes=false" >> $GITHUB_OUTPUT
fi
- name: Print full diff
run: cat ./misskey-js.diff
- name: send message
if: steps.check-changes.outputs.changes == 'true'
uses: thollander/actions-comment-pull-request@v3
with:
comment-tag: check-misskey-js-autogen
message: |-
Thank you for sending us a great Pull Request! 👍
Please regenerate misskey-js type definitions! 🙏
example:
```sh
pnpm run build-misskey-js-with-types
```
- name: send message
if: steps.check-changes.outputs.changes == 'false'
uses: thollander/actions-comment-pull-request@v3
with:
comment-tag: check-misskey-js-autogen
mode: delete
message: "Thank you!"
create_if_not_exists: false
- name: Make failure if changes are detected
if: steps.check-changes.outputs.changes == 'true'
run: exit 1

View File

@@ -1,29 +0,0 @@
name: Check Misskey JS version
on:
push:
branches: [ develop ]
paths:
- packages/misskey-js/package.json
- package.json
- .github/workflows/check-misskey-js-version.yml
pull_request:
branches: [ develop ]
paths:
- packages/misskey-js/package.json
- package.json
- .github/workflows/check-misskey-js-version.yml
jobs:
check-version:
# ルートの package.json と packages/misskey-js/package.json のバージョンが一致しているかを確認する
name: Check version
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v6.0.2
- name: Check version
run: |
if [ "$(jq -r '.version' package.json)" != "$(jq -r '.version' packages/misskey-js/package.json)" ]; then
echo "Version mismatch!"
exit 1
fi

View File

@@ -1,81 +0,0 @@
name: Check SPDX-License-Identifier
on:
push:
branches:
- master
- develop
pull_request:
jobs:
check-spdx-license-id:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v6.0.2
- name: Check
run: |
counter=0
search() {
local directory="$1"
find "$directory" -type f \
'(' \
-name "*.cjs" -and -not -name '*.config.cjs' -o \
-name "*.html" -o \
-name "*.js" -and -not -name '*.config.js' -o \
-name "*.mjs" -and -not -name '*.config.mjs' -o \
-name "*.scss" -o \
-name "*.ts" -and -not -name '*.config.ts' -o \
-name "*.vue" \
')' -and \
-not -name '*eslint*'
}
check() {
local file="$1"
if ! (
grep -q "SPDX-FileCopyrightText: syuilo and misskey-project" "$file" ||
grep -q "SPDX-License-Identifier: AGPL-3.0-only" "$file"
); then
echo "Missing: $file"
((counter++))
fi
}
directories=(
"cypress/e2e"
"packages/backend/migration"
"packages/backend/src"
"packages/backend/test"
"packages/frontend-shared/@types"
"packages/frontend-shared/js"
"packages/frontend-builder"
"packages/frontend/.storybook"
"packages/frontend/@types"
"packages/frontend/lib"
"packages/frontend/public"
"packages/frontend/src"
"packages/frontend/test"
"packages/frontend-embed/@types"
"packages/frontend-embed/src"
"packages/icons-subsetter/src"
"packages/misskey-bubble-game/src"
"packages/misskey-reversi/src"
"packages/sw/src"
"scripts"
)
for directory in "${directories[@]}"; do
for file in $(search $directory); do
check "$file"
done
done
if [ $counter -gt 0 ]; then
echo "SPDX-License-Identifier is missing in $counter files."
exit 1
else
echo "SPDX-License-Identifier is certainly described in all target files!"
exit 0
fi

View File

@@ -1,18 +0,0 @@
name: Check copyright year
on:
push:
branches:
- master
- develop
jobs:
check_copyright_year:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6.0.2
- run: |
if [ "$(grep Copyright COPYING | sed -e 's/.*2014-\([0-9]*\) .*/\1/g')" -ne "$(date +%Y)" ]; then
echo "Please change copyright year!"
exit 1
fi

View File

@@ -1,84 +0,0 @@
name: deploy-test-environment
on:
issue_comment:
types: [created]
workflow_dispatch:
inputs:
repository:
description: 'Repository to deploy (optional, use the repository where this workflow is stored by default)'
required: false
default: ''
branch_or_hash:
description: 'Branch or Commit hash to deploy (optional, use the branch where this workflow is stored by default)'
required: false
default: ''
wait_time:
description: 'Time to wait in seconds (optional, 1800 seconds by default)'
required: false
default: ''
jobs:
get-pr-ref:
runs-on: ubuntu-latest
if: github.event_name == 'issue_comment' && github.event.issue.pull_request && startsWith(github.event.comment.body, '/preview')
outputs:
is-allowed-user: ${{ steps.check-allowed-users.outputs.is-allowed-user }}
pr-ref: ${{ steps.get-ref.outputs.pr-ref }}
wait_time: ${{ steps.get-wait-time.outputs.wait_time }}
steps:
- name: Checkout
uses: actions/checkout@v6.0.2
- name: Check allowed users
id: check-allowed-users
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
ORG_ID: ${{ github.repository_owner_id }}
COMMENT_AUTHOR: ${{ github.event.comment.user.login }}
run: |
MEMBERSHIP_STATUS=$(curl -s -H "Authorization: Bearer $GITHUB_TOKEN" \
-H "Accept: application/vnd.github+json" \
-H "X-GitHub-Api-Version: 2022-11-28" \
"https://api.github.com/organizations/$ORG_ID/public_members/$COMMENT_AUTHOR" \
-o /dev/null -w '%{http_code}\n' -s)
if [ "$MEMBERSHIP_STATUS" -eq 204 ]; then
echo "is-allowed-user=true" > $GITHUB_OUTPUT
else
echo "is-allowed-user=false" > $GITHUB_OUTPUT
fi
- name: Get PR ref
id: get-ref
run: |
PR_REF="refs/pull/${{ github.event.issue.number }}/head"
echo "pr-ref=$PR_REF" >> $GITHUB_OUTPUT
- name: Extract wait time
id: get-wait-time
env:
COMMENT_BODY: ${{ github.event.comment.body }}
run: |
WAIT_TIME=$(echo "$COMMENT_BODY" | grep -oP '(?<=/preview\s)\d+' || echo "1800")
echo "wait_time=$WAIT_TIME" > $GITHUB_OUTPUT
deploy-test-environment-pr-comment:
needs: get-pr-ref
if: needs.get-pr-ref.outputs.is-allowed-user == 'true'
uses: joinmisskey/misskey-tga/.github/workflows/deploy-test-environment.yml@main
with:
repository: ${{ github.repository }}
branch_or_hash: ${{ needs.get-pr-ref.outputs.pr-ref }}
wait_time: ${{ needs.get-pr-ref.outputs.wait_time }}
secrets:
DISCORD_WEBHOOK_URL: ${{ secrets.DISCORD_WEBHOOK_URL }}
deploy-test-environment-wd:
if: github.event_name == 'workflow_dispatch'
uses: joinmisskey/misskey-tga/.github/workflows/deploy-test-environment.yml@main
with:
repository: ${{ inputs.repository || github.repository }}
branch_or_hash: ${{ inputs.branch_or_hash || github.ref_name }}
wait_time: ${{ inputs.wait_time || '1800' }}
secrets:
DISCORD_WEBHOOK_URL: ${{ secrets.DISCORD_WEBHOOK_URL }}

View File

@@ -6,83 +6,28 @@ on:
- develop
workflow_dispatch:
env:
REGISTRY_IMAGE: misskey/misskey
jobs:
# see https://docs.docker.com/build/ci/github-actions/multi-platform/#distribute-build-across-multiple-runners
build:
name: Build
push_to_registry:
name: Push Docker image to Docker Hub
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
platform:
- linux/amd64
- linux/arm64
if: github.repository == 'misskey-dev/misskey'
steps:
- name: Prepare
run: |
platform=${{ matrix.platform }}
echo "PLATFORM_PAIR=${platform//\//-}" >> $GITHUB_ENV
- name: Check out the repo
uses: actions/checkout@v6.0.2
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v4
uses: actions/checkout@v2
- name: Docker meta
id: meta
uses: docker/metadata-action@v3
with:
images: misskey/misskey
- name: Log in to Docker Hub
uses: docker/login-action@v4
uses: docker/login-action@v1
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
- name: Build and push by digest
id: build
uses: docker/build-push-action@v7
- name: Build and Push to Docker Hub
uses: docker/build-push-action@v2
with:
context: .
push: true
platforms: ${{ matrix.platform }}
provenance: false
tags: misskey/misskey:develop
labels: develop
cache-from: type=gha
cache-to: type=gha,mode=max
outputs: type=image,name=${{ env.REGISTRY_IMAGE }},push-by-digest=true,name-canonical=true,push=true
- name: Export digest
run: |
mkdir -p /tmp/digests
digest="${{ steps.build.outputs.digest }}"
touch "/tmp/digests/${digest#sha256:}"
- name: Upload digest
uses: actions/upload-artifact@v7
with:
name: digests-${{ env.PLATFORM_PAIR }}
path: /tmp/digests/*
if-no-files-found: error
retention-days: 1
merge:
runs-on: ubuntu-latest
needs:
- build
steps:
- name: Download digests
uses: actions/download-artifact@v8
with:
path: /tmp/digests
pattern: digests-*
merge-multiple: true
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v4
- name: Login to Docker Hub
uses: docker/login-action@v4
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
- name: Create manifest list and push
working-directory: /tmp/digests
run: |
docker buildx imagetools create --tag ${{ env.REGISTRY_IMAGE }}:develop \
$(printf '${{ env.REGISTRY_IMAGE }}@sha256:%s ' *)
- name: Inspect image
run: |
docker buildx imagetools inspect ${{ env.REGISTRY_IMAGE }}:develop

View File

@@ -5,101 +5,28 @@ on:
types: [published]
workflow_dispatch:
env:
REGISTRY_IMAGE: misskey/misskey
TAGS: |
type=edge
type=ref,event=pr
type=ref,event=branch
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
type=semver,pattern={{major}}
jobs:
# see https://docs.docker.com/build/ci/github-actions/multi-platform/#distribute-build-across-multiple-runners
build:
name: Build
push_to_registry:
name: Push Docker image to Docker Hub
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
platform:
- linux/amd64
- linux/arm64
steps:
- name: Prepare
run: |
platform=${{ matrix.platform }}
echo "PLATFORM_PAIR=${platform//\//-}" >> $GITHUB_ENV
- name: Check out the repo
uses: actions/checkout@v6.0.2
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v4
uses: actions/checkout@v2
- name: Docker meta
id: meta
uses: docker/metadata-action@v6
uses: docker/metadata-action@v3
with:
images: ${{ env.REGISTRY_IMAGE }}
tags: ${{ env.TAGS }}
images: misskey/misskey
- name: Log in to Docker Hub
uses: docker/login-action@v4
uses: docker/login-action@v1
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
- name: Build and Push to Docker Hub
id: build
uses: docker/build-push-action@v7
uses: docker/build-push-action@v2
with:
context: .
push: true
platforms: ${{ matrix.platform }}
provenance: false
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha
cache-to: type=gha,mode=max
outputs: type=image,name=${{ env.REGISTRY_IMAGE }},push-by-digest=true,name-canonical=true,push=true
- name: Export digest
run: |
mkdir -p /tmp/digests
digest="${{ steps.build.outputs.digest }}"
touch "/tmp/digests/${digest#sha256:}"
- name: Upload digest
uses: actions/upload-artifact@v7
with:
name: digests-${{ env.PLATFORM_PAIR }}
path: /tmp/digests/*
if-no-files-found: error
retention-days: 1
merge:
runs-on: ubuntu-latest
needs:
- build
steps:
- name: Download digests
uses: actions/download-artifact@v8
with:
path: /tmp/digests
pattern: digests-*
merge-multiple: true
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v4
- name: Docker meta
id: meta
uses: docker/metadata-action@v6
with:
images: ${{ env.REGISTRY_IMAGE }}
tags: ${{ env.TAGS }}
- name: Login to Docker Hub
uses: docker/login-action@v4
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
- name: Create manifest list and push
working-directory: /tmp/digests
run: |
docker buildx imagetools create $(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") \
$(printf '${{ env.REGISTRY_IMAGE }}@sha256:%s ' *)
- name: Inspect image
run: |
docker buildx imagetools inspect ${{ env.REGISTRY_IMAGE }}:${{ steps.meta.outputs.version }}

View File

@@ -1,53 +0,0 @@
---
name: Dockle
on:
push:
branches:
- master
- develop
pull_request:
jobs:
dockle:
runs-on: ubuntu-latest
env:
DOCKER_CONTENT_TRUST: 1
DOCKLE_VERSION: 0.4.15
steps:
- uses: actions/checkout@v6.0.2
- name: Download and install dockle v${{ env.DOCKLE_VERSION }}
run: |
set -eux
curl -L -o dockle.deb "https://github.com/goodwithtech/dockle/releases/download/v${DOCKLE_VERSION}/dockle_${DOCKLE_VERSION}_Linux-64bit.deb"
sudo dpkg -i dockle.deb
- name: Build web image (docker build)
run: |
set -eux
docker build -t "misskey-web:ci" .
docker image ls
- name: Mount tmpfs for Dockle tar
env:
TMPFS_SIZE: 8G
run: |
set -eux
sudo mkdir -p /mnt/dockle-tmp
sudo mount -t tmpfs -o size=${{ env.TMPFS_SIZE }} tmpfs /mnt/dockle-tmp
free -h
df -h
- name: Save image tar into tmpfs
run: |
set -eux
docker save misskey-web:ci -o /mnt/dockle-tmp/misskey-web.tar
ls -lh /mnt/dockle-tmp/misskey-web.tar
- name: Run Dockle Scan (tar input)
run: |
set -eux
dockle --exit-code 1 --input /mnt/dockle-tmp/misskey-web.tar

View File

@@ -1,67 +0,0 @@
# this name is used in report-api-diff.yml so be careful when change name
name: Get api.json from Misskey
on:
pull_request:
branches:
- master
- develop
paths:
- packages/backend/**
- .github/workflows/get-api-diff.yml
jobs:
get-from-misskey:
runs-on: ubuntu-latest
permissions:
contents: read
strategy:
matrix:
api-json-name: [api-base.json, api-head.json]
include:
- api-json-name: api-base.json
ref: ${{ github.base_ref }}
- api-json-name: api-head.json
ref: refs/pull/${{ github.event.number }}/merge
steps:
- uses: actions/checkout@v6.0.2
with:
ref: ${{ matrix.ref }}
submodules: true
- name: Setup pnpm
uses: pnpm/action-setup@v6.0.3
- name: Use Node.js
uses: actions/setup-node@v6.4.0
with:
node-version-file: '.node-version'
cache: 'pnpm'
- run: pnpm i --frozen-lockfile
- name: Check pnpm-lock.yaml
run: git diff --exit-code pnpm-lock.yaml
- name: Copy Configure
run: cp .config/example.yml .config/default.yml
- name: Build
run: pnpm build
- name: Generate API JSON
run: pnpm --filter backend generate-api-json
- name: Copy API.json
run: cp packages/backend/built/api.json ${{ matrix.api-json-name }}
- name: Upload Artifact
uses: actions/upload-artifact@v7
with:
name: api-artifact-${{ matrix.api-json-name }}
path: ${{ matrix.api-json-name }}
save-pr-number:
runs-on: ubuntu-latest
steps:
- name: Save PR number
env:
PR_NUMBER: ${{ github.event.number }}
run: |
echo "$PR_NUMBER" > ./pr_number
- uses: actions/upload-artifact@v7
with:
name: api-artifact-pr-number
path: pr_number

View File

@@ -1,87 +0,0 @@
# this name is used in report-backend-memory.yml so be careful when change name
name: Get backend memory usage
on:
pull_request:
branches:
- master
- develop
paths:
- packages/backend/**
- packages/misskey-js/**
- .github/workflows/get-backend-memory.yml
jobs:
get-memory-usage:
runs-on: ubuntu-latest
permissions:
contents: read
strategy:
matrix:
memory-json-name: [memory-base.json, memory-head.json]
include:
- memory-json-name: memory-base.json
ref: ${{ github.base_ref }}
- memory-json-name: memory-head.json
ref: refs/pull/${{ github.event.number }}/merge
services:
postgres:
image: postgres:18
ports:
- 54312:5432
env:
POSTGRES_DB: test-misskey
POSTGRES_HOST_AUTH_METHOD: trust
redis:
image: redis:8
ports:
- 56312:6379
steps:
- uses: actions/checkout@v6.0.2
with:
ref: ${{ matrix.ref }}
submodules: true
- name: Setup pnpm
uses: pnpm/action-setup@v6.0.3
- name: Use Node.js
uses: actions/setup-node@v6.4.0
with:
node-version-file: '.node-version'
cache: 'pnpm'
- run: pnpm i --frozen-lockfile
- name: Check pnpm-lock.yaml
run: git diff --exit-code pnpm-lock.yaml
- name: Copy Configure
run: cp .github/misskey/test.yml .config/default.yml
- name: Compile Configure
run: pnpm compile-config
- name: Build
run: pnpm build
- name: Run migrations
run: pnpm --filter backend migrate
- name: Measure memory usage
run: |
# Start the server and measure memory usage
node packages/backend/scripts/measure-memory.mjs > ${{ matrix.memory-json-name }}
- name: Upload Artifact
uses: actions/upload-artifact@v7
with:
name: memory-artifact-${{ matrix.memory-json-name }}
path: ${{ matrix.memory-json-name }}
save-pr-number:
runs-on: ubuntu-latest
permissions: {}
steps:
- name: Save PR number
env:
PR_NUMBER: ${{ github.event.number }}
run: |
echo "$PR_NUMBER" > ./pr_number
- uses: actions/upload-artifact@v7
with:
name: memory-artifact-pr-number
path: pr_number

View File

@@ -11,6 +11,6 @@ jobs:
pull-requests: write
runs-on: ubuntu-latest
steps:
- uses: actions/labeler@v6
- uses: actions/labeler@v4
with:
repo-token: "${{ secrets.GITHUB_TOKEN }}"

View File

@@ -1,121 +1,39 @@
name: Lint
on:
push:
branches:
- master
- develop
paths:
- packages/backend/**
- packages/frontend/**
- packages/frontend-shared/**
- packages/frontend-builder/**
- packages/frontend-embed/**
- packages/icons-subsetter/**
- packages/sw/**
- packages/misskey-js/**
- packages/misskey-bubble-game/**
- packages/misskey-reversi/**
- packages/misskey-world/**
- packages/frontend-misskey-world-engine/**
- packages/shared/eslint.config.js
- .github/workflows/lint.yml
pull_request:
paths:
- packages/backend/**
- packages/frontend/**
- packages/frontend-shared/**
- packages/frontend-builder/**
- packages/frontend-embed/**
- packages/icons-subsetter/**
- packages/sw/**
- packages/misskey-js/**
- packages/misskey-bubble-game/**
- packages/misskey-reversi/**
- packages/misskey-world/**
- packages/frontend-misskey-world-engine/**
- packages/shared/eslint.config.js
- .github/workflows/lint.yml
jobs:
pnpm_install:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6.0.2
with:
fetch-depth: 0
submodules: true
- name: Setup pnpm
uses: pnpm/action-setup@v6.0.3
- uses: actions/setup-node@v6.4.0
with:
node-version-file: '.node-version'
cache: 'pnpm'
- run: pnpm i --frozen-lockfile
lint:
needs: [pnpm_install]
runs-on: ubuntu-latest
continue-on-error: true
strategy:
matrix:
workspace:
- backend
- frontend
- frontend-shared
- frontend-builder
- frontend-embed
- icons-subsetter
- sw
- misskey-js
- misskey-bubble-game
- misskey-reversi
- misskey-world
- frontend-misskey-world-engine
env:
eslint-cache-version: v1
eslint-cache-path: ${{ github.workspace }}/node_modules/.cache/eslint-${{ matrix.workspace }}
steps:
- uses: actions/checkout@v6.0.2
with:
fetch-depth: 0
submodules: true
- name: Setup pnpm
uses: pnpm/action-setup@v6.0.3
- uses: actions/setup-node@v6.4.0
with:
node-version-file: '.node-version'
cache: 'pnpm'
- run: pnpm i --frozen-lockfile
- name: Restore eslint cache
uses: actions/cache@v5.0.5
with:
path: ${{ env.eslint-cache-path }}
key: eslint-${{ env.eslint-cache-version }}-${{ matrix.workspace }}-${{ hashFiles('**/pnpm-lock.yaml') }}-${{ github.ref_name }}-${{ github.sha }}
restore-keys: eslint-${{ env.eslint-cache-version }}-${{ matrix.workspace }}-${{ hashFiles('**/pnpm-lock.yaml') }}-
- run: pnpm --filter ${{ matrix.workspace }} run eslint --cache --cache-location ${{ env.eslint-cache-path }} --cache-strategy content
typecheck:
needs: [pnpm_install]
runs-on: ubuntu-latest
continue-on-error: true
strategy:
matrix:
workspace:
- backend
- frontend
- sw
- misskey-js
steps:
- uses: actions/checkout@v6.0.2
with:
fetch-depth: 0
submodules: true
- name: Setup pnpm
uses: pnpm/action-setup@v6.0.3
- uses: actions/setup-node@v6.4.0
with:
node-version-file: '.node-version'
cache: 'pnpm'
- run: pnpm i --frozen-lockfile
- run: pnpm --filter "${{ matrix.workspace }}^..." run build
- run: pnpm --filter ${{ matrix.workspace }} run typecheck
name: Lint
on:
push:
branches:
- master
- develop
pull_request:
jobs:
backend:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
with:
submodules: true
- uses: actions/setup-node@v3
with:
node-version: 18.x
cache: 'yarn'
cache-dependency-path: |
packages/backend/yarn.lock
- run: yarn install
- run: yarn --cwd ./packages/backend lint
client:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
with:
submodules: true
- uses: actions/setup-node@v3
with:
node-version: 18.x
cache: 'yarn'
cache-dependency-path: |
packages/client/yarn.lock
- run: yarn install
- run: yarn --cwd ./packages/client lint

View File

@@ -1,33 +0,0 @@
name: Lint
on:
push:
paths:
- packages/i18n/**
- locales/**
- .github/workflows/locale.yml
pull_request:
paths:
- packages/i18n/**
- locales/**
- .github/workflows/locale.yml
jobs:
locale_verify:
runs-on: ubuntu-latest
continue-on-error: true
steps:
- uses: actions/checkout@v6.0.2
with:
fetch-depth: 0
submodules: true
- name: Setup pnpm
uses: pnpm/action-setup@v6.0.3
- uses: actions/setup-node@v6.4.0
with:
node-version-file: ".node-version"
cache: "pnpm"
- run: pnpm i --frozen-lockfile
- run: pnpm --filter i18n build
- name: Verify Locales
working-directory: ./packages/i18n
run: pnpm run verify

36
.github/workflows/ok-to-test.yml vendored Normal file
View File

@@ -0,0 +1,36 @@
# If someone with write access comments "/ok-to-test" on a pull request, emit a repository_dispatch event
name: Ok To Test
on:
issue_comment:
types: [created]
jobs:
ok-to-test:
runs-on: ubuntu-latest
# Only run for PRs, not issue comments
if: ${{ github.event.issue.pull_request }}
steps:
# Generate a GitHub App installation access token from an App ID and private key
# To create a new GitHub App:
# https://developer.github.com/apps/building-github-apps/creating-a-github-app/
# See app.yml for an example app manifest
- name: Generate token
id: generate_token
uses: tibdex/github-app-token@v1
with:
app_id: ${{ secrets.DEPLOYBOT_APP_ID }}
private_key: ${{ secrets.DEPLOYBOT_PRIVATE_KEY }}
- name: Slash Command Dispatch
uses: peter-evans/slash-command-dispatch@v1
env:
TOKEN: ${{ steps.generate_token.outputs.token }}
with:
token: ${{ env.TOKEN }} # GitHub App installation access token
# token: ${{ secrets.PERSONAL_ACCESS_TOKEN }} # PAT or OAuth token will also work
reaction-token: ${{ secrets.GITHUB_TOKEN }}
issue-type: pull-request
commands: deploy
named-args: true
permission: write

View File

@@ -1,41 +0,0 @@
name: On Release Created (Publish misskey-js)
on:
release:
types: [created]
workflow_dispatch:
jobs:
publish-misskey-js:
name: Publish misskey-js
runs-on: ubuntu-latest
permissions:
contents: read
id-token: write
steps:
- uses: actions/checkout@v6.0.2
with:
submodules: true
- name: Setup pnpm
uses: pnpm/action-setup@v6.0.3
- name: Use Node.js
uses: actions/setup-node@v6.4.0
with:
node-version-file: '.node-version'
cache: 'pnpm'
# see https://docs.github.com/actions/use-cases-and-examples/publishing-packages/publishing-nodejs-packages#publishing-packages-to-the-npm-registry
registry-url: 'https://registry.npmjs.org'
# Ensure npm 11.5.1 or later is installed
- name: Update npm
run: npm install -g npm@latest
- name: Publish package
run: |
pnpm i --frozen-lockfile
pnpm build
pnpm --filter misskey-js publish --access public --no-git-checks --provenance
env:
NODE_AUTH_TOKEN: ${{ secrets.NODE_AUTH_TOKEN }}
NPM_TOKEN: ${{ secrets.NODE_AUTH_TOKEN }}

95
.github/workflows/pr-preview-deploy.yml vendored Normal file
View File

@@ -0,0 +1,95 @@
# Run secret-dependent integration tests only after /deploy approval
on:
pull_request:
types: [opened, reopened, synchronize]
repository_dispatch:
types: [deploy-command]
name: Deploy preview environment
jobs:
# Repo owner has commented /deploy on a (fork-based) pull request
deploy-preview-environment:
runs-on: ubuntu-latest
if:
github.event_name == 'repository_dispatch' &&
github.event.client_payload.slash_command.sha != '' &&
contains(github.event.client_payload.pull_request.head.sha, github.event.client_payload.slash_command.sha)
steps:
- uses: actions/github-script@v5
id: check-id
env:
number: ${{ github.event.client_payload.pull_request.number }}
job: ${{ github.job }}
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
result-encoding: string
script: |
const { data: pull } = await github.rest.pulls.get({
...context.repo,
pull_number: process.env.number
});
const ref = pull.head.sha;
const { data: checks } = await github.rest.checks.listForRef({
...context.repo,
ref
});
const check = checks.check_runs.filter(c => c.name === process.env.job);
return check[0].id;
- uses: actions/github-script@v5
env:
check_id: ${{ steps.check-id.outputs.result }}
details_url: ${{ github.server_url }}/${{ github.repository }}/runs/${{ github.run_id }}
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
await github.rest.checks.update({
...context.repo,
check_run_id: process.env.check_id,
status: 'in_progress',
details_url: process.env.details_url
});
# Check out merge commit
- name: Fork based /deploy checkout
uses: actions/checkout@v2
with:
ref: 'refs/pull/${{ github.event.client_payload.pull_request.number }}/merge'
# <insert integration tests needing secrets>
- name: Context
uses: okteto/context@latest
with:
token: ${{ secrets.OKTETO_TOKEN }}
- name: Deploy preview environment
uses: ikuradon/deploy-preview@latest
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
name: pr-${{ github.event.client_payload.pull_request.number }}-syuilo
timeout: 15m
# Update check run called "integration-fork"
- uses: actions/github-script@v5
id: update-check-run
if: ${{ always() }}
env:
# Conveniently, job.status maps to https://developer.github.com/v3/checks/runs/#update-a-check-run
conclusion: ${{ job.status }}
check_id: ${{ steps.check-id.outputs.result }}
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
const { data: result } = await github.rest.checks.update({
...context.repo,
check_run_id: process.env.check_id,
status: 'completed',
conclusion: process.env.conclusion
});
return result;

View File

@@ -0,0 +1,21 @@
# file: .github/workflows/preview-closed.yaml
on:
pull_request:
types:
- closed
name: Destroy preview environment
jobs:
destroy-preview-environment:
runs-on: ubuntu-latest
steps:
- name: Context
uses: okteto/context@latest
with:
token: ${{ secrets.OKTETO_TOKEN }}
- name: Destroy preview environment
uses: okteto/destroy-preview@latest
with:
name: pr-${{ github.event.number }}-syuilo

View File

@@ -1,48 +0,0 @@
name: "Release Manager: sync changelog with PR"
on:
push:
branches:
- develop
paths:
- 'CHANGELOG.md'
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
permissions:
contents: write
issues: write
pull-requests: write
jobs:
edit:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
# headが$GITHUB_REF_NAME, baseが$STABLE_BRANCHかつopenのPRを1つ取得
- name: Get PR
run: |
echo "pr_number=$(gh pr list --limit 1 --search "head:$GITHUB_REF_NAME base:$STABLE_BRANCH is:open" --json number --jq '.[] | .number')" >> $GITHUB_OUTPUT
id: get_pr
env:
STABLE_BRANCH: ${{ vars.STABLE_BRANCH }}
- name: Get target version
if: steps.get_pr.outputs.pr_number != ''
uses: misskey-dev/release-manager-actions/.github/actions/get-target-version@v2
id: v
# CHANGELOG.mdの内容を取得
- name: Get changelog
if: steps.get_pr.outputs.pr_number != ''
uses: misskey-dev/release-manager-actions/.github/actions/get-changelog@v2
with:
version: ${{ steps.v.outputs.target_version }}
id: changelog
# PRのnotesを更新
- name: Update PR
if: steps.get_pr.outputs.pr_number != ''
run: |
gh pr edit "$PR_NUMBER" --body "$CHANGELOG"
env:
PR_NUMBER: ${{ steps.get_pr.outputs.pr_number }}
CHANGELOG: ${{ steps.changelog.outputs.changelog }}

View File

@@ -1,136 +0,0 @@
name: "Release Manager [Dispatch]"
on:
workflow_dispatch:
inputs:
## Specify the type of the next release.
#version_increment_type:
# type: choice
# description: 'VERSION INCREMENT TYPE'
# default: 'patch'
# required: false
# options:
# - 'major'
# - 'minor'
# - 'patch'
merge:
type: boolean
description: 'MERGE RELEASE BRANCH TO MAIN'
default: false
start-rc:
type: boolean
description: 'Start Release Candidate'
default: false
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
permissions:
contents: write
issues: write
pull-requests: write
jobs:
get-pr:
runs-on: ubuntu-latest
outputs:
pr_number: ${{ steps.get_pr.outputs.pr_number }}
steps:
- uses: actions/checkout@v6
# headが$GITHUB_REF_NAME, baseが$STABLE_BRANCHかつopenのPRを1つ取得
- name: Get PRs
run: |
echo "pr_number=$(gh pr list --limit 1 --search "head:$GITHUB_REF_NAME base:$STABLE_BRANCH is:open" --json number --jq '.[] | .number')" >> $GITHUB_OUTPUT
id: get_pr
env:
STABLE_BRANCH: ${{ vars.STABLE_BRANCH }}
merge:
uses: misskey-dev/release-manager-actions/.github/workflows/merge.yml@v2
needs: get-pr
if: ${{ needs.get-pr.outputs.pr_number != '' && inputs.merge == true }}
with:
pr_number: ${{ needs.get-pr.outputs.pr_number }}
user: 'github-actions[bot]'
package_jsons_to_rewrite: ${{ vars.PACKAGE_JSONS_TO_REWRITE }}
# Text to prepend to the changelog
# The first line must be `## Unreleased`
changes_template: |
## Unreleased
### General
-
### Client
-
### Server
-
use_external_app_to_release: ${{ vars.USE_RELEASE_APP == 'true' }}
indent: ${{ vars.INDENT }}
secrets:
RELEASE_APP_ID: ${{ secrets.RELEASE_APP_ID }}
RELEASE_APP_PRIVATE_KEY: ${{ secrets.RELEASE_APP_PRIVATE_KEY }}
create-prerelease:
uses: misskey-dev/release-manager-actions/.github/workflows/create-prerelease.yml@v2
needs: get-pr
if: ${{ needs.get-pr.outputs.pr_number != '' && inputs.merge != true }}
with:
pr_number: ${{ needs.get-pr.outputs.pr_number }}
user: 'github-actions[bot]'
package_jsons_to_rewrite: ${{ vars.PACKAGE_JSONS_TO_REWRITE }}
use_external_app_to_release: ${{ vars.USE_RELEASE_APP == 'true' }}
indent: ${{ vars.INDENT }}
draft_prerelease_channel: alpha
ready_start_prerelease_channel: beta
prerelease_channel: ${{ inputs.start-rc && 'rc' || '' }}
reset_number_on_channel_change: true
secrets:
RELEASE_APP_ID: ${{ secrets.RELEASE_APP_ID }}
RELEASE_APP_PRIVATE_KEY: ${{ secrets.RELEASE_APP_PRIVATE_KEY }}
create-target:
uses: misskey-dev/release-manager-actions/.github/workflows/create-target.yml@v2
needs: get-pr
if: ${{ needs.get-pr.outputs.pr_number == '' }}
with:
user: 'github-actions[bot]'
# The script for version increment.
# process.env.CURRENT_VERSION: The current version.
#
# Misskey calender versioning (yyyy.MM.patch) example
version_increment_script: |
const now = new Date();
const year = now.toLocaleDateString('en-US', { year: 'numeric', timeZone: 'Asia/Tokyo' });
const month = now.toLocaleDateString('en-US', { month: 'numeric', timeZone: 'Asia/Tokyo' });
const [major, minor, _patch] = process.env.CURRENT_VERSION.split('.');
const patch = Number(_patch.split('-')[0]);
if (Number.isNaN(patch)) {
console.error('Invalid patch version', year, month, process.env.CURRENT_VERSION, major, minor, _patch);
throw new Error('Invalid patch version');
}
if (year !== major || month !== minor) {
return `${year}.${month}.0`;
} else {
return `${major}.${minor}.${patch + 1}`;
}
##Semver example
#version_increment_script: |
# const [major, minor, patch] = process.env.CURRENT_VERSION.split('.');
# if ("${{ inputs.version_increment_type }}" === "major") {
# return `${Number(major) + 1}.0.0`;
# } else if ("${{ inputs.version_increment_type }}" === "minor") {
# return `${major}.${Number(minor) + 1}.0`;
# } else {
# return `${major}.${minor}.${Number(patch) + 1}`;
# }
package_jsons_to_rewrite: ${{ vars.PACKAGE_JSONS_TO_REWRITE }}
use_external_app_to_release: ${{ vars.USE_RELEASE_APP == 'true' }}
indent: ${{ vars.INDENT }}
stable_branch: ${{ vars.STABLE_BRANCH }}
draft_prerelease_channel: alpha
secrets:
RELEASE_APP_ID: ${{ secrets.RELEASE_APP_ID }}
RELEASE_APP_PRIVATE_KEY: ${{ secrets.RELEASE_APP_PRIVATE_KEY }}

View File

@@ -1,104 +0,0 @@
name: Report API Diff
on:
workflow_run:
types: [completed]
workflows:
- Get api.json from Misskey # get-api-diff.yml
jobs:
compare-diff:
runs-on: ubuntu-latest
if: ${{ github.event.workflow_run.conclusion == 'success' }}
permissions:
pull-requests: write
# api-artifact
steps:
- name: Download artifact
uses: actions/github-script@v9
with:
script: |
const fs = require('fs');
let allArtifacts = await github.rest.actions.listWorkflowRunArtifacts({
owner: context.repo.owner,
repo: context.repo.repo,
run_id: context.payload.workflow_run.id,
});
let matchArtifacts = allArtifacts.data.artifacts.filter((artifact) => {
return artifact.name.startsWith("api-artifact-") || artifact.name == "api-artifact"
});
await Promise.all(matchArtifacts.map(async (artifact) => {
let download = await github.rest.actions.downloadArtifact({
owner: context.repo.owner,
repo: context.repo.repo,
artifact_id: artifact.id,
archive_format: 'zip',
});
await fs.promises.writeFile(`${process.env.GITHUB_WORKSPACE}/${artifact.name}.zip`, Buffer.from(download.data));
}));
- name: Extract all artifacts
run: |
find . -mindepth 1 -maxdepth 1 -type f -name '*.zip' -exec unzip {} -d artifacts ';'
ls -la
- name: Load PR Number
id: load-pr-num
run: echo "pr-number=$(cat artifacts/pr_number)" >> "$GITHUB_OUTPUT"
- name: Output base
run: cat ./artifacts/api-base.json
- name: Output head
run: cat ./artifacts/api-head.json
- name: Arrange json files
run: |
jq '.' ./artifacts/api-base.json > ./api-base.json
jq '.' ./artifacts/api-head.json > ./api-head.json
- name: Get diff of 2 files
run: diff -u --label=base --label=head ./api-base.json ./api-head.json | cat > api.json.diff
- name: Get full diff
run: diff --label=base --label=head --new-line-format='+%L' --old-line-format='-%L' --unchanged-line-format=' %L' ./api-base.json ./api-head.json | cat > api-full.json.diff
- name: Echo full diff
run: cat ./api-full.json.diff
- name: Upload full diff to Artifact
uses: actions/upload-artifact@v7
with:
name: api-artifact
path: |
api-full.json.diff
api-base.json
api-head.json
- id: out-diff
name: Build diff Comment
run: |
HEADER="このPRによるapi.jsonの差分"
FOOTER="[Get diff files from Workflow Page](https://github.com/${GITHUB_REPOSITORY}/actions/runs/${GITHUB_RUN_ID})"
DIFF_BYTES="$(stat ./api.json.diff -c '%s' | tr -d '\n')"
echo "$HEADER" > ./output.md
if (( "$DIFF_BYTES" <= 1 )); then
echo '差分はありません。' >> ./output.md
else
echo '<details>' >> ./output.md
echo '<summary>差分はこちら</summary>' >> ./output.md
echo >> ./output.md
echo '```diff' >> ./output.md
cat ./api.json.diff >> ./output.md
echo '```' >> ./output.md
echo '</details>' >> .output.md
fi
echo "$FOOTER" >> ./output.md
- uses: thollander/actions-comment-pull-request@v3
with:
pr-number: ${{ steps.load-pr-num.outputs.pr-number }}
comment-tag: show_diff
file-path: ./output.md
- name: Tell error to PR
uses: thollander/actions-comment-pull-request@v3
if: failure() && steps.load-pr-num.outputs.pr-number
with:
pr-number: ${{ steps.load-pr-num.outputs.pr-number }}
comment-tag: show_diff_error
message: |
api.jsonの差分作成中にエラーが発生しました。詳細は[Workflowのログ](https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }})を確認してください。

View File

@@ -1,177 +0,0 @@
name: Report backend memory
on:
workflow_run:
types: [completed]
workflows:
- Get backend memory usage # get-backend-memory.yml
jobs:
compare-memory:
runs-on: ubuntu-latest
if: ${{ github.event.workflow_run.conclusion == 'success' }}
permissions:
pull-requests: write
steps:
- name: Download artifact
uses: actions/github-script@v9
with:
script: |
const fs = require('fs');
let allArtifacts = await github.rest.actions.listWorkflowRunArtifacts({
owner: context.repo.owner,
repo: context.repo.repo,
run_id: context.payload.workflow_run.id,
});
let matchArtifacts = allArtifacts.data.artifacts.filter((artifact) => {
return artifact.name.startsWith("memory-artifact-") || artifact.name == "memory-artifact"
});
await Promise.all(matchArtifacts.map(async (artifact) => {
let download = await github.rest.actions.downloadArtifact({
owner: context.repo.owner,
repo: context.repo.repo,
artifact_id: artifact.id,
archive_format: 'zip',
});
await fs.promises.writeFile(`${process.env.GITHUB_WORKSPACE}/${artifact.name}.zip`, Buffer.from(download.data));
}));
- name: Extract all artifacts
run: |
find . -mindepth 1 -maxdepth 1 -type f -name '*.zip' -exec unzip {} -d artifacts ';'
ls -la artifacts/
- name: Load PR Number
id: load-pr-num
run: echo "pr-number=$(cat artifacts/pr_number)" >> "$GITHUB_OUTPUT"
- name: Output base
run: cat ./artifacts/memory-base.json
- name: Output head
run: cat ./artifacts/memory-head.json
- name: Compare memory usage
id: compare
run: |
BASE_MEMORY=$(cat ./artifacts/memory-base.json)
HEAD_MEMORY=$(cat ./artifacts/memory-head.json)
variation() {
calc() {
BASE=$(echo "$BASE_MEMORY" | jq -r ".${1}.${2} // 0")
HEAD=$(echo "$HEAD_MEMORY" | jq -r ".${1}.${2} // 0")
DIFF=$((HEAD - BASE))
if [ "$BASE" -gt 0 ]; then
DIFF_PERCENT=$(echo "scale=2; ($DIFF * 100) / $BASE" | bc)
else
DIFF_PERCENT=0
fi
# Convert KB to MB for readability
BASE_MB=$(echo "scale=2; $BASE / 1024" | bc)
HEAD_MB=$(echo "scale=2; $HEAD / 1024" | bc)
DIFF_MB=$(echo "scale=2; $DIFF / 1024" | bc)
JSON=$(jq -c -n \
--argjson base "$BASE_MB" \
--argjson head "$HEAD_MB" \
--argjson diff "$DIFF_MB" \
--argjson diff_percent "$DIFF_PERCENT" \
'{base: $base, head: $head, diff: $diff, diff_percent: $diff_percent}')
echo "$JSON"
}
JSON=$(jq -c -n \
--argjson VmRSS "$(calc $1 VmRSS)" \
--argjson VmHWM "$(calc $1 VmHWM)" \
--argjson VmSize "$(calc $1 VmSize)" \
--argjson VmData "$(calc $1 VmData)" \
'{VmRSS: $VmRSS, VmHWM: $VmHWM, VmSize: $VmSize, VmData: $VmData}')
echo "$JSON"
}
JSON=$(jq -c -n \
--argjson beforeGc "$(variation beforeGc)" \
--argjson afterGc "$(variation afterGc)" \
--argjson afterRequest "$(variation afterRequest)" \
'{beforeGc: $beforeGc, afterGc: $afterGc, afterRequest: $afterRequest}')
echo "res=$JSON" >> "$GITHUB_OUTPUT"
- id: build-comment
name: Build memory comment
env:
RES: ${{ steps.compare.outputs.res }}
run: |
HEADER="## Backend memory usage comparison"
FOOTER="[See workflow logs for details](https://github.com/${GITHUB_REPOSITORY}/actions/runs/${GITHUB_RUN_ID})"
echo "$HEADER" > ./output.md
echo >> ./output.md
table() {
echo "| Metric | base (MB) | head (MB) | Diff (MB) | Diff (%) |" >> ./output.md
echo "|--------|------:|------:|------:|------:|" >> ./output.md
line() {
METRIC=$2
BASE=$(echo "$RES" | jq -r ".${1}.${2}.base")
HEAD=$(echo "$RES" | jq -r ".${1}.${2}.head")
DIFF=$(echo "$RES" | jq -r ".${1}.${2}.diff")
DIFF_PERCENT=$(echo "$RES" | jq -r ".${1}.${2}.diff_percent")
if (( $(echo "$DIFF_PERCENT > 0" | bc -l) )); then
DIFF="+$DIFF"
DIFF_PERCENT="+$DIFF_PERCENT"
fi
# highlight VmRSS
if [ "$2" = "VmRSS" ]; then
METRIC="**${METRIC}**"
BASE="**${BASE}**"
HEAD="**${HEAD}**"
DIFF="**${DIFF}**"
DIFF_PERCENT="**${DIFF_PERCENT}**"
fi
echo "| ${METRIC} | ${BASE} MB | ${HEAD} MB | ${DIFF} MB | ${DIFF_PERCENT}% |" >> ./output.md
}
line $1 VmRSS
line $1 VmHWM
line $1 VmSize
line $1 VmData
}
echo "### Before GC" >> ./output.md
table beforeGc
echo >> ./output.md
echo "### After GC" >> ./output.md
table afterGc
echo >> ./output.md
echo "### After Request" >> ./output.md
table afterRequest
echo >> ./output.md
# Determine if this is a significant change (more than 5% increase)
if [ "$(echo "$RES" | jq -r '.afterGc.VmRSS.diff_percent | tonumber > 5')" = "true" ]; then
echo "⚠️ **Warning**: Memory usage has increased by more than 5%. Please verify this is not an unintended change." >> ./output.md
echo >> ./output.md
fi
echo "$FOOTER" >> ./output.md
- uses: thollander/actions-comment-pull-request@v3
with:
pr-number: ${{ steps.load-pr-num.outputs.pr-number }}
comment-tag: show_memory_diff
file-path: ./output.md
- name: Tell error to PR
uses: thollander/actions-comment-pull-request@v3
if: failure() && steps.load-pr-num.outputs.pr-number
with:
pr-number: ${{ steps.load-pr-num.outputs.pr-number }}
comment-tag: show_memory_diff_error
message: |
An error occurred while comparing backend memory usage. See [workflow logs](https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}) for details.

View File

@@ -1,51 +0,0 @@
name: Request release review
on:
issue_comment:
types: [created]
jobs:
reply:
if: github.event.comment.body == '/request-release-review'
runs-on: ubuntu-latest
permissions:
contents: read
issues: write
pull-requests: write
steps:
- name: Reply
uses: actions/github-script@v9
with:
script: |
const body = `To dev team (@misskey-dev/dev):
リリースが提案されています :rocket:
GOの場合はapprove、NO GOの場合はその旨コメントをお願いいたします。
判断にあたって考慮すべき観点は、
- やり残したことはないか?
- CHANGELOGは過不足ないか
- バージョンに問題はないか?(月跨いでいるのに更新忘れているなど)
- 再考すべき仕様・実装はないか?
- ベータ版を検証したサーバーから不具合の報告等は上がってないか?
- (セキュリティの修正や重要なバグ修正などのため)リリースを急いだ方が良いか?そうではないか?
- Actionsが落ちていないか
などが挙げられます。
ご協力ありがとうございます :sparkles:
`
const issue_number = context.payload.issue ? context.payload.issue.number : (context.payload.pull_request && context.payload.pull_request.number)
if (!issue_number) {
console.log('No issue or PR number found in payload; skipping')
} else {
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number,
body,
})
}

View File

@@ -1,108 +0,0 @@
name: Storybook
on:
push:
branches:
- master
- develop
pull_request_target:
branches-ignore:
# Since pull requests targets master mostly is the "develop" branch.
# Storybook CI is checked on the "push" event of "develop" branch so it would cause a duplicate build.
# This is a waste of chromatic build quota, so we don't run storybook CI on pull requests targets master.
- master
jobs:
build:
# Chromatic is not likely to be available for fork repositories, so we disable for fork repositories.
if: github.repository == 'misskey-dev/misskey'
runs-on: ubuntu-latest
env:
NODE_OPTIONS: "--max_old_space_size=7168"
steps:
- uses: actions/checkout@v6.0.2
if: github.event_name != 'pull_request_target'
with:
fetch-depth: 0
submodules: true
- uses: actions/checkout@v6.0.2
if: github.event_name == 'pull_request_target'
with:
fetch-depth: 0
submodules: true
ref: "refs/pull/${{ github.event.number }}/merge"
- name: Checkout actual HEAD
if: github.event_name == 'pull_request_target'
run: git checkout "$(git rev-list --parents -n1 HEAD | cut -d" " -f3)"
- name: Setup pnpm
uses: pnpm/action-setup@v6.0.3
- name: Use Node.js
uses: actions/setup-node@v6.4.0
with:
node-version-file: '.node-version'
cache: 'pnpm'
- run: pnpm i --frozen-lockfile
- name: Check pnpm-lock.yaml
run: git diff --exit-code pnpm-lock.yaml
- name: Build dependent packages
run: pnpm -F misskey-js -F misskey-bubble-game -F misskey-reversi build
- name: Build storybook
run: pnpm --filter frontend build-storybook
- name: Publish to Chromatic
if: github.event_name != 'pull_request_target' && github.ref == 'refs/heads/master'
run: pnpm --filter frontend chromatic --exit-once-uploaded -d storybook-static
env:
CHROMATIC_PROJECT_TOKEN: ${{ secrets.CHROMATIC_PROJECT_TOKEN }}
- name: Publish to Chromatic
if: github.event_name != 'pull_request_target' && github.ref != 'refs/heads/master'
id: chromatic_push
run: |
DIFF="${{ github.event.before }} HEAD"
if [ "$DIFF" = "0000000000000000000000000000000000000000 HEAD" ]; then
DIFF="HEAD"
fi
CHROMATIC_PARAMETER="$(node packages/frontend/.storybook/changes.js $(git diff-tree --no-commit-id --name-only -r $(echo "$DIFF") | xargs))"
if [ "$CHROMATIC_PARAMETER" = " --skip" ]; then
echo "skip=true" >> $GITHUB_OUTPUT
fi
if pnpm --filter frontend chromatic -d storybook-static $(echo "$CHROMATIC_PARAMETER"); then
echo "success=true" >> $GITHUB_OUTPUT
else
echo "success=false" >> $GITHUB_OUTPUT
fi
env:
CHROMATIC_PROJECT_TOKEN: ${{ secrets.CHROMATIC_PROJECT_TOKEN }}
- name: Publish to Chromatic
if: github.event_name == 'pull_request_target'
id: chromatic_pull_request
run: |
CHROMATIC_PARAMETER="$(node packages/frontend/.storybook/changes.js $(git diff --name-only origin/${GITHUB_BASE_REF}...origin/${GITHUB_HEAD_REF} | xargs))"
if [ "$CHROMATIC_PARAMETER" = " --skip" ]; then
echo "skip=true" >> $GITHUB_OUTPUT
fi
BRANCH="${{ github.event.pull_request.head.user.login }}:$GITHUB_HEAD_REF"
if [ "$BRANCH" = "misskey-dev:$GITHUB_HEAD_REF" ]; then
BRANCH="$GITHUB_HEAD_REF"
fi
pnpm --filter frontend chromatic --exit-once-uploaded -d storybook-static --branch-name "$BRANCH" $(echo "$CHROMATIC_PARAMETER")
env:
CHROMATIC_PROJECT_TOKEN: ${{ secrets.CHROMATIC_PROJECT_TOKEN }}
- name: Notify that Chromatic detects changes
uses: actions/github-script@v9
if: github.event_name != 'pull_request_target' && steps.chromatic_push.outputs.success == 'false'
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
github.rest.repos.createCommitComment({
owner: context.repo.owner,
repo: context.repo.repo,
commit_sha: context.sha,
body: 'Chromatic detects changes. Please [review the changes on Chromatic](https://www.chromatic.com/builds?appId=6428f7d7b962f0b79f97d6e4).'
})
- name: Upload Artifacts
uses: actions/upload-artifact@v7
with:
name: storybook
path: packages/frontend/storybook-static

View File

@@ -1,206 +0,0 @@
name: Test (backend)
on:
push:
branches:
- master
- develop
paths:
- packages/backend/**
# for permissions
- packages/misskey-js/**
- .github/workflows/test-backend.yml
- .github/misskey/test.yml
pull_request:
paths:
- packages/backend/**
# for permissions
- packages/misskey-js/**
- .github/workflows/test-backend.yml
- .github/misskey/test.yml
workflow_dispatch:
inputs:
force_ffmpeg_cache_update:
description: 'Force update ffmpeg cache'
required: false
default: false
type: boolean
jobs:
unit:
name: Unit tests (backend)
runs-on: ubuntu-latest
strategy:
matrix:
node-version-file:
- .node-version
- .github/min.node-version
services:
postgres:
image: postgres:18
ports:
- 54312:5432
env:
POSTGRES_DB: test-misskey
POSTGRES_HOST_AUTH_METHOD: trust
redis:
image: redis:8
ports:
- 56312:6379
meilisearch:
image: getmeili/meilisearch:v1.42.1
ports:
- 57712:7700
env:
MEILI_NO_ANALYTICS: true
MEILI_ENV: development
steps:
- uses: actions/checkout@v6.0.2
with:
submodules: true
- name: Setup pnpm
uses: pnpm/action-setup@v6.0.3
- name: Get current date
id: current-date
run: echo "today=$(date +'%Y-%m-%d')" >> $GITHUB_OUTPUT
- name: Setup and Restore ffmpeg/ffprobe Cache
id: cache-ffmpeg
uses: actions/cache@v5
with:
path: |
/usr/local/bin/ffmpeg
/usr/local/bin/ffprobe
# daily cache
key: ${{ runner.os }}-ffmpeg-${{ steps.current-date.outputs.today }}
restore-keys: |
${{ runner.os }}-ffmpeg-${{ steps.current-date.outputs.today }}
- name: Install FFmpeg
if: steps.cache-ffmpeg.outputs.cache-hit != 'true' || github.event.inputs.force_ffmpeg_cache_update == true
run: |
for i in {1..3}; do
echo "Attempt $i: Installing FFmpeg..."
curl -s -L https://johnvansickle.com/ffmpeg/releases/ffmpeg-release-amd64-static.tar.xz -o ffmpeg.tar.xz && \
tar -xf ffmpeg.tar.xz && \
mv ffmpeg-*-static/ffmpeg /usr/local/bin/ && \
mv ffmpeg-*-static/ffprobe /usr/local/bin/ && \
rm -rf ffmpeg.tar.xz ffmpeg-*-static/ && \
break || sleep 10
if [ $i -eq 3 ]; then
echo "Failed to install FFmpeg after 3 attempts"
exit 1
fi
done
- name: Use Node.js
uses: actions/setup-node@v6.4.0
with:
node-version-file: ${{ matrix.node-version-file }}
cache: 'pnpm'
- run: pnpm i --frozen-lockfile
- name: Check pnpm-lock.yaml
run: git diff --exit-code pnpm-lock.yaml
- name: Copy Configure
run: cp .github/misskey/test.yml .config
- name: Build
run: pnpm build
- name: Test
run: pnpm --filter backend test-and-coverage
- name: Upload to Codecov
uses: codecov/codecov-action@v6
with:
token: ${{ secrets.CODECOV_TOKEN }}
files: ./packages/backend/coverage/coverage-final.json
e2e:
name: E2E tests (backend)
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
node-version-file:
- .node-version
- .github/min.node-version
services:
postgres:
image: postgres:18
ports:
- 54312:5432
env:
POSTGRES_DB: test-misskey
POSTGRES_HOST_AUTH_METHOD: trust
redis:
image: redis:8
ports:
- 56312:6379
steps:
- uses: actions/checkout@v6.0.2
with:
submodules: true
- name: Setup pnpm
uses: pnpm/action-setup@v6.0.3
- name: Use Node.js
uses: actions/setup-node@v6.4.0
with:
node-version-file: ${{ matrix.node-version-file }}
cache: 'pnpm'
- run: pnpm i --frozen-lockfile
- name: Check pnpm-lock.yaml
run: git diff --exit-code pnpm-lock.yaml
- name: Copy Configure
run: cp .github/misskey/test.yml .config
- name: Build
run: pnpm build
- name: Test
run: pnpm --filter backend test-and-coverage:e2e
- name: Upload to Codecov
uses: codecov/codecov-action@v6
with:
token: ${{ secrets.CODECOV_TOKEN }}
files: ./packages/backend/coverage/coverage-final.json
migration:
name: Migration tests (backend)
runs-on: ubuntu-latest
strategy:
matrix:
node-version-file:
- .node-version
#- .github/min.node-version
services:
postgres:
image: postgres:18
ports:
- 54312:5432
env:
POSTGRES_DB: test-misskey
POSTGRES_HOST_AUTH_METHOD: trust
steps:
- uses: actions/checkout@v6.0.2
with:
submodules: true
- name: Setup pnpm
uses: pnpm/action-setup@v6.0.3
- name: Get current date
id: current-date
run: echo "today=$(date +'%Y-%m-%d')" >> $GITHUB_OUTPUT
- name: Use Node.js
uses: actions/setup-node@v6.4.0
with:
node-version-file: ${{ matrix.node-version-file }}
cache: 'pnpm'
- run: pnpm i --frozen-lockfile
- name: Check pnpm-lock.yaml
run: git diff --exit-code pnpm-lock.yaml
- name: Copy Configure
run: cp .github/misskey/test.yml .config
- name: Build
run: pnpm build
- name: Run migrations
run: MISSKEY_CONFIG_YML=test.yml pnpm --filter backend migrate
- name: Check no migrations are remaining
run: MISSKEY_CONFIG_YML=test.yml pnpm --filter backend check-migrations

View File

@@ -1,111 +0,0 @@
name: Test (federation)
on:
push:
branches:
- master
- develop
paths:
- packages/backend/**
- packages/misskey-js/**
- .github/workflows/test-federation.yml
pull_request:
paths:
- packages/backend/**
- packages/misskey-js/**
- .github/workflows/test-federation.yml
workflow_dispatch:
inputs:
force_ffmpeg_cache_update:
description: 'Force update ffmpeg cache'
required: false
default: false
type: boolean
jobs:
test:
name: Federation test
runs-on: ubuntu-latest
strategy:
matrix:
node-version-file:
- .node-version
- .github/min.node-version
steps:
- uses: actions/checkout@v6
with:
submodules: true
- name: Setup pnpm
uses: pnpm/action-setup@v6.0.3
- name: Get current date
id: current-date
run: echo "today=$(date +'%Y-%m-%d')" >> $GITHUB_OUTPUT
- name: Setup and Restore ffmpeg/ffprobe Cache
id: cache-ffmpeg
uses: actions/cache@v5
with:
path: |
/usr/local/bin/ffmpeg
/usr/local/bin/ffprobe
# daily cache
key: ${{ runner.os }}-ffmpeg-${{ steps.current-date.outputs.today }}
restore-keys: |
${{ runner.os }}-ffmpeg-${{ steps.current-date.outputs.today }}
- name: Install FFmpeg
if: steps.cache-ffmpeg.outputs.cache-hit != 'true' || github.event.inputs.force_ffmpeg_cache_update == true
run: |
for i in {1..3}; do
echo "Attempt $i: Installing FFmpeg..."
curl -s -L https://johnvansickle.com/ffmpeg/releases/ffmpeg-release-amd64-static.tar.xz -o ffmpeg.tar.xz && \
tar -xf ffmpeg.tar.xz && \
mv ffmpeg-*-static/ffmpeg /usr/local/bin/ && \
mv ffmpeg-*-static/ffprobe /usr/local/bin/ && \
rm -rf ffmpeg.tar.xz ffmpeg-*-static/ && \
break || sleep 10
if [ $i -eq 3 ]; then
echo "Failed to install FFmpeg after 3 attempts"
exit 1
fi
done
- name: Use Node.js
uses: actions/setup-node@v6.4.0
with:
node-version-file: ${{ matrix.node-version-file }}
cache: 'pnpm'
- name: Build Misskey
run: |
pnpm i --frozen-lockfile
pnpm build
- name: Setup
run: |
echo "NODE_VERSION=$(cat ${{ matrix.node-version-file }})" >> $GITHUB_ENV
cd packages/backend/test-federation
bash ./setup.sh
sudo chmod 644 ./certificates/*.test.key
- name: Start servers
id: start_servers
continue-on-error: true
# https://github.com/docker/compose/issues/1294#issuecomment-374847206
run: |
cd packages/backend/test-federation
docker compose up -d --scale tester=0
- name: Print start_servers error
if: ${{ steps.start_servers.outcome == 'failure' }}
run: |
cd packages/backend/test-federation
docker compose logs | tail -n 300
exit 1
- name: Test
run: |
cd packages/backend/test-federation
docker compose run --no-deps tester
- name: Log
if: always()
run: |
cd packages/backend/test-federation
docker compose logs
- name: Stop servers
if: always()
run: |
cd packages/backend/test-federation
docker compose down

View File

@@ -1,125 +0,0 @@
name: Test (frontend)
on:
push:
branches:
- master
- develop
paths:
- packages/frontend/**
# for permissions
- packages/misskey-js/**
# for e2e
- packages/backend/**
- .github/workflows/test-frontend.yml
- .github/misskey/test.yml
pull_request:
paths:
- packages/frontend/**
# for permissions
- packages/misskey-js/**
# for e2e
- packages/backend/**
- .github/workflows/test-frontend.yml
- .github/misskey/test.yml
jobs:
vitest:
name: Unit tests (frontend)
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6.0.2
with:
submodules: true
- name: Setup pnpm
uses: pnpm/action-setup@v6.0.3
- name: Use Node.js
uses: actions/setup-node@v6.4.0
with:
node-version-file: '.node-version'
cache: 'pnpm'
- run: pnpm i --frozen-lockfile
- name: Check pnpm-lock.yaml
run: git diff --exit-code pnpm-lock.yaml
- name: Copy Configure
run: cp .github/misskey/test.yml .config
- name: Build
run: pnpm build
- name: Test
run: pnpm --filter frontend test-and-coverage
- name: Upload Coverage
uses: codecov/codecov-action@v6
with:
token: ${{ secrets.CODECOV_TOKEN }}
files: ./packages/frontend/coverage/coverage-final.json
e2e:
name: E2E tests (frontend)
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
browser: [chrome]
services:
postgres:
image: postgres:18
ports:
- 54312:5432
env:
POSTGRES_DB: test-misskey
POSTGRES_HOST_AUTH_METHOD: trust
redis:
image: redis:8
ports:
- 56312:6379
steps:
- uses: actions/checkout@v6.0.2
with:
submodules: true
# https://github.com/cypress-io/cypress-docker-images/issues/150
#- name: Install mplayer for FireFox
# run: sudo apt install mplayer -y
# if: ${{ matrix.browser == 'firefox' }}
#- uses: browser-actions/setup-firefox@latest
# if: ${{ matrix.browser == 'firefox' }}
- name: Setup pnpm
uses: pnpm/action-setup@v6.0.3
- name: Use Node.js
uses: actions/setup-node@v6.4.0
with:
node-version-file: '.node-version'
cache: 'pnpm'
- run: pnpm i --frozen-lockfile
- name: Copy Configure
run: cp .github/misskey/test.yml .config
- name: Build
run: pnpm build
# https://github.com/cypress-io/cypress/issues/4351#issuecomment-559489091
- name: ALSA Env
run: echo -e 'pcm.!default {\n type hw\n card 0\n}\n\nctl.!default {\n type hw\n card 0\n}' > ~/.asoundrc
# XXX: This tries reinstalling Cypress if the binary is not cached
# Remove this when the cache issue is fixed
- name: Cypress install
run: pnpm exec cypress install
- name: Cypress run
uses: cypress-io/github-action@v7.1.9
timeout-minutes: 15
with:
install: false
start: pnpm start:test
wait-on: 'http://localhost:61812'
headed: true
browser: ${{ matrix.browser }}
- uses: actions/upload-artifact@v7
if: failure()
with:
name: ${{ matrix.browser }}-cypress-screenshots
path: cypress/screenshots
- uses: actions/upload-artifact@v7
if: always()
with:
name: ${{ matrix.browser }}-cypress-videos
path: cypress/videos

View File

@@ -1,54 +0,0 @@
# This workflow will do a clean install of node dependencies, build the source code and run tests across different versions of node
# For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions
name: Test (misskey.js)
on:
push:
branches: [ develop ]
paths:
- packages/misskey-js/**
- .github/workflows/test-misskey-js.yml
pull_request:
branches: [ develop ]
paths:
- packages/misskey-js/**
- .github/workflows/test-misskey-js.yml
jobs:
test:
name: Unit tests (misskey.js)
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v6.0.2
- name: Setup pnpm
uses: pnpm/action-setup@v6.0.3
- name: Setup Node.js
uses: actions/setup-node@v6.4.0
with:
node-version-file: '.node-version'
cache: 'pnpm'
- name: Install dependencies
run: pnpm i --frozen-lockfile
- name: Check pnpm-lock.yaml
run: git diff --exit-code pnpm-lock.yaml
- name: Build
run: pnpm --filter misskey-js build
- name: Test
run: pnpm --filter misskey-js test
env:
CI: true
- name: Upload Coverage
uses: codecov/codecov-action@v6
with:
token: ${{ secrets.CODECOV_TOKEN }}
files: ./packages/misskey-js/coverage/coverage-final.json

View File

@@ -1,35 +0,0 @@
name: Test (production install and build)
on:
push:
branches:
- master
- develop
pull_request:
env:
NODE_ENV: production
jobs:
production:
name: Production build
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6.0.2
with:
submodules: true
- name: Setup pnpm
uses: pnpm/action-setup@v6.0.3
- name: Use Node.js
uses: actions/setup-node@v6.4.0
with:
node-version-file: '.node-version'
cache: 'pnpm'
- run: pnpm i --frozen-lockfile
- name: Check pnpm-lock.yaml
run: git diff --exit-code pnpm-lock.yaml
- name: Copy Configure
run: cp .github/misskey/test.yml .config/default.yml
- name: Build
run: pnpm build

122
.github/workflows/test.yml vendored Normal file
View File

@@ -0,0 +1,122 @@
name: Test
on:
push:
branches:
- master
- develop
pull_request:
jobs:
mocha:
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [18.x]
services:
postgres:
image: postgres:13
ports:
- 54312:5432
env:
POSTGRES_DB: test-misskey
POSTGRES_HOST_AUTH_METHOD: trust
redis:
image: redis:6
ports:
- 56312:6379
steps:
- uses: actions/checkout@v2
with:
submodules: true
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v3
with:
node-version: ${{ matrix.node-version }}
cache: 'yarn'
cache-dependency-path: |
packages/backend/yarn.lock
packages/client/yarn.lock
- name: Install dependencies
run: yarn install
- name: Check yarn.lock
run: git diff --exit-code yarn.lock
- name: Copy Configure
run: cp .github/misskey/test.yml .config
- name: Build
run: yarn build
- name: Test
run: yarn mocha
e2e:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
node-version: [18.x]
browser: [chrome]
services:
postgres:
image: postgres:13
ports:
- 54312:5432
env:
POSTGRES_DB: test-misskey
POSTGRES_HOST_AUTH_METHOD: trust
redis:
image: redis:6
ports:
- 56312:6379
steps:
- uses: actions/checkout@v2
with:
submodules: true
# https://github.com/cypress-io/cypress-docker-images/issues/150
#- name: Install mplayer for FireFox
# run: sudo apt install mplayer -y
# if: ${{ matrix.browser == 'firefox' }}
#- uses: browser-actions/setup-firefox@latest
# if: ${{ matrix.browser == 'firefox' }}
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v3
with:
node-version: ${{ matrix.node-version }}
cache: 'yarn'
cache-dependency-path: |
packages/backend/yarn.lock
packages/client/yarn.lock
- name: Install dependencies
run: yarn install
- name: Check yarn.lock
run: git diff --exit-code yarn.lock
- name: Copy Configure
run: cp .github/misskey/test.yml .config
- name: Build
run: yarn build
# https://github.com/cypress-io/cypress/issues/4351#issuecomment-559489091
- name: ALSA Env
run: echo -e 'pcm.!default {\n type hw\n card 0\n}\n\nctl.!default {\n type hw\n card 0\n}' > ~/.asoundrc
- name: Cypress run
uses: cypress-io/github-action@v4
with:
install: false
start: npm run start:test
wait-on: 'http://localhost:61812'
headless: false
browser: ${{ matrix.browser }}
- uses: actions/upload-artifact@v2
if: failure()
with:
name: ${{ matrix.browser }}-cypress-screenshots
path: cypress/screenshots
- uses: actions/upload-artifact@v2
if: always()
with:
name: ${{ matrix.browser }}-cypress-videos
path: cypress/videos

View File

@@ -1,40 +0,0 @@
name: api.json validation
on:
push:
branches:
- master
- develop
paths:
- packages/backend/**
- .github/workflows/validate-api-json.yml
pull_request:
paths:
- packages/backend/**
- .github/workflows/validate-api-json.yml
jobs:
validate-api-json:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6.0.2
with:
submodules: true
- name: Setup pnpm
uses: pnpm/action-setup@v6.0.3
- name: Use Node.js
uses: actions/setup-node@v6.4.0
with:
node-version-file: '.node-version'
cache: 'pnpm'
- name: Install Redocly CLI
run: npm i -g @redocly/cli
- run: pnpm i --frozen-lockfile
- name: Check pnpm-lock.yaml
run: git diff --exit-code pnpm-lock.yaml
- name: Copy Configure
run: cp .config/example.yml .config/default.yml
- name: Build and generate
run: pnpm build && pnpm --filter backend generate-api-json
- name: Validation
run: npx @redocly/cli lint --extends=minimal ./packages/backend/built/api.json

41
.gitignore vendored
View File

@@ -9,20 +9,6 @@
node_modules
report.*.json
# Yarn
.yarn/*
!.yarn/patches
!.yarn/plugins
!.yarn/releases
!.yarn/sdks
!.yarn/versions
packages/frontend/.yarn/cache
packages/backend/.yarn/cache
packages/sw/.yarn/cache
# pnpm
.pnpm-store
# Cypress
cypress/screenshots
cypress/videos
@@ -33,24 +19,15 @@ coverage
# config
/.config/*
!/.config/example.yml
!/.config/docker_example.yml
!/.config/docker_example.env
!/.config/cypress-devcontainer.yml
docker-compose.yml
./compose.yml
.devcontainer/compose.yml
!/.devcontainer/compose.yml
# misskey
/build
built
built-test
js-built
src-js
/data
/.cache-loader
/db
/meili_data
/elasticsearch
npm-debug.log
*.pem
run.bat
@@ -61,16 +38,6 @@ api-docs.json
.DS_Store
/files
ormconfig.json
temp
/packages/frontend/src/**/*.stories.ts
tsdoc-metadata.json
misskey-assets
# Vite temporary files
vite.config.js.timestamp-*
vite.config.ts.timestamp-*
vite.config.local-dev.js.timestamp-*
vite.config.local-dev.ts.timestamp-*
# blender backups
*.blend1
@@ -78,9 +45,3 @@ vite.config.local-dev.ts.timestamp-*
*.blend3
*.blend4
*.blend5
# VSCode addon
.favorites.json
# Affinity
*.af~lock~

3
.gitmodules vendored
View File

@@ -0,0 +1,3 @@
[submodule "misskey-assets"]
path = misskey-assets
url = https://github.com/misskey-dev/assets.git

View File

@@ -1 +1 @@
22.15.0
v16.15.0

2
.npmrc Normal file
View File

@@ -0,0 +1,2 @@
save-exact = true
package-lock = false

Some files were not shown because too many files have changed in this diff Show More