mirror of
https://github.com/misskey-dev/misskey.git
synced 2026-06-16 21:44:53 +02:00
Compare commits
95 Commits
copilot/fi
...
syuilo-pat
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
72a4413394 | ||
|
|
eb7a2e107f | ||
|
|
1684dc9c05 | ||
|
|
08c6efb044 | ||
|
|
62b323b58b | ||
|
|
a3227c99ed | ||
|
|
f4bca4641c | ||
|
|
e233556700 | ||
|
|
6665c398d6 | ||
|
|
bf3c1f6686 | ||
|
|
f6ea52b1be | ||
|
|
b950f905e5 | ||
|
|
a19da1258d | ||
|
|
408d05654c | ||
|
|
3074784d4d | ||
|
|
a09a2c2eee | ||
|
|
717931cfcb | ||
|
|
9027129b58 | ||
|
|
b73ac26612 | ||
|
|
b528ff9c59 | ||
|
|
a82ba0d775 | ||
|
|
b78e0168b0 | ||
|
|
33f59b3469 | ||
|
|
5b478dda9d | ||
|
|
90725d6a8c | ||
|
|
86542f07d3 | ||
|
|
45022bc766 | ||
|
|
35711fc8e1 | ||
|
|
45f140aa86 | ||
|
|
22ce7b58ca | ||
|
|
37107c9818 | ||
|
|
a5a43c8c06 | ||
|
|
723d8add2f | ||
|
|
9d20152e05 | ||
|
|
37412f0e1b | ||
|
|
712b51c142 | ||
|
|
2b4bdbfde7 | ||
|
|
39032c4b1b | ||
|
|
f5a3d8996d | ||
|
|
d55e936653 | ||
|
|
6229ac365e | ||
|
|
6d9412b338 | ||
|
|
a23a72b015 | ||
|
|
93bd9d551d | ||
|
|
35d6c20828 | ||
|
|
7c9942f014 | ||
|
|
665adfccb7 | ||
|
|
973b5b50a9 | ||
|
|
985de915b3 | ||
|
|
0227148c89 | ||
|
|
7bfd85cdba | ||
|
|
21f51be5b7 | ||
|
|
b45f18cd14 | ||
|
|
6176cca0a4 | ||
|
|
9569310adb | ||
|
|
b28338c812 | ||
|
|
0f5da63328 | ||
|
|
23715c649c | ||
|
|
1dc5c60b2b | ||
|
|
3a3057a1b1 | ||
|
|
8a85ee1d45 | ||
|
|
c1b73f947c | ||
|
|
bda98ba1d9 | ||
|
|
351fe2f70a | ||
|
|
6d15fe32d0 | ||
|
|
1391269a67 | ||
|
|
86092f2faf | ||
|
|
c640aeb27a | ||
|
|
0be3142d54 | ||
|
|
37bfcb604f | ||
|
|
024f8bb102 | ||
|
|
277a1ef31f | ||
|
|
8e536eb4b9 | ||
|
|
360e805638 | ||
|
|
c95aef7535 | ||
|
|
758a085812 | ||
|
|
a6de986f83 | ||
|
|
12e590a63f | ||
|
|
a5b1f839ac | ||
|
|
d7ceaa9c88 | ||
|
|
55b0fbd172 | ||
|
|
6532cb85e7 | ||
|
|
5dc508346c | ||
|
|
c9c6ef2772 | ||
|
|
4d6256e91d | ||
|
|
cdf0438154 | ||
|
|
29cecd750f | ||
|
|
68e3476a16 | ||
|
|
3cb003366f | ||
|
|
787de92c2f | ||
|
|
22577e2134 | ||
|
|
0355420c81 | ||
|
|
4750980cef | ||
|
|
92e0e8edf7 | ||
|
|
5cb3a91b15 |
2
.claude/.gitignore
vendored
Normal file
2
.claude/.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
/settings.local.json
|
||||
/.credentials.json
|
||||
76
.claude/THIRD_PARTY_LICENSES.md
Normal file
76
.claude/THIRD_PARTY_LICENSES.md
Normal file
@@ -0,0 +1,76 @@
|
||||
# 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/` 内のパス | 上流パス | 上流 frontmatter `origin` | 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 §1](../AGENTS.md#1-spdx-ヘッダー必須)) は Misskey 本体コード向けで、`.claude/` 配下の `.md` / `.sh` には適用されない。
|
||||
|
||||
---
|
||||
|
||||
## 3. 新規追加時の手順
|
||||
|
||||
`.claude/` に新たにサードパーティ由来のファイルを取り込む際は:
|
||||
|
||||
1. ライセンスを確認 (互換性: MIT / Apache-2.0 / BSD は OK、GPL/AGPL は要相談)
|
||||
2. 各ファイル冒頭に SPDX ヘッダ + 出典コメントを追加
|
||||
3. 本ファイル §1 のテーブルに 1 行追記
|
||||
4. 必要なら新しいセクションでライセンス全文を同梱
|
||||
5. AGENTS.md からの参照を確認 (現状の [AGENTS.md §ツール固有の補助ファイル](../AGENTS.md) で `THIRD_PARTY_LICENSES.md` を案内済。CLAUDE.md は `@AGENTS.md` 経由で読み込むので個別の追記は不要)
|
||||
23
.claude/agents/README.md
Normal file
23
.claude/agents/README.md
Normal file
@@ -0,0 +1,23 @@
|
||||
# `.claude/agents/` — プロジェクト固有のサブエージェント
|
||||
|
||||
Misskey の特定領域に特化したレビュー / 調査エージェントを `.claude/agents/<name>.md` 形式で配置する。
|
||||
|
||||
frontmatter (`name` + `description` + `tools`) は、Claude が **自動でエージェントを呼び出すか判断する** 唯一の手がかりになる。`description` には用途を具体的かつ網羅的に書くこと (動詞 + 対象 + トリガー条件)。
|
||||
|
||||
## 実装済サブエージェント
|
||||
|
||||
| エージェント名 | 役割 | 優先度 |
|
||||
|---|---|---|
|
||||
| [misskey-api-reviewer](misskey-api-reviewer.md) | NestJS DI + meta/paramDef + UUID 重複 + endpoint-list.ts 登録 + ApiError throw + misskey-js 再生成 + e2e + CHANGELOG をチェック | 高 (登録漏れで 404 / autogen CI 落ち頻発) |
|
||||
| [vue-component-reviewer](vue-component-reviewer.md) | Mk\* 命名 / `<script lang="ts" setup>` / type-only defineProps / SCSS module / CSS 変数 / i18n.ts と i18n.tsx の使い分け / os.\* 経由 / a11y / `*.stories.impl.ts` 併設をチェック | 中 (CI 直撃は SPDX / locales 編集違反のみ。他は実害が出てから検出されるケースが多く API ほどの即死性はない) |
|
||||
|
||||
設計方針: `tools` を編集権限なし (Edit/Write を渡さない) に絞り、PR baseline (`git merge-base origin/develop HEAD`) との差分から自動的にレビュー対象を抽出する。
|
||||
|
||||
## 新規エージェントを追加する場合
|
||||
|
||||
- `.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` 等の **読み取り系コマンドに限定して使う** こと。書き込み・削除・ネットワーク送信を伴う操作は本文中の例示・指示に含めないこと (エージェント本文がガードレールになる)。
|
||||
- 主要参照ファイルへのリンクは、リポジトリルートからの相対パス (例: `../../packages/backend/...`) で貼る。絶対パスは contributor のホームディレクトリ依存になるので使わない。
|
||||
- 差分抽出は `git merge-base origin/develop HEAD` を baseline にする (PR / ブランチ全体を見るため)。`git diff HEAD` 単体は **未コミット差分しか取れず、コミット済の PR では空になって誤判定する** ので使わない。
|
||||
- 完成したらこの README の表にも 1 行追加する。
|
||||
167
.claude/agents/misskey-api-reviewer.md
Normal file
167
.claude/agents/misskey-api-reviewer.md
Normal file
@@ -0,0 +1,167 @@
|
||||
---
|
||||
name: misskey-api-reviewer
|
||||
description: Misskey の API エンドポイント (packages/backend/src/server/api/endpoints/) の追加・変更を専門レビューする。SPDX / meta / paramDef / UUID 重複 / endpoint-list.ts 登録 / ApiError throw / misskey-js 再生成 / e2e / CHANGELOG を機械的にチェック。バックエンド API を追加・変更した PR レビューで呼び出す。
|
||||
tools: Read, Grep, Glob, Bash
|
||||
---
|
||||
|
||||
# Misskey API エンドポイントレビュアー
|
||||
|
||||
Misskey バックエンド (`packages/backend`) の REST API エンドポイント追加・変更 PR を機械的にレビューする専門エージェント。規約の根拠は [.claude/skills/add-api-endpoint/SKILL.md](../skills/add-api-endpoint/SKILL.md)。
|
||||
|
||||
## 役割
|
||||
|
||||
`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/add-api-endpoint/SKILL.md](../skills/add-api-endpoint/SKILL.md) — 実装側の規約 (本エージェントの根拠)
|
||||
- [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 と共通)
|
||||
176
.claude/agents/vue-component-reviewer.md
Normal file
176
.claude/agents/vue-component-reviewer.md
Normal file
@@ -0,0 +1,176 @@
|
||||
---
|
||||
name: vue-component-reviewer
|
||||
description: Misskey フロントエンド (packages/frontend/src/components/ / pages/) の Vue 3 SFC 変更を専門レビューする。SPDX (HTML コメント) / Mk* 命名 / <script lang="ts" setup> / type-only defineProps / <style lang="scss" module> / CSS 変数 / i18n.ts と i18n.tsx の使い分け / os.* 経由 / a11y / Storybook (*.stories.impl.ts) を機械的にチェック。フロントエンドの .vue 変更を含む PR レビューで呼び出す。
|
||||
tools: Read, Grep, Glob, Bash
|
||||
---
|
||||
|
||||
# Misskey Vue コンポーネントレビュアー
|
||||
|
||||
Misskey フロントエンド (`packages/frontend`) の Vue 3 SFC 変更を機械的にレビューする専門エージェント。規約の根拠は [.claude/skills/add-mk-component/SKILL.md](../skills/add-mk-component/SKILL.md)。
|
||||
|
||||
## 役割
|
||||
|
||||
`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 形式) は禁止。既存 SFC の慣習・SFC 先頭として自然な形式に統一するため (CI の `spdx` ジョブはコメント形式ではなく SPDX 文字列の有無のみを検査するため、形式が違っても CI は通るが、規約違反として指摘する)。
|
||||
|
||||
### 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/add-mk-component/SKILL.md](../skills/add-mk-component/SKILL.md) — 実装側の規約 (本エージェントの根拠)
|
||||
- [.claude/skills/add-i18n-key/SKILL.md](../skills/add-i18n-key/SKILL.md) — i18n キー追加のルール
|
||||
- [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 と共通)
|
||||
38
.claude/commands/README.md
Normal file
38
.claude/commands/README.md
Normal file
@@ -0,0 +1,38 @@
|
||||
# `.claude/commands/` — プロジェクト固有のスラッシュコマンド
|
||||
|
||||
Misskey 開発で繰り返し使うワークフローを `/command-name` で呼び出せるよう、`.claude/commands/<name>.md` 形式で配置している。
|
||||
|
||||
## 実装済みコマンド
|
||||
|
||||
### Misskey オリジナル
|
||||
|
||||
| コマンド | 用途 | 典型ユースケース |
|
||||
| --- | --- | --- |
|
||||
| [`/check-misskey-js`](./check-misskey-js.md) | `pnpm build-misskey-js-with-types` を走らせ、`packages/misskey-js/src/autogen/` の差分を報告 | backend の API endpoint を追加・変更した直後 |
|
||||
| [`/changelog-add`](./changelog-add.md) | `CHANGELOG.md` の `## Unreleased` 配下、対応するサブセクションに 1 行追記 | ユーザー影響のある変更をコミットする直前 |
|
||||
| [`/migrate-new`](./migrate-new.md) | TypeORM `migration:create` の薄いラッパー (拡張子変換 + SPDX 付与 + `check-migrations` で pending DDL 検出) | 手書き SQL / データ移行用に空雛形が欲しい時 |
|
||||
|
||||
### ECC ([everything-claude-code](https://github.com/affaan-m/everything-claude-code)) 由来 (MIT)
|
||||
|
||||
ECC の MIT ライセンスファイルを Misskey の規約に合わせて再構成したもの。出典は [.claude/THIRD_PARTY_LICENSES.md](../THIRD_PARTY_LICENSES.md) を参照。
|
||||
|
||||
| コマンド | 用途 | 典型ユースケース |
|
||||
| --- | --- | --- |
|
||||
| [`/quality-gate`](./quality-gate.md) | `pnpm lint` + 各パッケージの unit test を順次実行する軽量品質ゲート | 完了前の軽量チェック (重い E2E は CI 側に委譲) |
|
||||
| [`/harness-audit`](./harness-audit.md) | `.claude/` ハーネスを 7 カテゴリで採点し改善優先度を提示 | 設定の点検 / 新しい skill / agent / hook を入れた後 |
|
||||
|
||||
## 使い分け
|
||||
|
||||
- **`/migrate-new` vs [`create-migration` skill](../skills/create-migration/SKILL.md)**:
|
||||
- 雛形だけ素早く欲しい → `/migrate-new`
|
||||
- エンティティ差分から自動生成、または CONCURRENTLY などの注意点を含めて完全に誘導してほしい → `create-migration` skill (`migration:generate`)
|
||||
- **`/changelog-add` vs 手動編集**:
|
||||
- サブセクションの placeholder `-` 置換や、過去リリースセクションへの誤編集を避けるため、原則コマンドを使う。
|
||||
- **`/quality-gate` のスコープ**:
|
||||
- 編集途中の軽量チェック (lint + unit test) は `/quality-gate` で十分。重い e2e / federation / Cypress は CI 側で実行されるため、ローカルでは原則回さない。
|
||||
|
||||
## 新規追加時の方針
|
||||
|
||||
- 既存の `superpowers` / `pr-review-toolkit` などのプラグイン提供スラッシュコマンドで足りる場合は新規追加しない。
|
||||
- frontmatter には最低限 `description` を指定し、引数を取るなら `argument-hint`、可能なら `allowed-tools` も指定する (permission prompt を最小化するため)。
|
||||
- 長時間ビルド (2 分超) を伴うコマンドはインライン `` !`<cmd>` `` を使わず、本文で `Bash` ツール呼び出し時の `timeout` を指示する。
|
||||
49
.claude/commands/changelog-add.md
Normal file
49
.claude/commands/changelog-add.md
Normal file
@@ -0,0 +1,49 @@
|
||||
---
|
||||
description: CHANGELOG.md の Unreleased セクションに 1 行追記する
|
||||
argument-hint: <general|client|server> <Prefix>: <description>
|
||||
allowed-tools: Bash(awk:*), Bash(git diff:*), Read, Edit
|
||||
---
|
||||
|
||||
## 引数
|
||||
|
||||
引数: `$ARGUMENTS`
|
||||
|
||||
## 現在の Unreleased セクション
|
||||
|
||||
!`awk '/^## Unreleased/,/^## [0-9]/' CHANGELOG.md`
|
||||
|
||||
## タスク
|
||||
|
||||
1. **引数の解析**
|
||||
`$ARGUMENTS` を以下の形式として解釈する:
|
||||
- 第 1 トークン: scope = `general` / `client` / `server` のいずれか (case-insensitive)
|
||||
- 残り: エントリ本文。`Enhance:` / `Fix:` / `Feat:` のいずれかで始まる前提
|
||||
- 不正な scope や、Prefix が見当たらない場合はエラー終了し、ユーザーに `argument-hint` の書式を提示する
|
||||
|
||||
scope は次のように見出しに変換する: `general` → `### General` / `client` → `### Client` / `server` → `### Server`。
|
||||
|
||||
2. **対象サブセクションの状態判定**
|
||||
上の context (現在の Unreleased セクション) を見て、対象サブセクションが以下のどちらかを判定する:
|
||||
- **空 (placeholder のみ)**: 見出し直下に `-` 単独行のみがある状態
|
||||
- **既存エントリあり**: `- Enhance: ...` / `- Fix: ...` / `- Feat: ...` の行が 1 つ以上ある状態
|
||||
|
||||
3. **CHANGELOG.md の編集**
|
||||
`Read` で CHANGELOG.md 全体を確認した後、`Edit` ツールで以下のように更新する:
|
||||
|
||||
- **空の場合**: 該当サブセクションの placeholder `-` 行を `- <整形済みエントリ>` で置換する。例: `### General\n-\n` → `### General\n- Enhance: 新しい機能\n`
|
||||
- **既存ありの場合**: 既存エントリ群の **末尾** (次の空行直前) に新エントリを **append** する。順序入れ替えはしない。
|
||||
|
||||
`Edit` の `old_string` には、置換対象のサブセクション付近のユニークな文脈 (見出し + 直後の数行) を含め、誤マッチを防ぐ。
|
||||
|
||||
4. **不可侵の徹底**
|
||||
- `## Unreleased` 以下の対象サブセクションのみ編集する。
|
||||
- `## 2026.x.x` 以下の過去リリースセクションは絶対に変更しない ([AGENTS.md §CHANGELOG](../../AGENTS.md#changelog) 参照)。
|
||||
|
||||
5. **結果確認**
|
||||
`git diff CHANGELOG.md` を実行し、想定通り 1 行のみ追加されていることを表示して、ユーザーに確認させる。
|
||||
|
||||
## 例
|
||||
|
||||
- `/changelog-add server Fix: 通知が遅延する問題を修正` → `### Server` 末尾に追記
|
||||
- `/changelog-add client Enhance: ノートの表示を改善` → `### Client` 末尾に追記
|
||||
- `/changelog-add general Feat: 新機能の追加` → `### General` 末尾に追記 (placeholder 置換)
|
||||
42
.claude/commands/check-misskey-js.md
Normal file
42
.claude/commands/check-misskey-js.md
Normal file
@@ -0,0 +1,42 @@
|
||||
---
|
||||
description: backend の API 変更後に misskey-js を再生成し、生成物の差分を報告する
|
||||
allowed-tools: Bash(pnpm build-misskey-js-with-types:*), Bash(git status:*), Bash(git diff:*), Bash(git branch:*)
|
||||
---
|
||||
|
||||
## 概要
|
||||
|
||||
backend の API endpoint やスキーマを変更した後、`packages/misskey-js/src/autogen/` の自動生成型を最新化するためのコマンド。内部で `pnpm build-misskey-js-with-types` (backend build → `api.json` 生成 → misskey-js 型生成 → ビルド → API extractor) を一括実行する。
|
||||
|
||||
## 現在の状態 (再生成前)
|
||||
|
||||
- 現ブランチ: !`git branch --show-current`
|
||||
- 既存の misskey-js 関連変更: !`git status --short -- packages/misskey-js/`
|
||||
|
||||
## タスク
|
||||
|
||||
以下の手順を順番に実行してください。
|
||||
|
||||
1. **再生成の実行**
|
||||
`Bash` ツールで以下のコマンドを `timeout: 600000` (10 分) を指定して実行する。内部で backend ビルドと型再生成を行うため、デフォルトの 2 分タイムアウトでは不足する。
|
||||
|
||||
```bash
|
||||
pnpm build-misskey-js-with-types
|
||||
```
|
||||
|
||||
2. **差分の確認**
|
||||
完了後、以下を実行して `packages/misskey-js/src/autogen/` の差分を確認する (`built/` は `.gitignore` 対象なので追跡対象外):
|
||||
|
||||
```bash
|
||||
git status --short -- packages/misskey-js/
|
||||
git diff --stat -- packages/misskey-js/src/autogen/
|
||||
```
|
||||
|
||||
3. **結果報告**
|
||||
- **差分なし** → 「backend の変更は misskey-js の公開型に影響していません」と報告する。追加コミットは不要。
|
||||
- **差分あり** → 変更ファイル一覧をユーザーに示し、`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/` 以外) に予期せぬ差分が出た場合は、ローカルの編集が混入している可能性があるため、一旦中止して原因を調査する。
|
||||
146
.claude/commands/harness-audit.md
Normal file
146
.claude/commands/harness-audit.md
Normal file
@@ -0,0 +1,146 @@
|
||||
---
|
||||
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 | docs/* の同期状態を評価。プロジェクト側 `.claude/memory/` は未採用方針 (auto-memory はユーザーホーム側で自動運用) のため、ここを採点起点にせず既定 5/10 から開始する |
|
||||
| 5 | Eval Coverage | 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 (testing.md 網羅、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/docs/plugins.md と照合。
|
||||
|
||||
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 への依存はゼロ。
|
||||
81
.claude/commands/migrate-new.md
Normal file
81
.claude/commands/migrate-new.md
Normal file
@@ -0,0 +1,81 @@
|
||||
---
|
||||
description: TypeORM migration の空雛形を生成する。スキーマ差分から自動生成したい時は create-migration skill を使うこと
|
||||
argument-hint: <PascalCaseName>
|
||||
allowed-tools: Bash(pnpm:*), Bash(ls:*), Bash(test:*), Bash(head:*), Read, Edit
|
||||
---
|
||||
|
||||
## 引数
|
||||
|
||||
引数: `$ARGUMENTS`
|
||||
|
||||
## タスク
|
||||
|
||||
1. **PascalCaseName の検証**
|
||||
`$ARGUMENTS` が `^[A-Z][A-Za-z0-9]+$` に一致するか確認する。一致しない場合はエラー終了し、`AddFooBar` / `BirthdayIndex` のような形式を案内する。
|
||||
|
||||
2. **既存ファイルの存在確認**
|
||||
|
||||
```bash
|
||||
ls packages/backend/migration/*$ARGUMENTS.{js,ts} 2>/dev/null
|
||||
```
|
||||
|
||||
既に同名 (タイムスタンプ違い) のファイルが存在する場合、上書きせずユーザーに別名を促す。
|
||||
|
||||
3. **TypeORM 公式 CLI で空雛形を生成 (`-o --esm` 必須)**
|
||||
`create-migration` skill の方針に従い、`Date.now()` を手書きするのではなく TypeORM CLI を使う。`-o --esm` で **最初から JS(ESM) を生成** させ、後続の `.ts → .js` 変換や `import { MigrationInterface }` 削除といった TS 固有構文の除去を不要にする (`-o --esm` を付けないと `.ts` + CommonJS / `implements MigrationInterface` 付きで生成され、Misskey の `ormconfig.js` (`migration/*.js` のみロード) と既存 migration スタイルに合わない):
|
||||
|
||||
```bash
|
||||
pnpm --filter backend exec typeorm migration:create -o --esm migration/$ARGUMENTS
|
||||
```
|
||||
|
||||
出力: `packages/backend/migration/<UnixMs>-<PascalCaseName>.js`
|
||||
|
||||
4. **生成ファイルパスの取得**
|
||||
後続ステップで使うパスを変数に受ける (`<ms>` を手書きしない):
|
||||
|
||||
```bash
|
||||
dst=$(ls -t packages/backend/migration/*$ARGUMENTS.js | head -1)
|
||||
```
|
||||
|
||||
以降のステップでは `$dst` を編集対象として扱う。完成後の典型的な形は次のようになる (参考: [packages/backend/migration/1767169026317-birthday-index.js](../../packages/backend/migration/1767169026317-birthday-index.js)):
|
||||
|
||||
```js
|
||||
export class <PascalCaseName><ms> {
|
||||
name = '<PascalCaseName><ms>'
|
||||
|
||||
async up(queryRunner) {
|
||||
}
|
||||
|
||||
async down(queryRunner) {
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
5. **SPDX ヘッダーの追加**
|
||||
`Edit` ツールで、ファイル冒頭に以下を挿入する。CI の `spdx` ジョブが失敗するため必須:
|
||||
|
||||
```js
|
||||
/*
|
||||
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
```
|
||||
|
||||
6. **migration の pending DDL 検査**
|
||||
|
||||
```bash
|
||||
pnpm --filter backend check-migrations
|
||||
```
|
||||
|
||||
TypeORM schema builder で pending DDL を検出する検査 ([scripts/check_migrations_clean.js](../../packages/backend/scripts/check_migrations_clean.js))。空雛形を作っただけの段階ではエンティティ差分との不整合が残る場合があるため、`up`/`down` を埋めた後にも再実行して 0 件になるか確認する。
|
||||
|
||||
7. **結果報告**
|
||||
- 生成ファイルパスを示す。
|
||||
- `up()` / `down()` の中身が空であることを伝え、SQL を書く必要があると案内する。
|
||||
- `down()` を空のまま放置すると本番ロールバック時に詰むため、必ず `up` の完全な巻き戻しを実装するよう促す。
|
||||
- 詳細な手順 (`migration:generate` を使うべきケース、CONCURRENTLY などの注意点) は `create-migration` skill を参照するよう案内する。
|
||||
|
||||
## 注意
|
||||
|
||||
- このコマンドは **空雛形を素早く出して手書きする** 用途。エンティティ (`packages/backend/src/models/*.ts`) を変更した差分から SQL を自動生成したい場合は、このコマンドではなく `create-migration` skill 経由で `migration:generate` を使うこと。
|
||||
- マージ済み migration ファイルは絶対に編集しない ([AGENTS.md §3](../../AGENTS.md#3-マージ済み-migration-を絶対に編集しない))。
|
||||
122
.claude/commands/quality-gate.md
Normal file
122
.claude/commands/quality-gate.md
Normal file
@@ -0,0 +1,122 @@
|
||||
---
|
||||
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 / コマンド
|
||||
|
||||
- `/check-misskey-js` コマンド — API 変更時の misskey-js 再生成
|
||||
- [AGENTS.md §必須コマンド](../../AGENTS.md#必須コマンド) — pnpm コマンド一覧の正典
|
||||
|
||||
## 元 ECC 版との差分
|
||||
|
||||
- ジェネリックな言語自動判定を排除し、Misskey 固定 pipeline に。
|
||||
- formatter フェーズなし (Misskey は ESLint --fix のみ採用)。
|
||||
- e2e / federation / Cypress は重いため除外し CI 側に委譲。
|
||||
18
.claude/docs/README.md
Normal file
18
.claude/docs/README.md
Normal file
@@ -0,0 +1,18 @@
|
||||
# Misskey – Claude Code 補助ドキュメント
|
||||
|
||||
ルート `CLAUDE.md` には書かれていないが、開発時に参照すると便利な情報を分野別にまとめている。**Claude は必要になったタイミングで該当ファイルを Read すれば良い** (auto-load しない)。
|
||||
|
||||
## 索引
|
||||
|
||||
| ファイル | いつ読むか |
|
||||
|---|---|
|
||||
| [architecture.md](./architecture.md) | パッケージ構成・ビルド構造を把握したい時 / 新パッケージを跨ぐ変更を計画する時 |
|
||||
| [backend.md](./backend.md) | `packages/backend` を編集する時 (NestJS / TypeORM / API endpoint / migration) |
|
||||
| [frontend.md](./frontend.md) | `packages/frontend` を編集する時 (Vue 3 / Mk* / i18n / SCSS Modules / `os.ts`) |
|
||||
| [testing.md](./testing.md) | テストを書く・走らせる時 (Vitest 構成、Cypress、Storybook) |
|
||||
| [plugins.md](./plugins.md) | 有効化済の Claude Code プラグインの用途を確認したい時 |
|
||||
|
||||
## 補足: ルール vs ドキュメント
|
||||
|
||||
- 事故直結ルール (SPDX / locales / migration) と必須コマンド・CHANGELOG 書式は、リポジトリルートの [AGENTS.md](../../AGENTS.md) に集約されている。Claude Code は CLAUDE.md からの `@AGENTS.md` で常時コンテキストに乗せる。Codex / Copilot も同じファイルを読む。
|
||||
- `.claude/docs/*.md` (このディレクトリ) は **オンデマンド参照**。Claude が「知っておいた方が良いが常に持つ必要はない」内容をここに置く。
|
||||
47
.claude/docs/architecture.md
Normal file
47
.claude/docs/architecture.md
Normal file
@@ -0,0 +1,47 @@
|
||||
# アーキテクチャ概要
|
||||
|
||||
## モノレポ構成 (pnpm workspaces)
|
||||
|
||||
pnpm workspace の正は [pnpm-workspace.yaml](../../pnpm-workspace.yaml) で、以下 11 パッケージと、`packages/misskey-js` 内の sub-workspace `packages/misskey-js/generator` (型生成用の内部ジェネレータ。直接編集しない) で構成される。`package.json` の `workspaces` 配列も併記しているが、実体は pnpm-workspace.yaml が読まれる:
|
||||
|
||||
| パッケージ | 役割 |
|
||||
|---|---|
|
||||
| `packages/backend` | NestJS 11 + Fastify 5 + TypeORM 0.3 (PostgreSQL) + Redis。HTTP/WebSocket/ActivityPub サーバー本体。 |
|
||||
| `packages/frontend` | Vue 3.5 + Vite。Web クライアント本体。 |
|
||||
| `packages/frontend-embed` | 埋め込み専用ビュー (ノート単体プレビュー等)。 |
|
||||
| `packages/frontend-shared` | frontend と frontend-embed で共有するユーティリティ・コンポーネント。 |
|
||||
| `packages/frontend-builder` | フロントエンドビルド支援 (Vite plugin など)。 |
|
||||
| `packages/sw` | Service Worker。 |
|
||||
| `packages/misskey-js` | JS/TS クライアント SDK (MIT サブパッケージ)。`src/autogen/` 配下のみ backend の OpenAPI から `pnpm build-misskey-js-with-types` で自動生成され、それ以外 (`src/index.ts` / `src/api.ts` 等) は手書き保守する。autogen 配下を直接編集しないこと。 |
|
||||
| `packages/misskey-reversi` | 内蔵リバーシゲームのロジック。 |
|
||||
| `packages/misskey-bubble-game` | 内蔵バブルゲームのロジック。 |
|
||||
| `packages/i18n` | locales 読み込み/型生成のサポート。 |
|
||||
| `packages/icons-subsetter` | アイコンのサブセット化ツール。 |
|
||||
|
||||
その他に `packages/shared` (workspaces には含まれないが共有ファイル置き場) もある。
|
||||
|
||||
## 重要な依存関係
|
||||
|
||||
```
|
||||
frontend ── misskey-js (auto-generated) ── backend (OpenAPI)
|
||||
▲
|
||||
└── frontend-embed, sw も依存
|
||||
```
|
||||
|
||||
- backend の API (meta / paramDef / response) を変更したら **必ず** `pnpm build-misskey-js-with-types` を実行し、misskey-js の生成物を更新する。忘れると CI の `check-misskey-js-autogen` ジョブが落ちる。
|
||||
|
||||
## ビルドツール
|
||||
|
||||
- **Backend**: `rolldown` (Rust 製・Rollup 互換 API のバンドラ) でバンドル。型チェックは `tsgo` (TypeScript native preview)。
|
||||
- **Frontend**: Vite。型チェックは `vue-tsc`。
|
||||
- **Lint**: ESLint 9 (Flat Config) + `@misskey-dev/eslint-plugin`。
|
||||
|
||||
## 国際化
|
||||
|
||||
- `locales/` 直下に 40 言語の YAML (ja-JP.yml + 他 39 言語)。
|
||||
- **`ja-JP.yml` のみ手動編集可** (Crowdin 経由で他言語へ自動配信)。
|
||||
- フロントエンドからの参照は引数なしか引数ありかで使い分ける。詳細は [frontend.md](./frontend.md#国際化-i18n)。
|
||||
|
||||
## ライセンス
|
||||
|
||||
リポジトリ本体は AGPL-3.0-only。**AGPL-3.0-only 管轄かつ SPDX CI 対象ディレクトリ** の新規 `.ts` / `.js` / `.cjs` / `.mjs` / `.vue` / `.scss` / `.html` ファイルには冒頭に SPDX ヘッダー必須。`packages/misskey-js` は MIT サブパッケージなので AGPL ヘッダーを一律に付けない。条件と除外の詳細は [AGENTS.md §1](../../AGENTS.md#1-spdx-ヘッダー必須) 参照。
|
||||
124
.claude/docs/backend.md
Normal file
124
.claude/docs/backend.md
Normal file
@@ -0,0 +1,124 @@
|
||||
# Backend (`packages/backend`) 規約
|
||||
|
||||
NestJS 11 + Fastify 5 + TypeORM 0.3 (PostgreSQL) + Redis。
|
||||
|
||||
## アーキテクチャ
|
||||
|
||||
- **DI コンテナ**: NestJS の `@Injectable()` サービス + Repository (TypeORM) パターン。
|
||||
- **DI トークン**: `@/di-symbols.js` の `DI` から `@Inject(DI.xxx)` で注入。
|
||||
- **ビルド**: `rolldown -c` で `built/` にバンドル。型チェックは `tsgo`。
|
||||
|
||||
## API エンドポイント
|
||||
|
||||
### 配置
|
||||
|
||||
`packages/backend/src/server/api/endpoints/<category>/<name>.ts` (一部はトップ直下)。
|
||||
|
||||
### 三点セット (`endpoints/ping.ts` 参照)
|
||||
|
||||
各エンドポイントファイルは以下の 3 つを export する:
|
||||
|
||||
```ts
|
||||
export const meta = {
|
||||
tags: ['<tag>'],
|
||||
requireCredential: true, // または false (必ず明示)
|
||||
requireModerator: false, // 必要なら true
|
||||
kind: 'read:account', // OAuth scope
|
||||
res: {
|
||||
type: 'object',
|
||||
optional: false, nullable: false,
|
||||
properties: { /* ... */ },
|
||||
},
|
||||
errors: {
|
||||
sampleError: {
|
||||
message: 'Sample error message.',
|
||||
code: 'SAMPLE_ERROR',
|
||||
id: 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx', // UUID v4 (`x`=hex, `y`=8/9/a/b)。`crypto.randomUUID()` で生成し、他エンドポイントと重複させない
|
||||
},
|
||||
},
|
||||
} as const;
|
||||
|
||||
export const paramDef = {
|
||||
type: 'object',
|
||||
properties: { /* JSON Schema */ },
|
||||
required: [],
|
||||
} as const;
|
||||
|
||||
@Injectable()
|
||||
export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
|
||||
constructor(
|
||||
// @Inject(DI.xxx) private xxxRepository: XxxRepository,
|
||||
) {
|
||||
super(meta, paramDef, async (ps, me) => {
|
||||
// 実装。エラーは throw new ApiError(meta.errors.xxx);
|
||||
});
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 注意点
|
||||
|
||||
- **公開 API エラーとしてクライアントに返したいものは `throw new ApiError(meta.errors.<key>)` を使う**。`meta.errors` に列挙して `ApiError` でラップしないと misskey-js 側の型に出ず、レスポンスも 500 になる。
|
||||
- 一方で **想定外の例外 (DB 不整合 / 下層サービスの bug 等) は握り潰さず再 throw する**。既存 endpoint も「期待される業務エラーは `ApiError` に変換し、それ以外は `throw err;` で再 throw」の二段構え (例: [`endpoints/i/pin.ts`](../../packages/backend/src/server/api/endpoints/i/pin.ts) の `catch` 節)。生 `throw` を全面禁止すると未知例外が 200 で潰れて debug 困難になる。
|
||||
- `meta.errors.<key>.id` は **UUID** 形式。新規追加時は他エンドポイントと重複しないよう確認する。
|
||||
- `requireCredential` は `true` / `false` を必ず明示する。
|
||||
- 新規エンドポイント追加後は **`pnpm build-misskey-js-with-types`** を実行する (`misskey-js` の自動生成ファイルを更新)。
|
||||
|
||||
### ルート登録
|
||||
|
||||
エンドポイントは **glob 自動収集されない**。新規ファイルを `endpoints/<category>/<name>.ts` に置いただけでは API ルーティングに乗らず、404 になる。`packages/backend/src/server/api/endpoint-list.ts` にアルファベット順で 1 行追加するのが必須:
|
||||
|
||||
```ts
|
||||
export * as '<category>/<name>' from './endpoints/<category>/<name>.js';
|
||||
```
|
||||
|
||||
`EndpointsModule.ts` がこのファイルの全エクスポートを `Object.entries()` で反復し、NestJS の provider (`provide: 'ep:<path>'`) を生成する。詳細は [.claude/skills/add-api-endpoint/SKILL.md](../skills/add-api-endpoint/SKILL.md) のステップ 4 を参照。
|
||||
|
||||
## モデル / Repository
|
||||
|
||||
- エンティティ: `packages/backend/src/models/<Name>.ts` (`@Entity` + `@Column`)。
|
||||
- DI 経由で注入される Repository を経由してアクセス。
|
||||
|
||||
## Migration
|
||||
|
||||
詳細手順 (手書き方式 = AGENTS.md §3 と整合):
|
||||
|
||||
> エンティティ差分からの自動生成や `CREATE INDEX CONCURRENTLY` 等のオプションを使いたい場合は [.claude/skills/create-migration/SKILL.md](../skills/create-migration/SKILL.md) の TypeORM CLI 手順を使う。手書き / CLI どちらでも `check-migrations` (pending DDL 検出) さえ通せば等価。
|
||||
|
||||
1. **タイムスタンプ取得**: `node -e "console.log(Date.now())"`
|
||||
2. **ファイル名**: `packages/backend/migration/{timestamp}-{PascalCaseName}.js` (拡張子は `.js`)
|
||||
3. **雛形**:
|
||||
|
||||
```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 を完全に巻き戻す
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
4. **検証**:
|
||||
- `pnpm --filter backend check-migrations` (TypeORM schema builder で pending DDL を検出する。エンティティと migration の不一致が残っているとここで非ゼロ終了する。実体は [scripts/check_migrations_clean.js](../../packages/backend/scripts/check_migrations_clean.js))
|
||||
- `pnpm migrate` (ローカル DB に適用)
|
||||
- `pnpm revert` (ロールバック確認)
|
||||
5. **エンティティとの整合性**: 関連する `src/models/*.ts` の `@Column` / `@Entity` も同時に更新する。
|
||||
|
||||
> マージ済み migration の編集は **絶対禁止** ([AGENTS.md §3](../../AGENTS.md#3-マージ済み-migration-を絶対に編集しない))。
|
||||
|
||||
## テスト
|
||||
|
||||
- Unit: `pnpm --filter backend test` (`vitest.config.unit.ts`)
|
||||
- E2E: `pnpm --filter backend test:e2e` (`vitest.config.e2e.ts`)
|
||||
- Federation: `pnpm --filter backend test:fed` (`vitest.config.fed.ts`)
|
||||
- 配置: `packages/backend/test/` 配下。
|
||||
76
.claude/docs/frontend.md
Normal file
76
.claude/docs/frontend.md
Normal file
@@ -0,0 +1,76 @@
|
||||
# Frontend (`packages/frontend`) 規約
|
||||
|
||||
Vue 3.5 + Vite + Storybook + Cypress E2E。
|
||||
|
||||
## コンポーネント命名
|
||||
|
||||
- 共有 / 再利用コンポーネントは **`Mk` プレフィックス** (例: `MkButton.vue`, `MkInput.vue`, `MkAbuseReport.vue`)。
|
||||
- ページ単位のものは `packages/frontend/src/pages/` 配下に置く。
|
||||
|
||||
## 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>
|
||||
```
|
||||
|
||||
## 国際化 (i18n)
|
||||
|
||||
- 文字列リテラルを直書きしない。
|
||||
- 引数なし: `i18n.ts.<path>` で参照する (例: `i18n.ts.deleted`)。
|
||||
- 引数あり: `i18n.tsx.<path>(...)` で関数呼び出しする (例: `i18n.tsx.takeOverConfirm({ name })`)。
|
||||
- 新規キーは **`locales/ja-JP.yml` のみ** に追加する (他言語は Crowdin で自動配信)。
|
||||
- `i18n` は `packages/frontend/src/i18n.ts` (または共有モジュール) から import する。
|
||||
|
||||
## モーダル / 通知
|
||||
|
||||
- `os.ts` (`packages/frontend/src/os.ts`) 経由で呼ぶ。
|
||||
- `os.alert(...)` / `os.confirm(...)` / `os.popup(...)` / `os.success(...)` など。
|
||||
- ブラウザ標準の `window.alert()` / `window.confirm()` を **直接呼ばない**。
|
||||
|
||||
## アクセシビリティ (PR レビューで指摘されやすい点)
|
||||
|
||||
- クリックハンドラを付けるなら `<button>` を使うか、`role="button"` + `tabindex` を付ける。
|
||||
- フォーム要素には `<label>` または `aria-label` を付ける。
|
||||
- キーボード操作可能であること。
|
||||
|
||||
## Storybook
|
||||
|
||||
新規共有コンポーネントには `<ComponentName>.stories.impl.ts` を併設するのが慣習 (`MkButton.stories.impl.ts` 等の例多数)。
|
||||
|
||||
```bash
|
||||
pnpm --filter frontend storybook-dev # localhost:6006
|
||||
```
|
||||
|
||||
## ビルド・開発
|
||||
|
||||
- 開発: `pnpm dev` (ルート) で backend + frontend が watch で立ち上がる。
|
||||
- ビルド: `pnpm --filter frontend build`
|
||||
- 型チェック: `pnpm --filter frontend typecheck` (vue-tsc)
|
||||
- ESLint: `pnpm --filter frontend eslint`
|
||||
|
||||
## テスト
|
||||
|
||||
- Unit (Vitest): `pnpm --filter frontend test`
|
||||
- Cypress E2E: `pnpm e2e` (ルートから; `start-server-and-test` で起動)
|
||||
28
.claude/docs/plugins.md
Normal file
28
.claude/docs/plugins.md
Normal file
@@ -0,0 +1,28 @@
|
||||
# 有効化済 Claude Code プラグイン
|
||||
|
||||
`.claude/settings.json` で 14 プラグインが有効化されている。それぞれの典型的な利用シーンを 1 行で示す。
|
||||
|
||||
| プラグイン | 用途 |
|
||||
| --- | --- |
|
||||
| `frontend-design` | UI コンポーネント / ページの設計・デザイン作業 (Vue 3 編集に有効) |
|
||||
| `superpowers` | TDD・debugging・brainstorming・planning 等のメタスキル群 |
|
||||
| `context7` | OSS ドキュメントの取得 (Vue 3, NestJS, TypeORM, Vitest 等) — 訓練データの古さを補う |
|
||||
| `code-review` | コードレビュー (`/code-review`) |
|
||||
| `code-simplifier` | コード整理 (`code-simplifier:code-simplifier` サブエージェント経由) |
|
||||
| `github` | GitHub PR / Issue 操作 (gh ベースだが補助コマンドあり) |
|
||||
| `skill-creator` | 新スキルの作成・改善・評価 |
|
||||
| `feature-dev` | 機能開発ガイド (`/feature-dev:feature-dev` / 内部に `code-architect` / `code-explorer` / `code-reviewer` サブエージェント) |
|
||||
| `claude-md-management` | CLAUDE.md の作成・改善 (`/claude-md-management:revise-claude-md` / `claude-md-improver` エージェント) |
|
||||
| `typescript-lsp` | TypeScript LSP 連携 (型情報を活用) |
|
||||
| `security-guidance` | セキュリティレビュー (`/security-review`) |
|
||||
| `pr-review-toolkit` | PR レビュー一式。サブエージェント: `code-reviewer` / `code-simplifier` / `comment-analyzer` / `pr-test-analyzer` / `silent-failure-hunter` / `type-design-analyzer` |
|
||||
| `claude-code-setup` | Claude Code 自動化セットアップ提案 |
|
||||
| `playwright` | ブラウザ自動操作 (フロントエンド動作確認時に有用) |
|
||||
|
||||
## 使い分けの指針
|
||||
|
||||
- **API 関連の調査**: `context7` で対象ライブラリのドキュメントを取得 → 編集。
|
||||
- **PR 作成前**: `pr-review-toolkit` の各エージェント (code-reviewer / silent-failure-hunter 等) を並列で走らせる。
|
||||
- **新機能の設計**: `feature-dev` → brainstorming → 実装の流れ。
|
||||
- **UI 確認**: `playwright` で `pnpm dev` の画面を直接操作。
|
||||
- **将来追加検討**: PostgreSQL MCP — TypeORM + 342 migration の調査効率化。read-only ロールで登録し、接続先 (`misskey` DB) と権限分離に注意する。
|
||||
69
.claude/docs/testing.md
Normal file
69
.claude/docs/testing.md
Normal file
@@ -0,0 +1,69 @@
|
||||
# テスト構成
|
||||
|
||||
## Backend 全般の前提: `.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`) の前提ではない (ポート競合の元になるため使わないこと)。
|
||||
|
||||
## Backend (Vitest 4, 3 設定)
|
||||
|
||||
| 種別 | 設定ファイル | 実行コマンド |
|
||||
| --- | --- | --- |
|
||||
| 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/`
|
||||
- 事前準備は [§Backend 全般の前提: `.config/test.yml`](#backend-全般の前提-configtestyml) を参照。
|
||||
- カバレッジ: `pnpm --filter backend test-and-coverage`
|
||||
|
||||
## Frontend (Vitest)
|
||||
|
||||
```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 でカバー)。
|
||||
|
||||
## E2E (Cypress)
|
||||
|
||||
ルートから実行する:
|
||||
|
||||
```bash
|
||||
pnpm e2e # start:test サーバーを立てて Cypress run
|
||||
pnpm cy:open # 対話的に開く
|
||||
```
|
||||
|
||||
- 設定: ルート `cypress.config.ts`。テスト本体は `cypress/` 配下。
|
||||
|
||||
## Storybook (frontend)
|
||||
|
||||
```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 (テスト・開発共通)
|
||||
|
||||
```bash
|
||||
docker compose -f compose.local-db.yml up -d
|
||||
```
|
||||
18
.claude/settings.json
Normal file
18
.claude/settings.json
Normal file
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"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
|
||||
}
|
||||
}
|
||||
33
.claude/skills/README.md
Normal file
33
.claude/skills/README.md
Normal file
@@ -0,0 +1,33 @@
|
||||
# `.claude/skills/` — プロジェクト固有のカスタムスキル
|
||||
|
||||
Misskey 固有の繰り返しタスクを Claude にスムーズに実行させるための **カスタムスキル** を `.claude/skills/<name>/SKILL.md` 形式で配置する。
|
||||
|
||||
frontmatter (`name` + `description`) は、Claude が **自動でスキルを呼び出すか判断する** 唯一の手がかりになる。`description` には用途を具体的かつ網羅的に書く (動詞 + 対象 + トリガー条件)。
|
||||
|
||||
## 実装済スキル
|
||||
|
||||
### Misskey 固有 (本リポジトリ向け書き起こし)
|
||||
|
||||
| スキル名 | 役割 | 優先度 |
|
||||
| --- | --- | --- |
|
||||
| [create-migration](create-migration/SKILL.md) | TypeORM CLI (`migration:generate` / `migration:create`) でマイグレーションを生成し、SPDX / up-down / `check-migrations` まで誘導 | 高 (342 既存 / 規約厳しい) |
|
||||
| [add-api-endpoint](add-api-endpoint/SKILL.md) | NestJS DI + meta/paramDef 規約で API エンドポイント追加。`endpoint-list.ts` 登録と `misskey-js` 再生成を含む | 高 |
|
||||
| [add-i18n-key](add-i18n-key/SKILL.md) | `locales/ja-JP.yml` のみ編集する補助。型は `packages/i18n` が自動再生成 | 中 |
|
||||
| [add-mk-component](add-mk-component/SKILL.md) | `Mk*` 命名 + SPDX (HTML) + SCSS module + `*.stories.impl.ts` 併設の Vue コンポーネントを一括スキャフォールド | 中 |
|
||||
|
||||
### ECC (everything-claude-code) 由来 — MIT セレクトインポート
|
||||
|
||||
[.claude/THIRD_PARTY_LICENSES.md](../THIRD_PARTY_LICENSES.md) §1 に出典・改変メモ・MIT 全文を集約。
|
||||
|
||||
| スキル名 | 役割 | 優先度 |
|
||||
| --- | --- | --- |
|
||||
| [context-budget](context-budget/SKILL.md) | agents / skills / MCP / CLAUDE.md の token overhead を見える化し、肥大コンポーネントを検出 | 中 |
|
||||
|
||||
設計方針: `create-migration` は手動の `Date.now()` 命名ではなく TypeORM 公式 CLI (`migration:generate` / `migration:create`) を採用。Storybook ファイル名は `*.stories.impl.ts` 規約に準拠する。
|
||||
|
||||
## 新規スキルを追加する場合
|
||||
|
||||
- `.claude/skills/<name>/SKILL.md` に YAML frontmatter (`name` + `description`) と本文 Markdown を書く。
|
||||
- `disable-model-invocation: true` は付けない (auto-invoke させたいため)。
|
||||
- 主要参照ファイルへのリンクは、リポジトリルートからの相対パス (例: `../../packages/backend/...`) で貼る。絶対パスは contributor のホームディレクトリ依存になるので使わない。
|
||||
- 完成したらこの README の表にも 1 行追加する。
|
||||
253
.claude/skills/add-api-endpoint/SKILL.md
Normal file
253
.claude/skills/add-api-endpoint/SKILL.md
Normal file
@@ -0,0 +1,253 @@
|
||||
---
|
||||
name: add-api-endpoint
|
||||
description: Misskey の REST API エンドポイント (/api/<category>/<name>) を NestJS DI + meta/paramDef 規約で追加する。バックエンドに新しい API ルートを足す時に必ず使う。endpoint-list.ts への手動登録、e2e テスト、misskey-js 再生成、CHANGELOG までの一連の手順を含む。
|
||||
---
|
||||
|
||||
# Misskey API エンドポイント追加スキル
|
||||
|
||||
`packages/backend/src/server/api/endpoints/<category>/<name>.ts` に新規エンドポイントを追加するためのワークフロー。**手順 4 (endpoint-list.ts 登録) を忘れると 404 になる** 点に最大の注意を払う。
|
||||
|
||||
## 最重要事実 (見落とすと壊れる)
|
||||
|
||||
1. エンドポイントは **glob 自動収集されない**。[packages/backend/src/server/api/endpoint-list.ts](../../../packages/backend/src/server/api/endpoint-list.ts) への 1 行追加が必須。
|
||||
2. `meta` / `paramDef` を変えたら **misskey-js の再生成が必須**。`pnpm build-misskey-js-with-types` を忘れると CI の `check-misskey-js-autogen` で必ず落ちる。
|
||||
3. `meta.errors` の各 `id` は **UUID**。重複させない (既存全 UUID と衝突確認)。
|
||||
|
||||
## ステップ 1: ファイル配置と SPDX
|
||||
|
||||
`packages/backend/src/server/api/endpoints/<category>/<name>.ts` に新規作成する。`<category>` は機能領域 (例: `notes`, `users`, `admin/announcements`)。
|
||||
|
||||
冒頭に SPDX ヘッダーを必ず付ける:
|
||||
|
||||
```ts
|
||||
/*
|
||||
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
```
|
||||
|
||||
## ステップ 2: 最小テンプレート (シンプル read 系)
|
||||
|
||||
[endpoints/ping.ts](../../../packages/backend/src/server/api/endpoints/ping.ts) をベースに書く。認証不要・パラメータなし・小さなレスポンスの例:
|
||||
|
||||
```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) => {
|
||||
// 実装
|
||||
});
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## ステップ 3: 認証付き / DI / errors を含むテンプレート
|
||||
|
||||
[endpoints/notes/create.ts](../../../packages/backend/src/server/api/endpoints/notes/create.ts) を参照する。要点:
|
||||
|
||||
```ts
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||
import { ApiError } from '@/server/api/error.js';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
// import ms from 'ms'; // limit.duration に ms('1hour') 等を渡すとき (default import)
|
||||
|
||||
export const meta = {
|
||||
tags: ['notes'],
|
||||
requireCredential: true, // 認証必須なら true
|
||||
prohibitMoved: false, // moved user を拒否するか
|
||||
kind: 'write:notes', // OAuth scope (requireCredential 時に必須)
|
||||
limit: {
|
||||
duration: 3600000, // ms('1hour')
|
||||
max: 300,
|
||||
},
|
||||
errors: {
|
||||
noSuchNote: {
|
||||
message: 'No such note.',
|
||||
code: 'NO_SUCH_NOTE',
|
||||
id: 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx', // ★ UUID v4 を必ず生成 (`x`=hex, `y`=8/9/a/b)。下の「UUID 生成」を参照
|
||||
},
|
||||
},
|
||||
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) => {
|
||||
const note = await this.notesRepository.findOneBy({ id: ps.noteId });
|
||||
if (note == null) throw new ApiError(meta.errors.noSuchNote);
|
||||
// 実装
|
||||
});
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### meta フィールド早見表
|
||||
|
||||
| フィールド | 用途 |
|
||||
|---|---|
|
||||
| `tags` | OpenAPI タグ (機能領域) |
|
||||
| `requireCredential` | 認証必須か |
|
||||
| `requireModerator` / `requireAdmin` | 権限制限 |
|
||||
| `prohibitMoved` | アカウント移行済ユーザーを拒否 |
|
||||
| `kind` | OAuth scope (`read:notes` / `write:notes` 等)。`requireCredential: true` 時必須 |
|
||||
| `limit` | レート制限 (`{ duration, max, key?, minInterval? }`) |
|
||||
| `errors` | エラー定義。各要素に `message` / `code` / `id` (UUID v4) 必須 |
|
||||
| `res` | JSON Schema or `ref: '<EntityName>'` (packed entity 参照) |
|
||||
| `requireFile` | ファイルアップロード必須 |
|
||||
| `secure` | secure cookie 必要 |
|
||||
| `allowGet` | GET メソッド許可 |
|
||||
| `cacheSec` | レスポンスキャッシュ秒数 |
|
||||
| `description` | OpenAPI 説明 |
|
||||
|
||||
詳細は [endpoints.ts](../../../packages/backend/src/server/api/endpoints.ts) の型定義 (lines 11-125) を参照。
|
||||
|
||||
### paramDef の特殊フォーマット
|
||||
|
||||
JSON Schema (AJV) ベースだが、Misskey 拡張を使える:
|
||||
|
||||
- `format: 'misskey:id'` — ID 文字列バリデーション
|
||||
- `allOf` / `anyOf` / `oneOf` — 複合条件
|
||||
- `default` — デフォルト値
|
||||
|
||||
詳細は [endpoint-base.ts](../../../packages/backend/src/server/api/endpoint-base.ts) を参照。
|
||||
|
||||
### エラー throw
|
||||
|
||||
**「公開 API エラーとして API クライアントに返したいもの」は必ず `throw new ApiError(meta.errors.<key>)` を使う**。`meta.errors` に列挙した上で `ApiError` でラップしないと、misskey-js 側の型情報に出ず、レスポンスも 500 になる。第 2 引数で追加情報を渡せる:
|
||||
|
||||
```ts
|
||||
throw new ApiError(meta.errors.invalidParam, { reason: 'too short' });
|
||||
```
|
||||
|
||||
一方で、**想定外の例外 (DB 不整合 / 下層サービスの bug など) を握り潰すために `try/catch` で `ApiError` に変換するのは避ける**。既存 endpoint も「期待される業務エラーは `ApiError` に変換し、それ以外は `throw err;` で再 throw する」という二段構えになっている。`packages/backend/src/server/api/endpoints/notes/create.ts` の `catch` 節 (末尾の `throw err;`) を参照。生の `throw` を全面禁止すると未知例外も 200 で潰れて debug が困難になるので、このバランスを保つ。
|
||||
|
||||
詳細は [error.ts](../../../packages/backend/src/server/api/error.ts) の `ApiError` クラスを参照。
|
||||
|
||||
### UUID 生成
|
||||
|
||||
```bash
|
||||
node -e "console.log(crypto.randomUUID())"
|
||||
```
|
||||
|
||||
その UUID が他のエンドポイントの `id` と衝突していないか必ず確認:
|
||||
|
||||
```bash
|
||||
grep -r "id: '<生成した UUID>'" packages/backend/src/server/api/endpoints/
|
||||
```
|
||||
|
||||
## ステップ 4: ★必須 — endpoint-list.ts に登録
|
||||
|
||||
[packages/backend/src/server/api/endpoint-list.ts](../../../packages/backend/src/server/api/endpoint-list.ts) の同カテゴリ末尾に 1 行追加する(既存の並びを崩さない):
|
||||
|
||||
```ts
|
||||
export * as '<category>/<name>' from './endpoints/<category>/<name>.js';
|
||||
```
|
||||
|
||||
ファイル冒頭のコメント (`When you add new endpoint, you should add it to this file.`) の通り、このリストが API ルーティングの単一の真実。**忘れると 404**。
|
||||
|
||||
`EndpointsModule.ts` がこのファイルの全エクスポートを `Object.entries()` で反復し、NestJS provider (`provide: 'ep:<path>'`) を生成する。
|
||||
|
||||
## ステップ 5: e2e テスト追加
|
||||
|
||||
[packages/backend/test/e2e/endpoints.ts](../../../packages/backend/test/e2e/endpoints.ts) に対応する `describe` / `test` を追加する。`api()` ヘルパーで叩く:
|
||||
|
||||
```ts
|
||||
describe('<category>/<name>', () => {
|
||||
test('正常系', async () => {
|
||||
const res = await api('<category>/<name>', { /* params */ }, alice);
|
||||
assert.strictEqual(res.status, 200);
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
実行: `pnpm --filter backend test:e2e`
|
||||
|
||||
## ステップ 6: misskey-js 再生成 (★必須)
|
||||
|
||||
`meta` / `paramDef` / `res` を変えたら必ず実行する:
|
||||
|
||||
```bash
|
||||
pnpm build-misskey-js-with-types
|
||||
```
|
||||
|
||||
これで以下が更新される:
|
||||
|
||||
- `packages/backend/built/api.json` (OpenAPI spec)
|
||||
- `packages/misskey-js/generator/api.json`
|
||||
- `packages/misskey-js/src/autogen/*.ts` (TypeScript 型)
|
||||
|
||||
PR に `packages/misskey-js/src/autogen/` 配下の差分が含まれていないと、CI の `check-misskey-js-autogen` で落ちる。
|
||||
|
||||
## ステップ 7: Lint と typecheck
|
||||
|
||||
```bash
|
||||
pnpm --filter backend lint
|
||||
```
|
||||
|
||||
(typecheck = `tsgo --noEmit` / ESLint = `eslint`)
|
||||
|
||||
## ステップ 8: CHANGELOG
|
||||
|
||||
ユーザー影響がある (新機能 / 既存挙動変更) なら、`CHANGELOG.md` の `## Unreleased` → `### Server` に 1 行追加する ([AGENTS.md §CHANGELOG](../../../AGENTS.md#changelog) 参照):
|
||||
|
||||
```
|
||||
- Feat: /api/<category>/<name> を追加
|
||||
```
|
||||
|
||||
純粋なリファクタや内部用なら不要。
|
||||
|
||||
## 参照ファイル
|
||||
|
||||
- [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)
|
||||
- [test/e2e/endpoints.ts (テスト例)](../../../packages/backend/test/e2e/endpoints.ts)
|
||||
- [scripts/generate_api_json.js (misskey-js 生成元)](../../../packages/backend/scripts/generate_api_json.js)
|
||||
115
.claude/skills/add-i18n-key/SKILL.md
Normal file
115
.claude/skills/add-i18n-key/SKILL.md
Normal file
@@ -0,0 +1,115 @@
|
||||
---
|
||||
name: add-i18n-key
|
||||
description: Misskey の i18n キーを追加・修正する。locales/ja-JP.yml のみ編集可能で、他言語ファイル (en-US.yml 等 39 言語) は Crowdin の自動配信先のため絶対に触らない。型は packages/i18n が ja-JP.yml から自動再生成する。frontend からは i18n.ts.<key> または i18n.tsx.<key>(...) で参照する。
|
||||
---
|
||||
|
||||
# Misskey 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 翻訳資産も道連れになるので慎重に。追加・改名併用 (新キー追加 → 移行 → 旧キー削除) を検討する。
|
||||
|
||||
## ステップ 1: ja-JP.yml にキーを追加
|
||||
|
||||
[locales/ja-JP.yml](../../../locales/ja-JP.yml) を編集する。YAML の階層構造を維持し、関連するセクションに配置する:
|
||||
|
||||
```yaml
|
||||
# トップレベル単純キー
|
||||
save: "保存"
|
||||
|
||||
# ネストしたカテゴリ (アンダースコア接頭辞は内部カテゴリ)
|
||||
_settings:
|
||||
general: "全般"
|
||||
appearance: "外観"
|
||||
|
||||
# パラメータ付き (ICU MessageFormat 互換)
|
||||
greeting: "こんにちは、{name}さん"
|
||||
```
|
||||
|
||||
### 命名のお作法
|
||||
|
||||
- 単純キー: lowerCamelCase (例: `saveChanges`, `confirmDelete`)。
|
||||
- カテゴリ: アンダースコア接頭辞 (例: `_settings`, `_abuseUserReport`)。
|
||||
- 既存セクション内に置く場合はアルファベット順を維持する (新セクション全体を末尾に追加するのは可)。
|
||||
|
||||
## ステップ 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 スクリプトが 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` で発覚する。
|
||||
|
||||
## ステップ 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` は MessageFormat 関数。
|
||||
|
||||
## ステップ 4: 検証
|
||||
|
||||
```bash
|
||||
# i18n パッケージの型再生成 + typecheck
|
||||
pnpm --filter i18n lint
|
||||
|
||||
# frontend で新キー参照箇所の型チェック
|
||||
pnpm --filter frontend lint
|
||||
```
|
||||
|
||||
## 例: 「ノートを削除しますか?」確認ダイアログを追加する
|
||||
|
||||
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)
|
||||
174
.claude/skills/add-mk-component/SKILL.md
Normal file
174
.claude/skills/add-mk-component/SKILL.md
Normal file
@@ -0,0 +1,174 @@
|
||||
---
|
||||
name: add-mk-component
|
||||
description: Misskey フロントエンドの新規 Vue 3 コンポーネントを追加する。Mk* 命名 / SPDX (HTML コメント) / <script setup lang="ts"> / <style lang="scss" module> / *.stories.impl.ts 併設の規約をまとめて適用する。新しい共有 UI コンポーネントを packages/frontend/src/components/ に作る時に使う。
|
||||
---
|
||||
|
||||
# Misskey Vue コンポーネント追加スキル
|
||||
|
||||
`packages/frontend/src/components/` に新しい共有コンポーネントを追加するための規約。
|
||||
|
||||
## 大前提
|
||||
|
||||
- 共有 / 再利用コンポーネントは **必ず `Mk` プレフィックス** (例: `MkButton`, `MkInput`)。ページ固有部品など `Mk` プレフィックスでないものは原則 `pages/` 側に置く。
|
||||
- 新規では `<style lang="scss" module>` (CSS Modules) を既定とする。古い `scoped` 形式が混在しているが、新規では使わない。
|
||||
- 文字列リテラルの直書きは禁止。文言は必ず `i18n.ts.<key>` 経由で参照する (新キーは `add-i18n-key` スキルを参照)。
|
||||
- `alert()` / `confirm()` / `window.prompt()` は使わない。`os.alert` / `os.confirm` / `os.popup` などを使う。
|
||||
|
||||
## ステップ 1: ファイル配置
|
||||
|
||||
`packages/frontend/src/components/Mk<Name>.vue` に新規作成する。
|
||||
|
||||
ストーリーが必要 (= ほぼ常に必要) なら、同階層に `Mk<Name>.stories.impl.ts` も作る。Storybook の規約は `*.stories.impl.ts` であって、`*.stories.ts` ではない。
|
||||
|
||||
## ステップ 2: SPDX ヘッダー (HTML コメント形式)
|
||||
|
||||
`.vue` ファイル冒頭に必須:
|
||||
|
||||
```html
|
||||
<!--
|
||||
SPDX-FileCopyrightText: syuilo and misskey-project
|
||||
SPDX-License-Identifier: AGPL-3.0-only
|
||||
-->
|
||||
```
|
||||
|
||||
`/* ... */` (TS / JS 形式) ではなく **HTML コメント** で書くこと。既存の `.vue` ファイルがすべて HTML コメント形式を使っており、SFC の先頭として自然な形式に統一するため (CI の `spdx` ジョブはコメント形式ではなく SPDX 文字列の有無のみを検査する)。
|
||||
|
||||
## ステップ 3: 最小テンプレート
|
||||
|
||||
[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">
|
||||
<slot></slot>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
const props = defineProps<{
|
||||
variant?: 'primary' | 'secondary';
|
||||
}>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
(ev: 'click'): void;
|
||||
}>();
|
||||
</script>
|
||||
|
||||
<style lang="scss" module>
|
||||
.root {
|
||||
padding: 12px 14px;
|
||||
border-radius: var(--MI-radius);
|
||||
background: var(--MI_THEME-panel);
|
||||
}
|
||||
</style>
|
||||
```
|
||||
|
||||
### 規約ポイント
|
||||
|
||||
| 項目 | 規約 |
|
||||
|---|---|
|
||||
| `<script>` | `<script lang="ts" setup>`。型パラメータが必要なら `generic="T extends ..."` を付ける ([MkInput.vue 参照](../../../packages/frontend/src/components/MkInput.vue)) |
|
||||
| `defineProps` / `defineEmits` | **type-only** (`<{ ... }>`) 形式。runtime の object 形式は使わない |
|
||||
| `<style>` | `lang="scss" module` を既定。クラス参照は `:class="$style.foo"` |
|
||||
| CSS 変数 | `var(--MI_THEME-...)` (テーマ) / `var(--MI-radius)` (UI 共通) — ハードコードしない |
|
||||
| アイコン | Tabler icons のクラス (`<i class="ti ti-info-circle">`) を使う |
|
||||
|
||||
## ステップ 4: i18n と os の利用
|
||||
|
||||
```vue
|
||||
<script lang="ts" setup>
|
||||
import { i18n } from '@/i18n.js';
|
||||
import * as os from '@/os.js';
|
||||
|
||||
async function onClick() {
|
||||
const { canceled } = await os.confirm({
|
||||
type: 'warning',
|
||||
text: i18n.ts._notes.deleteConfirm,
|
||||
});
|
||||
if (canceled) return;
|
||||
os.toast(i18n.ts.deleted);
|
||||
}
|
||||
</script>
|
||||
```
|
||||
|
||||
### `os` の主なヘルパー (詳細は [os.ts](../../../packages/frontend/src/os.ts))
|
||||
|
||||
| 関数 | 用途 |
|
||||
|---|---|
|
||||
| `os.alert({ type, title?, text })` | 単方向アラート |
|
||||
| `os.confirm({ type, title, text })` | yes/no 確認 (`{ canceled }` を返す) |
|
||||
| `os.toast(message)` | 一時通知 |
|
||||
| `os.popup(component, props, handlers)` | 任意コンポーネントの非同期ポップアップ |
|
||||
| `os.popupMenu(items, anchor?)` | コンテキストメニュー |
|
||||
| `os.form(title, fields)` | フォームダイアログ |
|
||||
| `os.apiWithDialog(endpoint, data)` | API 呼出し + エラー時ダイアログ表示 |
|
||||
|
||||
## ステップ 5: Storybook ストーリー併設
|
||||
|
||||
[MkButton.stories.impl.ts](../../../packages/frontend/src/components/MkButton.stories.impl.ts) を雛形として参考にする。`.stories.impl.ts` も `packages/frontend/src/` 配下の `.ts` ファイルなので [AGENTS.md §1 SPDX ヘッダー必須](../../../AGENTS.md#1-spdx-ヘッダー必須) の対象であり、冒頭に SPDX ヘッダーを必ず付ける (HTML コメント形式ではなく `/* */` 形式)。形式 (以下の `MkXxx` は実際のコンポーネント名に置換する):
|
||||
|
||||
```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 MkXxx from './MkXxx.vue';
|
||||
|
||||
export const Default = {
|
||||
render(args) {
|
||||
return {
|
||||
components: { MkXxx },
|
||||
setup() {
|
||||
return { args };
|
||||
},
|
||||
template: '<MkXxx v-bind="args">slot content</MkXxx>',
|
||||
};
|
||||
},
|
||||
args: {
|
||||
variant: 'primary',
|
||||
},
|
||||
parameters: {
|
||||
layout: 'centered',
|
||||
},
|
||||
} satisfies StoryObj<typeof MkXxx>;
|
||||
```
|
||||
|
||||
`Vue` SFC は default export なので、`import MkXxx from './MkXxx.vue';` のように名前付き import ではなく default import で書く。実行確認は `pnpm --filter frontend storybook-dev`。
|
||||
|
||||
## ステップ 6: Lint と typecheck
|
||||
|
||||
```bash
|
||||
pnpm --filter frontend lint
|
||||
```
|
||||
|
||||
(typecheck = vue-tsc 等、ESLint = `@misskey-dev/eslint-plugin` 含む)
|
||||
|
||||
ESLint --fix をピンポイントで:
|
||||
|
||||
```bash
|
||||
pnpm exec eslint --fix packages/frontend/src/components/Mk<Name>.vue
|
||||
```
|
||||
|
||||
## ステップ 7: 既存コンポーネントとの整合性確認
|
||||
|
||||
- 似た用途の既存 `Mk*` コンポーネントを参考に、スタイルやプロップ命名を揃える。
|
||||
- `_button` / `_panel` / `_selectable` などの **共通 utility class** (グローバルスタイルにある) を活用できるか確認する。
|
||||
- 大きな機能なら、Storybook stories で各バリエーションを網羅する。
|
||||
|
||||
## 参照ファイル
|
||||
|
||||
- [MkInfo.vue (シンプル例)](../../../packages/frontend/src/components/MkInfo.vue)
|
||||
- [MkButton.vue (汎用ボタン例)](../../../packages/frontend/src/components/MkButton.vue)
|
||||
- [MkInput.vue (generics + 多機能例)](../../../packages/frontend/src/components/MkInput.vue)
|
||||
- [MkButton.stories.impl.ts (Storybook 雛形)](../../../packages/frontend/src/components/MkButton.stories.impl.ts)
|
||||
- [packages/frontend/src/os.ts](../../../packages/frontend/src/os.ts)
|
||||
- [packages/frontend/src/i18n.ts](../../../packages/frontend/src/i18n.ts)
|
||||
148
.claude/skills/context-budget/SKILL.md
Normal file
148
.claude/skills/context-budget/SKILL.md
Normal file
@@ -0,0 +1,148 @@
|
||||
---
|
||||
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 を使う。
|
||||
156
.claude/skills/create-migration/SKILL.md
Normal file
156
.claude/skills/create-migration/SKILL.md
Normal file
@@ -0,0 +1,156 @@
|
||||
---
|
||||
name: create-migration
|
||||
description: Misskey の TypeORM マイグレーションを公式 CLI (migration:generate / migration:create) で正しく生成し、SPDX ヘッダー付与・up/down 整合・check-migrations 確認まで誘導する。エンティティのスキーマ変更を含むあらゆる DB 変更、または手書き SQL によるデータ移行が必要な時に使用する。
|
||||
---
|
||||
|
||||
# Misskey マイグレーション作成スキル
|
||||
|
||||
`packages/backend/migration/` に新規 TypeORM マイグレーションを追加するためのワークフロー。
|
||||
|
||||
## 大前提 (絶対 NG)
|
||||
|
||||
- **既にマージ済み (develop / master) のマイグレーションファイルを編集しない** ([AGENTS.md §3](../../../AGENTS.md#3-マージ済み-migration-を絶対に編集しない))。本番履歴の改変は深刻なデータ不整合を引き起こす。スキーマ変更は **常に新しいタイムスタンプで新規ファイル** を作る。
|
||||
- ファイル名のタイムスタンプ部分を後から書き換えない (順序が壊れる)。
|
||||
|
||||
> 作り方は AGENTS.md §3 の「`Date.now()` で UNIX ms を取得 → `{ms}-{PascalName}.js` を手書き」が最低ライン。エンティティ差分から自動生成したい (= TypeORM の `migration:generate` を使う) 場合は本 skill の手順に従う。**どちらでも構わない**が、エンティティ変更を伴う時は CLI 経由のほうが取り漏れが減るので推奨。
|
||||
|
||||
## ステップ 1: どちらの方式を使うか決める
|
||||
|
||||
| 状況 | 方式 |
|
||||
|---|---|
|
||||
| エンティティ (`packages/backend/src/models/*.ts`) を `@Column` / `@Index` / `@Entity` 等で先に変更し、差分から自動生成したい | `typeorm migration:generate` (本 skill の手順) |
|
||||
| 手書き SQL / データ移行 / `CREATE INDEX CONCURRENTLY` など、エンティティ差分では表現できない変更 | `typeorm migration:create` で空雛形を作るか、`migrate-new` command で手書き雛形を作る |
|
||||
| 列追加 1 本のような小規模変更で、既存ファイルをコピーした方が速い | AGENTS.md §3 の手順 (`Date.now()` + 手書き) でよい |
|
||||
|
||||
迷ったら **まずエンティティを変更 → `migration:generate`** が原則。既存 342 ファイルのほぼすべてが `queryRunner.query(\`SQL...\`)` の raw SQL なので、CLI 出力でも手書きでもスタイルは揃う。
|
||||
|
||||
## ステップ 2: CLI 実行
|
||||
|
||||
ルートディレクトリから以下を実行する。`<PascalName>` は変更内容を表す PascalCase (例: `AddBirthdayIndex`, `AddCategoryToAvatarDecorations`)。
|
||||
|
||||
### 2-A. エンティティ差分から生成
|
||||
|
||||
[CONTRIBUTING.md §Migration作成方法](../../../CONTRIBUTING.md#migration作成方法) に記載の基本形:
|
||||
|
||||
```bash
|
||||
# packages/backend ディレクトリで実行する場合 (CONTRIBUTING.md 記載形式)
|
||||
pnpm dlx typeorm migration:generate -d ormconfig.js -o --esm <PascalName>
|
||||
```
|
||||
|
||||
**リポジトリルートから実行する場合** (AI が使う推奨形式。`pnpm --filter backend exec` を使うと backend の TypeORM バージョンと一致するため確実):
|
||||
|
||||
```bash
|
||||
pnpm --filter backend exec typeorm migration:generate -d ormconfig.js -o --esm migration/<PascalName>
|
||||
```
|
||||
|
||||
> **`--esm` について**: `-o` / `--outputJs` は「TS ではなく JS を出力する」オプション、`--esm` は「ESM 形式 (`export class ...`) で出力する」オプション。Misskey の既存 migration はすべて ESM JS であるため **両方が必須**。`--esm` を省略すると CommonJS 形式の JS が生成されスタイルが揃わない。
|
||||
|
||||
事前準備:
|
||||
|
||||
- `pnpm build-pre` を実行して `built/meta.json` を生成する (`loadConfig()` が `built/meta.json` を必須とするため。`pnpm build` 済みであれば不要)。
|
||||
- `.config/default.yml` が存在すること (なければ `.config/example.yml` を参考に作成する)。
|
||||
- `pnpm --filter backend compile-config` を実行して `built/.config.json` を生成する (`ormconfig.js` が `loadConfig()` 経由で必須とする。未実行だと "Compiled configuration file not found." エラーになる)。
|
||||
- `pnpm --filter backend build` でエンティティを最新ビルド (CLI は `built/` を読む)。
|
||||
- ローカル DB を起動する (`docker compose -f compose.local-db.yml up -d`)。
|
||||
|
||||
### 2-B. 空の手書きマイグレーション
|
||||
|
||||
```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 形式なので、後で手作業で `.ts → .js` リネーム + `import { MigrationInterface }` 削除 + `class ... implements MigrationInterface` 削除をしないと走らない。`-o --esm` を付ければそのまま `.js` ESM で出るので、後処理は SPDX ヘッダー付与 (ステップ 3) だけで済む。
|
||||
|
||||
## ステップ 3: SPDX ヘッダー付与
|
||||
|
||||
CLI 出力には SPDX ヘッダーが含まれない。**必ず冒頭に追加する** (CI の `spdx` ジョブが失敗するため)。
|
||||
|
||||
```js
|
||||
/*
|
||||
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
```
|
||||
|
||||
## ステップ 4: up / down の整合確認
|
||||
|
||||
- `up()` の各ステートメントに対し、`down()` で完全に巻き戻せること。
|
||||
- 列追加 (`ADD COLUMN`) ↔ 列削除 (`DROP COLUMN`)、テーブル作成 ↔ テーブル削除、
|
||||
FK 追加 ↔ FK 削除、インデックス作成 ↔ インデックス削除 を必ずペアで書く。
|
||||
- `down()` を空のまま残さない。本番ロールバック時に詰む。
|
||||
|
||||
### インデックス追加時の注意 (CREATE INDEX CONCURRENTLY)
|
||||
|
||||
大規模テーブルへの `CREATE INDEX` は本番で長時間ロックする恐れがある。`CONCURRENTLY` で発行するときは **migration 側にも対応が必要**: PostgreSQL は `CREATE INDEX CONCURRENTLY` を transaction 内で実行できないため、migration class に以下を仕込んで TypeORM に「この migration は transaction を張らない」と指示する。
|
||||
|
||||
参照実装: [packages/backend/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'` (各 migration が個別 transaction)、未設定時は `'all'` (全 migration を 1 つの transaction でラップ) ([ormconfig.js:19](../../../packages/backend/ormconfig.js#L19))。普段は `'all'` 前提なので、CONCURRENTLY を使う migration を書く時だけこのフラグの存在を意識すれば良い。
|
||||
|
||||
### 関連エンティティとの一致
|
||||
|
||||
`migration:generate` を使った場合、エンティティ側の `@Column` / `@Entity` 修正と DB スキーマが食い違うとビルド全体がズレる。生成後に該当エンティティと SQL の対応を目視確認すること。
|
||||
|
||||
## ステップ 5: 検証
|
||||
|
||||
ルートから実行:
|
||||
|
||||
```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 が同期しているか」の検査。
|
||||
|
||||
## ステップ 6: 既存ファイル参照テンプレ
|
||||
|
||||
新規ファイルを書くときは、変更パターンが近い既存ファイルを **必ずひとつ開いて並べて書く**。スタイルが激しくズレた PR は差し戻されやすい。
|
||||
|
||||
| パターン | 参照ファイル |
|
||||
|---|---|
|
||||
| インデックス追加 + 関数定義 | [packages/backend/migration/1767169026317-birthday-index.js](../../../packages/backend/migration/1767169026317-birthday-index.js) |
|
||||
| 列追加のみ | [packages/backend/migration/1766652173085-add-category-to-avatar-decorations.js](../../../packages/backend/migration/1766652173085-add-category-to-avatar-decorations.js) |
|
||||
| テーブル新規作成 + FK | [packages/backend/migration/1761569941833-add-channel-muting.js](../../../packages/backend/migration/1761569941833-add-channel-muting.js) |
|
||||
|
||||
クラス命名規則は **PascalCase 名 + 13 桁タイムスタンプ** (例: `class BirthdayIndex1767169026317`)。`name` プロパティもクラス名と同一文字列にする。
|
||||
|
||||
## ステップ 7: CHANGELOG (ユーザー影響がある場合)
|
||||
|
||||
スキーマ変更がユーザーに見える挙動を生む場合のみ、`CHANGELOG.md` の `## Unreleased` → `### Server` または `### General` に 1 行追加する ([AGENTS.md §CHANGELOG](../../../AGENTS.md#changelog) 参照)。内部リファクタや純粋なインデックス追加は不要。
|
||||
@@ -182,6 +182,9 @@ id: 'aidx'
|
||||
# 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
|
||||
|
||||
@@ -194,6 +194,9 @@ id: 'aidx'
|
||||
# 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
|
||||
|
||||
@@ -328,6 +328,9 @@ id: 'aidx'
|
||||
# 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
|
||||
|
||||
@@ -36,7 +36,7 @@ services:
|
||||
POSTGRES_PASSWORD: postgres
|
||||
POSTGRES_DB: misskey
|
||||
volumes:
|
||||
- postgres-data:/var/lib/postgresql/data
|
||||
- postgres-data:/var/lib/postgresql
|
||||
healthcheck:
|
||||
test: "pg_isready -U $$POSTGRES_USER -d $$POSTGRES_DB"
|
||||
interval: 5s
|
||||
|
||||
@@ -19,7 +19,6 @@
|
||||
"editorconfig.editorconfig",
|
||||
"dbaeumer.vscode-eslint",
|
||||
"Vue.volar",
|
||||
"Orta.vscode-jest",
|
||||
"dbaeumer.vscode-eslint",
|
||||
"mrmlnc.vscode-json5"
|
||||
]
|
||||
|
||||
@@ -169,6 +169,9 @@ id: 'aidx'
|
||||
# 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
|
||||
|
||||
53
.github/copilot-instructions.md
vendored
53
.github/copilot-instructions.md
vendored
@@ -1,3 +1,54 @@
|
||||
# Copilot Instructions for Misskey
|
||||
|
||||
- en-US.yml を編集しないでください。
|
||||
このファイルは GitHub Copilot の repository-wide instructions として使われる。Copilot code review では `AGENTS.md` が読まれない環境があるため、レビューや軽微な実装判断に必要な規約はこのファイル単体で満たすこと。
|
||||
|
||||
このリポジトリは Misskey の pnpm workspace モノレポ。主要な実装は `packages/backend` (NestJS / TypeORM) と `packages/frontend` (Vue 3) にある。より詳しいガイドはリポジトリルートの `AGENTS.md` を参照してよいが、このファイルの要件を省略してそちらへの参照だけで済ませないこと。
|
||||
|
||||
## Always follow
|
||||
|
||||
- AGPL-3.0-only 管轄かつ SPDX CI 対象ディレクトリに新規 `.ts` / `.js` / `.cjs` / `.mjs` / `.scss` ファイルを追加する場合は、必ず次の SPDX ヘッダーを付ける。詳細な対象判定は `AGENTS.md` と `.github/workflows/check-spdx-license-id.yml` を参照すること。
|
||||
|
||||
```text
|
||||
/*
|
||||
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
```
|
||||
|
||||
- AGPL-3.0-only 管轄かつ SPDX CI 対象ディレクトリに新規 `.vue` / `.html` ファイルを追加する場合は、必ず次の SPDX ヘッダーを付ける。
|
||||
|
||||
```text
|
||||
<!--
|
||||
SPDX-FileCopyrightText: syuilo and misskey-project
|
||||
SPDX-License-Identifier: AGPL-3.0-only
|
||||
-->
|
||||
```
|
||||
|
||||
`packages/misskey-js` は MIT ライセンスのサブパッケージなので、この AGPL ヘッダーを一律に付けない。サブパッケージ固有の `package.json` / `LICENSE` / 既存ファイルのヘッダーに従う。
|
||||
|
||||
- `locales/` 配下の YAML は `ja-JP.yml` のみ手動編集してよい。他言語は Crowdin の自動配信先なので手動編集しないこと。
|
||||
- `packages/backend/migration/{timestamp}-*.js` のうち、既にマージ済みの migration は絶対に編集しない。スキーマ変更が必要な場合は新しい timestamp で migration を追加し、`up()` と `down()` の両方を実装すること。
|
||||
- ユーザー影響のある変更は `CHANGELOG.md` の `## Unreleased` 配下の `### General` / `### Client` / `### Server` のいずれかに 1 行追加する。内部リファクタのみなら不要。
|
||||
- API 変更時は `pnpm build-misskey-js-with-types` の実行が必要になる。
|
||||
|
||||
## 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`
|
||||
|
||||
> **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` はより詳細な正典だが、Copilot code review ではこのファイルが主な入口になる。両方が読まれる環境では `AGENTS.md` を補助情報として使ってよい。
|
||||
|
||||
3
.github/dependabot.yml
vendored
3
.github/dependabot.yml
vendored
@@ -34,9 +34,6 @@ updates:
|
||||
patterns:
|
||||
- "storybook*"
|
||||
- "@storybook/*"
|
||||
swc-core:
|
||||
patterns:
|
||||
- "@swc/core*"
|
||||
typescript-eslint:
|
||||
patterns:
|
||||
- "@typescript-eslint/*"
|
||||
|
||||
4
.github/workflows/api-misskey-js.yml
vendored
4
.github/workflows/api-misskey-js.yml
vendored
@@ -19,10 +19,10 @@ jobs:
|
||||
uses: actions/checkout@v6.0.2
|
||||
|
||||
- name: Setup pnpm
|
||||
uses: pnpm/action-setup@v4.4.0
|
||||
uses: pnpm/action-setup@v6.0.3
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v6.3.0
|
||||
uses: actions/setup-node@v6.4.0
|
||||
with:
|
||||
node-version-file: '.node-version'
|
||||
cache: 'pnpm'
|
||||
|
||||
2
.github/workflows/changelog-check.yml
vendored
2
.github/workflows/changelog-check.yml
vendored
@@ -14,7 +14,7 @@ jobs:
|
||||
- name: Checkout head
|
||||
uses: actions/checkout@v6.0.2
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v6.3.0
|
||||
uses: actions/setup-node@v6.4.0
|
||||
with:
|
||||
node-version-file: '.node-version'
|
||||
|
||||
|
||||
12
.github/workflows/check-misskey-js-autogen.yml
vendored
12
.github/workflows/check-misskey-js-autogen.yml
vendored
@@ -25,11 +25,11 @@ jobs:
|
||||
ref: refs/pull/${{ github.event.pull_request.number }}/merge
|
||||
|
||||
- name: setup pnpm
|
||||
uses: pnpm/action-setup@v4
|
||||
uses: pnpm/action-setup@v6
|
||||
|
||||
- name: setup node
|
||||
id: setup-node
|
||||
uses: actions/setup-node@v6.3.0
|
||||
uses: actions/setup-node@v6.4.0
|
||||
with:
|
||||
node-version-file: '.node-version'
|
||||
cache: pnpm
|
||||
@@ -53,7 +53,7 @@ jobs:
|
||||
|
||||
# packages/misskey-js/generator/built/autogen
|
||||
- name: Upload Generated
|
||||
uses: actions/upload-artifact@v6
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: generated-misskey-js
|
||||
path: packages/misskey-js/generator/built/autogen
|
||||
@@ -73,7 +73,7 @@ jobs:
|
||||
ref: refs/pull/${{ github.event.pull_request.number }}/merge
|
||||
|
||||
- name: Upload From Merged
|
||||
uses: actions/upload-artifact@v6
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: actual-misskey-js
|
||||
path: packages/misskey-js/src/autogen
|
||||
@@ -86,13 +86,13 @@ jobs:
|
||||
pull-requests: write
|
||||
steps:
|
||||
- name: download generated-misskey-js
|
||||
uses: actions/download-artifact@v7
|
||||
uses: actions/download-artifact@v8
|
||||
with:
|
||||
name: generated-misskey-js
|
||||
path: misskey-js-generated
|
||||
|
||||
- name: download actual-misskey-js
|
||||
uses: actions/download-artifact@v7
|
||||
uses: actions/download-artifact@v8
|
||||
with:
|
||||
name: actual-misskey-js
|
||||
path: misskey-js-actual
|
||||
|
||||
14
.github/workflows/docker-develop.yml
vendored
14
.github/workflows/docker-develop.yml
vendored
@@ -29,15 +29,15 @@ jobs:
|
||||
- name: Check out the repo
|
||||
uses: actions/checkout@v6.0.2
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
uses: docker/setup-buildx-action@v4
|
||||
- name: Log in to Docker Hub
|
||||
uses: docker/login-action@v3
|
||||
uses: docker/login-action@v4
|
||||
with:
|
||||
username: ${{ secrets.DOCKER_USERNAME }}
|
||||
password: ${{ secrets.DOCKER_PASSWORD }}
|
||||
- name: Build and push by digest
|
||||
id: build
|
||||
uses: docker/build-push-action@v6
|
||||
uses: docker/build-push-action@v7
|
||||
with:
|
||||
context: .
|
||||
push: true
|
||||
@@ -53,7 +53,7 @@ jobs:
|
||||
digest="${{ steps.build.outputs.digest }}"
|
||||
touch "/tmp/digests/${digest#sha256:}"
|
||||
- name: Upload digest
|
||||
uses: actions/upload-artifact@v6
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: digests-${{ env.PLATFORM_PAIR }}
|
||||
path: /tmp/digests/*
|
||||
@@ -66,15 +66,15 @@ jobs:
|
||||
- build
|
||||
steps:
|
||||
- name: Download digests
|
||||
uses: actions/download-artifact@v7
|
||||
uses: actions/download-artifact@v8
|
||||
with:
|
||||
path: /tmp/digests
|
||||
pattern: digests-*
|
||||
merge-multiple: true
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
uses: docker/setup-buildx-action@v4
|
||||
- name: Login to Docker Hub
|
||||
uses: docker/login-action@v3
|
||||
uses: docker/login-action@v4
|
||||
with:
|
||||
username: ${{ secrets.DOCKER_USERNAME }}
|
||||
password: ${{ secrets.DOCKER_PASSWORD }}
|
||||
|
||||
18
.github/workflows/docker.yml
vendored
18
.github/workflows/docker.yml
vendored
@@ -34,21 +34,21 @@ jobs:
|
||||
- name: Check out the repo
|
||||
uses: actions/checkout@v6.0.2
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
uses: docker/setup-buildx-action@v4
|
||||
- name: Docker meta
|
||||
id: meta
|
||||
uses: docker/metadata-action@v5
|
||||
uses: docker/metadata-action@v6
|
||||
with:
|
||||
images: ${{ env.REGISTRY_IMAGE }}
|
||||
tags: ${{ env.TAGS }}
|
||||
- name: Log in to Docker Hub
|
||||
uses: docker/login-action@v3
|
||||
uses: docker/login-action@v4
|
||||
with:
|
||||
username: ${{ secrets.DOCKER_USERNAME }}
|
||||
password: ${{ secrets.DOCKER_PASSWORD }}
|
||||
- name: Build and Push to Docker Hub
|
||||
id: build
|
||||
uses: docker/build-push-action@v6
|
||||
uses: docker/build-push-action@v7
|
||||
with:
|
||||
context: .
|
||||
push: true
|
||||
@@ -64,7 +64,7 @@ jobs:
|
||||
digest="${{ steps.build.outputs.digest }}"
|
||||
touch "/tmp/digests/${digest#sha256:}"
|
||||
- name: Upload digest
|
||||
uses: actions/upload-artifact@v6
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: digests-${{ env.PLATFORM_PAIR }}
|
||||
path: /tmp/digests/*
|
||||
@@ -77,21 +77,21 @@ jobs:
|
||||
- build
|
||||
steps:
|
||||
- name: Download digests
|
||||
uses: actions/download-artifact@v7
|
||||
uses: actions/download-artifact@v8
|
||||
with:
|
||||
path: /tmp/digests
|
||||
pattern: digests-*
|
||||
merge-multiple: true
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
uses: docker/setup-buildx-action@v4
|
||||
- name: Docker meta
|
||||
id: meta
|
||||
uses: docker/metadata-action@v5
|
||||
uses: docker/metadata-action@v6
|
||||
with:
|
||||
images: ${{ env.REGISTRY_IMAGE }}
|
||||
tags: ${{ env.TAGS }}
|
||||
- name: Login to Docker Hub
|
||||
uses: docker/login-action@v3
|
||||
uses: docker/login-action@v4
|
||||
with:
|
||||
username: ${{ secrets.DOCKER_USERNAME }}
|
||||
password: ${{ secrets.DOCKER_PASSWORD }}
|
||||
|
||||
8
.github/workflows/get-api-diff.yml
vendored
8
.github/workflows/get-api-diff.yml
vendored
@@ -30,9 +30,9 @@ jobs:
|
||||
ref: ${{ matrix.ref }}
|
||||
submodules: true
|
||||
- name: Setup pnpm
|
||||
uses: pnpm/action-setup@v4.4.0
|
||||
uses: pnpm/action-setup@v6.0.3
|
||||
- name: Use Node.js
|
||||
uses: actions/setup-node@v6.3.0
|
||||
uses: actions/setup-node@v6.4.0
|
||||
with:
|
||||
node-version-file: '.node-version'
|
||||
cache: 'pnpm'
|
||||
@@ -48,7 +48,7 @@ jobs:
|
||||
- name: Copy API.json
|
||||
run: cp packages/backend/built/api.json ${{ matrix.api-json-name }}
|
||||
- name: Upload Artifact
|
||||
uses: actions/upload-artifact@v6
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: api-artifact-${{ matrix.api-json-name }}
|
||||
path: ${{ matrix.api-json-name }}
|
||||
@@ -61,7 +61,7 @@ jobs:
|
||||
PR_NUMBER: ${{ github.event.number }}
|
||||
run: |
|
||||
echo "$PR_NUMBER" > ./pr_number
|
||||
- uses: actions/upload-artifact@v6
|
||||
- uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: api-artifact-pr-number
|
||||
path: pr_number
|
||||
|
||||
10
.github/workflows/get-backend-memory.yml
vendored
10
.github/workflows/get-backend-memory.yml
vendored
@@ -35,7 +35,7 @@ jobs:
|
||||
POSTGRES_DB: test-misskey
|
||||
POSTGRES_HOST_AUTH_METHOD: trust
|
||||
redis:
|
||||
image: redis:7
|
||||
image: redis:8
|
||||
ports:
|
||||
- 56312:6379
|
||||
|
||||
@@ -45,9 +45,9 @@ jobs:
|
||||
ref: ${{ matrix.ref }}
|
||||
submodules: true
|
||||
- name: Setup pnpm
|
||||
uses: pnpm/action-setup@v4.4.0
|
||||
uses: pnpm/action-setup@v6.0.3
|
||||
- name: Use Node.js
|
||||
uses: actions/setup-node@v6.3.0
|
||||
uses: actions/setup-node@v6.4.0
|
||||
with:
|
||||
node-version-file: '.node-version'
|
||||
cache: 'pnpm'
|
||||
@@ -67,7 +67,7 @@ jobs:
|
||||
# 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@v6
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: memory-artifact-${{ matrix.memory-json-name }}
|
||||
path: ${{ matrix.memory-json-name }}
|
||||
@@ -81,7 +81,7 @@ jobs:
|
||||
PR_NUMBER: ${{ github.event.number }}
|
||||
run: |
|
||||
echo "$PR_NUMBER" > ./pr_number
|
||||
- uses: actions/upload-artifact@v6
|
||||
- uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: memory-artifact-pr-number
|
||||
path: pr_number
|
||||
|
||||
14
.github/workflows/lint.yml
vendored
14
.github/workflows/lint.yml
vendored
@@ -41,8 +41,8 @@ jobs:
|
||||
fetch-depth: 0
|
||||
submodules: true
|
||||
- name: Setup pnpm
|
||||
uses: pnpm/action-setup@v4.4.0
|
||||
- uses: actions/setup-node@v6.3.0
|
||||
uses: pnpm/action-setup@v6.0.3
|
||||
- uses: actions/setup-node@v6.4.0
|
||||
with:
|
||||
node-version-file: '.node-version'
|
||||
cache: 'pnpm'
|
||||
@@ -74,14 +74,14 @@ jobs:
|
||||
fetch-depth: 0
|
||||
submodules: true
|
||||
- name: Setup pnpm
|
||||
uses: pnpm/action-setup@v4.4.0
|
||||
- uses: actions/setup-node@v6.3.0
|
||||
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@v4.3.0
|
||||
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 }}
|
||||
@@ -105,8 +105,8 @@ jobs:
|
||||
fetch-depth: 0
|
||||
submodules: true
|
||||
- name: Setup pnpm
|
||||
uses: pnpm/action-setup@v4.4.0
|
||||
- uses: actions/setup-node@v6.3.0
|
||||
uses: pnpm/action-setup@v6.0.3
|
||||
- uses: actions/setup-node@v6.4.0
|
||||
with:
|
||||
node-version-file: '.node-version'
|
||||
cache: 'pnpm'
|
||||
|
||||
4
.github/workflows/locale.yml
vendored
4
.github/workflows/locale.yml
vendored
@@ -21,8 +21,8 @@ jobs:
|
||||
fetch-depth: 0
|
||||
submodules: true
|
||||
- name: Setup pnpm
|
||||
uses: pnpm/action-setup@v4.4.0
|
||||
- uses: actions/setup-node@v6.3.0
|
||||
uses: pnpm/action-setup@v6.0.3
|
||||
- uses: actions/setup-node@v6.4.0
|
||||
with:
|
||||
node-version-file: ".node-version"
|
||||
cache: "pnpm"
|
||||
|
||||
7
.github/workflows/on-release-created.yml
vendored
7
.github/workflows/on-release-created.yml
vendored
@@ -20,14 +20,17 @@ jobs:
|
||||
with:
|
||||
submodules: true
|
||||
- name: Setup pnpm
|
||||
uses: pnpm/action-setup@v4.4.0
|
||||
uses: pnpm/action-setup@v6.0.3
|
||||
- name: Use Node.js
|
||||
uses: actions/setup-node@v6.3.0
|
||||
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
|
||||
|
||||
4
.github/workflows/report-api-diff.yml
vendored
4
.github/workflows/report-api-diff.yml
vendored
@@ -16,7 +16,7 @@ jobs:
|
||||
# api-artifact
|
||||
steps:
|
||||
- name: Download artifact
|
||||
uses: actions/github-script@v8.0.0
|
||||
uses: actions/github-script@v9
|
||||
with:
|
||||
script: |
|
||||
const fs = require('fs');
|
||||
@@ -60,7 +60,7 @@ jobs:
|
||||
- name: Echo full diff
|
||||
run: cat ./api-full.json.diff
|
||||
- name: Upload full diff to Artifact
|
||||
uses: actions/upload-artifact@v6
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: api-artifact
|
||||
path: |
|
||||
|
||||
2
.github/workflows/report-backend-memory.yml
vendored
2
.github/workflows/report-backend-memory.yml
vendored
@@ -15,7 +15,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Download artifact
|
||||
uses: actions/github-script@v8.0.0
|
||||
uses: actions/github-script@v9
|
||||
with:
|
||||
script: |
|
||||
const fs = require('fs');
|
||||
|
||||
2
.github/workflows/request-release-review.yml
vendored
2
.github/workflows/request-release-review.yml
vendored
@@ -14,7 +14,7 @@ jobs:
|
||||
pull-requests: write
|
||||
steps:
|
||||
- name: Reply
|
||||
uses: actions/github-script@v8
|
||||
uses: actions/github-script@v9
|
||||
with:
|
||||
script: |
|
||||
const body = `To dev team (@misskey-dev/dev):
|
||||
|
||||
8
.github/workflows/storybook.yml
vendored
8
.github/workflows/storybook.yml
vendored
@@ -37,9 +37,9 @@ jobs:
|
||||
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@v4.4.0
|
||||
uses: pnpm/action-setup@v6.0.3
|
||||
- name: Use Node.js
|
||||
uses: actions/setup-node@v6.3.0
|
||||
uses: actions/setup-node@v6.4.0
|
||||
with:
|
||||
node-version-file: '.node-version'
|
||||
cache: 'pnpm'
|
||||
@@ -90,7 +90,7 @@ jobs:
|
||||
env:
|
||||
CHROMATIC_PROJECT_TOKEN: ${{ secrets.CHROMATIC_PROJECT_TOKEN }}
|
||||
- name: Notify that Chromatic detects changes
|
||||
uses: actions/github-script@v8.0.0
|
||||
uses: actions/github-script@v9
|
||||
if: github.event_name != 'pull_request_target' && steps.chromatic_push.outputs.success == 'false'
|
||||
with:
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
@@ -102,7 +102,7 @@ jobs:
|
||||
body: 'Chromatic detects changes. Please [review the changes on Chromatic](https://www.chromatic.com/builds?appId=6428f7d7b962f0b79f97d6e4).'
|
||||
})
|
||||
- name: Upload Artifacts
|
||||
uses: actions/upload-artifact@v6
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: storybook
|
||||
path: packages/frontend/storybook-static
|
||||
|
||||
24
.github/workflows/test-backend.yml
vendored
24
.github/workflows/test-backend.yml
vendored
@@ -45,11 +45,11 @@ jobs:
|
||||
POSTGRES_DB: test-misskey
|
||||
POSTGRES_HOST_AUTH_METHOD: trust
|
||||
redis:
|
||||
image: redis:7
|
||||
image: redis:8
|
||||
ports:
|
||||
- 56312:6379
|
||||
meilisearch:
|
||||
image: getmeili/meilisearch:v1.38.2
|
||||
image: getmeili/meilisearch:v1.42.1
|
||||
ports:
|
||||
- 57712:7700
|
||||
env:
|
||||
@@ -61,13 +61,13 @@ jobs:
|
||||
with:
|
||||
submodules: true
|
||||
- name: Setup pnpm
|
||||
uses: pnpm/action-setup@v4.4.0
|
||||
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@v4
|
||||
uses: actions/cache@v5
|
||||
with:
|
||||
path: |
|
||||
/usr/local/bin/ffmpeg
|
||||
@@ -93,7 +93,7 @@ jobs:
|
||||
fi
|
||||
done
|
||||
- name: Use Node.js
|
||||
uses: actions/setup-node@v6.3.0
|
||||
uses: actions/setup-node@v6.4.0
|
||||
with:
|
||||
node-version-file: ${{ matrix.node-version-file }}
|
||||
cache: 'pnpm'
|
||||
@@ -107,7 +107,7 @@ jobs:
|
||||
- name: Test
|
||||
run: pnpm --filter backend test-and-coverage
|
||||
- name: Upload to Codecov
|
||||
uses: codecov/codecov-action@v5
|
||||
uses: codecov/codecov-action@v6
|
||||
with:
|
||||
token: ${{ secrets.CODECOV_TOKEN }}
|
||||
files: ./packages/backend/coverage/coverage-final.json
|
||||
@@ -131,7 +131,7 @@ jobs:
|
||||
POSTGRES_DB: test-misskey
|
||||
POSTGRES_HOST_AUTH_METHOD: trust
|
||||
redis:
|
||||
image: redis:7
|
||||
image: redis:8
|
||||
ports:
|
||||
- 56312:6379
|
||||
|
||||
@@ -140,9 +140,9 @@ jobs:
|
||||
with:
|
||||
submodules: true
|
||||
- name: Setup pnpm
|
||||
uses: pnpm/action-setup@v4.4.0
|
||||
uses: pnpm/action-setup@v6.0.3
|
||||
- name: Use Node.js
|
||||
uses: actions/setup-node@v6.3.0
|
||||
uses: actions/setup-node@v6.4.0
|
||||
with:
|
||||
node-version-file: ${{ matrix.node-version-file }}
|
||||
cache: 'pnpm'
|
||||
@@ -156,7 +156,7 @@ jobs:
|
||||
- name: Test
|
||||
run: pnpm --filter backend test-and-coverage:e2e
|
||||
- name: Upload to Codecov
|
||||
uses: codecov/codecov-action@v5
|
||||
uses: codecov/codecov-action@v6
|
||||
with:
|
||||
token: ${{ secrets.CODECOV_TOKEN }}
|
||||
files: ./packages/backend/coverage/coverage-final.json
|
||||
@@ -184,12 +184,12 @@ jobs:
|
||||
with:
|
||||
submodules: true
|
||||
- name: Setup pnpm
|
||||
uses: pnpm/action-setup@v4.4.0
|
||||
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.3.0
|
||||
uses: actions/setup-node@v6.4.0
|
||||
with:
|
||||
node-version-file: ${{ matrix.node-version-file }}
|
||||
cache: 'pnpm'
|
||||
|
||||
6
.github/workflows/test-federation.yml
vendored
6
.github/workflows/test-federation.yml
vendored
@@ -36,13 +36,13 @@ jobs:
|
||||
with:
|
||||
submodules: true
|
||||
- name: Setup pnpm
|
||||
uses: pnpm/action-setup@v4.4.0
|
||||
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@v4
|
||||
uses: actions/cache@v5
|
||||
with:
|
||||
path: |
|
||||
/usr/local/bin/ffmpeg
|
||||
@@ -68,7 +68,7 @@ jobs:
|
||||
fi
|
||||
done
|
||||
- name: Use Node.js
|
||||
uses: actions/setup-node@v6.3.0
|
||||
uses: actions/setup-node@v6.4.0
|
||||
with:
|
||||
node-version-file: ${{ matrix.node-version-file }}
|
||||
cache: 'pnpm'
|
||||
|
||||
18
.github/workflows/test-frontend.yml
vendored
18
.github/workflows/test-frontend.yml
vendored
@@ -32,9 +32,9 @@ jobs:
|
||||
with:
|
||||
submodules: true
|
||||
- name: Setup pnpm
|
||||
uses: pnpm/action-setup@v4.4.0
|
||||
uses: pnpm/action-setup@v6.0.3
|
||||
- name: Use Node.js
|
||||
uses: actions/setup-node@v6.3.0
|
||||
uses: actions/setup-node@v6.4.0
|
||||
with:
|
||||
node-version-file: '.node-version'
|
||||
cache: 'pnpm'
|
||||
@@ -48,7 +48,7 @@ jobs:
|
||||
- name: Test
|
||||
run: pnpm --filter frontend test-and-coverage
|
||||
- name: Upload Coverage
|
||||
uses: codecov/codecov-action@v5
|
||||
uses: codecov/codecov-action@v6
|
||||
with:
|
||||
token: ${{ secrets.CODECOV_TOKEN }}
|
||||
files: ./packages/frontend/coverage/coverage-final.json
|
||||
@@ -71,7 +71,7 @@ jobs:
|
||||
POSTGRES_DB: test-misskey
|
||||
POSTGRES_HOST_AUTH_METHOD: trust
|
||||
redis:
|
||||
image: redis:7
|
||||
image: redis:8
|
||||
ports:
|
||||
- 56312:6379
|
||||
|
||||
@@ -86,9 +86,9 @@ jobs:
|
||||
#- uses: browser-actions/setup-firefox@latest
|
||||
# if: ${{ matrix.browser == 'firefox' }}
|
||||
- name: Setup pnpm
|
||||
uses: pnpm/action-setup@v4.4.0
|
||||
uses: pnpm/action-setup@v6.0.3
|
||||
- name: Use Node.js
|
||||
uses: actions/setup-node@v6.3.0
|
||||
uses: actions/setup-node@v6.4.0
|
||||
with:
|
||||
node-version-file: '.node-version'
|
||||
cache: 'pnpm'
|
||||
@@ -105,7 +105,7 @@ jobs:
|
||||
- name: Cypress install
|
||||
run: pnpm exec cypress install
|
||||
- name: Cypress run
|
||||
uses: cypress-io/github-action@v6
|
||||
uses: cypress-io/github-action@v7.1.9
|
||||
timeout-minutes: 15
|
||||
with:
|
||||
install: false
|
||||
@@ -113,12 +113,12 @@ jobs:
|
||||
wait-on: 'http://localhost:61812'
|
||||
headed: true
|
||||
browser: ${{ matrix.browser }}
|
||||
- uses: actions/upload-artifact@v6
|
||||
- uses: actions/upload-artifact@v7
|
||||
if: failure()
|
||||
with:
|
||||
name: ${{ matrix.browser }}-cypress-screenshots
|
||||
path: cypress/screenshots
|
||||
- uses: actions/upload-artifact@v6
|
||||
- uses: actions/upload-artifact@v7
|
||||
if: always()
|
||||
with:
|
||||
name: ${{ matrix.browser }}-cypress-videos
|
||||
|
||||
6
.github/workflows/test-misskey-js.yml
vendored
6
.github/workflows/test-misskey-js.yml
vendored
@@ -25,10 +25,10 @@ jobs:
|
||||
uses: actions/checkout@v6.0.2
|
||||
|
||||
- name: Setup pnpm
|
||||
uses: pnpm/action-setup@v4.4.0
|
||||
uses: pnpm/action-setup@v6.0.3
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v6.3.0
|
||||
uses: actions/setup-node@v6.4.0
|
||||
with:
|
||||
node-version-file: '.node-version'
|
||||
cache: 'pnpm'
|
||||
@@ -48,7 +48,7 @@ jobs:
|
||||
CI: true
|
||||
|
||||
- name: Upload Coverage
|
||||
uses: codecov/codecov-action@v5
|
||||
uses: codecov/codecov-action@v6
|
||||
with:
|
||||
token: ${{ secrets.CODECOV_TOKEN }}
|
||||
files: ./packages/misskey-js/coverage/coverage-final.json
|
||||
|
||||
4
.github/workflows/test-production.yml
vendored
4
.github/workflows/test-production.yml
vendored
@@ -20,9 +20,9 @@ jobs:
|
||||
with:
|
||||
submodules: true
|
||||
- name: Setup pnpm
|
||||
uses: pnpm/action-setup@v4.4.0
|
||||
uses: pnpm/action-setup@v6.0.3
|
||||
- name: Use Node.js
|
||||
uses: actions/setup-node@v6.3.0
|
||||
uses: actions/setup-node@v6.4.0
|
||||
with:
|
||||
node-version-file: '.node-version'
|
||||
cache: 'pnpm'
|
||||
|
||||
4
.github/workflows/validate-api-json.yml
vendored
4
.github/workflows/validate-api-json.yml
vendored
@@ -21,9 +21,9 @@ jobs:
|
||||
with:
|
||||
submodules: true
|
||||
- name: Setup pnpm
|
||||
uses: pnpm/action-setup@v4.4.0
|
||||
uses: pnpm/action-setup@v6.0.3
|
||||
- name: Use Node.js
|
||||
uses: actions/setup-node@v6.3.0
|
||||
uses: actions/setup-node@v6.4.0
|
||||
with:
|
||||
node-version-file: '.node-version'
|
||||
cache: 'pnpm'
|
||||
|
||||
3
.gitmodules
vendored
3
.gitmodules
vendored
@@ -1,3 +0,0 @@
|
||||
[submodule "fluent-emojis"]
|
||||
path = fluent-emojis
|
||||
url = https://github.com/misskey-dev/emojis.git
|
||||
|
||||
139
AGENTS.md
Normal file
139
AGENTS.md
Normal file
@@ -0,0 +1,139 @@
|
||||
# Misskey – AI Agent Guide
|
||||
|
||||
このファイルは Misskey リポジトリで動く AI コーディングエージェント (Claude Code / OpenAI Codex / GitHub Copilot 等) が共通で参照する **最低限のルールと索引**。次の 3 経路から参照・読み込みされる:
|
||||
|
||||
- **Claude Code**: ルート `CLAUDE.md` から `@AGENTS.md` で取り込まれる
|
||||
- **OpenAI Codex**: ルート `AGENTS.md` を直接読み込む
|
||||
- **GitHub Copilot**: `.github/copilot-instructions.md` (本ファイルを参照しつつ、Copilot code review 向けに必須規約を再掲するファイル) 経由で参照する
|
||||
|
||||
人間 contributor 向けの一般規約 (Issue / PR の出し方、ActivityPub 拡張など) は [CONTRIBUTING.md](CONTRIBUTING.md) を参照。本ファイルは AI が **コードを書く・直す** 際に踏み外してはいけない事項に絞っている。
|
||||
|
||||
---
|
||||
|
||||
## 事故直結ルール (必ず守る)
|
||||
|
||||
違反すると CI 失敗または本番事故になる。順守すること。
|
||||
|
||||
### 1. SPDX ヘッダー必須
|
||||
|
||||
AGPL-3.0-only 管轄かつ SPDX CI 対象ディレクトリに新規 `.ts` / `.js` / `.cjs` / `.mjs` / `.vue` / `.scss` / `.html` ファイルを追加する場合、冒頭に以下を必ず付ける。欠落すると CI (`spdx` ジョブ) が失敗する。CI の対象判定は [.github/workflows/check-spdx-license-id.yml](.github/workflows/check-spdx-license-id.yml) の `directories` 配列を参照 (`*.config.{ts,js,cjs,mjs}` と `*eslint*` は除外)。
|
||||
|
||||
`packages/misskey-js` は MIT ライセンスのサブパッケージなので、この AGPL ヘッダーを一律に付けない。サブパッケージ固有の `package.json` / `LICENSE` / 既存ファイルのヘッダーに従う。
|
||||
|
||||
`.ts` / `.js` / `.cjs` / `.mjs` / `.scss`:
|
||||
|
||||
```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
|
||||
-->
|
||||
```
|
||||
|
||||
### 2. locales/*.yml は `ja-JP.yml` のみ編集可
|
||||
|
||||
`locales/` 配下の YAML は **`ja-JP.yml` のみ手動編集してよい**。他言語ファイル (`en-US.yml` 等) は Crowdin の自動配信先で、手動編集すると上書きで失われる。根拠: `locales/README.md` (ja-JP.yml 以外を手動編集しない運用) と `crowdin.yml` (`ja-JP.yml` → `locales/%locale%.yml` の同期設定)。
|
||||
|
||||
### 3. マージ済み migration を絶対に編集しない
|
||||
|
||||
`packages/backend/migration/{unixMs}-{PascalName}.js` のうち、既に `develop` / `master` にマージ済みのファイルは **絶対に変更しない**。本番環境で履歴改変が起きると深刻なデータ不整合を引き起こす。
|
||||
|
||||
スキーマ変更が必要な場合は **新しいタイムスタンプで新規ファイル** を作成する:
|
||||
|
||||
- ファイル名: `node -e "console.log(Date.now())"` で UNIX ms を取得し、`{ms}-<descriptive-name>.js` として置く。命名スタイルは既存履歴で混在しており (`1716129964060-ChannelIdDenormalizedForMiPoll.js` のような PascalCase、`1721666053703-fixDriveUrl.js` のような camelCase、`1672704136584-remove-latestStatus.js` のような kebab-case)、変更を表す単一の英語名であれば良い。クラス名側は PascalCase + 13 桁タイムスタンプ (`class FixDriveUrl1721666053703 { ... }`) を必ず守ること。
|
||||
- `up()` と `down()` の両方を必ず実装する (`down` は `up` の完全な巻き戻し)。
|
||||
- `pnpm --filter backend check-migrations` を通す。これは **TypeORM schema builder で pending DDL を検出する** 検査 ([packages/backend/scripts/check_migrations_clean.js](packages/backend/scripts/check_migrations_clean.js))。エンティティの `@Column` / `@Entity` 変更が migration に取り込まれていないとここで検出される。タイムスタンプの順序自体を直接検査するわけではない (順序が壊れた場合の失敗は別経路で出る)。
|
||||
|
||||
エンティティ差分から TypeORM CLI で自動生成したい / `CREATE INDEX CONCURRENTLY` 等のオプションを使いたい場合は [.claude/skills/create-migration/SKILL.md](.claude/skills/create-migration/SKILL.md) を参照。手書き / CLI どちらの方式でも上記 3 点 (履歴改変禁止 / `up`+`down` / `check-migrations`) が満たせれば良い。
|
||||
|
||||
---
|
||||
|
||||
## 必須コマンド
|
||||
|
||||
| 用途 | コマンド |
|
||||
| --- | --- |
|
||||
| 全体ビルド | `pnpm build` |
|
||||
| 開発サーバー (backend + frontend watch) | `pnpm dev` |
|
||||
| Lint (typecheck + eslint, 全パッケージ) | `pnpm lint` (= `pnpm --no-bail -r lint`。最初の失敗で止まらず全パッケージの結果を収集する) |
|
||||
| Backend unit test (Vitest) | `pnpm --filter backend test` |
|
||||
| Backend e2e test | `pnpm --filter backend test:e2e` |
|
||||
| Backend federation test | `pnpm --filter backend test:fed` |
|
||||
| Frontend test (Vitest) | `pnpm --filter frontend test` |
|
||||
| Cypress E2E (要 `start:test`) | `pnpm e2e` |
|
||||
| Storybook dev (frontend) | `pnpm --filter frontend storybook-dev` |
|
||||
| Migration 適用 | `pnpm migrate` |
|
||||
| Migration ロールバック | `pnpm revert` |
|
||||
| Migration の pending DDL 検査 (エンティティ差分の取り込み漏れ検出) | `pnpm --filter backend check-migrations` |
|
||||
| `misskey-js` 再生成 (API 変更後必須) | `pnpm build-misskey-js-with-types` |
|
||||
|
||||
> Backend の TypeScript 型チェックは `pnpm --filter backend typecheck` (tsgo)。
|
||||
> 個別ファイルへの ESLint --fix は `pnpm exec eslint --fix <path>`。
|
||||
> **backend テスト (`test` / `test:e2e` / `test:fed`) 実行前に `.config/test.yml` が必要** (未作成だとテスト自体が起動しない)。コピー手順と詳細は [.claude/docs/testing.md §Backend 全般の前提](.claude/docs/testing.md#backend-全般の前提-configtestyml) を参照。
|
||||
|
||||
---
|
||||
|
||||
## CHANGELOG
|
||||
|
||||
ユーザー影響のある変更 (機能追加・修正・改善) は `CHANGELOG.md` の冒頭 `## Unreleased` セクションに 1 行追加する。リファクタリング等の内部変更は不要。
|
||||
|
||||
### セクション構造
|
||||
|
||||
`## Unreleased` 配下に **3 つのサブセクション** が用意されている:
|
||||
|
||||
- `### General` — 共通 / 横断的な変更
|
||||
- `### Client` — `packages/frontend` 系
|
||||
- `### Server` — `packages/backend` 系
|
||||
|
||||
### エントリ書式
|
||||
|
||||
該当サブセクションに `- <Prefix>: <概要>` の形式で追加。Prefix は先頭大文字。
|
||||
|
||||
```text
|
||||
- Enhance: ノートの詳細表示での公開範囲の表示を改善
|
||||
- Fix: 通知が約10秒遅延する問題を修正
|
||||
- Feat: 新機能の追加
|
||||
```
|
||||
|
||||
### 触ってはいけない範囲
|
||||
|
||||
- `## Unreleased` **以外** のセクション (過去リリース) は変更しない。
|
||||
- `## Unreleased` の見出しと 3 つの空サブセクション骨格自体は維持する (リリーススクリプトが期待する構造)。
|
||||
|
||||
> 参考: コミットメッセージ側は `enhance(frontend): ...` / `fix(backend): ...` の小文字 + スコープ形式 ([CONTRIBUTING.md](CONTRIBUTING.md) 参照)。CHANGELOG とは書式が異なる点に注意。
|
||||
|
||||
---
|
||||
|
||||
## オンデマンド参照 (必要時に Read すること)
|
||||
|
||||
以下は AI が **作業対象に応じて必要なときだけ** 開く詳細ドキュメント。常時コンテキストには載せない。
|
||||
|
||||
| 何をしたい時 | 参照先 |
|
||||
| --- | --- |
|
||||
| パッケージ構成・依存関係を把握したい | [.claude/docs/architecture.md](.claude/docs/architecture.md) |
|
||||
| `packages/backend` を編集する (NestJS / TypeORM / migration / API endpoint) | [.claude/docs/backend.md](.claude/docs/backend.md) |
|
||||
| `packages/frontend` を編集する (Vue 3 / Mk* / i18n / SCSS module / `os.ts`) | [.claude/docs/frontend.md](.claude/docs/frontend.md) |
|
||||
| テストを書く・走らせる (Vitest / Cypress / Storybook) | [.claude/docs/testing.md](.claude/docs/testing.md) |
|
||||
| 有効化済 Claude Code プラグインの用途を確認 | [.claude/docs/plugins.md](.claude/docs/plugins.md) |
|
||||
|
||||
---
|
||||
|
||||
## ツール固有の補助ファイル
|
||||
|
||||
`.claude/` 配下は Claude Code 固有の skills / agents / slash commands を集約している (Codex / Copilot は読み飛ばしてよい):
|
||||
|
||||
- `.claude/skills/` — 繰り返しタスク用の skill 定義 (例: `add-api-endpoint`, `create-migration`)
|
||||
- `.claude/agents/` — 専門レビューエージェント (例: `misskey-api-reviewer`, `vue-component-reviewer`)
|
||||
- `.claude/commands/` — Claude Code のスラッシュコマンド (例: `/check-misskey-js`, `/changelog-add`)
|
||||
- `.claude/docs/` — オンデマンド参照ドキュメント (上記の表で示したもの。Codex / Copilot からも内容自体は読める)
|
||||
- `.claude/settings.json` — Claude Code の有効プラグイン (`enabledPlugins`) のみを記載した共有設定。hook は意図的に登録しない (各 contributor が `.claude/settings.local.json` で opt-in する方針)
|
||||
- `.claude/settings.local.json` — 個人ローカル設定 (`.gitignore` 済)
|
||||
|
||||
サードパーティ由来 (everything-claude-code 由来の MIT ライセンスファイル等) の出典は [.claude/THIRD_PARTY_LICENSES.md](.claude/THIRD_PARTY_LICENSES.md) を参照。
|
||||
71
CHANGELOG.md
71
CHANGELOG.md
@@ -1,15 +1,82 @@
|
||||
## 2026.4.0
|
||||
## 2026.5.2
|
||||
|
||||
### Note
|
||||
- config に `threadPoolSize` オプションが追加されました。
|
||||
- デフォルトは `1` で、ワーカーごとに指定した数のスレッドが作成されます。
|
||||
- スレッドプールは CPU バウンドな処理をオフロードするために使用されるため、みだりに大きな値を指定しないでください。
|
||||
|
||||
### General
|
||||
-
|
||||
- Enhance: Unicode 17.0 に収録されている絵文字の処理・表示に対応
|
||||
- Fluent Emojiや端末ネイティブの絵文字を利用している場合は、最新の絵文字に対応しておらず正しく表示できない可能性があります。絵文字が表示できない場合は、表示に使用する絵文字をTwemojiに切り替えてご利用ください。
|
||||
- 投稿通知設定したユーザーをリストで見ることができるように
|
||||
- Node.js 26がサポートされました
|
||||
|
||||
### Client
|
||||
- Enhance: テーマのプレビュー時、リロードせずにもとのテーマに戻せるように
|
||||
- Enhance: Fluent Emojiを更新し、Unicode 15+相当の絵文字の表示に対応
|
||||
- Fix: テーマエディター使用時に、最初の変更のみ適用される問題を修正
|
||||
- Fix: テーマのプレビュー時、既存のテーマとIDが被っている場合にプレビューできない問題を修正
|
||||
- Fix: テーマのインストールエラーの表示を改善
|
||||
- Fix: リスト編集画面におけるユーザー追加時のユーザー選択ダイアログにおいて、自身のアカウントが検索結果の一覧に表示されない問題を修正
|
||||
- Fix: デッキのカラムから開いたアンテナ・リストの編集ウィンドウを、"ポップアウト"、"新しいタブで表示"、"リンクをコピー"した場合に誤ったリンクが与えられる問題を修正
|
||||
- Fix: チャンネルの作成ロールポリシーにて、ヘッダーにロールポリシーの値が表示されない問題を修正
|
||||
|
||||
### Server
|
||||
- Enhance: RSA 署名処理のオフロード
|
||||
|
||||
|
||||
## 2026.5.1
|
||||
|
||||
### General
|
||||
- Enhance: チャンネルの作成の可否をロールポリシーで制御できるように
|
||||
- Fix: `.devcontainer/compose.yml`のvolumeのマウントパスを修正
|
||||
|
||||
### Client
|
||||
- Enhance: ノートの詳細表示での公開範囲の表示を改善
|
||||
(Cherry-picked from https://github.com/kokonect-link/cherrypick/commit/ecc75563f4e428b66adccc379bf317b5b21ed8e6)
|
||||
- Fix: ロール設定画面でロールをアサイン/アサイン解除した際、リロードしなくても画面に反映されるよう修正
|
||||
|
||||
### Server
|
||||
- Fix: ID生成アルゴリズムにULIDを使用している場合に通知が約10秒遅延する問題を修正
|
||||
- Fix: 公開範囲がフォロワーの投稿が通知されない問題を修正
|
||||
- Fix: URLプレビューが動作しない問題を修正
|
||||
|
||||
|
||||
## 2026.5.0
|
||||
|
||||
### General
|
||||
- Enhance: アバターデコレーションにカテゴリを設定できるように
|
||||
|
||||
### Client
|
||||
- Enhance: チャンネル指定リノートでリノート先のチャンネルに移動できるように
|
||||
- Enhance: ベータ版でのアップデート時のダイアログの更新情報リンクをGitHubのReleasesページに遷移するようにし、正しく閲覧できるように
|
||||
- Fix: 一部のページ内リンクが正しく動作しない問題を修正
|
||||
- Fix: ドライブへの画像アップロード時にファイル名の変更が無視される不具合を修正
|
||||
- Fix: 連合が無効化されたサーバーで一部の設定項目が空欄で表示される問題を修正
|
||||
- Fix: オーディオ、動画の再生速度メニューが開けない問題を修正
|
||||
|
||||
### Server
|
||||
- Enhance: メモリ使用量を削減
|
||||
- Enhance: 起動の高速化
|
||||
(Cherry-picked from https://github.com/MisskeyIO/misskey/pull/1410)
|
||||
- Enhance: バックエンドの開発モード時の安定性向上
|
||||
- Enhance: バックエンドビルド・テスト時に使用する依存関係の整理(swc/esbuild→Rolldown, Jest→Vitest)
|
||||
- Fix: ファイルシステムを用いる処理におけるパスの取り扱いを改善
|
||||
- Fix: `/api-doc` にアクセスできない問題を修正
|
||||
- Fix: support `alsoKnownAs` from remote actors as either array or unwrapped singleton
|
||||
- Fix: ローカルに存在しないリモートアカウントに対するアカウント削除リクエストを受信した際に、そのユーザーを新規作成して削除する挙動を修正
|
||||
- Fix: Inboxでの特定のエラーによる失敗はDelayedにしない
|
||||
- Fix: ID生成アルゴリズムにULIDを使用している場合にMisskeyが正しく動作しない問題を修正
|
||||
- Fix: リレー経由で届いたノートがリノートとして表示される問題を修正
|
||||
- Fix: robots.txtの内容を調整
|
||||
- Fix: 特定のユーザーに管理者権限を持つロールが複数ついている際に、取得できるユーザーIDが重複する問題を修正
|
||||
(Cherry-picked from https://github.com/lqvp/misskey-tempura/commit/17ed4108cec4b6bd2fd989db5a9091db91fa37a7)
|
||||
- Fix: ブロックしたサーバーからのInboxジョブが蓄積し続ける問題を修正
|
||||
(Cherry-picked from https://github.com/lqvp/misskey-tempura/commit/3f0f4bfe923f2b3a7837017b54841598f421c6ef)
|
||||
- Fix: support activity with `actor` as an id string or embedded object in inbox processor and ActivityPub inbox service
|
||||
- Fix: コンフィグファイルに `meilisearch` の設定がある状態でほかの検索プロバイダを利用すると、UI上からリモートのノートの検索ができない問題を修正
|
||||
- Fix: ノートに関する通知で公開範囲が考慮されていない問題を修正
|
||||
(Cherry-picked from https://github.com/lqvp/misskey-tempura/commit/cbce96c520a138b8bcd16890ff6f2952830fa166 originally presented in https://github.com/yojo-art/cherrypick/pull/743)
|
||||
|
||||
## 2026.3.2
|
||||
|
||||
|
||||
7
CLAUDE.md
Normal file
7
CLAUDE.md
Normal file
@@ -0,0 +1,7 @@
|
||||
# Misskey – Claude Code Guide
|
||||
|
||||
ルール本体は [AGENTS.md](AGENTS.md) (Codex / Copilot と共有する単一ソース)。本ファイルは Claude Code 用の薄いラッパーで、`@AGENTS.md` 構文で本体規約をセッション開始時にコンテキストへ展開する。
|
||||
|
||||
Claude Code 固有の補助 (skills / agents / slash commands / docs) は `.claude/` 配下にコミット済。個人ローカル設定は `.claude/settings.local.json` に、MCP 認証情報は `.claude/.credentials.json` に置く (いずれも `.gitignore` 済)。
|
||||
|
||||
@AGENTS.md
|
||||
@@ -575,11 +575,12 @@ enumの列挙の内容の削除は、その値をもつレコードを全て削
|
||||
### Migration作成方法
|
||||
packages/backendで:
|
||||
```sh
|
||||
pnpm dlx typeorm migration:generate -d ormconfig.js -o <migration name>
|
||||
pnpm dlx typeorm migration:generate -d ormconfig.js -o --esm <migration name>
|
||||
```
|
||||
|
||||
- 生成後、ファイルをmigration下に移してください
|
||||
- 作成されたスクリプトは不必要な変更を含むため除去してください
|
||||
- `-o` (`--outputJs`) で JS 形式、`--esm` で ESM 形式に生成する。Misskey の既存 migration はすべて ESM JS なので両方のオプションが必要
|
||||
|
||||
### コネクションには`markRaw`せよ
|
||||
**Vueのコンポーネントのdataオプションとして**misskey.jsのコネクションを設定するとき、必ず`markRaw`でラップしてください。インスタンスが不必要にリアクティブ化されることで、misskey.js内の処理で不具合が発生するとともに、パフォーマンス上の問題にも繋がる。なお、Composition APIを使う場合はこの限りではない(リアクティブ化はマニュアルなため)。
|
||||
|
||||
7
COPYING
7
COPYING
@@ -3,13 +3,6 @@ Copyright © 2014-2026 syuilo and contributors
|
||||
|
||||
And is distributed under The GNU Affero General Public License Version 3, you should have received a copy of the license file as LICENSE.
|
||||
|
||||
|
||||
Misskey includes several third-party Open-Source softwares.
|
||||
|
||||
Emoji keywords for Unicode 11 and below by Mu-An Chiou
|
||||
License: MIT
|
||||
https://github.com/muan/emojilib/blob/master/LICENSE
|
||||
|
||||
RsaSignature2017 implementation by Transmute Industries Inc
|
||||
License: MIT
|
||||
https://github.com/transmute-industries/RsaSignature2017/blob/master/LICENSE
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# syntax = docker/dockerfile:1.21
|
||||
# syntax = docker/dockerfile:1.23
|
||||
|
||||
ARG NODE_VERSION=22.22.0-bookworm
|
||||
ARG NODE_VERSION=22.22.2-bookworm
|
||||
|
||||
# build assets & compile TypeScript
|
||||
|
||||
@@ -102,9 +102,7 @@ COPY --chown=misskey:misskey --from=native-builder /misskey/packages/misskey-js/
|
||||
COPY --chown=misskey:misskey --from=native-builder /misskey/packages/misskey-reversi/built ./packages/misskey-reversi/built
|
||||
COPY --chown=misskey:misskey --from=native-builder /misskey/packages/misskey-bubble-game/built ./packages/misskey-bubble-game/built
|
||||
COPY --chown=misskey:misskey --from=native-builder /misskey/packages/backend/built ./packages/backend/built
|
||||
COPY --chown=misskey:misskey --from=native-builder /misskey/packages/backend/src-js ./packages/backend/src-js
|
||||
COPY --chown=misskey:misskey --from=native-builder /misskey/packages/i18n/built ./packages/i18n/built
|
||||
COPY --chown=misskey:misskey --from=native-builder /misskey/fluent-emojis /misskey/fluent-emojis
|
||||
COPY --chown=misskey:misskey . ./
|
||||
|
||||
ENV LD_PRELOAD=/usr/local/lib/libjemalloc.so
|
||||
|
||||
@@ -190,6 +190,9 @@ id: "aidx"
|
||||
# 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
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"lib": ["dom", "es5"],
|
||||
"target": "es5",
|
||||
"lib": ["dom"],
|
||||
"target": "esnext",
|
||||
"types": ["cypress", "node"]
|
||||
},
|
||||
"include": ["./**/*.ts"]
|
||||
|
||||
Submodule fluent-emojis deleted from cae981eb4c
@@ -1408,6 +1408,7 @@ frame: "Frame"
|
||||
presets: "Preset"
|
||||
zeroPadding: "Zero padding"
|
||||
nothingToConfigure: "No configurable options available"
|
||||
viewRenotedChannel: "Show renoted channel"
|
||||
_imageEditing:
|
||||
_vars:
|
||||
caption: "File caption"
|
||||
@@ -3401,6 +3402,8 @@ _imageEffector:
|
||||
threshold: "Threshold"
|
||||
centerX: "Center X"
|
||||
centerY: "Center Y"
|
||||
density: "Density"
|
||||
zoomLinesOutlineThickness: "Outline shadow thickness"
|
||||
zoomLinesMaskSize: "Center diameter"
|
||||
circle: "Circular"
|
||||
drafts: "Drafts"
|
||||
|
||||
@@ -1408,6 +1408,7 @@ frame: "Marco"
|
||||
presets: "Predefinido"
|
||||
zeroPadding: "Relleno cero"
|
||||
nothingToConfigure: "No hay nada que configurar"
|
||||
viewRenotedChannel: "Ver el canal al que te has suscrito"
|
||||
_imageEditing:
|
||||
_vars:
|
||||
caption: "Título del archivo"
|
||||
|
||||
@@ -1217,6 +1217,7 @@ keepScreenOn: "デバイスの画面を常にオンにする"
|
||||
verifiedLink: "このリンク先の所有者であることが確認されました"
|
||||
notifyNotes: "投稿を通知"
|
||||
unnotifyNotes: "投稿の通知を解除"
|
||||
notifyUsers: "投稿通知を設定したユーザー"
|
||||
authentication: "認証"
|
||||
authenticationRequiredToContinue: "続けるには認証を行ってください"
|
||||
dateAndTime: "日時"
|
||||
@@ -1409,6 +1410,8 @@ presets: "プリセット"
|
||||
zeroPadding: "ゼロ埋め"
|
||||
nothingToConfigure: "設定項目はありません"
|
||||
viewRenotedChannel: "リノート先のチャンネルを見る"
|
||||
previewingTheme: "テーマのプレビュー中"
|
||||
previewingThemeRestore: "元に戻す"
|
||||
|
||||
_imageEditing:
|
||||
_vars:
|
||||
@@ -2122,6 +2125,7 @@ _role:
|
||||
canSearchNotes: "ノート検索の利用"
|
||||
canSearchUsers: "ユーザー検索の利用"
|
||||
canUseTranslator: "翻訳機能の利用"
|
||||
canCreateChannel: "チャンネルの作成"
|
||||
avatarDecorationLimit: "アイコンデコレーションの最大取付個数"
|
||||
canImportAntennas: "アンテナのインポートを許可"
|
||||
canImportBlocking: "ブロックのインポートを許可"
|
||||
|
||||
@@ -2098,6 +2098,7 @@ _role:
|
||||
canSearchNotes: "노트 검색 이용 가능 여부"
|
||||
canSearchUsers: "유저 검색 이용"
|
||||
canUseTranslator: "번역 기능의 사용"
|
||||
canCreateChannel: "패널 생성"
|
||||
avatarDecorationLimit: "아바타 장식의 최대 붙임 개수"
|
||||
canImportAntennas: "안테나 가져오기 허용"
|
||||
canImportBlocking: "차단 목록 가져오기 허용"
|
||||
|
||||
@@ -1332,7 +1332,9 @@ overrideByAccount: "Переопределить этим аккаунтом"
|
||||
untitled: "Без названия"
|
||||
noName: "Имя не указано"
|
||||
skip: "Пропустить"
|
||||
restore: "Восстановить"
|
||||
syncBetweenDevices: "Синхронизировать между устройствами"
|
||||
paste: "вставить"
|
||||
postForm: "Форма отправки"
|
||||
textCount: "Количество символов"
|
||||
information: "Описание"
|
||||
@@ -2395,6 +2397,8 @@ _imageEffector:
|
||||
opacity: "Непрозрачность"
|
||||
lightness: "Осветление"
|
||||
drafts: "Черновик"
|
||||
_drafts:
|
||||
restore: "Восстановить"
|
||||
_qr:
|
||||
showTabTitle: "Отображение"
|
||||
raw: "Текст"
|
||||
|
||||
@@ -1335,7 +1335,7 @@ markAsSensitiveConfirm: "ต้องการตั้งค่าสื่อ
|
||||
unmarkAsSensitiveConfirm: "ต้องการยกเลิกการระบุว่าสื่อนี้มีเนื้อหาละเอียดอ่อนหรือไม่?"
|
||||
preferences: "การตั้งค่าสภาพแวดล้อม"
|
||||
accessibility: "การช่วยการเข้าถึง"
|
||||
preferencesProfile: "โปรไฟล์การกำหนดค่า"
|
||||
preferencesProfile: "โปรไฟล์ของการตั้งค่า"
|
||||
copyPreferenceId: "คัดลือก ID การตั้งค่า"
|
||||
resetToDefaultValue: "คืนค่าเป็นค่าเริ่มต้น"
|
||||
overrideByAccount: "เขียนทับด้วยบัญชี"
|
||||
@@ -1345,10 +1345,10 @@ skip: "ข้าม"
|
||||
restore: "กู้คืน"
|
||||
syncBetweenDevices: "ซิงค์ระหว่างอุปกรณ์"
|
||||
preferenceSyncConflictTitle: "การตั้งค่ามีอยู่บนเซิร์ฟเวอร์"
|
||||
preferenceSyncConflictText: "การตั้งค่าที่เปิดใช้งานการซิงค์จะบันทึกค่าลงในเซิร์ฟเวอร์ อย่างไรก็ดี พบว่ามีค่าการตั้งค่านี้ที่เคยบันทึกไว้ในเซิร์ฟเวอร์แล้ว ต้องการดำเนินการอย่างไร?"
|
||||
preferenceSyncConflictText: "รายการตั้งค่าที่เปิดการซิงก์จะถูกบันทึกลงเซิร์ฟเวอร์ แต่รายการตั้งค่านี้ได้ถูกบันทึกลงเซิร์ฟเวอร์ไว้อยู่แล้ว ต้องการดำเนินการอย่างไร?"
|
||||
preferenceSyncConflictChoiceMerge: "รวมเข้าด้วยกัน"
|
||||
preferenceSyncConflictChoiceServer: "เขียนทับด้วยค่าการตั้งค่าเซิร์ฟเวอร์"
|
||||
preferenceSyncConflictChoiceDevice: "เขียนทับด้วยค่าการตั้งค่าอุปกรณ์"
|
||||
preferenceSyncConflictChoiceServer: "เขียนทับด้วยค่าการตั้งค่าของเซิร์ฟเวอร์"
|
||||
preferenceSyncConflictChoiceDevice: "เขียนทับด้วยค่าการตั้งค่าของอุปกรณ์"
|
||||
preferenceSyncConflictChoiceCancel: "ยกเลิกการเปิดใช้งานการซิงค์"
|
||||
paste: "วาง"
|
||||
emojiPalette: "จานสีเอโมจิ"
|
||||
@@ -1408,6 +1408,7 @@ frame: "เฟรม"
|
||||
presets: "พรีเซ็ต"
|
||||
zeroPadding: "ห่างเป็น 0"
|
||||
nothingToConfigure: "ไม่มีอะไรให้ต้ังค่า"
|
||||
viewRenotedChannel: "แสดงช่องที่ถูกรีโน้ต"
|
||||
_imageEditing:
|
||||
_vars:
|
||||
caption: "แคปชั่นของไฟล์"
|
||||
@@ -2097,6 +2098,7 @@ _role:
|
||||
canSearchNotes: "การใช้การค้นหาโน้ต"
|
||||
canSearchUsers: "ค้นหาผู้ใช้"
|
||||
canUseTranslator: "การใช้งานแปล"
|
||||
canCreateChannel: "สร้างช่องใหม่"
|
||||
avatarDecorationLimit: "จำนวนของตกแต่งไอคอนสูงสุดที่สามารถติดตั้งได้"
|
||||
canImportAntennas: "อนุญาตให้นำเข้าเสาอากาศ"
|
||||
canImportBlocking: "อนุญาตให้นำเข้าการบล็อก"
|
||||
@@ -2136,7 +2138,7 @@ _sensitiveMediaDetection:
|
||||
sensitivityDescription: "เมื่อความไวต่ำ Misdetection (ผลบวกลวง) จะลดลง, เมื่อความไวสูง Missed detection (ผลลบลวง) จะลดลง"
|
||||
setSensitiveFlagAutomatically: "ทำเครื่องหมายว่ามีเนื้อหาละเอียดอ่อน"
|
||||
setSensitiveFlagAutomaticallyDescription: "ผลลัพธ์ของการตรวจจับภายในนั้นจะยังคงอยู่ ถึงแม้ว่าจะปิดตัวเลือกนี้"
|
||||
analyzeVideos: "เปิดใช้งานวิเคราะห์ของวิดีโอ"
|
||||
analyzeVideos: "เปิดใช้งานวิเคราะห์วิดีโอ"
|
||||
analyzeVideosDescription: "การวิเคราะห์วิดีโอนอกเหนือจากรูปภาพนั้น การทำสิ่งนี้จะทำให้เพิ่มภาระบนเซิร์ฟเวอร์เล็กน้อย"
|
||||
_emailUnavailable:
|
||||
used: "ที่อยู่อีเมลนี้ได้ถูกใช้ไปแล้ว"
|
||||
@@ -2586,7 +2588,7 @@ _widgetOptions:
|
||||
period: "ระยะเวลา"
|
||||
_cw:
|
||||
hide: "ซ่อน"
|
||||
show: "โหลดเพิ่มเติม"
|
||||
show: "ดูเพิ่มเติม"
|
||||
chars: "{count} ตัวอักษร"
|
||||
files: "{count} ไฟล์"
|
||||
_poll:
|
||||
@@ -3029,7 +3031,7 @@ _externalResourceInstaller:
|
||||
description: "เกิดปัญหาระหว่างการติดตั้งธีม กรุณาลองอีกครั้ง. รายละเอียดข้อผิดพลาดสามารถดูได้ในคอนโซล Javascript"
|
||||
_dataSaver:
|
||||
_media:
|
||||
title: "โหลดสื่อ"
|
||||
title: "ปิดใช้งานการโหลดสื่ออัตโนมัติ"
|
||||
description: "กันไม่ให้ภาพและวิดีโอโหลดโดยอัตโนมัติ แตะรูปภาพ/วิดีโอที่ซ่อนอยู่เพื่อโหลด"
|
||||
_avatar:
|
||||
title: "ปิดใช้งานภาพเคลื่อนไหวของไอคอนประจำตัว"
|
||||
@@ -3111,7 +3113,7 @@ _urlPreviewSetting:
|
||||
summaryProxyDescription: "สร้างการแสดงตัวอย่างด้วย summary Proxy แทนที่จะใช้เนื้อหา Misskey"
|
||||
summaryProxyDescription2: "พารามิเตอร์ต่อไปนี้จะถูกใช้เป็นสตริงการสืบค้นเพื่อเชื่อมต่อกับพร็อกซี หากฝั่งพร็อกซีไม่รองรับการตั้งค่าเหล่านี้จะถูกละเว้น"
|
||||
_mediaControls:
|
||||
pip: "รูปภาพในรูปภาม"
|
||||
pip: "ภาพซ้อนภาพ (PiP)"
|
||||
playbackRate: "ความเร็วในการเล่น"
|
||||
loop: "เล่นวนซ้ำ"
|
||||
_contextMenu:
|
||||
|
||||
@@ -1408,6 +1408,7 @@ frame: "Çerçeve"
|
||||
presets: "Ön ayar"
|
||||
zeroPadding: "Sıfır doldurma"
|
||||
nothingToConfigure: "Ayarlar seçeneği bulunmamaktadır."
|
||||
viewRenotedChannel: "Show renoted channel"
|
||||
_imageEditing:
|
||||
_vars:
|
||||
caption: "Dosya başlığı"
|
||||
|
||||
@@ -63,7 +63,7 @@ copyUserId: "复制用户 ID"
|
||||
copyNoteId: "复制帖子 ID"
|
||||
copyFileId: "复制文件ID"
|
||||
copyFolderId: "复制文件夹ID"
|
||||
copyProfileUrl: "复制个人资料URL"
|
||||
copyProfileUrl: "复制个人资料链接"
|
||||
searchUser: "搜索用户"
|
||||
searchThisUsersNotes: "搜索用户帖子"
|
||||
reply: "回复"
|
||||
@@ -101,12 +101,12 @@ somethingHappened: "出错了"
|
||||
retry: "重试"
|
||||
pageLoadError: "页面加载失败。"
|
||||
pageLoadErrorDescription: "这通常是由于网络或浏览器缓存的原因。请清除缓存或等待片刻后重试。"
|
||||
serverIsDead: "没有服务器响应。 请稍后再试。"
|
||||
serverIsDead: "服务器未响应。 请稍后再试。"
|
||||
youShouldUpgradeClient: "请重新加载并使用新版本的客户端查看此页面。"
|
||||
enterListName: "输入列表名称"
|
||||
privacy: "隐私"
|
||||
makeFollowManuallyApprove: "关注请求需要批准"
|
||||
defaultNoteVisibility: "默认可见性"
|
||||
defaultNoteVisibility: "默认可见范围"
|
||||
follow: "关注"
|
||||
followRequest: "申请关注"
|
||||
followRequests: "关注请求"
|
||||
@@ -137,10 +137,10 @@ pinnedEmojisForReactionSettingDescription: "可以设置发表回应时置顶显
|
||||
pinnedEmojisSettingDescription: "可以设置输入表情符号时置顶显示的表情符号"
|
||||
emojiPickerDisplay: "选择器显示设置"
|
||||
overwriteFromPinnedEmojisForReaction: "使用「置顶(回应)」设置覆盖"
|
||||
overwriteFromPinnedEmojis: "从全局设置覆盖"
|
||||
overwriteFromPinnedEmojis: "使用全局设置覆盖"
|
||||
reactionSettingDescription2: "拖动重新排序,单击删除,点击 + 添加。"
|
||||
rememberNoteVisibility: "保存上次设置的可见性"
|
||||
attachCancel: "取消添加附件"
|
||||
attachCancel: "移除附件"
|
||||
deleteFile: "删除文件"
|
||||
markAsSensitive: "标记为敏感内容"
|
||||
unmarkAsSensitive: "取消标记为敏感内容"
|
||||
@@ -149,12 +149,12 @@ mute: "屏蔽"
|
||||
unmute: "取消隐藏"
|
||||
renoteMute: "隐藏转帖"
|
||||
renoteUnmute: "取消隐藏转帖"
|
||||
block: "屏蔽"
|
||||
unblock: "取消屏蔽"
|
||||
block: "禁止对方与我互动"
|
||||
unblock: "允许对方与我互动"
|
||||
suspend: "冻结"
|
||||
unsuspend: "解除冻结"
|
||||
blockConfirm: "确定要屏蔽吗?"
|
||||
unblockConfirm: "确定要取消屏蔽吗?"
|
||||
blockConfirm: "确定要禁止对方与我互动吗?"
|
||||
unblockConfirm: "确定要允许对方与我互动吗?"
|
||||
suspendConfirm: "要冻结吗?"
|
||||
unsuspendConfirm: "要解除冻结吗?"
|
||||
selectList: "选择列表"
|
||||
@@ -184,7 +184,7 @@ flagAsCat: "喵!!!!!!!!!!!!"
|
||||
flagAsCatDescription: "喵喵喵??"
|
||||
flagShowTimelineReplies: "在时间线上显示帖子的回复"
|
||||
flagShowTimelineRepliesDescription: "启用时,时间线除了显示用户的帖子外,还会显示其他用户对帖子的回复。"
|
||||
autoAcceptFollowed: "自动允许回关请求"
|
||||
autoAcceptFollowed: "自动允许我关注的人的关注请求"
|
||||
addAccount: "添加账户"
|
||||
reloadAccountsList: "更新账户列表"
|
||||
loginFailed: "登录失败"
|
||||
@@ -207,7 +207,7 @@ selectSelf: "选择自己"
|
||||
selectUser: "选择用户"
|
||||
recipient: "收件人"
|
||||
annotation: "注解"
|
||||
federation: "联合"
|
||||
federation: "联邦"
|
||||
instances: "服务器"
|
||||
registeredAt: "初次观测"
|
||||
latestRequestReceivedAt: "上次收到的请求"
|
||||
@@ -244,11 +244,11 @@ silencedInstances: "被静音的服务器"
|
||||
silencedInstancesDescription: "设置要静音的服务器,以换行分隔。被静音的服务器内所有的账户都被视为「静音」状态,且关注操作均需要被批准。已被屏蔽的实例不受影响。"
|
||||
mediaSilencedInstances: "已隐藏媒体文件的服务器"
|
||||
mediaSilencedInstancesDescription: "设置要隐藏媒体文件的服务器,以换行分隔。被设置的服务器内所有账号的文件均按照「敏感内容」处理,且将无法使用自定义表情符号。已被屏蔽的实例不受影响。"
|
||||
federationAllowedHosts: "允许联合的服务器"
|
||||
federationAllowedHostsDescription: "设定允许联合的服务器,以换行分隔。"
|
||||
muteAndBlock: "隐藏/屏蔽"
|
||||
mutedUsers: "已隐藏的用户"
|
||||
blockedUsers: "已屏蔽的用户"
|
||||
federationAllowedHosts: "允许联邦交互的服务器"
|
||||
federationAllowedHostsDescription: "设定允许联邦通信的服务器,以换行分隔。"
|
||||
muteAndBlock: "屏蔽用户/禁止用户与我互动"
|
||||
mutedUsers: "已屏蔽的用户"
|
||||
blockedUsers: "禁止与我互动的用户"
|
||||
noUsers: "无用户"
|
||||
editProfile: "编辑资料"
|
||||
noteDeleteConfirm: "确定要删除该帖子吗?"
|
||||
@@ -261,7 +261,7 @@ default: "默认"
|
||||
defaultValueIs: "默认值: {value}"
|
||||
noCustomEmojis: "没有自定义表情符号"
|
||||
noJobs: "没有任务"
|
||||
federating: "联合中"
|
||||
federating: "联邦通信中"
|
||||
blocked: "已屏蔽"
|
||||
suspended: "停止投递"
|
||||
all: "全部"
|
||||
@@ -277,7 +277,7 @@ retypedNotMatch: "两次输入不一致!"
|
||||
currentPassword: "现在的密码"
|
||||
newPassword: "新密码"
|
||||
newPasswordRetype: "重新输入密码:"
|
||||
attachFile: "插入附件"
|
||||
attachFile: "添加附件"
|
||||
more: "更多!"
|
||||
featured: "热门"
|
||||
usernameOrUserId: "用户名或用户 ID"
|
||||
@@ -342,7 +342,7 @@ selectFolders: "选择多个文件夹"
|
||||
fileNotSelected: "未选择文件"
|
||||
renameFile: "重命名文件"
|
||||
folderName: "文件夹名称"
|
||||
createFolder: "创建文件夹"
|
||||
createFolder: "新建文件夹"
|
||||
renameFolder: "重命名文件夹"
|
||||
deleteFolder: "删除文件夹"
|
||||
folder: "文件夹"
|
||||
@@ -353,7 +353,7 @@ emptyFolder: "此文件夹中无文件"
|
||||
dropHereToUpload: "将文件拖动到这里来上传"
|
||||
unableToDelete: "无法删除"
|
||||
inputNewFileName: "请输入新文件名"
|
||||
inputNewDescription: "请输入新标题"
|
||||
inputNewDescription: "请输入新的描述文本"
|
||||
inputNewFolderName: "请输入新文件夹名"
|
||||
circularReferenceFolder: "目标文件夹是要移动的文件夹的子文件夹。"
|
||||
hasChildFilesOrFolders: "此文件夹中有文件,无法删除。"
|
||||
@@ -396,10 +396,10 @@ driveCapacityPerLocalAccount: "每个用户的网盘容量"
|
||||
driveCapacityPerRemoteAccount: "每个远程用户的网盘容量"
|
||||
inMb: "以兆字节(MegaByte)为单位"
|
||||
bannerUrl: "横幅 URL"
|
||||
backgroundImageUrl: "背景图 URL"
|
||||
backgroundImageUrl: "背景图片的链接"
|
||||
basicInfo: "基本信息"
|
||||
pinnedUsers: "置顶用户"
|
||||
pinnedUsersDescription: "输入您想要固定到“发现”页面的用户,一行一个。"
|
||||
pinnedUsersDescription: "在「发现」页面中使用换行标记想要置顶的用户。"
|
||||
pinnedPages: "固定页面"
|
||||
pinnedPagesDescription: "输入您要固定到服务器首页的页面路径,以换行符分隔。"
|
||||
pinnedClipId: "置顶的便签 ID"
|
||||
@@ -432,7 +432,7 @@ antennaExcludeBots: "排除机器人账户"
|
||||
antennaKeywordsDescription: "AND 条件用空格分隔,OR 条件用换行符分隔。"
|
||||
notifyAntenna: "开启通知"
|
||||
withFileAntenna: "仅带有附件的帖子"
|
||||
excludeNotesInSensitiveChannel: "排除敏感频道内的帖子"
|
||||
excludeNotesInSensitiveChannel: "排除敏感频道的帖子"
|
||||
enableServiceworker: "启用 ServiceWorker"
|
||||
antennaUsersDescription: "指定用户名,用换行符进行分隔"
|
||||
caseSensitive: "区分大小写"
|
||||
@@ -476,7 +476,7 @@ passwordLessLogin: "无密码登录"
|
||||
passwordLessLoginDescription: "不使用密码,仅使用安全密钥或 Passkey 登录"
|
||||
resetPassword: "重置密码"
|
||||
newPasswordIs: "新的密码是「{password}」"
|
||||
reduceUiAnimation: "减少 UI 动画"
|
||||
reduceUiAnimation: "减少 UI 动效"
|
||||
share: "分享"
|
||||
notFound: "未找到"
|
||||
notFoundDescription: "没有与指定 URL 对应的页面。"
|
||||
@@ -543,7 +543,7 @@ regenerate: "重新生成"
|
||||
fontSize: "字体大小"
|
||||
mediaListWithOneImageAppearance: "仅一张图片的媒体列表高度"
|
||||
limitTo: "上限为 {x}"
|
||||
showMediaListByGridInWideArea: "在大屏幕上并排显示媒体列表"
|
||||
showMediaListByGridInWideArea: "在宽屏上并排显示媒体列表"
|
||||
noFollowRequests: "没有关注请求"
|
||||
openImageInNewTab: "在新标签页中打开图片"
|
||||
dashboard: "管理面板"
|
||||
@@ -581,7 +581,7 @@ s3ForcePathStyleDesc: "启用 s3ForcePathStyle 会强制将存储桶名称指定
|
||||
serverLogs: "服务器日志"
|
||||
deleteAll: "全部删除"
|
||||
showFixedPostForm: "在时间线顶部显示发帖框"
|
||||
showFixedPostFormInChannel: "在时间线顶部显示发帖对话框(频道)"
|
||||
showFixedPostFormInChannel: "在时间线顶部显示发帖框(频道)"
|
||||
withRepliesByDefaultForNewlyFollowed: "在时间线中默认包含新关注用户的回复"
|
||||
newNoteRecived: "有新的帖子"
|
||||
newNote: "新帖子"
|
||||
@@ -656,7 +656,7 @@ expandTweet: "展开帖子"
|
||||
themeEditor: "主题编辑器"
|
||||
description: "描述"
|
||||
describeFile: "添加描述"
|
||||
enterFileDescription: "输入标题"
|
||||
enterFileDescription: "输入描述文本"
|
||||
author: "作者"
|
||||
leaveConfirm: "存在未保存的更改。要放弃更改吗?"
|
||||
manage: "管理"
|
||||
@@ -664,7 +664,7 @@ plugins: "插件"
|
||||
preferencesBackups: "备份设置"
|
||||
deck: "Deck"
|
||||
undeck: "取消 Deck"
|
||||
useBlurEffectForModal: "对话框使用模糊效果"
|
||||
useBlurEffectForModal: "发帖背景使用模糊效果"
|
||||
useFullReactionPicker: "使用全功能的回应工具栏"
|
||||
width: "宽度"
|
||||
height: "高度"
|
||||
@@ -773,13 +773,13 @@ yes: "是"
|
||||
no: "否"
|
||||
driveFilesCount: "网盘的文件数"
|
||||
driveUsage: "网盘的空间用量"
|
||||
noCrawle: "要求搜索引擎不索引该用户"
|
||||
noCrawleDescription: "要求搜索引擎不要收录(索引)您的用户页面,帖子,页面等。"
|
||||
noCrawle: "拒绝搜索引擎的索引"
|
||||
noCrawleDescription: "拒绝搜索引擎收录(索引)您的个人资料,帖子,页面等。"
|
||||
lockedAccountInfo: "即使启用该功能,只要帖子可见范围不是「仅关注者」,任何人都可以看到您的帖子。"
|
||||
alwaysMarkSensitive: "默认将媒体文件标记为敏感内容"
|
||||
loadRawImages: "添加附件图像的缩略图时使用原始图像质量"
|
||||
disableShowingAnimatedImages: "不播放动画"
|
||||
disableShowingAnimatedImages_caption: "如果即使关闭了此设置但动画仍无法播放,则可能是浏览器或操作系统的辅助功能设置,又或者是省电设置等产生了干扰。"
|
||||
disableShowingAnimatedImages: "不播放动态图像"
|
||||
disableShowingAnimatedImages_caption: "如果即使禁用了此设置,动态图像仍无法播放,可能是由于浏览器或操作系统的辅助功能设置、省电设置或其他因素所致。"
|
||||
highlightSensitiveMedia: "高亮显示敏感媒体"
|
||||
verificationEmailSent: "已发送确认电子邮件。请访问电子邮件中的链接以完成设置。"
|
||||
notSet: "未设置"
|
||||
@@ -851,7 +851,7 @@ fullView: "全屏"
|
||||
quitFullView: "退出全屏"
|
||||
addDescription: "添加描述"
|
||||
userPagePinTip: "在帖子的菜单中选择“置顶”,即可显示该条帖子。"
|
||||
notSpecifiedMentionWarning: "有未指定的提及"
|
||||
notSpecifiedMentionWarning: "有未添加到收件人的提及"
|
||||
info: "关于"
|
||||
userInfo: "用户信息"
|
||||
unknown: "未知"
|
||||
@@ -877,9 +877,9 @@ noMaintainerInformationWarning: "尚未设置管理员信息。"
|
||||
noInquiryUrlWarning: "尚未设置联络地址。"
|
||||
noBotProtectionWarning: "尚未设置 Bot 防御。"
|
||||
configure: "设置"
|
||||
postToGallery: "创建新图集"
|
||||
postToGallery: "发表相册"
|
||||
postToHashtag: "发布至该话题"
|
||||
gallery: "图集"
|
||||
gallery: "相册"
|
||||
recentPosts: "最新发布"
|
||||
popularPosts: "热门投稿"
|
||||
shareWithNote: "分享到帖文"
|
||||
@@ -1032,7 +1032,7 @@ browserPushNotificationDisabledDescription: "{serverName}无权限发送通知
|
||||
windowMaximize: "最大化"
|
||||
windowMinimize: "最小化"
|
||||
windowRestore: "还原"
|
||||
caption: "标题"
|
||||
caption: "描述文本"
|
||||
loggedInAsBot: "以 Bot 账户登录"
|
||||
tools: "工具"
|
||||
cannotLoad: "无法加载"
|
||||
@@ -1073,7 +1073,7 @@ thisPostMayBeAnnoying: "这个帖子可能会让其他人感到困扰。"
|
||||
thisPostMayBeAnnoyingHome: "发到首页"
|
||||
thisPostMayBeAnnoyingCancel: "取消"
|
||||
thisPostMayBeAnnoyingIgnore: "就这样发布"
|
||||
collapseRenotes: "省略显示已经看过的转发内容"
|
||||
collapseRenotes: "折叠已经看过的转贴"
|
||||
collapseRenotesDescription: "折叠显示回应或转发过的帖文。"
|
||||
internalServerError: "内部服务器错误"
|
||||
internalServerErrorDescription: "内部服务器发生了预期外的错误"
|
||||
@@ -1081,9 +1081,9 @@ copyErrorInfo: "复制错误信息"
|
||||
joinThisServer: "在本服务器上注册"
|
||||
exploreOtherServers: "探索其他服务器"
|
||||
letsLookAtTimeline: "看看时间线"
|
||||
disableFederationConfirm: "确定要禁用联合?"
|
||||
disableFederationConfirmWarn: "禁用联合不会将帖子设为私有。在大多数情况下,不需要禁用联合。"
|
||||
disableFederationOk: "联合禁用"
|
||||
disableFederationConfirm: "确定要禁用联邦交互?"
|
||||
disableFederationConfirmWarn: "即使禁用联邦交互,也不会将帖子设为私有。在大多数情况下,没有必要禁用联邦交互。"
|
||||
disableFederationOk: "禁用联邦"
|
||||
invitationRequiredToRegister: "此服务器目前只允许拥有邀请码的人注册。"
|
||||
emailNotSupported: "此服务器不支持发送邮件"
|
||||
postToTheChannel: "发布到频道"
|
||||
@@ -1093,7 +1093,7 @@ likeOnly: "仅点赞"
|
||||
likeOnlyForRemote: "全部(远程仅点赞)"
|
||||
nonSensitiveOnly: "仅限非敏感内容"
|
||||
nonSensitiveOnlyForLocalLikeOnlyForRemote: "仅限非敏感内容(远程仅点赞)"
|
||||
rolesAssignedToMe: "指派给自己的角色"
|
||||
rolesAssignedToMe: "我的角色"
|
||||
resetPasswordConfirm: "确定重置密码?"
|
||||
sensitiveWords: "敏感词"
|
||||
sensitiveWordsDescription: "包含这些词的帖子将只在首页可见。可用换行来设定多个词。"
|
||||
@@ -1148,7 +1148,7 @@ pleaseAgreeAllToContinue: "必须全部勾选「同意」才能够继续。"
|
||||
continue: "继续"
|
||||
preservedUsernames: "保留的用户名"
|
||||
preservedUsernamesDescription: "列出需要保留的用户名,使用换行来作为分割。被指定的用户名在建立账户时无法使用,但由管理员所创建的账户不受该限制。此外,现有的账户也不会受到影响。"
|
||||
createNoteFromTheFile: "从文件创建帖子"
|
||||
createNoteFromTheFile: "使用该文件发帖"
|
||||
archive: "归档"
|
||||
archived: "已归档"
|
||||
unarchive: "取消归档"
|
||||
@@ -1158,7 +1158,7 @@ thisChannelArchived: "该频道已被归档。"
|
||||
displayOfNote: "显示帖子"
|
||||
initialAccountSetting: "初始设定"
|
||||
youFollowing: "正在关注"
|
||||
preventAiLearning: "拒绝接受生成式 AI 的学习"
|
||||
preventAiLearning: "拒绝用于训练生成式 AI"
|
||||
preventAiLearningDescription: "要求文章生成 AI 或图像生成 AI 不能够以发布的帖子和图像等内容作为学习对象。这是通过在 HTML 响应中包含 noai 标志来实现的,这不能完全阻止 AI 学习你的发布内容,并不是所有 AI 都会遵守这类请求。"
|
||||
options: "选项"
|
||||
specifyUser: "指定用户"
|
||||
@@ -1226,8 +1226,8 @@ notificationRecieveConfig: "通知接收设置"
|
||||
mutualFollow: "互相关注"
|
||||
followingOrFollower: "关注中或关注者"
|
||||
fileAttachedOnly: "仅限媒体"
|
||||
showRepliesToOthersInTimeline: "在时间线中包含给别人的回复"
|
||||
hideRepliesToOthersInTimeline: "在时间线中隐藏给别人的回复"
|
||||
showRepliesToOthersInTimeline: "在时间线中显示对他人的回复"
|
||||
hideRepliesToOthersInTimeline: "在时间线中隐藏对他人的回复"
|
||||
showRepliesToOthersInTimelineAll: "在时间线中显示所有现在关注的人的回复"
|
||||
hideRepliesToOthersInTimelineAll: "在时间线中隐藏所有现在关注的人的回复"
|
||||
confirmShowRepliesAll: "此操作不可撤销。确认要在时间线中显示所有现在关注的人的回复吗?"
|
||||
@@ -1325,22 +1325,22 @@ lockdown: "锁定"
|
||||
pleaseSelectAccount: "请选择帐户"
|
||||
availableRoles: "可用角色"
|
||||
acknowledgeNotesAndEnable: "理解注意事项后再开启。"
|
||||
federationSpecified: "此服务器已开启联合白名单。只能与管理员指定的服务器通信。"
|
||||
federationDisabled: "此服务器已禁用联合。无法与其它服务器上的用户通信。"
|
||||
federationSpecified: "此服务器已开启联邦白名单模式。只能与管理员指定的服务器通信。"
|
||||
federationDisabled: "此服务器已禁用联邦功能。无法与其它服务器上的用户通信。"
|
||||
draft: "草稿"
|
||||
draftsAndScheduledNotes: "草稿和定时发送"
|
||||
confirmOnReact: "发送回应前需要确认"
|
||||
reactAreYouSure: "要用「{emoji}」进行回应吗?"
|
||||
markAsSensitiveConfirm: "要将此媒体标记为敏感吗?"
|
||||
unmarkAsSensitiveConfirm: "要将此媒体解除敏感标记吗?"
|
||||
markAsSensitiveConfirm: "确定标记此媒体为敏感内容吗?"
|
||||
unmarkAsSensitiveConfirm: "确定取消标记为敏感内容吗?"
|
||||
preferences: "偏好设置"
|
||||
accessibility: "辅助功能"
|
||||
preferencesProfile: "设置的配置文件"
|
||||
copyPreferenceId: "复制设置 ID"
|
||||
resetToDefaultValue: "重置为默认值"
|
||||
overrideByAccount: "覆盖账号"
|
||||
overrideByAccount: "使用账户设置"
|
||||
untitled: "未命名"
|
||||
noName: "没有名字"
|
||||
noName: "未命名"
|
||||
skip: "跳过"
|
||||
restore: "恢复"
|
||||
syncBetweenDevices: "设备间同步"
|
||||
@@ -1351,7 +1351,7 @@ preferenceSyncConflictChoiceServer: "服务器上的设定值"
|
||||
preferenceSyncConflictChoiceDevice: "设备上的设定值"
|
||||
preferenceSyncConflictChoiceCancel: "取消同步"
|
||||
paste: "粘贴"
|
||||
emojiPalette: "表情符号调色板"
|
||||
emojiPalette: "表情符号选择器"
|
||||
postForm: "发帖窗口"
|
||||
textCount: "字数"
|
||||
information: "关于"
|
||||
@@ -1368,7 +1368,7 @@ embed: "嵌入"
|
||||
settingsMigrating: "正在迁移设置,请稍候。(之后也可以在设置 → 其它 → 迁移旧设置来手动迁移)"
|
||||
readonly: "只读"
|
||||
goToDeck: "返回至 Deck"
|
||||
federationJobs: "联合作业"
|
||||
federationJobs: "联邦作业"
|
||||
driveAboutTip: "网盘可以显示以前上传的文件。<br>\n也可以在发布帖子时重复使用文件,或在发布帖子前预先上传文件。<br>\n<b>删除文件时,其将从至今为止所有用到该文件的地方(如帖子、页面、头像、横幅)消失。</b><br>\n也可以新建文件夹来整理文件。"
|
||||
scrollToClose: "滑动并关闭"
|
||||
advice: "建议"
|
||||
@@ -1382,7 +1382,7 @@ unmuteX: "取消对{x}的隐藏"
|
||||
abort: "中止"
|
||||
tip: "提示和技巧"
|
||||
redisplayAllTips: "重新显示所有的提示和技巧"
|
||||
hideAllTips: "隐藏所有的提示和技巧"
|
||||
hideAllTips: "隐藏所有的 “提示与技巧”"
|
||||
defaultImageCompressionLevel: "默认图像压缩等级"
|
||||
defaultImageCompressionLevel_description: "较低的等级可以保持画质,但会增加文件大小。<br>较高的等级可以减少文件大小,但相对应的画质将会降低。"
|
||||
defaultCompressionLevel: "默认压缩等级"
|
||||
@@ -1394,7 +1394,7 @@ pluginsAreDisabledBecauseSafeMode: "因启用了安全模式,所有插件均
|
||||
customCssIsDisabledBecauseSafeMode: "因启用了安全模式,无法应用自定义 CSS。"
|
||||
themeIsDefaultBecauseSafeMode: "启用安全模式时将使用默认主题。关闭安全模式后将还原。"
|
||||
thankYouForTestingBeta: "感谢您协助测试 beta 版!"
|
||||
createUserSpecifiedNote: "创建指定用户的帖子"
|
||||
createUserSpecifiedNote: "提及该用户并发帖"
|
||||
schedulePost: "定时发布"
|
||||
scheduleToPostOnX: "预定在 {x} 发出"
|
||||
scheduledToPostOnX: "已预定在 {x} 发出"
|
||||
@@ -1511,10 +1511,10 @@ _chat:
|
||||
mutual: "仅相互关注"
|
||||
none: "没有人"
|
||||
_emojiPalette:
|
||||
palettes: "调色板"
|
||||
enableSyncBetweenDevicesForPalettes: "启用调色板的设备间同步"
|
||||
paletteForMain: "主调色板"
|
||||
paletteForReaction: "回应用调色板"
|
||||
palettes: "表情符号托盘"
|
||||
enableSyncBetweenDevicesForPalettes: "在设备间同步表情符号托盘"
|
||||
paletteForMain: "主表情符号托盘"
|
||||
paletteForReaction: "回应时的表情符号托盘"
|
||||
_settings:
|
||||
driveBanner: "可在此管理和设置网盘、确认使用量及配置上传文件的设置。"
|
||||
pluginBanner: "使用插件可以扩展客户端的功能。可以在此安装、单独管理插件。"
|
||||
@@ -1535,9 +1535,9 @@ _settings:
|
||||
timelineAndNote: "时间线和帖子"
|
||||
makeEveryTextElementsSelectable: "使所有的文字均可选择"
|
||||
makeEveryTextElementsSelectable_description: "若开启,在某些情况下可能降低用户体验。"
|
||||
useStickyIcons: "使图标跟随滚动"
|
||||
useStickyIcons: "用户头像跟随页面滚动"
|
||||
enableHighQualityImagePlaceholders: "显示高质量图像的占位符"
|
||||
uiAnimations: "UI 动画"
|
||||
uiAnimations: "UI 动效"
|
||||
showNavbarSubButtons: "在导航栏中显示副按钮"
|
||||
ifOn: "启用时"
|
||||
ifOff: "关闭时"
|
||||
@@ -1552,7 +1552,7 @@ _settings:
|
||||
showAvailableReactionsFirstInNote: "在顶部显示可用的回应"
|
||||
showPageTabBarBottom: "在下方显示页面标签栏"
|
||||
emojiPaletteBanner: "可以将固定显示在表情符号选择器中的预设注册为调色板,也可以自定义表情符号选择器的显示方式。"
|
||||
enableAnimatedImages: "启用动画图像"
|
||||
enableAnimatedImages: "启用动态图像"
|
||||
settingsPersistence_title: "设置持久化"
|
||||
settingsPersistence_description1: "启用设置持久化可防止设置信息丢失。"
|
||||
settingsPersistence_description2: "根据环境不同,有可能无法开启。"
|
||||
@@ -1580,12 +1580,12 @@ _accountSettings:
|
||||
requireSigninToViewContents: "需要登录才能显示内容"
|
||||
requireSigninToViewContentsDescription1: "您发布的所有帖子将变成需要登入后才会显示。有望防止爬虫收集各种信息。"
|
||||
requireSigninToViewContentsDescription2: "没有 URL 预览(OGP)、内嵌网页、引用帖子的功能的服务器也将无法显示。"
|
||||
requireSigninToViewContentsDescription3: "这些限制可能不适用于联合到远程服务器的内容。"
|
||||
requireSigninToViewContentsDescription3: "对于已通过联邦分发到远程服务器的内容,这些限制可能不适用。"
|
||||
makeNotesFollowersOnlyBefore: "可将过去的帖子设为仅关注者可见"
|
||||
makeNotesFollowersOnlyBeforeDescription: "开启此设定时,超过设定的时间或日期后,帖子将变为仅关注者可见。关闭后帖子的公开状态将恢复成原本的设定。"
|
||||
makeNotesHiddenBefore: "将过去的帖子设为私密"
|
||||
makeNotesHiddenBeforeDescription: "开启此设定时,超过设定的时间或日期后,帖子将变为仅自己可见。关闭后帖子的公开状态将恢复成原本的设定。"
|
||||
mayNotEffectForFederatedNotes: "与远程服务器联合的帖子在远端可能会没有效果。"
|
||||
mayNotEffectForFederatedNotes: "对于已通过联邦投递到远程服务器的帖子,此操作在远端可能无法生效。"
|
||||
mayNotEffectSomeSituations: "此限制功能非常简单,在与远程服务器联合等情形时可能不适用。"
|
||||
notesHavePassedSpecifiedPeriod: "超过指定时间的帖子"
|
||||
notesOlderThanSpecifiedDateAndTime: "指定日期前的帖子"
|
||||
@@ -1637,7 +1637,7 @@ _announcement:
|
||||
_initialAccountSetting:
|
||||
accountCreated: "账户创建完成了!"
|
||||
letsStartAccountSetup: "马上来进行账户的初始设定吧。"
|
||||
letsFillYourProfile: "首先,来设定你的个人档案吧!"
|
||||
letsFillYourProfile: "首先,设置一下您的个人资料吧!"
|
||||
profileSetting: "个人资料设置"
|
||||
privacySetting: "隐私设置"
|
||||
theseSettingsCanEditLater: "也可以在稍后修改这里的设置。"
|
||||
@@ -1689,10 +1689,10 @@ _initialTutorial:
|
||||
public: "向所有用户公开。\n"
|
||||
home: "仅在首页时间线上发布。 关注者、从个人资料页查看过来的用户、以及通过转帖也能被别的用户看见。"
|
||||
followers: "仅对关注者可见。 除了您自己之外,没有人可以转贴,并且只有您的关注者可以查看它。\n"
|
||||
direct: "它将仅向指定用户公开,并且他们也会收到通知。 您可以使用它来代替私信。\n"
|
||||
direct: "仅对指定用户公开,且收件人将收到通知。"
|
||||
doNotSendConfidencialOnDirect1: "发送敏感信息时请注意。\n"
|
||||
doNotSendConfidencialOnDirect2: "目标服务器的管理员可以看到发布的内容,因此如果您向不受信任的服务器上的用户发送私信,则在处理敏感信息时需要小心。"
|
||||
localOnly: "不将帖子推送到其它服务器。 无论上述公开范围如何,其它服务器的用户将无法看到附加了此设定的帖子。\n"
|
||||
localOnly: "不将帖子通过联邦推送到其它服务器。 无论上述公开范围如何,其它服务器的用户将无法看到附加了此设定的帖子。\n"
|
||||
_cw:
|
||||
title: "隐藏内容 (CW)\n"
|
||||
description: "显示「注解」里的内容而不是正文。点击「查看更多」将会把正文显示出来。"
|
||||
@@ -1701,7 +1701,7 @@ _initialTutorial:
|
||||
note: "茨了带巧克力的甜甜圈🍩😋"
|
||||
useCases: "用于服务器条款所规定的帖子,或对剧透内容和敏感内容进行自主规制。"
|
||||
_howToMakeAttachmentsSensitive:
|
||||
title: "如何将附件标注为敏感内容?"
|
||||
title: "如何标记附件为敏感内容?"
|
||||
description: "对于服务器方针所要求要求的,又或者不适合直接展示的附件,请添加「敏感」标记。\n"
|
||||
tryThisFile: "试试看,将附加到此窗口的图像标注为敏感!"
|
||||
_exampleNote:
|
||||
@@ -1746,7 +1746,7 @@ _serverSettings:
|
||||
singleUserMode: "单用户模式"
|
||||
singleUserMode_description: "若此服务器只有自己使用,开启此模式将最佳化性能。"
|
||||
signToActivityPubGet: "对 GET 请求签名"
|
||||
signToActivityPubGet_description: "通常情况下请保持启用。若遇到联合通信方面的问题,将其关闭可能会有所改善,但另一方面有可能会造成无法通信。"
|
||||
signToActivityPubGet_description: "通常情况下请保持启用。若遇到联邦通信方面的问题,将其关闭可能会有所改善,但另一方面有可能会造成无法通信。"
|
||||
proxyRemoteFiles: "代理远程文件"
|
||||
proxyRemoteFiles_description: "如果启用,远程服务器的文件将由代理提供。可有效保护图像预览缩略图的生成与用户隐私。"
|
||||
allowExternalApRedirect: "允许通过 ActivityPub 重定向查询"
|
||||
@@ -1771,7 +1771,7 @@ _accountMigration:
|
||||
moveTo: "把这个账户迁移到新的账户"
|
||||
moveToLabel: "迁移后的账户"
|
||||
moveCannotBeUndone: "一旦迁移账户,就无法撤销。"
|
||||
moveAccountDescription: "\n迁移到新帐户。\n ・现有的关注者自动关注新帐户\n ・此帐户的所有关注者都将被删除\n ・您将无法再使用此帐户发帖。\n关注者迁移是自动的,但关注中迁移必须手动完成。请在迁移前在此帐户上导出关注列表,并在迁移后立即在目标帐户上执行导入。\n列表、隐藏、屏蔽也是如此,因此您必须手动迁移它。\n(此描述适用于该服务器(Misskey v13.12.0 或更高版本)。其他 ActivityPub 软件(例如 Mastodon)的行为可能有所不同。)"
|
||||
moveAccountDescription: "\n迁移到新帐户。\n ・现有的关注者自动关注新帐户\n ・此帐户的所有关注者都将被删除\n ・您将无法再使用此帐户发帖。\n关注者迁移是自动的,但关注中迁移必须手动完成。请在迁移前在此帐户上导出关注列表,并在迁移后立即在目标帐户上执行导入。\n列表、屏蔽列表、禁止与我互动的列表也是如此,因此您必须手动迁移它。\n(此描述适用于该服务器(Misskey v13.12.0 或更高版本)。其他 ActivityPub 软件(例如 Mastodon)的行为可能有所不同。)"
|
||||
moveAccountHowTo: "要进行账户迁移,请现在目标账户中为此账户建立一个别名。\n建立别名后,请像这样输入目标账户:@username@server.example.com"
|
||||
startMigration: "迁移"
|
||||
migrationConfirm: "确定要把此账户迁移到 {account} 吗?一旦确定后,此操作无法取消,此账户也无法以原来的状态使用。\n同时,请确认迁移后的账户,已创造别名。"
|
||||
@@ -1889,7 +1889,7 @@ _achievements:
|
||||
description: "第一次将帖子加入收藏"
|
||||
_myNoteFavorited1:
|
||||
title: "想要星星"
|
||||
description: "自己的帖子被其他人加入收藏了"
|
||||
description: "自己的帖子被其他人收藏了"
|
||||
_profileFilled:
|
||||
title: "整装待发"
|
||||
description: "设置了个人资料"
|
||||
@@ -2083,7 +2083,7 @@ _role:
|
||||
maxFileSize: "可上传的最大文件大小"
|
||||
maxFileSize_caption: "可能在反向代理或 CDN 等前端存在其它设定值。"
|
||||
alwaysMarkNsfw: "总是将文件标记为 NSFW"
|
||||
canUpdateBioMedia: "可以更新头像和横幅"
|
||||
canUpdateBioMedia: "允许更新头像和横幅"
|
||||
pinMax: "帖子置顶数量限制"
|
||||
antennaMax: "可创建的最大天线数量"
|
||||
wordMuteMax: "折叠词的字数限制"
|
||||
@@ -2098,9 +2098,10 @@ _role:
|
||||
canSearchNotes: "是否可以搜索帖子"
|
||||
canSearchUsers: "使用用户检索"
|
||||
canUseTranslator: "使用翻译功能"
|
||||
canCreateChannel: "创建频道"
|
||||
avatarDecorationLimit: "可添加头像挂件的最大个数"
|
||||
canImportAntennas: "允许导入天线"
|
||||
canImportBlocking: "允许导入屏蔽列表"
|
||||
canImportBlocking: "允许导入禁止与我互动的列表"
|
||||
canImportFollowing: "允许导入关注列表"
|
||||
canImportMuting: "允许导入隐藏列表"
|
||||
canImportUserLists: "允许导入用户列表"
|
||||
@@ -2108,7 +2109,7 @@ _role:
|
||||
uploadableFileTypes: "可上传的文件类型"
|
||||
uploadableFileTypes_caption: "指定 MIME 类型。可用换行指定多个类型,也可以用星号(*)作为通配符。(如 image/*)"
|
||||
uploadableFileTypes_caption2: "文件根据文件的不同,可能无法判断其类型。若要允许此类文件,请在指定中添加 {x}。"
|
||||
noteDraftLimit: "可在服务器上创建多少草稿"
|
||||
noteDraftLimit: "可在服务器上创建的草稿数量"
|
||||
scheduledNoteLimit: "可同时创建的定时帖子数量"
|
||||
watermarkAvailable: "能否使用水印功能"
|
||||
_condition:
|
||||
@@ -2165,7 +2166,7 @@ _ad:
|
||||
back: "返回"
|
||||
reduceFrequencyOfThisAd: "减少此广告的频率"
|
||||
hide: "不显示"
|
||||
timezoneinfo: "星期几是由服务器的时区所指定的。"
|
||||
timezoneinfo: "星期几是根据服务器的时区确定的。"
|
||||
adsSettings: "广告设置"
|
||||
notesPerOneAd: "在实时更新时间线中插入广告的间隔(帖子个数)"
|
||||
setZeroToDisable: "设为 0 将不在实时更新时间线中投放广告"
|
||||
@@ -2229,7 +2230,7 @@ _aboutMisskey:
|
||||
_displayOfSensitiveMedia:
|
||||
respect: "隐藏敏感媒体"
|
||||
ignore: "显示敏感媒体"
|
||||
force: "隐藏所有内容"
|
||||
force: "隐藏所有媒体"
|
||||
_instanceTicker:
|
||||
none: "不显示"
|
||||
remote: "仅远程用户"
|
||||
@@ -2253,7 +2254,7 @@ _channel:
|
||||
allowRenoteToExternal: "允许转发到频道外和引用"
|
||||
_menuDisplay:
|
||||
sideFull: "横向"
|
||||
sideIcon: "横向(图标)"
|
||||
sideIcon: "横向(图标)"
|
||||
top: "顶部"
|
||||
hide: "隐藏"
|
||||
_wordMute:
|
||||
@@ -2315,7 +2316,7 @@ _theme:
|
||||
mention: "提及"
|
||||
mentionMe: "提及"
|
||||
renote: "转发"
|
||||
modalBg: "对话框背景"
|
||||
modalBg: "发帖背景"
|
||||
divider: "分割线"
|
||||
scrollbarHandle: "滚动条"
|
||||
scrollbarHandleHover: "滚动条(悬停)"
|
||||
@@ -2334,7 +2335,7 @@ _theme:
|
||||
fgHighlighted: "高亮显示文本"
|
||||
_sfx:
|
||||
note: "帖子"
|
||||
noteMy: "我的帖子"
|
||||
noteMy: "发帖"
|
||||
notification: "通知"
|
||||
reaction: "选择回应时"
|
||||
chatMessage: "私信"
|
||||
@@ -2403,8 +2404,8 @@ _2fa:
|
||||
_permissions:
|
||||
"read:account": "查看账户信息"
|
||||
"write:account": "更改帐户信息"
|
||||
"read:blocks": "查看屏蔽列表"
|
||||
"write:blocks": "编辑屏蔽列表"
|
||||
"read:blocks": "查看禁止与我互动的列表"
|
||||
"write:blocks": "编辑禁止与我互动的列表"
|
||||
"read:drive": "查看网盘"
|
||||
"write:drive": "管理网盘文件"
|
||||
"read:favorites": "查看收藏夹"
|
||||
@@ -2429,10 +2430,10 @@ _permissions:
|
||||
"write:user-groups": "编辑用户组"
|
||||
"read:channels": "查看频道"
|
||||
"write:channels": "管理频道"
|
||||
"read:gallery": "浏览图集"
|
||||
"write:gallery": "编辑图集"
|
||||
"read:gallery-likes": "浏览喜欢的图集"
|
||||
"write:gallery-likes": "管理喜欢的图集"
|
||||
"read:gallery": "浏览相册"
|
||||
"write:gallery": "管理相册"
|
||||
"read:gallery-likes": "浏览喜欢的相册"
|
||||
"write:gallery-likes": "管理喜欢的相册"
|
||||
"read:flash": "查看 Play"
|
||||
"write:flash": "编辑 Play"
|
||||
"read:flash-likes": "查看 Play 的点赞"
|
||||
@@ -2456,7 +2457,7 @@ _permissions:
|
||||
"write:admin:unsuspend-user": "解除用户冻结"
|
||||
"write:admin:meta": "编辑实例元数据"
|
||||
"write:admin:user-note": "编辑管理笔记"
|
||||
"write:admin:roles": "编辑角色"
|
||||
"write:admin:roles": "管理角色"
|
||||
"read:admin:roles": "查看角色"
|
||||
"write:admin:relays": "编辑中继"
|
||||
"read:admin:relays": "查看中继"
|
||||
@@ -2466,8 +2467,8 @@ _permissions:
|
||||
"read:admin:announcements": "查看公告"
|
||||
"write:admin:avatar-decorations": "编辑头像挂件"
|
||||
"read:admin:avatar-decorations": "查看头像挂件"
|
||||
"write:admin:federation": "编辑联合相关信息"
|
||||
"write:admin:account": "编辑用户账户"
|
||||
"write:admin:federation": "编辑联邦相关信息"
|
||||
"write:admin:account": "管理用户账户"
|
||||
"read:admin:account": "查看用户相关情报"
|
||||
"write:admin:emoji": "编辑表情符号"
|
||||
"read:admin:emoji": "查看表情符号"
|
||||
@@ -2483,7 +2484,7 @@ _permissions:
|
||||
"read:invite-codes": "获取已发行的邀请码"
|
||||
"write:clip-favorite": "管理喜欢的便签"
|
||||
"read:clip-favorite": "查看便签的点赞"
|
||||
"read:federation": "查看联合相关信息"
|
||||
"read:federation": "查看联邦相关信息"
|
||||
"write:report-abuse": "举报用户"
|
||||
"write:chat": "撰写或删除消息"
|
||||
"read:chat": "查看私信"
|
||||
@@ -2530,7 +2531,7 @@ _widgets:
|
||||
photos: "照片"
|
||||
digitalClock: "数字时钟"
|
||||
unixClock: "UNIX 时钟"
|
||||
federation: "联合"
|
||||
federation: "联邦"
|
||||
instanceCloud: "服务器球状列表"
|
||||
postForm: "发帖窗口"
|
||||
slideshow: "幻灯片展示"
|
||||
@@ -2563,7 +2564,7 @@ _widgetOptions:
|
||||
graduationDots: "点"
|
||||
graduationArabic: "阿拉伯数字"
|
||||
fadeGraduations: "淡化表盘"
|
||||
sAnimation: "秒针动画"
|
||||
sAnimation: "秒针动效"
|
||||
sAnimationElastic: "跳动"
|
||||
sAnimationEaseOut: "平滑"
|
||||
twentyFour: "24 小时制"
|
||||
@@ -2621,11 +2622,11 @@ _visibility:
|
||||
followersDescription: "仅发送至关注者"
|
||||
specified: "指定用户"
|
||||
specifiedDescription: "仅发送至指定用户"
|
||||
disableFederation: "不参与联合"
|
||||
disableFederation: "仅限本地"
|
||||
disableFederationDescription: "不发送到其他服务器"
|
||||
_postForm:
|
||||
quitInspiteOfThereAreUnuploadedFilesConfirm: "还有未上传的文件,要丢弃并关闭窗口吗?"
|
||||
uploaderTip: "文件还未上传。可以在文件菜单中进行重命名、裁剪、添加水印、设置是否压缩等操作。文件将在发帖时自动上传。"
|
||||
uploaderTip: "文件尚未上传。您可以在文件菜单中设置重命名、裁剪图片、添加水印以及是否压缩等功能。文件将在帖子发布时自动上传。"
|
||||
replyPlaceholder: "回复这个帖子..."
|
||||
quotePlaceholder: "引用这个帖子..."
|
||||
channelPlaceholder: "发布到频道…"
|
||||
@@ -2660,12 +2661,12 @@ _profile:
|
||||
metadataDescription: "最多可以在个人资料中以表格形式显示四条其他信息。"
|
||||
metadataLabel: "标签"
|
||||
metadataContent: "内容"
|
||||
changeAvatar: "修改头像"
|
||||
changeBanner: "修改横幅"
|
||||
changeAvatar: "更换头像"
|
||||
changeBanner: "更换横幅"
|
||||
verifiedLinkDescription: "如果将内容设置为 URL,当链接所指向的网页内包含自己的个人资料链接时,可以显示一个已验证图标。"
|
||||
avatarDecorationMax: "最多可添加 {max} 个挂件"
|
||||
followedMessage: "被关注时显示的消息"
|
||||
followedMessageDescription: "可以设置被关注时向对方显示的短消息。"
|
||||
followedMessage: "被关注时的信息"
|
||||
followedMessageDescription: "被关注时,可设置向关注者显示的信息。"
|
||||
followedMessageDescriptionForLockedAccount: "需要批准才能关注的情况下,消息会在请求被批准后显示。"
|
||||
_exportOrImport:
|
||||
allNotes: "所有帖子"
|
||||
@@ -2673,13 +2674,13 @@ _exportOrImport:
|
||||
clips: "便签"
|
||||
followingList: "关注中"
|
||||
muteList: "隐藏"
|
||||
blockingList: "屏蔽"
|
||||
blockingList: "禁止与我互动的列表"
|
||||
userLists: "列表"
|
||||
excludeMutingUsers: "排除已隐藏用户"
|
||||
excludeInactiveUsers: "排除不活跃用户"
|
||||
withReplies: "在时间线中包含导入用户的回复"
|
||||
_charts:
|
||||
federation: "联合"
|
||||
federation: "联邦"
|
||||
apRequest: "请求"
|
||||
usersIncDec: "用户数量:增加/减少"
|
||||
usersTotal: "用户总数"
|
||||
@@ -2977,7 +2978,7 @@ _moderationLogTypes:
|
||||
deleteAccount: "删除帐户"
|
||||
deletePage: "删除页面"
|
||||
deleteFlash: "删除 Play"
|
||||
deleteGalleryPost: "删除图集内容"
|
||||
deleteGalleryPost: "删除相册内容"
|
||||
deleteChatRoom: "删除群聊"
|
||||
updateProxyAccountDescription: "更新代理账户的简介"
|
||||
_fileViewer:
|
||||
@@ -3034,7 +3035,7 @@ _dataSaver:
|
||||
description: "防止自动加载图像和视频。 点击隐藏的图像/视频即可加载它们。\n"
|
||||
_avatar:
|
||||
title: "头像"
|
||||
description: "停止播放头像的动画。 由于动画图片的文件大小可能比普通图像大,这可以进一步减少数据流量。"
|
||||
description: "不播放头像的动画。 由于动态图像的文件大小远大于一般图像,停止播放能够进一步节省数据流量。"
|
||||
_urlPreviewThumbnail:
|
||||
title: "不显示 URL预览缩略图"
|
||||
description: "将不再加载 URL 预览缩略图。"
|
||||
@@ -3053,7 +3054,7 @@ _reversi:
|
||||
gameSettings: "对局设置"
|
||||
chooseBoard: "选择棋盘"
|
||||
blackOrWhite: "先手/后手"
|
||||
blackIs: "{name}执黑(先手)"
|
||||
blackIs: "{name}执黑(先手)"
|
||||
rules: "规则"
|
||||
thisGameIsStartedSoon: "对局即将开始"
|
||||
waitingForOther: "等待对手准备"
|
||||
@@ -3069,7 +3070,7 @@ _reversi:
|
||||
surrendered: "已认输"
|
||||
timeout: "超时"
|
||||
drawn: "平局"
|
||||
won: "{name}获胜"
|
||||
won: "{name} 获胜"
|
||||
black: "黑"
|
||||
white: "白"
|
||||
total: "总计"
|
||||
@@ -3078,7 +3079,7 @@ _reversi:
|
||||
allGames: "所有对局"
|
||||
ended: "结束"
|
||||
playing: "对局中"
|
||||
isLlotheo: "落子少的一方获胜(又名奥赛罗)"
|
||||
isLlotheo: "落子少的一方获胜(黑白棋规则)"
|
||||
loopedMap: "循环棋盘"
|
||||
canPutEverywhere: "无限制放置模式"
|
||||
timeLimitForEachTurn: "1回合的时间限制"
|
||||
@@ -3088,8 +3089,8 @@ _reversi:
|
||||
shareToTlTheGameWhenStart: "开始时在时间线发布对局"
|
||||
iStartedAGame: "对局开始!#MisskeyReversi"
|
||||
opponentHasSettingsChanged: "对手更改了设定"
|
||||
allowIrregularRules: "允许非常规规则(完全自由)"
|
||||
disallowIrregularRules: "禁止非常规规则"
|
||||
allowIrregularRules: "允许特殊规则(完全自由)"
|
||||
disallowIrregularRules: "禁止特殊规则"
|
||||
showBoardLabels: "显示行号和列号"
|
||||
useAvatarAsStone: "用头像作为棋子"
|
||||
_offlineScreen:
|
||||
@@ -3246,7 +3247,7 @@ _search:
|
||||
searchScopeLocal: "本地"
|
||||
searchScopeServer: "指定服务器"
|
||||
searchScopeUser: "指定用户"
|
||||
pleaseEnterServerHost: "请填写服务器主机名"
|
||||
pleaseEnterServerHost: "请填写服务器的主机名称"
|
||||
pleaseSelectUser: "请选择用户"
|
||||
serverHostPlaceholder: "如:misskey.example.com"
|
||||
_serverSetupWizard:
|
||||
@@ -3275,13 +3276,13 @@ _serverSetupWizard:
|
||||
largeScaleServerAdvice: "运营大规模服务器可能需要高级基础设施知识,如负载均衡和数据库复制。"
|
||||
doYouConnectToFediverse: "要加入 Fediverse 吗?"
|
||||
doYouConnectToFediverse_description1: "若加入由分散性服务器所构成的网络(Fediverse),将能与其它服务器交换内容。"
|
||||
doYouConnectToFediverse_description2: "加入 Fediverse 在这里被称为「联合」。"
|
||||
youCanConfigureMoreFederationSettingsLater: "可在之后进行如哪些服务器可以进行联合等高级设置。"
|
||||
doYouConnectToFediverse_description2: "加入 Fediverse 在这里被称为「联邦」。"
|
||||
youCanConfigureMoreFederationSettingsLater: "可在之后进行如哪些服务器允许进行联邦交互等高级设置。"
|
||||
remoteContentsCleaning: "自动清理传入内容"
|
||||
remoteContentsCleaning_description: "加入联合后,服务器将持续接收大量内容。打开自动清理后,将自动删除无法找到的旧内容,可节省存储空间。"
|
||||
remoteContentsCleaning_description: "开启联邦互通后,服务器将持续接收大量内容。打开自动清理后,将自动删除无法找到的旧内容,可节省存储空间。"
|
||||
adminInfo: "管理员信息"
|
||||
adminInfo_description: "设置用于接受询问的管理员信息。"
|
||||
adminInfo_mustBeFilled: "开放服务器或开启了联合的情况下必须输入。"
|
||||
adminInfo_mustBeFilled: "开放服务器或启用了联邦的情况下必须输入。"
|
||||
followingSettingsAreRecommended: "推荐以下设置"
|
||||
applyTheseSettings: "使用此设置"
|
||||
skipSettings: "跳过设置"
|
||||
@@ -3301,7 +3302,7 @@ _uploader:
|
||||
doneConfirm: "部分文件尚未上传,是否继续?"
|
||||
maxFileSizeIsX: "可上传最大 {x} 的文件。"
|
||||
allowedTypes: "可上传的文件类型"
|
||||
tip: "文件还没有被上传。可在此对话框中进行上传前确认、重命名、压缩、裁剪等操作。准备完成后,点击「上传」即可开始上传。"
|
||||
tip: "文件尚未上传。在此对话框中,您可以进行上传前的确认、重命名、压缩和裁剪等操作。准备就绪后,点击“上传”按钮即可开始上传。"
|
||||
_clientPerformanceIssueTip:
|
||||
title: "如果觉得电池耗电过高"
|
||||
makeSureDisabledAdBlocker: "请关闭广告拦截器"
|
||||
|
||||
51
package.json
51
package.json
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "misskey",
|
||||
"version": "2026.4.0-alpha.1",
|
||||
"version": "2026.5.2-beta.0",
|
||||
"codename": "nasubi",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/misskey-dev/misskey.git"
|
||||
},
|
||||
"packageManager": "pnpm@10.33.0",
|
||||
"packageManager": "pnpm@11.1.2",
|
||||
"workspaces": [
|
||||
"packages/misskey-js",
|
||||
"packages/i18n",
|
||||
@@ -28,9 +28,9 @@
|
||||
"build": "pnpm build-pre && pnpm -r build && pnpm build-assets",
|
||||
"build-storybook": "pnpm --filter frontend build-storybook",
|
||||
"build-misskey-js-with-types": "pnpm build-pre && pnpm --filter backend... --filter=!misskey-js build && pnpm --filter backend generate-api-json --no-build && ncp packages/backend/built/api.json packages/misskey-js/generator/api.json && pnpm --filter misskey-js update-autogen-code && pnpm --filter misskey-js build && pnpm --filter misskey-js api",
|
||||
"start": "cd packages/backend && pnpm compile-config && node ./built/boot/entry.js",
|
||||
"start:inspect": "cd packages/backend && pnpm compile-config && node --inspect ./built/boot/entry.js",
|
||||
"start:test": "ncp ./.github/misskey/test.yml ./.config/test.yml && cd packages/backend && cross-env NODE_ENV=test pnpm compile-config && cross-env NODE_ENV=test node ./built/boot/entry.js",
|
||||
"start": "cd packages/backend && pnpm compile-config && node ./built/entry.js",
|
||||
"start:inspect": "cd packages/backend && pnpm compile-config && node --inspect ./built/entry.js",
|
||||
"start:test": "ncp ./.github/misskey/test.yml ./.config/test.yml && cd packages/backend && cross-env NODE_ENV=test pnpm compile-config && cross-env NODE_ENV=test node ./built/entry.js",
|
||||
"cli": "cd packages/backend && pnpm cli",
|
||||
"init": "pnpm migrate",
|
||||
"migrate": "cd packages/backend && pnpm migrate",
|
||||
@@ -44,8 +44,8 @@
|
||||
"cy:run": "pnpm cypress run",
|
||||
"e2e": "pnpm start-server-and-test start:test http://localhost:61812 cy:run",
|
||||
"e2e-dev-container": "ncp ./.config/cypress-devcontainer.yml ./.config/test.yml && pnpm start-server-and-test start:test http://localhost:61812 cy:run",
|
||||
"jest": "cd packages/backend && pnpm jest",
|
||||
"jest-and-coverage": "cd packages/backend && pnpm jest-and-coverage",
|
||||
"backend-unit-test": "cd packages/backend && pnpm test",
|
||||
"backend-unit-test-and-coverage": "cd packages/backend && pnpm test-and-coverage",
|
||||
"test": "pnpm -r test",
|
||||
"test-and-coverage": "pnpm -r test-and-coverage",
|
||||
"clean": "node scripts/clean.mjs",
|
||||
@@ -53,44 +53,33 @@
|
||||
"cleanall": "pnpm clean-all"
|
||||
},
|
||||
"dependencies": {
|
||||
"cssnano": "7.1.3",
|
||||
"esbuild": "0.27.4",
|
||||
"cssnano": "8.0.0",
|
||||
"esbuild": "0.28.0",
|
||||
"execa": "9.6.1",
|
||||
"ignore-walk": "8.0.0",
|
||||
"js-yaml": "4.1.1",
|
||||
"postcss": "8.5.8",
|
||||
"tar": "7.5.13",
|
||||
"terser": "5.46.1"
|
||||
"postcss": "8.5.14",
|
||||
"tar": "7.5.14",
|
||||
"terser": "5.47.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/js": "9.39.4",
|
||||
"@misskey-dev/eslint-plugin": "2.1.0",
|
||||
"@types/js-yaml": "4.0.9",
|
||||
"@types/node": "24.12.0",
|
||||
"@typescript-eslint/eslint-plugin": "8.57.2",
|
||||
"@typescript-eslint/parser": "8.57.2",
|
||||
"@typescript/native-preview": "7.0.0-dev.20260116.1",
|
||||
"@types/node": "24.12.2",
|
||||
"@typescript-eslint/eslint-plugin": "8.59.2",
|
||||
"@typescript-eslint/parser": "8.59.2",
|
||||
"@typescript/native-preview": "7.0.0-dev.20260426.1",
|
||||
"cross-env": "10.1.0",
|
||||
"cypress": "15.13.0",
|
||||
"cypress": "15.14.2",
|
||||
"eslint": "9.39.4",
|
||||
"globals": "17.4.0",
|
||||
"globals": "17.6.0",
|
||||
"ncp": "2.0.0",
|
||||
"pnpm": "10.33.0",
|
||||
"start-server-and-test": "2.1.5",
|
||||
"pnpm": "11.1.2",
|
||||
"start-server-and-test": "3.0.2",
|
||||
"typescript": "5.9.3"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@tensorflow/tfjs-core": "4.22.0"
|
||||
},
|
||||
"pnpm": {
|
||||
"overrides": {
|
||||
"@aiscript-dev/aiscript-languageserver": "-",
|
||||
"chokidar": "5.0.0",
|
||||
"lodash": "4.17.23"
|
||||
},
|
||||
"ignoredBuiltDependencies": [
|
||||
"@sentry-internal/node-cpu-profiler",
|
||||
"exifreader"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,29 +0,0 @@
|
||||
{
|
||||
"$schema": "https://swc.rs/schema.json",
|
||||
"jsc": {
|
||||
"parser": {
|
||||
"syntax": "typescript",
|
||||
"jsx": true,
|
||||
"dynamicImport": true,
|
||||
"decorators": true
|
||||
},
|
||||
"transform": {
|
||||
"legacyDecorator": true,
|
||||
"decoratorMetadata": true,
|
||||
"react": {
|
||||
"runtime": "automatic",
|
||||
"importSource": "@kitajs/html"
|
||||
}
|
||||
},
|
||||
"experimental": {
|
||||
"keepImportAssertions": true
|
||||
},
|
||||
"baseUrl": "src",
|
||||
"paths": {
|
||||
"@/*": ["*"]
|
||||
},
|
||||
"target": "es2022"
|
||||
},
|
||||
"minify": false,
|
||||
"sourceMaps": "inline"
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
user-agent: *
|
||||
allow: /
|
||||
|
||||
# todo: sitemap
|
||||
@@ -1,121 +0,0 @@
|
||||
import fs from 'node:fs';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import { dirname, join } from 'node:path';
|
||||
import { build } from 'esbuild';
|
||||
import { swcPlugin } from 'esbuild-plugin-swc';
|
||||
|
||||
const _filename = fileURLToPath(import.meta.url);
|
||||
const _dirname = dirname(_filename);
|
||||
const _package = JSON.parse(fs.readFileSync(_dirname + '/package.json', 'utf-8'));
|
||||
|
||||
const resolveTsPathsPlugin = {
|
||||
name: 'resolve-ts-paths',
|
||||
setup(build) {
|
||||
build.onResolve({ filter: /^\.{1,2}\/.*\.js$/ }, (args) => {
|
||||
if (args.importer) {
|
||||
const absPath = join(args.resolveDir, args.path);
|
||||
const tsPath = absPath.slice(0, -3) + '.ts';
|
||||
if (fs.existsSync(tsPath)) return { path: tsPath };
|
||||
const tsxPath = absPath.slice(0, -3) + '.tsx';
|
||||
if (fs.existsSync(tsxPath)) return { path: tsxPath };
|
||||
}
|
||||
});
|
||||
},
|
||||
};
|
||||
|
||||
const externalIpaddrPlugin = {
|
||||
name: 'external-ipaddr',
|
||||
setup(build) {
|
||||
build.onResolve({ filter: /^ipaddr\.js$/ }, (args) => {
|
||||
return { path: args.path, external: true };
|
||||
});
|
||||
},
|
||||
};
|
||||
|
||||
/** @type {import('esbuild').BuildOptions} */
|
||||
const options = {
|
||||
entryPoints: ['./src/boot/entry.ts'],
|
||||
minify: true,
|
||||
keepNames: true,
|
||||
bundle: true,
|
||||
outdir: './built/boot',
|
||||
target: 'node22',
|
||||
platform: 'node',
|
||||
format: 'esm',
|
||||
sourcemap: 'linked',
|
||||
packages: 'external',
|
||||
banner: {
|
||||
js: 'import { createRequire as topLevelCreateRequire } from "module";' +
|
||||
'import ___url___ from "url";' +
|
||||
'const require = topLevelCreateRequire(import.meta.url);' +
|
||||
'const __filename = ___url___.fileURLToPath(import.meta.url);' +
|
||||
'const __dirname = ___url___.fileURLToPath(new URL(".", import.meta.url));',
|
||||
},
|
||||
plugins: [
|
||||
externalIpaddrPlugin,
|
||||
resolveTsPathsPlugin,
|
||||
swcPlugin({
|
||||
jsc: {
|
||||
parser: {
|
||||
syntax: 'typescript',
|
||||
decorators: true,
|
||||
dynamicImport: true,
|
||||
},
|
||||
transform: {
|
||||
legacyDecorator: true,
|
||||
decoratorMetadata: true,
|
||||
},
|
||||
experimental: {
|
||||
keepImportAssertions: true,
|
||||
},
|
||||
baseUrl: join(_dirname, 'src'),
|
||||
paths: {
|
||||
'@/*': ['*'],
|
||||
},
|
||||
target: 'esnext',
|
||||
keepClassNames: true,
|
||||
},
|
||||
}),
|
||||
externalIpaddrPlugin,
|
||||
],
|
||||
// external: [
|
||||
// 'slacc-*',
|
||||
// 'class-transformer',
|
||||
// 'class-validator',
|
||||
// '@sentry/*',
|
||||
// '@nestjs/websockets/socket-module',
|
||||
// '@nestjs/microservices/microservices-module',
|
||||
// '@nestjs/microservices',
|
||||
// '@napi-rs/canvas-win32-x64-msvc',
|
||||
// 'mock-aws-s3',
|
||||
// 'aws-sdk',
|
||||
// 'nock',
|
||||
// 'sharp',
|
||||
// 'jsdom',
|
||||
// 're2',
|
||||
// '@napi-rs/canvas',
|
||||
// ],
|
||||
};
|
||||
|
||||
const args = process.argv.slice(2).map(arg => arg.toLowerCase());
|
||||
|
||||
if (!args.includes('--no-clean')) {
|
||||
fs.rmSync('./built', { recursive: true, force: true });
|
||||
}
|
||||
|
||||
await buildSrc();
|
||||
|
||||
async function buildSrc() {
|
||||
console.log(`[${_package.name}] start building...`);
|
||||
|
||||
await build(options)
|
||||
.then(() => {
|
||||
console.log(`[${_package.name}] build succeeded.`);
|
||||
})
|
||||
.catch((err) => {
|
||||
process.stderr.write(err.stderr || err.message || err);
|
||||
process.exit(1);
|
||||
});
|
||||
|
||||
console.log(`[${_package.name}] finish building.`);
|
||||
}
|
||||
@@ -1,220 +0,0 @@
|
||||
/*
|
||||
* For a detailed explanation regarding each configuration property and type check, visit:
|
||||
* https://jestjs.io/docs/en/configuration.html
|
||||
*/
|
||||
|
||||
module.exports = {
|
||||
// All imported modules in your tests should be mocked automatically
|
||||
// automock: false,
|
||||
|
||||
// Stop running tests after `n` failures
|
||||
// bail: 0,
|
||||
|
||||
// The directory where Jest should store its cached dependency information
|
||||
// cacheDirectory: "C:\\Users\\ai\\AppData\\Local\\Temp\\jest",
|
||||
|
||||
// Automatically clear mock calls and instances between every test
|
||||
// clearMocks: false,
|
||||
|
||||
// Indicates whether the coverage information should be collected while executing the test
|
||||
// collectCoverage: false,
|
||||
|
||||
// An array of glob patterns indicating a set of files for which coverage information should be collected
|
||||
collectCoverageFrom: ['src/**/*.ts', '!src/**/*.test.ts'],
|
||||
|
||||
// The directory where Jest should output its coverage files
|
||||
coverageDirectory: "coverage",
|
||||
|
||||
// An array of regexp pattern strings used to skip coverage collection
|
||||
// coveragePathIgnorePatterns: [
|
||||
// "\\\\node_modules\\\\"
|
||||
// ],
|
||||
|
||||
// Indicates which provider should be used to instrument code for coverage
|
||||
coverageProvider: "v8",
|
||||
|
||||
// A list of reporter names that Jest uses when writing coverage reports
|
||||
// coverageReporters: [
|
||||
// "json",
|
||||
// "text",
|
||||
// "lcov",
|
||||
// "clover"
|
||||
// ],
|
||||
|
||||
// An object that configures minimum threshold enforcement for coverage results
|
||||
// coverageThreshold: undefined,
|
||||
|
||||
// A path to a custom dependency extractor
|
||||
// dependencyExtractor: undefined,
|
||||
|
||||
// Make calling deprecated APIs throw helpful error messages
|
||||
// errorOnDeprecated: false,
|
||||
|
||||
// Force coverage collection from ignored files using an array of glob patterns
|
||||
// forceCoverageMatch: [],
|
||||
|
||||
// A path to a module which exports an async function that is triggered once before all test suites
|
||||
// globalSetup: undefined,
|
||||
|
||||
// A path to a module which exports an async function that is triggered once after all test suites
|
||||
// globalTeardown: undefined,
|
||||
|
||||
// A set of global variables that need to be available in all test environments
|
||||
globals: {
|
||||
},
|
||||
|
||||
// The maximum amount of workers used to run your tests. Can be specified as % or a number. E.g. maxWorkers: 10% will use 10% of your CPU amount + 1 as the maximum worker number. maxWorkers: 2 will use a maximum of 2 workers.
|
||||
// maxWorkers: "50%",
|
||||
|
||||
// An array of directory names to be searched recursively up from the requiring module's location
|
||||
// moduleDirectories: [
|
||||
// "node_modules"
|
||||
// ],
|
||||
|
||||
// An array of file extensions your modules use
|
||||
// moduleFileExtensions: [
|
||||
// "js",
|
||||
// "json",
|
||||
// "jsx",
|
||||
// "ts",
|
||||
// "tsx",
|
||||
// "node"
|
||||
// ],
|
||||
|
||||
// A map from regular expressions to module names or to arrays of module names that allow to stub out resources with a single module
|
||||
moduleNameMapper: {
|
||||
// Do not resolve .wasm.js to .wasm by the rule below
|
||||
'^(.+)\\.wasm\\.js$': '$1.wasm.js',
|
||||
// SWC converts @/foo/bar.js to `../../src/foo/bar.js`, and then this rule
|
||||
// converts it again to `../../src/foo/bar` which then can be resolved to
|
||||
// `.ts` files.
|
||||
// See https://github.com/swc-project/jest/issues/64#issuecomment-1029753225
|
||||
// TODO: Use `--allowImportingTsExtensions` on TypeScript 5.0 so that we can
|
||||
// directly import `.ts` files without this hack.
|
||||
'^((?:\\.{1,2}|[A-Z:])*/.*)\\.js$': '$1',
|
||||
},
|
||||
|
||||
// An array of regexp pattern strings, matched against all module paths before considered 'visible' to the module loader
|
||||
// modulePathIgnorePatterns: [],
|
||||
|
||||
// Activates notifications for test results
|
||||
// notify: false,
|
||||
|
||||
// An enum that specifies notification mode. Requires { notify: true }
|
||||
// notifyMode: "failure-change",
|
||||
|
||||
// A preset that is used as a base for Jest's configuration
|
||||
//preset: "ts-jest/presets/js-with-ts-esm",
|
||||
|
||||
// Run tests from one or more projects
|
||||
// projects: undefined,
|
||||
|
||||
// Use this configuration option to add custom reporters to Jest
|
||||
// reporters: undefined,
|
||||
|
||||
// Automatically reset mock state between every test
|
||||
// resetMocks: false,
|
||||
|
||||
// Reset the module registry before running each individual test
|
||||
// resetModules: false,
|
||||
|
||||
// A path to a custom resolver
|
||||
// resolver: './jest-resolver.cjs',
|
||||
|
||||
// Automatically restore mock state between every test
|
||||
restoreMocks: true,
|
||||
|
||||
// The root directory that Jest should scan for tests and modules within
|
||||
// rootDir: undefined,
|
||||
|
||||
// A list of paths to directories that Jest should use to search for files in
|
||||
roots: [
|
||||
"<rootDir>"
|
||||
],
|
||||
|
||||
// Allows you to use a custom runner instead of Jest's default test runner
|
||||
// runner: "jest-runner",
|
||||
|
||||
// The paths to modules that run some code to configure or set up the testing environment before each test
|
||||
// setupFiles: [],
|
||||
|
||||
// A list of paths to modules that run some code to configure or set up the testing framework before each test
|
||||
// setupFilesAfterEnv: [],
|
||||
|
||||
// The number of seconds after which a test is considered as slow and reported as such in the results.
|
||||
// slowTestThreshold: 5,
|
||||
|
||||
// A list of paths to snapshot serializer modules Jest should use for snapshot testing
|
||||
// snapshotSerializers: [],
|
||||
|
||||
// The test environment that will be used for testing
|
||||
testEnvironment: "node",
|
||||
|
||||
// Options that will be passed to the testEnvironment
|
||||
// testEnvironmentOptions: {},
|
||||
|
||||
// Adds a location field to test results
|
||||
// testLocationInResults: false,
|
||||
|
||||
// The glob patterns Jest uses to detect test files
|
||||
testMatch: [
|
||||
"<rootDir>/test/unit/**/*.ts",
|
||||
"<rootDir>/src/**/*.test.ts",
|
||||
],
|
||||
|
||||
// An array of regexp pattern strings that are matched against all test paths, matched tests are skipped
|
||||
// testPathIgnorePatterns: [
|
||||
// "\\\\node_modules\\\\"
|
||||
// ],
|
||||
|
||||
// The regexp pattern or array of patterns that Jest uses to detect test files
|
||||
// testRegex: [],
|
||||
|
||||
// This option allows the use of a custom results processor
|
||||
// testResultsProcessor: undefined,
|
||||
|
||||
// This option allows use of a custom test runner
|
||||
// testRunner: "jasmine2",
|
||||
|
||||
// This option sets the URL for the jsdom environment. It is reflected in properties such as location.href
|
||||
// testURL: "http://localhost",
|
||||
|
||||
// Setting this value to "fake" allows the use of fake timers for functions such as "setTimeout"
|
||||
// timers: "real",
|
||||
|
||||
// A map from regular expressions to paths to transformers
|
||||
transform: {
|
||||
"^.+\\.(t|j)sx?$": ["@swc/jest"],
|
||||
},
|
||||
|
||||
// An array of regexp pattern strings that are matched against all source file paths, matched files will skip transformation
|
||||
// transformIgnorePatterns: [
|
||||
// "\\\\node_modules\\\\",
|
||||
// "\\.pnp\\.[^\\\\]+$"
|
||||
// ],
|
||||
|
||||
// An array of regexp pattern strings that are matched against all modules before the module loader will automatically return a mock for them
|
||||
// unmockedModulePathPatterns: undefined,
|
||||
|
||||
// Indicates whether each individual test should be reported during the run
|
||||
// verbose: undefined,
|
||||
|
||||
// An array of regexp patterns that are matched against all source file paths before re-running tests in watch mode
|
||||
// watchPathIgnorePatterns: [],
|
||||
|
||||
// Whether to use watchman for file crawling
|
||||
// watchman: true,
|
||||
|
||||
extensionsToTreatAsEsm: ['.ts', '.tsx'],
|
||||
|
||||
testTimeout: 60000,
|
||||
|
||||
// Let Jest kill the test worker whenever it grows too much
|
||||
// (It seems there's a known memory leak issue in Node.js' vm.Script used by Jest)
|
||||
// https://github.com/facebook/jest/issues/11956
|
||||
maxWorkers: 1, // Make it use worker (that can be killed and restarted)
|
||||
logHeapUsage: true, // To debug when out-of-memory happens on CI
|
||||
workerIdleMemoryLimit: '1GiB', // Limit the worker to 1GB (GitHub Workflows dies at 2GB)
|
||||
|
||||
maxConcurrency: 32,
|
||||
};
|
||||
@@ -1,15 +0,0 @@
|
||||
/*
|
||||
* For a detailed explanation regarding each configuration property and type check, visit:
|
||||
* https://jestjs.io/docs/en/configuration.html
|
||||
*/
|
||||
|
||||
const base = require('./jest.config.cjs')
|
||||
|
||||
module.exports = {
|
||||
...base,
|
||||
globalSetup: "<rootDir>/built-test/entry.js",
|
||||
setupFilesAfterEnv: ["<rootDir>/test/jest.setup.ts"],
|
||||
testMatch: [
|
||||
"<rootDir>/test/e2e/**/*.ts",
|
||||
],
|
||||
};
|
||||
@@ -1,13 +0,0 @@
|
||||
/*
|
||||
* For a detailed explanation regarding each configuration property and type check, visit:
|
||||
* https://jestjs.io/docs/en/configuration.html
|
||||
*/
|
||||
|
||||
const base = require('./jest.config.cjs');
|
||||
|
||||
module.exports = {
|
||||
...base,
|
||||
testMatch: [
|
||||
'<rootDir>/test-federation/test/**/*.test.ts',
|
||||
],
|
||||
};
|
||||
@@ -1,15 +0,0 @@
|
||||
/*
|
||||
* For a detailed explanation regarding each configuration property and type check, visit:
|
||||
* https://jestjs.io/docs/en/configuration.html
|
||||
*/
|
||||
|
||||
const base = require('./jest.config.cjs')
|
||||
|
||||
module.exports = {
|
||||
...base,
|
||||
globalSetup: "<rootDir>/test/jest.setup.unit.cjs",
|
||||
testMatch: [
|
||||
"<rootDir>/test/unit/**/*.ts",
|
||||
"<rootDir>/src/**/*.test.ts",
|
||||
],
|
||||
};
|
||||
@@ -1,31 +0,0 @@
|
||||
#!/usr/bin/env node
|
||||
import child_process from 'node:child_process';
|
||||
import path from 'node:path';
|
||||
import url from 'node:url';
|
||||
|
||||
import semver from 'semver';
|
||||
|
||||
const __filename = url.fileURLToPath(import.meta.url);
|
||||
const __dirname = path.dirname(__filename);
|
||||
|
||||
const args = [];
|
||||
args.push(...[
|
||||
...semver.satisfies(process.version, '^20.17.0 || ^22.0.0 || ^24.10.0') ? ['--no-experimental-require-module'] : [],
|
||||
'--experimental-vm-modules',
|
||||
'--experimental-import-meta-resolve',
|
||||
path.join(__dirname, 'node_modules/jest/bin/jest.js'),
|
||||
...process.argv.slice(2),
|
||||
]);
|
||||
|
||||
const child = child_process.spawn(process.execPath, args, { stdio: 'inherit' });
|
||||
child.on('error', (err) => {
|
||||
console.error('Failed to start Jest:', err);
|
||||
process.exit(1);
|
||||
});
|
||||
child.on('exit', (code, signal) => {
|
||||
if (code === null) {
|
||||
process.exit(128 + signal);
|
||||
} else {
|
||||
process.exit(code);
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,22 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
export class AddCategoryToAvatarDecorations1766652173085 {
|
||||
name = 'AddCategoryToAvatarDecorations1766652173085';
|
||||
|
||||
/**
|
||||
* @param {QueryRunner} queryRunner
|
||||
*/
|
||||
async up(queryRunner) {
|
||||
await queryRunner.query('ALTER TABLE "avatar_decoration" ADD "category" character varying(128)');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {QueryRunner} queryRunner
|
||||
*/
|
||||
async down(queryRunner) {
|
||||
await queryRunner.query('ALTER TABLE "avatar_decoration" DROP COLUMN "category"');
|
||||
}
|
||||
};
|
||||
@@ -1,6 +1,6 @@
|
||||
import { DataSource } from 'typeorm';
|
||||
import { loadConfig } from './src-js/config.js';
|
||||
import { entities } from './src-js/postgres.js';
|
||||
import { loadConfig } from './built/config.js';
|
||||
import { entities } from './built/postgres.js';
|
||||
|
||||
const isConcurrentIndexMigrationEnabled = process.env.MISSKEY_MIGRATION_CREATE_INDEX_CONCURRENTLY === '1';
|
||||
|
||||
|
||||
@@ -4,170 +4,151 @@
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"engines": {
|
||||
"node": "^22.15.0 || ^24.10.0"
|
||||
"node": "^22.15.0 || ^24.10.0 || ^26.0.0"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "pnpm compile-config && node ./built/boot/entry.js",
|
||||
"start:inspect": "pnpm compile-config && node --inspect ./built/boot/entry.js",
|
||||
"start:test": "cross-env NODE_ENV=test pnpm compile-config && cross-env NODE_ENV=test node ./built/boot/entry.js",
|
||||
"start": "pnpm compile-config && node ./built/entry.js",
|
||||
"start:inspect": "pnpm compile-config && node --inspect ./built/entry.js",
|
||||
"start:test": "cross-env NODE_ENV=test pnpm compile-config && cross-env NODE_ENV=test node ./built/entry.js",
|
||||
"migrate": "pnpm compile-config && pnpm typeorm migration:run -d ormconfig.js",
|
||||
"revert": "pnpm compile-config && pnpm typeorm migration:revert -d ormconfig.js",
|
||||
"cli": "pnpm compile-config && node ./src-js/boot/cli.js",
|
||||
"cli": "pnpm compile-config && node ./built/cli.js",
|
||||
"check:connect": "pnpm compile-config && node ./scripts/check_connect.js",
|
||||
"compile-config": "node ./scripts/compile_config.js",
|
||||
"build": "swc src -d src-js -D --strip-leading-paths && node ./build.js",
|
||||
"build:test": "swc test-server -d built-test -D --config-file test-server/.swcrc --strip-leading-paths",
|
||||
"watch:swc": "swc src -d built -D -w --strip-leading-paths",
|
||||
"build": "rolldown -c",
|
||||
"build:unit": "rolldown -c --sourcemap",
|
||||
"build:e2e": "rolldown -c --e2e",
|
||||
"build:tsc": "tsgo -p tsconfig.json && tsc-alias -p tsconfig.json",
|
||||
"watch": "pnpm compile-config && node ./scripts/watch.mjs",
|
||||
"restart": "pnpm build && pnpm start",
|
||||
"dev": "pnpm compile-config && node ./scripts/dev.mjs",
|
||||
"dev": "pnpm compile-config && rolldown -c --watch",
|
||||
"typecheck": "tsgo --noEmit && tsgo -p test --noEmit && tsgo -p test-federation --noEmit",
|
||||
"eslint": "eslint --quiet \"{src,test-federation}/**/*.ts\"",
|
||||
"lint": "pnpm typecheck && pnpm eslint",
|
||||
"jest": "cross-env NODE_ENV=test pnpm compile-config && cross-env NODE_ENV=test node ./jest.js --forceExit --config jest.config.unit.cjs",
|
||||
"jest:e2e": "cross-env NODE_ENV=test pnpm compile-config && cross-env NODE_ENV=test node ./jest.js --forceExit --config jest.config.e2e.cjs",
|
||||
"jest:fed": "pnpm compile-config && node ./jest.js --forceExit --config jest.config.fed.cjs",
|
||||
"jest-and-coverage": "cross-env NODE_ENV=test pnpm compile-config && cross-env NODE_ENV=test node ./jest.js --coverage --forceExit --config jest.config.unit.cjs",
|
||||
"jest-and-coverage:e2e": "cross-env NODE_ENV=test pnpm compile-config && cross-env NODE_ENV=test node ./jest.js --coverage --forceExit --config jest.config.e2e.cjs",
|
||||
"jest-clear": "cross-env NODE_ENV=test pnpm compile-config && cross-env NODE_ENV=test node ./jest.js --clearCache",
|
||||
"test": "pnpm jest",
|
||||
"test:e2e": "pnpm build && pnpm build:test && pnpm jest:e2e",
|
||||
"test:fed": "pnpm jest:fed",
|
||||
"test-and-coverage": "pnpm jest-and-coverage",
|
||||
"test-and-coverage:e2e": "pnpm build && pnpm build:test && pnpm jest-and-coverage:e2e",
|
||||
"test": "pnpm build:unit && cross-env NODE_ENV=test pnpm compile-config && vitest --config vitest.config.unit.ts",
|
||||
"test:e2e": "pnpm build:e2e && cross-env NODE_ENV=test pnpm compile-config && vitest --config vitest.config.e2e.ts",
|
||||
"test:fed": "cross-env NODE_ENV=test pnpm compile-config && vitest --config vitest.config.fed.ts",
|
||||
"test-and-coverage": "pnpm build:unit && cross-env NODE_ENV=test pnpm compile-config && vitest --coverage --config vitest.config.unit.ts",
|
||||
"test-and-coverage:e2e": "pnpm build:e2e && cross-env NODE_ENV=test pnpm compile-config && vitest --coverage --config vitest.config.e2e.ts",
|
||||
"check-migrations": "node scripts/check_migrations_clean.js",
|
||||
"generate-api-json": "pnpm compile-config && node ./scripts/generate_api_json.js"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@swc/core-android-arm64": "1.3.11",
|
||||
"@swc/core-darwin-arm64": "1.15.21",
|
||||
"@swc/core-darwin-x64": "1.15.21",
|
||||
"@swc/core-freebsd-x64": "1.3.11",
|
||||
"@swc/core-linux-arm-gnueabihf": "1.15.21",
|
||||
"@swc/core-linux-arm64-gnu": "1.15.21",
|
||||
"@swc/core-linux-arm64-musl": "1.15.21",
|
||||
"@swc/core-linux-x64-gnu": "1.15.21",
|
||||
"@swc/core-linux-x64-musl": "1.15.21",
|
||||
"@swc/core-win32-arm64-msvc": "1.15.21",
|
||||
"@swc/core-win32-ia32-msvc": "1.15.21",
|
||||
"@swc/core-win32-x64-msvc": "1.15.21",
|
||||
"@tensorflow/tfjs": "4.22.0",
|
||||
"@tensorflow/tfjs-node": "4.22.0",
|
||||
"bufferutil": "4.1.0",
|
||||
"slacc-android-arm-eabi": "0.0.10",
|
||||
"slacc-android-arm64": "0.0.10",
|
||||
"slacc-darwin-arm64": "0.0.10",
|
||||
"slacc-darwin-universal": "0.0.10",
|
||||
"slacc-darwin-x64": "0.0.10",
|
||||
"slacc-freebsd-x64": "0.0.10",
|
||||
"slacc-linux-arm-gnueabihf": "0.0.10",
|
||||
"slacc-linux-arm64-gnu": "0.0.10",
|
||||
"slacc-linux-arm64-musl": "0.0.10",
|
||||
"slacc-linux-x64-gnu": "0.0.10",
|
||||
"slacc-linux-x64-musl": "0.0.10",
|
||||
"slacc-win32-arm64-msvc": "0.0.10",
|
||||
"slacc-win32-x64-msvc": "0.0.10",
|
||||
"slacc-android-arm-eabi": "0.1.5",
|
||||
"slacc-android-arm64": "0.1.5",
|
||||
"slacc-darwin-arm64": "0.1.5",
|
||||
"slacc-darwin-universal": "0.1.5",
|
||||
"slacc-darwin-x64": "0.1.5",
|
||||
"slacc-freebsd-x64": "0.1.5",
|
||||
"slacc-linux-arm-gnueabihf": "0.1.5",
|
||||
"slacc-linux-arm64-gnu": "0.1.5",
|
||||
"slacc-linux-arm64-musl": "0.1.5",
|
||||
"slacc-linux-x64-gnu": "0.1.5",
|
||||
"slacc-linux-x64-musl": "0.1.5",
|
||||
"slacc-win32-arm64-msvc": "0.1.5",
|
||||
"slacc-win32-x64-msvc": "0.1.5",
|
||||
"utf-8-validate": "6.0.6"
|
||||
},
|
||||
"dependencies": {
|
||||
"@aws-sdk/client-s3": "3.1016.0",
|
||||
"@aws-sdk/lib-storage": "3.1016.0",
|
||||
"@discordapp/twemoji": "16.0.1",
|
||||
"@aws-sdk/client-s3": "3.1044.0",
|
||||
"@aws-sdk/lib-storage": "3.1044.0",
|
||||
"@fastify/accepts": "5.0.4",
|
||||
"@fastify/cors": "11.2.0",
|
||||
"@fastify/express": "4.0.4",
|
||||
"@fastify/http-proxy": "11.4.2",
|
||||
"@fastify/multipart": "9.4.0",
|
||||
"@fastify/static": "9.0.0",
|
||||
"@fastify/express": "4.0.5",
|
||||
"@fastify/http-proxy": "11.4.4",
|
||||
"@fastify/multipart": "10.0.0",
|
||||
"@fastify/static": "9.1.3",
|
||||
"@kitajs/html": "4.2.13",
|
||||
"@misskey-dev/emoji-assets": "17.0.3",
|
||||
"@misskey-dev/emoji-data": "17.0.3",
|
||||
"@misskey-dev/sharp-read-bmp": "1.2.0",
|
||||
"@misskey-dev/summaly": "5.2.5",
|
||||
"@napi-rs/canvas": "0.1.97",
|
||||
"@nestjs/common": "11.1.17",
|
||||
"@nestjs/core": "11.1.17",
|
||||
"@nestjs/testing": "11.1.17",
|
||||
"@misskey-dev/summaly": "5.3.0",
|
||||
"@napi-rs/canvas": "1.0.0",
|
||||
"@nestjs/common": "11.1.19",
|
||||
"@nestjs/core": "11.1.19",
|
||||
"@nestjs/testing": "11.1.19",
|
||||
"@oxc-project/runtime": "0.129.0",
|
||||
"@peertube/http-signature": "1.7.0",
|
||||
"@sentry/node": "10.45.0",
|
||||
"@sentry/profiling-node": "10.45.0",
|
||||
"@sentry/node": "10.52.0",
|
||||
"@sentry/profiling-node": "10.52.0",
|
||||
"@simplewebauthn/server": "13.3.0",
|
||||
"@sinonjs/fake-timers": "15.1.1",
|
||||
"@smithy/node-http-handler": "4.5.0",
|
||||
"@swc/cli": "0.8.0",
|
||||
"@swc/core": "1.15.21",
|
||||
"@twemoji/parser": "16.0.0",
|
||||
"@sinonjs/fake-timers": "15.4.0",
|
||||
"@smithy/node-http-handler": "4.6.1",
|
||||
"accepts": "1.3.8",
|
||||
"ajv": "8.18.0",
|
||||
"ajv": "8.20.0",
|
||||
"archiver": "7.0.1",
|
||||
"async-mutex": "0.5.0",
|
||||
"bcryptjs": "3.0.3",
|
||||
"blurhash": "2.0.5",
|
||||
"body-parser": "2.2.2",
|
||||
"bullmq": "5.71.0",
|
||||
"bullmq": "5.76.6",
|
||||
"cacheable-lookup": "7.0.0",
|
||||
"chalk": "5.6.2",
|
||||
"chalk-template": "1.1.2",
|
||||
"chokidar": "5.0.0",
|
||||
"color-convert": "3.1.3",
|
||||
"content-disposition": "1.0.1",
|
||||
"content-disposition": "1.1.0",
|
||||
"date-fns": "4.1.0",
|
||||
"deep-email-validator": "0.1.21",
|
||||
"fastify": "5.8.4",
|
||||
"deep-email-validator": "0.1.27",
|
||||
"fastify": "5.8.5",
|
||||
"fastify-raw-body": "5.0.0",
|
||||
"feed": "5.2.0",
|
||||
"file-type": "21.3.4",
|
||||
"feed": "5.2.1",
|
||||
"file-type": "22.0.1",
|
||||
"fluent-ffmpeg": "2.1.3",
|
||||
"form-data": "4.0.5",
|
||||
"got": "14.6.6",
|
||||
"got": "15.0.5",
|
||||
"hpagent": "1.2.0",
|
||||
"http-link-header": "1.1.3",
|
||||
"i18n": "workspace:*",
|
||||
"ioredis": "5.10.1",
|
||||
"ip-cidr": "4.0.2",
|
||||
"ipaddr.js": "2.3.0",
|
||||
"ipaddr.js": "2.4.0",
|
||||
"is-svg": "6.1.0",
|
||||
"json5": "2.2.3",
|
||||
"jsonld": "9.0.0",
|
||||
"juice": "11.1.1",
|
||||
"meilisearch": "0.56.0",
|
||||
"mfm-js": "0.25.0",
|
||||
"meilisearch": "0.58.0",
|
||||
"mfm-js": "0.26.0",
|
||||
"mime-types": "3.0.2",
|
||||
"misskey-js": "workspace:*",
|
||||
"misskey-reversi": "workspace:*",
|
||||
"ms": "3.0.0-canary.202508261828",
|
||||
"nanoid": "5.1.7",
|
||||
"nanoid": "5.1.11",
|
||||
"nested-property": "4.0.0",
|
||||
"node-fetch": "3.3.2",
|
||||
"node-html-parser": "7.1.0",
|
||||
"nodemailer": "8.0.3",
|
||||
"nodemailer": "8.0.7",
|
||||
"nsfwjs": "4.3.0",
|
||||
"oauth2orize": "1.12.0",
|
||||
"oauth2orize-pkce": "0.1.2",
|
||||
"os-utils": "0.0.14",
|
||||
"otpauth": "9.5.0",
|
||||
"otpauth": "9.5.1",
|
||||
"pg": "8.20.0",
|
||||
"pkce-challenge": "6.0.0",
|
||||
"probe-image-size": "7.2.3",
|
||||
"probe-image-size": "7.3.0",
|
||||
"promise-limit": "2.7.0",
|
||||
"qrcode": "1.5.4",
|
||||
"random-seed": "0.3.0",
|
||||
"ratelimiter": "3.4.1",
|
||||
"re2": "1.23.3",
|
||||
"re2": "1.24.1",
|
||||
"reflect-metadata": "0.2.2",
|
||||
"rename": "1.0.4",
|
||||
"rss-parser": "3.13.0",
|
||||
"rxjs": "7.8.2",
|
||||
"sanitize-html": "2.17.2",
|
||||
"sanitize-html": "2.17.4",
|
||||
"secure-json-parse": "4.1.0",
|
||||
"semver": "7.7.4",
|
||||
"sharp": "0.33.5",
|
||||
"slacc": "0.0.10",
|
||||
"slacc": "0.1.5",
|
||||
"strict-event-emitter-types": "2.0.0",
|
||||
"stringz": "2.1.0",
|
||||
"systeminformation": "5.31.5",
|
||||
"systeminformation": "5.31.6",
|
||||
"tinycolor2": "1.6.0",
|
||||
"tmp": "0.2.5",
|
||||
"tsc-alias": "1.8.16",
|
||||
"tsc-alias": "1.8.17",
|
||||
"typeorm": "0.3.28",
|
||||
"ulid": "3.0.2",
|
||||
"vary": "1.1.2",
|
||||
@@ -176,25 +157,23 @@
|
||||
"xev": "3.0.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@jest/globals": "29.7.0",
|
||||
"@kitajs/ts-html-plugin": "4.1.4",
|
||||
"@nestjs/platform-express": "11.1.17",
|
||||
"@sentry/vue": "10.45.0",
|
||||
"@simplewebauthn/types": "12.0.0",
|
||||
"@swc/jest": "0.2.39",
|
||||
"@nestjs/platform-express": "11.1.19",
|
||||
"@rollup/plugin-esm-shim": "0.1.8",
|
||||
"@sentry/vue": "10.52.0",
|
||||
"@types/accepts": "1.3.7",
|
||||
"@types/archiver": "7.0.0",
|
||||
"@types/body-parser": "1.19.6",
|
||||
"@types/color-convert": "2.0.4",
|
||||
"@types/color-convert": "3.0.1",
|
||||
"@types/content-disposition": "0.5.9",
|
||||
"@types/fluent-ffmpeg": "2.1.28",
|
||||
"@types/http-link-header": "1.0.7",
|
||||
"@types/jest": "29.5.14",
|
||||
"@types/js-yaml": "4.0.9",
|
||||
"@types/jsonld": "1.5.15",
|
||||
"@types/mime-types": "3.0.1",
|
||||
"@types/ms": "2.1.0",
|
||||
"@types/node": "24.12.0",
|
||||
"@types/nodemailer": "7.0.11",
|
||||
"@types/node": "24.12.2",
|
||||
"@types/nodemailer": "8.0.0",
|
||||
"@types/oauth2orize": "1.11.5",
|
||||
"@types/oauth2orize-pkce": "0.1.2",
|
||||
"@types/pg": "8.20.0",
|
||||
@@ -206,28 +185,28 @@
|
||||
"@types/semver": "7.7.1",
|
||||
"@types/simple-oauth2": "5.0.8",
|
||||
"@types/sinonjs__fake-timers": "15.0.1",
|
||||
"@types/supertest": "6.0.3",
|
||||
"@types/supertest": "7.2.0",
|
||||
"@types/tinycolor2": "1.4.6",
|
||||
"@types/tmp": "0.2.6",
|
||||
"@types/vary": "1.1.3",
|
||||
"@types/web-push": "3.6.4",
|
||||
"@types/ws": "8.18.1",
|
||||
"@typescript-eslint/eslint-plugin": "8.57.2",
|
||||
"@typescript-eslint/parser": "8.57.2",
|
||||
"@typescript-eslint/eslint-plugin": "8.59.2",
|
||||
"@typescript-eslint/parser": "8.59.2",
|
||||
"@vitest/coverage-v8": "4.1.5",
|
||||
"aws-sdk-client-mock": "4.1.0",
|
||||
"cbor": "10.0.12",
|
||||
"cross-env": "10.1.0",
|
||||
"esbuild-plugin-swc": "1.0.1",
|
||||
"eslint-plugin-import": "2.32.0",
|
||||
"execa": "9.6.1",
|
||||
"fkill": "10.0.3",
|
||||
"jest": "29.7.0",
|
||||
"jest-mock": "29.7.0",
|
||||
"js-yaml": "4.1.1",
|
||||
"nodemon": "3.1.14",
|
||||
"pid-port": "2.1.0",
|
||||
"pid-port": "2.1.1",
|
||||
"rolldown": "1.0.0",
|
||||
"simple-oauth2": "5.1.0",
|
||||
"supertest": "7.2.2",
|
||||
"vite": "7.3.1"
|
||||
"vite": "8.0.11",
|
||||
"vitest": "4.1.5",
|
||||
"vitest-mock-extended": "4.0.0"
|
||||
}
|
||||
}
|
||||
|
||||
128
packages/backend/rolldown.config.ts
Normal file
128
packages/backend/rolldown.config.ts
Normal file
@@ -0,0 +1,128 @@
|
||||
import { defineConfig } from 'rolldown';
|
||||
import type { Plugin, ExternalOption } from 'rolldown';
|
||||
import { execa, execaNode } from 'execa';
|
||||
import type { ResultPromise } from 'execa';
|
||||
import esmShim from '@rollup/plugin-esm-shim';
|
||||
|
||||
/**
|
||||
* Watchモード時にバックエンドの起動・停止制御を行うプラグイン
|
||||
*/
|
||||
function backendDevServerPlugin(): Plugin {
|
||||
let backendProcess: ResultPromise | null = null;
|
||||
|
||||
async function runBuildAssets() {
|
||||
await execa('pnpm', ['run', 'build-assets'], {
|
||||
cwd: '../../',
|
||||
stdout: process.stdout,
|
||||
stderr: process.stderr,
|
||||
});
|
||||
}
|
||||
|
||||
async function killBackendProcess() {
|
||||
if (backendProcess) {
|
||||
backendProcess.catch(() => {}); // backendProcess.kill()によって発生する例外を無視するためにcatch()を呼び出す
|
||||
backendProcess.kill();
|
||||
await new Promise(resolve => backendProcess!.on('exit', resolve));
|
||||
backendProcess = null;
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
name: 'backend-dev-server',
|
||||
async closeBundle() {
|
||||
await runBuildAssets();
|
||||
if (backendProcess) {
|
||||
await killBackendProcess();
|
||||
}
|
||||
backendProcess = execaNode('./built/entry.js', [], {
|
||||
stdout: process.stdout,
|
||||
stderr: process.stderr,
|
||||
env: {
|
||||
NODE_ENV: 'development',
|
||||
},
|
||||
});
|
||||
},
|
||||
async watchChange() {
|
||||
if (backendProcess) {
|
||||
await killBackendProcess();
|
||||
await runBuildAssets();
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export default defineConfig((args) => {
|
||||
const isWatchMode = args.watch != null && args.watch !== 'false';
|
||||
const isE2E = args.e2e != null && args.e2e !== 'false';
|
||||
|
||||
// 通常のビルド時にexternalとするモジュール
|
||||
const externalModules: ExternalOption = [
|
||||
/^slacc-.*/,
|
||||
'class-transformer',
|
||||
'class-validator',
|
||||
/^@sentry\/.*/,
|
||||
/^@sentry-internal\/.*/,
|
||||
'@nestjs/websockets/socket-module',
|
||||
'@nestjs/microservices/microservices-module',
|
||||
'@nestjs/microservices',
|
||||
/^@napi-rs\/.*/,
|
||||
'mock-aws-s3',
|
||||
'aws-sdk',
|
||||
'nock',
|
||||
'sharp',
|
||||
'jsdom',
|
||||
're2',
|
||||
'ipaddr.js',
|
||||
'oauth2orize',
|
||||
'file-type',
|
||||
];
|
||||
|
||||
if (isE2E) {
|
||||
return {
|
||||
input: './test-server/entry.ts',
|
||||
platform: 'node',
|
||||
tsconfig: './test-server/tsconfig.json',
|
||||
plugins: [
|
||||
esmShim(),
|
||||
],
|
||||
output: {
|
||||
keepNames: true,
|
||||
sourcemap: true,
|
||||
dir: './built-test',
|
||||
cleanDir: true,
|
||||
format: 'esm',
|
||||
},
|
||||
external: externalModules,
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
input: [
|
||||
'./src/boot/entry.ts',
|
||||
'./src/boot/cli.ts',
|
||||
'./src/config.ts',
|
||||
'./src/postgres.ts',
|
||||
'./src/server/api/openapi/gen-spec.ts',
|
||||
],
|
||||
platform: 'node',
|
||||
tsconfig: true,
|
||||
plugins: [
|
||||
esmShim(),
|
||||
(isWatchMode ? backendDevServerPlugin() : undefined),
|
||||
],
|
||||
output: {
|
||||
keepNames: true,
|
||||
minify: !isWatchMode,
|
||||
sourcemap: isWatchMode,
|
||||
dir: './built',
|
||||
cleanDir: !isWatchMode,
|
||||
format: 'esm',
|
||||
},
|
||||
watch: {
|
||||
include: ['src/**/*.{ts,js,mjs,cjs,tsx,json}'],
|
||||
clearScreen: false,
|
||||
},
|
||||
// ビルドの高速化のために、watchモードのときは外部モジュールは全てバンドルしないようにする
|
||||
external: isWatchMode ? /^(?!@\/)[^.\/](?!:[\/\\])/ : externalModules,
|
||||
};
|
||||
}
|
||||
});
|
||||
@@ -4,8 +4,8 @@
|
||||
*/
|
||||
|
||||
import Redis from 'ioredis';
|
||||
import { loadConfig } from '../src-js/config.js';
|
||||
import { createPostgresDataSource } from '../src-js/postgres.js';
|
||||
import { loadConfig } from '../built/config.js';
|
||||
import { createPostgresDataSource } from '../built/postgres.js';
|
||||
|
||||
const config = loadConfig();
|
||||
|
||||
|
||||
@@ -1,63 +0,0 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import { execa, execaNode } from 'execa';
|
||||
|
||||
/** @type {import('execa').ExecaChildProcess | undefined} */
|
||||
let backendProcess;
|
||||
|
||||
async function execBuildAssets() {
|
||||
await execa('pnpm', ['run', 'build-assets'], {
|
||||
cwd: '../../',
|
||||
stdout: process.stdout,
|
||||
stderr: process.stderr,
|
||||
})
|
||||
}
|
||||
|
||||
function execStart() {
|
||||
// pnpm run start を呼び出したいが、windowsだとプロセスグループ単位でのkillが出来ずゾンビプロセス化するので
|
||||
// 上記と同等の動きをするコマンドで子・孫プロセスを作らないようにしたい
|
||||
backendProcess = execaNode('./built/boot/entry.js', [], {
|
||||
stdout: process.stdout,
|
||||
stderr: process.stderr,
|
||||
env: {
|
||||
'NODE_ENV': 'development',
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
async function killProc() {
|
||||
if (backendProcess) {
|
||||
backendProcess.catch(() => {}); // backendProcess.kill()によって発生する例外を無視するためにcatch()を呼び出す
|
||||
backendProcess.kill();
|
||||
await new Promise(resolve => backendProcess.on('exit', resolve));
|
||||
backendProcess = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
(async () => {
|
||||
execaNode(
|
||||
'./node_modules/nodemon/bin/nodemon.js',
|
||||
[
|
||||
'-w', 'src',
|
||||
'-e', 'ts,js,mjs,cjs,tsx,json,pug',
|
||||
'--exec', 'pnpm', 'run', 'build',
|
||||
],
|
||||
{
|
||||
stdio: [process.stdin, process.stdout, process.stderr, 'ipc'],
|
||||
serialization: "json",
|
||||
})
|
||||
.on('message', async (message) => {
|
||||
if (message.type === 'exit') {
|
||||
// かならずbuild->build-assetsの順番で呼び出したいので、
|
||||
// 少々トリッキーだがnodemonからのexitイベントを利用してbuild-assets->startを行う。
|
||||
// pnpm restartをbuildが終わる前にbuild-assetsが動いてしまうので、バラバラに呼び出す必要がある
|
||||
|
||||
await killProc();
|
||||
await execBuildAssets();
|
||||
execStart();
|
||||
}
|
||||
})
|
||||
})();
|
||||
@@ -19,10 +19,10 @@ async function main() {
|
||||
}
|
||||
|
||||
/** @type {import('../src/config.js')} */
|
||||
const { loadConfig } = await import('../src-js/config.js');
|
||||
const { loadConfig } = await import('../built/config.js');
|
||||
|
||||
/** @type {import('../src/server/api/openapi/gen-spec.js')} */
|
||||
const { genOpenapiSpec } = await import('../src-js/server/api/openapi/gen-spec.js');
|
||||
const { genOpenapiSpec } = await import('../built/gen-spec.js');
|
||||
|
||||
const config = loadConfig();
|
||||
const spec = genOpenapiSpec(config, true);
|
||||
|
||||
@@ -55,7 +55,7 @@ async function getMemoryUsage(pid) {
|
||||
|
||||
async function measureMemory() {
|
||||
// Start the Misskey backend server using fork to enable IPC
|
||||
const serverProcess = fork(join(__dirname, '../built/boot/entry.js'), ['expose-gc'], {
|
||||
const serverProcess = fork(join(__dirname, '../built/entry.js'), ['expose-gc'], {
|
||||
cwd: join(__dirname, '..'),
|
||||
env: {
|
||||
...process.env,
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
import { Global, Inject, Module } from '@nestjs/common';
|
||||
import * as Redis from 'ioredis';
|
||||
import { DataSource } from 'typeorm';
|
||||
import { MeiliSearch } from 'meilisearch';
|
||||
import { Meilisearch } from 'meilisearch';
|
||||
import { MiMeta } from '@/models/Meta.js';
|
||||
import { DI } from './di-symbols.js';
|
||||
import { Config, loadConfig } from './config.js';
|
||||
@@ -40,10 +40,10 @@ const $meilisearch: Provider = {
|
||||
useFactory: (config: Config) => {
|
||||
if (config.fulltextSearch?.provider === 'meilisearch') {
|
||||
if (!config.meilisearch) {
|
||||
throw new Error('MeiliSearch is enabled but no configuration is provided');
|
||||
throw new Error('Meilisearch is enabled but no configuration is provided');
|
||||
}
|
||||
|
||||
return new MeiliSearch({
|
||||
return new Meilisearch({
|
||||
host: `${config.meilisearch.ssl ? 'https' : 'http'}://${config.meilisearch.host}:${config.meilisearch.port}`,
|
||||
apiKey: config.meilisearch.apiKey,
|
||||
});
|
||||
|
||||
@@ -4,16 +4,26 @@
|
||||
*/
|
||||
|
||||
import { NestFactory } from '@nestjs/core';
|
||||
import { ChartManagementService } from '@/core/chart/ChartManagementService.js';
|
||||
import { QueueProcessorService } from '@/queue/QueueProcessorService.js';
|
||||
import { init } from 'slacc';
|
||||
import { NestLogger } from '@/NestLogger.js';
|
||||
import { QueueProcessorModule } from '@/queue/QueueProcessorModule.js';
|
||||
import { QueueStatsService } from '@/daemons/QueueStatsService.js';
|
||||
import { ServerStatsService } from '@/daemons/ServerStatsService.js';
|
||||
import { ServerService } from '@/server/ServerService.js';
|
||||
import { MainModule } from '@/MainModule.js';
|
||||
import type { Config } from '@/config.js';
|
||||
|
||||
let slaccInitialized = false;
|
||||
|
||||
export function initExtraThreadPool(config: Config) {
|
||||
if (slaccInitialized) return;
|
||||
|
||||
const threadPoolSize = Math.max(config.threadPoolSize ?? 1, 1);
|
||||
|
||||
init(threadPoolSize);
|
||||
|
||||
slaccInitialized = true;
|
||||
}
|
||||
|
||||
export async function server() {
|
||||
const { MainModule } = await import('../MainModule.js');
|
||||
const { ServerService } = await import('../server/ServerService.js');
|
||||
|
||||
const app = await NestFactory.createApplicationContext(MainModule, {
|
||||
logger: new NestLogger(),
|
||||
});
|
||||
@@ -22,6 +32,10 @@ export async function server() {
|
||||
await serverService.launch();
|
||||
|
||||
if (process.env.NODE_ENV !== 'test') {
|
||||
const { ChartManagementService } = await import('../core/chart/ChartManagementService.js');
|
||||
const { QueueStatsService } = await import('../daemons/QueueStatsService.js');
|
||||
const { ServerStatsService } = await import('../daemons/ServerStatsService.js');
|
||||
|
||||
app.get(ChartManagementService).start();
|
||||
app.get(QueueStatsService).start();
|
||||
app.get(ServerStatsService).start();
|
||||
@@ -31,6 +45,10 @@ export async function server() {
|
||||
}
|
||||
|
||||
export async function jobQueue() {
|
||||
const { QueueProcessorModule } = await import('../queue/QueueProcessorModule.js');
|
||||
const { QueueProcessorService } = await import('../queue/QueueProcessorService.js');
|
||||
const { ChartManagementService } = await import('../core/chart/ChartManagementService.js');
|
||||
|
||||
const jobQueue = await NestFactory.createApplicationContext(QueueProcessorModule, {
|
||||
logger: new NestLogger(),
|
||||
});
|
||||
|
||||
@@ -13,8 +13,6 @@ import chalk from 'chalk';
|
||||
import Xev from 'xev';
|
||||
import Logger from '@/logger.js';
|
||||
import { envOption } from '../env.js';
|
||||
import { masterMain } from './master.js';
|
||||
import { workerMain } from './worker.js';
|
||||
import { readyRef } from './ready.js';
|
||||
|
||||
import 'reflect-metadata';
|
||||
@@ -71,10 +69,12 @@ process.on('exit', code => {
|
||||
if (!envOption.disableClustering) {
|
||||
if (cluster.isPrimary) {
|
||||
logger.info(`Start main process... pid: ${process.pid}`);
|
||||
const { masterMain } = await import('./master.js');
|
||||
await masterMain();
|
||||
ev.mount();
|
||||
} else if (cluster.isWorker) {
|
||||
logger.info(`Start worker process... pid: ${process.pid}`);
|
||||
const { workerMain } = await import('./worker.js');
|
||||
await workerMain();
|
||||
} else {
|
||||
throw new Error('Unknown process type');
|
||||
@@ -82,6 +82,7 @@ if (!envOption.disableClustering) {
|
||||
} else {
|
||||
// 非clusterの場合はMasterのみが起動するため、Workerの処理は行わない(cluster.isWorker === trueの状態でこのブロックに来ることはない)
|
||||
logger.info(`Start main process... pid: ${process.pid}`);
|
||||
const { masterMain } = await import('./master.js');
|
||||
await masterMain();
|
||||
ev.mount();
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@ import { loadConfig } from '@/config.js';
|
||||
import type { Config } from '@/config.js';
|
||||
import { showMachineInfo } from '@/misc/show-machine-info.js';
|
||||
import { envOption } from '@/env.js';
|
||||
import { jobQueue, server } from './common.js';
|
||||
import { initExtraThreadPool, jobQueue, server } from './common.js';
|
||||
|
||||
const logger = new Logger('core', 'cyan');
|
||||
const bootLogger = logger.createSubLogger('boot', 'magenta');
|
||||
@@ -64,6 +64,8 @@ export async function masterMain() {
|
||||
|
||||
bootLogger.succ('Misskey initialized');
|
||||
|
||||
initExtraThreadPool(config);
|
||||
|
||||
if (config.sentryForBackend) {
|
||||
const Sentry = await import('@sentry/node');
|
||||
const { nodeProfilingIntegration } = await import('@sentry/profiling-node');
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
import cluster from 'node:cluster';
|
||||
import { envOption } from '@/env.js';
|
||||
import { loadConfig } from '@/config.js';
|
||||
import { jobQueue, server } from './common.js';
|
||||
import { initExtraThreadPool, jobQueue, server } from './common.js';
|
||||
|
||||
/**
|
||||
* Init worker process
|
||||
@@ -14,6 +14,8 @@ import { jobQueue, server } from './common.js';
|
||||
export async function workerMain() {
|
||||
const config = loadConfig();
|
||||
|
||||
initExtraThreadPool(config);
|
||||
|
||||
if (config.sentryForBackend) {
|
||||
const Sentry = await import('@sentry/node');
|
||||
const { nodeProfilingIntegration } = await import('@sentry/profiling-node');
|
||||
|
||||
@@ -85,6 +85,7 @@ type Source = {
|
||||
maxFileSize?: number;
|
||||
|
||||
clusterLimit?: number;
|
||||
threadPoolSize?: number;
|
||||
|
||||
id: string;
|
||||
|
||||
@@ -158,6 +159,7 @@ export type Config = {
|
||||
allowedPrivateNetworks: string[] | undefined;
|
||||
maxFileSize: number;
|
||||
clusterLimit: number | undefined;
|
||||
threadPoolSize: number;
|
||||
id: string;
|
||||
outgoingAddress: string | undefined;
|
||||
outgoingAddressFamily: 'ipv4' | 'ipv6' | 'dual' | undefined;
|
||||
@@ -190,6 +192,7 @@ export type Config = {
|
||||
userAgent: string;
|
||||
frontendManifestExists: boolean;
|
||||
frontendEmbedManifestExists: boolean;
|
||||
rootDir: string;
|
||||
mediaProxy: string;
|
||||
externalMediaProxyEnabled: boolean;
|
||||
videoThumbnailGenerator: string | null;
|
||||
@@ -312,6 +315,7 @@ export function loadConfig(): Config {
|
||||
allowedPrivateNetworks: config.allowedPrivateNetworks,
|
||||
maxFileSize: config.maxFileSize ?? 262144000,
|
||||
clusterLimit: config.clusterLimit,
|
||||
threadPoolSize: config.threadPoolSize ?? 1,
|
||||
outgoingAddress: config.outgoingAddress,
|
||||
outgoingAddressFamily: config.outgoingAddressFamily,
|
||||
deliverJobConcurrency: config.deliverJobConcurrency,
|
||||
@@ -330,6 +334,7 @@ export function loadConfig(): Config {
|
||||
userAgent: `Misskey/${version} (${config.url})`,
|
||||
frontendManifestExists: frontendManifestExists,
|
||||
frontendEmbedManifestExists: frontendEmbedManifestExists,
|
||||
rootDir,
|
||||
perChannelMaxNoteCacheCount: config.perChannelMaxNoteCacheCount ?? 1000,
|
||||
perUserNotificationsMaxCount: config.perUserNotificationsMaxCount ?? 500,
|
||||
deactivateAntennaThreshold: config.deactivateAntennaThreshold ?? (1000 * 60 * 60 * 24 * 7),
|
||||
|
||||
@@ -4,27 +4,31 @@
|
||||
*/
|
||||
|
||||
import * as fs from 'node:fs';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import { dirname } from 'node:path';
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { pathToFileURL } from 'node:url';
|
||||
import { resolve } from 'node:path';
|
||||
import { Injectable, Inject } from '@nestjs/common';
|
||||
import { Mutex } from 'async-mutex';
|
||||
import fetch from 'node-fetch';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import { bindThis } from '@/decorators.js';
|
||||
import type { Config } from '@/config.js';
|
||||
import type { NSFWJS, PredictionType } from 'nsfwjs/core';
|
||||
|
||||
const _filename = fileURLToPath(import.meta.url);
|
||||
const _dirname = dirname(_filename);
|
||||
|
||||
const REQUIRED_CPU_FLAGS_X64 = ['avx2', 'fma'];
|
||||
let isSupportedCpu: undefined | boolean = undefined;
|
||||
|
||||
@Injectable()
|
||||
export class AiService {
|
||||
private readonly modelDir: string;
|
||||
private model: NSFWJS;
|
||||
private modelLoadMutex: Mutex = new Mutex();
|
||||
|
||||
constructor(
|
||||
@Inject(DI.config)
|
||||
private config: Config,
|
||||
) {
|
||||
const md = resolve(this.config.rootDir, 'packages/backend/nsfw-model');
|
||||
this.modelDir = md.endsWith('/') ? md : md + '/';
|
||||
}
|
||||
|
||||
@bindThis
|
||||
@@ -46,7 +50,7 @@ export class AiService {
|
||||
const nsfw = await import('nsfwjs/core');
|
||||
await this.modelLoadMutex.runExclusive(async () => {
|
||||
if (this.model == null) {
|
||||
this.model = await nsfw.load(`file://${_dirname}/../../nsfw-model/`, { size: 299 });
|
||||
this.model = await nsfw.load(pathToFileURL(this.modelDir).toString(), { size: 299 });
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -5,29 +5,25 @@
|
||||
|
||||
import * as fs from 'node:fs';
|
||||
import * as Path from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import { dirname } from 'node:path';
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import type { Config } from '@/config.js';
|
||||
import { bindThis } from '@/decorators.js';
|
||||
|
||||
const _filename = fileURLToPath(import.meta.url);
|
||||
const _dirname = dirname(_filename);
|
||||
|
||||
const path = Path.resolve(_dirname, '../../../../files');
|
||||
|
||||
@Injectable()
|
||||
export class InternalStorageService {
|
||||
private readonly path: string;
|
||||
|
||||
constructor(
|
||||
@Inject(DI.config)
|
||||
private config: Config,
|
||||
) {
|
||||
this.path = Path.resolve(this.config.rootDir, 'files');
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public resolvePath(key: string) {
|
||||
return Path.resolve(path, key);
|
||||
return Path.resolve(this.path, key);
|
||||
}
|
||||
|
||||
@bindThis
|
||||
@@ -37,14 +33,14 @@ export class InternalStorageService {
|
||||
|
||||
@bindThis
|
||||
public saveFromPath(key: string, srcPath: string) {
|
||||
fs.mkdirSync(path, { recursive: true });
|
||||
fs.mkdirSync(this.path, { recursive: true });
|
||||
fs.copyFileSync(srcPath, this.resolvePath(key));
|
||||
return `${this.config.url}/files/${key}`;
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public saveFromBuffer(key: string, data: Buffer) {
|
||||
fs.mkdirSync(path, { recursive: true });
|
||||
fs.mkdirSync(this.path, { recursive: true });
|
||||
fs.writeFileSync(this.resolvePath(key), data);
|
||||
return `${this.config.url}/files/${key}`;
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import Logger from '@/logger.js';
|
||||
import { bindThis } from '@/decorators.js';
|
||||
import type { KEYWORD } from 'color-convert/conversions.js';
|
||||
import type { Keyword } from 'color-convert';
|
||||
|
||||
@Injectable()
|
||||
export class LoggerService {
|
||||
@@ -15,7 +15,7 @@ export class LoggerService {
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public getLogger(domain: string, color?: KEYWORD | undefined) {
|
||||
public getLogger(domain: string, color?: Keyword | undefined) {
|
||||
return new Logger(domain, color);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -63,20 +63,21 @@ type NotificationType = 'reply' | 'renote' | 'quote' | 'mention';
|
||||
class NotificationManager {
|
||||
private notifier: { id: MiUser['id']; };
|
||||
private note: MiNote;
|
||||
private queue: {
|
||||
private queue: Map<MiLocalUser['id'], {
|
||||
target: MiLocalUser['id'];
|
||||
reason: NotificationType;
|
||||
}[];
|
||||
}>;
|
||||
|
||||
constructor(
|
||||
private mutingsRepository: MutingsRepository,
|
||||
private notificationService: NotificationService,
|
||||
private followingsRepository: FollowingsRepository,
|
||||
notifier: { id: MiUser['id']; },
|
||||
note: MiNote,
|
||||
) {
|
||||
this.notifier = notifier;
|
||||
this.note = note;
|
||||
this.queue = [];
|
||||
this.queue = new Map();
|
||||
}
|
||||
|
||||
@bindThis
|
||||
@@ -84,7 +85,7 @@ class NotificationManager {
|
||||
// 自分自身へは通知しない
|
||||
if (this.notifier.id === notifiee) return;
|
||||
|
||||
const exist = this.queue.find(x => x.target === notifiee);
|
||||
const exist = this.queue.get(notifiee);
|
||||
|
||||
if (exist) {
|
||||
// 「メンションされているかつ返信されている」場合は、メンションとしての通知ではなく返信としての通知にする
|
||||
@@ -92,7 +93,7 @@ class NotificationManager {
|
||||
exist.reason = reason;
|
||||
}
|
||||
} else {
|
||||
this.queue.push({
|
||||
this.queue.set(notifiee, {
|
||||
reason: reason,
|
||||
target: notifiee,
|
||||
});
|
||||
@@ -101,7 +102,50 @@ class NotificationManager {
|
||||
|
||||
@bindThis
|
||||
public async notify() {
|
||||
for (const x of this.queue) {
|
||||
if (this.queue.size === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
let visibleUserIds: Set<MiUser['id']> | null;
|
||||
|
||||
switch (this.note.visibility) {
|
||||
case 'public':
|
||||
case 'home':
|
||||
visibleUserIds = null;
|
||||
break;
|
||||
|
||||
case 'specified':
|
||||
visibleUserIds = new Set(this.note.visibleUserIds);
|
||||
break;
|
||||
|
||||
case 'followers': {
|
||||
// TODO: フォロワー限定ノートにフォロワーではない人がメンションされた場合通知されるのが正しい挙動なのか確認(一部に挙動の不一致がありそう)。現状は通知されるためフィルタしない
|
||||
// const targetUserIds = this.queue.map(x => x.target);
|
||||
// const followers = await this.followingsRepository.find({
|
||||
// where: {
|
||||
// followeeId: this.note.userId,
|
||||
// followerId: In(targetUserIds),
|
||||
// isFollowerHibernated: false,
|
||||
// },
|
||||
// select: ['followerId'],
|
||||
// });
|
||||
// visibleUserIds = new Set(followers.map(f => f.followerId));
|
||||
visibleUserIds = null;
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
visibleUserIds = new Set();
|
||||
break;
|
||||
}
|
||||
|
||||
for (const x of this.queue.values()) {
|
||||
const isVisibleToTarget = visibleUserIds === null || visibleUserIds.has(x.target);
|
||||
|
||||
if (!isVisibleToTarget) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (x.reason === 'renote') {
|
||||
this.notificationService.createNotification(x.target, 'renote', {
|
||||
noteId: this.note.id,
|
||||
@@ -772,7 +816,7 @@ export class NoteCreateService implements OnApplicationShutdown {
|
||||
|
||||
this.webhookService.enqueueUserWebhook(user.id, 'note', { note: noteObj });
|
||||
|
||||
const nm = new NotificationManager(this.mutingsRepository, this.notificationService, user, note);
|
||||
const nm = new NotificationManager(this.mutingsRepository, this.notificationService, this.followingsRepository, user, note);
|
||||
|
||||
await this.createMentionedEvents(mentionedUsers, note, nm);
|
||||
|
||||
|
||||
@@ -246,7 +246,8 @@ export class NotificationService implements OnApplicationShutdown {
|
||||
|
||||
private toXListId(id: string): string {
|
||||
const { date, additional } = this.idService.parseFull(id);
|
||||
return date.toString() + '-' + additional.toString();
|
||||
// Redis Stream sequenceはunit64制約があるため、収まらない場合は下位64bitを取る
|
||||
return date.toString() + '-' + BigInt.asUintN(64, additional).toString();
|
||||
}
|
||||
|
||||
@bindThis
|
||||
|
||||
@@ -91,13 +91,27 @@ export class RelayService {
|
||||
return JSON.stringify(result);
|
||||
}
|
||||
|
||||
@bindThis
|
||||
private getAcceptedRelays(): Promise<MiRelay[]> {
|
||||
return this.relaysCache.fetch(() => this.relaysRepository.findBy({
|
||||
status: 'accepted',
|
||||
}));
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public async isRelayActor(actor: { inbox: string | null; sharedInbox: string | null }): Promise<boolean> {
|
||||
const relays = await this.getAcceptedRelays();
|
||||
return relays.some(relay =>
|
||||
(actor.inbox != null && relay.inbox === actor.inbox)
|
||||
|| (actor.sharedInbox != null && relay.inbox === actor.sharedInbox),
|
||||
);
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public async deliverToRelays(user: { id: MiUser['id']; host: null; }, activity: any): Promise<void> {
|
||||
if (activity == null) return;
|
||||
|
||||
const relays = await this.relaysCache.fetch(() => this.relaysRepository.findBy({
|
||||
status: 'accepted',
|
||||
}));
|
||||
const relays = await this.getAcceptedRelays();
|
||||
if (relays.length === 0) return;
|
||||
|
||||
const copy = deepClone(activity);
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user