mirror of
https://github.com/misskey-dev/misskey.git
synced 2026-08-01 12:56:03 +00:00
Compare commits
14 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| f68da80ebd | |||
| fcb4ced33d | |||
| 85e810533b | |||
| 661838c89e | |||
| 80d14231c5 | |||
| 28153f7b5f | |||
| 6d81c81ab2 | |||
| 6f54416f63 | |||
| a63c8ef939 | |||
| 015868f17f | |||
| dc3fd42d63 | |||
| 34a4b9b74c | |||
| 4a7ff6d7f4 | |||
| 3d87032881 |
@@ -20,9 +20,15 @@ reviews:
|
||||
instructions: |
|
||||
【レビュー対象外】
|
||||
- フォーマット違反、型エラー、SPDXヘッダーの記載漏れ等、静的解析で検出可能な問題については、別Workflow(CI/CD)でチェックされるため、レビュー対象外として無視してください。
|
||||
|
||||
【特別な対応を要するレビュー】
|
||||
- プルリクエストで変更されていない部分において、プルリクエストのタイトルや説明文に照らしてスコープ外であるが修正すべき点を見つけた場合は、このプルリクエストで修正させるのではなく、別のプルリクエストを開いてそれを修正するように指示してください。\
|
||||
なお、当該プルリクエストで行が追加/削除されたことによりスコープ外の問題が起こりうる場合は、例外的にこのプルリクエストで修正させてください。
|
||||
|
||||
【注力してほしい観点】
|
||||
- ビジネスロジックの不備、セキュリティ、パフォーマンス、設計パターンの適切性などに焦点を当ててレビューしてください。
|
||||
- プルリクエストのタイトルや説明文に照らしてスコープ外の変更が含まれていないかをレビューしてください。なお、明らかにスコープ外であると確信できるもの(例: 全く関係ないファイルを編集している等)のみを対象とし、スコープ外かどうかの判断に困る場合は指摘しないでください。\
|
||||
スコープ外の変更が含まれていた場合は指摘し、その部分について、このプルリクエストに含めずに別のプルリクエストを開いて変更するように指示してください。
|
||||
path_filters:
|
||||
- "!CHANGELOG.md"
|
||||
- "!**/__snapshots__/**"
|
||||
|
||||
@@ -70,6 +70,7 @@
|
||||
- Fix: Sentry 使用環境下にて、外部送信リクエストへ `sentry-trace` / `baggage` ヘッダーが既定で付与されないように
|
||||
- Fix: フォロワー限定投稿へのリプライをホーム投稿に出来る問題を修正
|
||||
- Fix: ファイルをアップロードするAPIにて、処理終了後に一時ファイルが削除されないことがある問題を修正
|
||||
- Fix: 初期設定で作成したアカウント以外でアカウント作成APIが使用できない問題を修正
|
||||
|
||||
## 2026.6.0
|
||||
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "misskey",
|
||||
"version": "2026.7.0-beta.3",
|
||||
"version": "2026.7.0-beta.5",
|
||||
"codename": "nasubi",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
||||
@@ -1,28 +1,28 @@
|
||||
## ⚙️ Backend Diagnostics Report
|
||||
|
||||
### Memory: After GC
|
||||
| Metric | @ Base | @ Head | Δ | MAD |
|
||||
| Metric | @ Base | @ Head | Δ | MAD |
|
||||
| --- | ---: | ---: | ---: | ---: |
|
||||
| **HeapUsed** | 152 MB <br> ± 1 MB | 168 MB <br> ± 1 MB | $\color{orange}{\text{+16 MB}}$<br>$\color{orange}{\text{+10.5\\%}}$ | 1.4 MB |
|
||||
| **PSS** | 202 MB <br> ± 1 MB | 218 MB <br> ± 1 MB | $\color{orange}{\text{+16 MB}}$<br>$\color{orange}{\text{+7.9\\%}}$ | 1.4 MB |
|
||||
| **USS** | 184 MB <br> ± 1 MB | 200 MB <br> ± 1 MB | $\color{orange}{\text{+16 MB}}$<br>$\color{orange}{\text{+8.7\\%}}$ | 1.4 MB |
|
||||
| **HeapUsed** | 152 MB <br> ± 1 MB | 168 MB <br> ± 1 MB | $\color{orange}{\text{+16~MB}}$<br>$\color{orange}{\text{+10.5\\%}}$ | 1.4 MB |
|
||||
| **PSS** | 202 MB <br> ± 1 MB | 218 MB <br> ± 1 MB | $\color{orange}{\text{+16~MB}}$<br>$\color{orange}{\text{+7.9\\%}}$ | 1.4 MB |
|
||||
| **USS** | 184 MB <br> ± 1 MB | 200 MB <br> ± 1 MB | $\color{orange}{\text{+16~MB}}$<br>$\color{orange}{\text{+8.7\\%}}$ | 1.4 MB |
|
||||
|
||||
<small><i>Only metrics showing significant changes are displayed.</i></small>
|
||||
|
||||
|
||||
### V8 Heap Snapshot Statistics
|
||||
|
||||
| Metric | @ Base | @ Head | Δ | MAD |
|
||||
| Metric | @ Base | @ Head | Δ | MAD |
|
||||
| --- | ---: | ---: | ---: | ---: |
|
||||
| $\color{gray}{\rule{8pt}{8pt}}$ **Total** | 40 MB <br> ± 200 KB | 44 MB <br> ± 200 KB | $\color{orange}{\text{+3.2 MB}}$<br>$\color{orange}{\text{+7.9\\%}}$ | 283 KB |
|
||||
| $\color{gray}{\rule{8pt}{8pt}}$ **Total** | 40 MB <br> ± 200 KB | 44 MB <br> ± 200 KB | $\color{orange}{\text{+3.2 MB}}$<br>$\color{orange}{\text{+7.9\\%}}$ | 283 KB |
|
||||
| | | | | |
|
||||
| $\color{orange}{\rule{8pt}{8pt}}$ **Code** | 4.7 MB | 5.1 MB | $\color{orange}{\text{+376 KB}}$ | 33 KB |
|
||||
| $\color{red}{\rule{8pt}{8pt}}$ **Strings** | 4.4 MB | 4.8 MB | $\color{orange}{\text{+352 KB}}$ | 31 KB |
|
||||
| $\color{cyan}{\rule{8pt}{8pt}}$ **JS arrays** | 4.1 MB | 4.5 MB | $\color{orange}{\text{+328 KB}}$ | 29 KB |
|
||||
| $\color{green}{\rule{8pt}{8pt}}$ **Typed arrays** | 3.8 MB | 4.1 MB | $\color{orange}{\text{+304 KB}}$ | 27 KB |
|
||||
| $\color{yellow}{\rule{8pt}{8pt}}$ **System objects** | 3.5 MB | 3.8 MB | $\color{orange}{\text{+280 KB}}$ | 25 KB |
|
||||
| $\color{violet}{\rule{8pt}{8pt}}$ **Other JS objs** | 3.2 MB | 3.5 MB | $\color{orange}{\text{+256 KB}}$ | 23 KB |
|
||||
| $\color{pink}{\rule{8pt}{8pt}}$ **Other non-JS objs** | 2.9 MB | 3.2 MB | $\color{orange}{\text{+232 KB}}$ | 21 KB |
|
||||
| $\color{orange}{\rule{8pt}{8pt}}$ **Code** | 4.7 MB | 5.1 MB | $\color{orange}{\text{+376 KB}}$ | 33 KB |
|
||||
| $\color{red}{\rule{8pt}{8pt}}$ **Strings** | 4.4 MB | 4.8 MB | $\color{orange}{\text{+352 KB}}$ | 31 KB |
|
||||
| $\color{cyan}{\rule{8pt}{8pt}}$ **JS arrays** | 4.1 MB | 4.5 MB | $\color{orange}{\text{+328 KB}}$ | 29 KB |
|
||||
| $\color{green}{\rule{8pt}{8pt}}$ **Typed arrays** | 3.8 MB | 4.1 MB | $\color{orange}{\text{+304 KB}}$ | 27 KB |
|
||||
| $\color{yellow}{\rule{8pt}{8pt}}$ **System objects** | 3.5 MB | 3.8 MB | $\color{orange}{\text{+280 KB}}$ | 25 KB |
|
||||
| $\color{violet}{\rule{8pt}{8pt}}$ **Other JS objs** | 3.2 MB | 3.5 MB | $\color{orange}{\text{+256 KB}}$ | 23 KB |
|
||||
| $\color{pink}{\rule{8pt}{8pt}}$ **Other non-JS objs** | 2.9 MB | 3.2 MB | $\color{orange}{\text{+232 KB}}$ | 21 KB |
|
||||
|
||||
|
||||
Download representative heap snapshot: [base](https://example.invalid/base) / [head](https://example.invalid/head)
|
||||
|
||||
@@ -64,7 +64,7 @@ test('throws when filtering leaves fewer than two heap snapshots per side', asyn
|
||||
})).toThrow('At least two samples per side are required');
|
||||
});
|
||||
|
||||
test('reports the difference of medians and leaves a paired-looking PSS delta uncoloured', async () => {
|
||||
test('omits a paired-looking PSS delta that stays within observed noise', async () => {
|
||||
const base = replacePssSamples(await loadFixture('base'), [290_000, 292_900, 295_800, 298_700, 301_600]);
|
||||
const head = replacePssSamples(await loadFixture('head'), [292_900, 296_300, 298_700, 301_600, 290_000]);
|
||||
|
||||
@@ -78,16 +78,10 @@ test('reports the difference of medians and leaves a paired-looking PSS delta un
|
||||
baseHeapSnapshotUrl: 'https://example.invalid/base',
|
||||
headHeapSnapshotUrl: 'https://example.invalid/head',
|
||||
});
|
||||
const row = findMetricRow(markdown, 'PSS');
|
||||
|
||||
expect(row).toContain('295.8 MB <br> ± 2.9 MB');
|
||||
expect(row).toContain('296.3 MB <br> ± 3.4 MB');
|
||||
expect(row).toContain('$\\text{+0.5 MB}$');
|
||||
expect(row).toContain('4.5 MB');
|
||||
expect(row.split('|')).toHaveLength(7);
|
||||
expect(row).not.toMatch(/within noise|increase|decrease|inconclusive/);
|
||||
expect(row).not.toContain('\\color{orange}');
|
||||
expect(findMetricRow(markdown, 'USS')).not.toContain('\\color{orange}');
|
||||
expect(markdown).not.toContain('| **PSS** |');
|
||||
expect(markdown).toContain('<small><i>Only metrics showing significant changes are displayed.</i></small>');
|
||||
expect(markdown).not.toContain('⚠️ **Warning**: Memory usage (PSS)');
|
||||
});
|
||||
|
||||
test('keeps the convergence warning without suppressing table colour or the PSS warning', async () => {
|
||||
@@ -131,6 +125,6 @@ test('renders an unavailable percentage when the base median is zero', async ()
|
||||
});
|
||||
|
||||
expect(findMetricRow(markdown, 'External')).toContain('<br>-');
|
||||
expect(markdown).toContain('| Metric | @ Base | @ Head | Δ | MAD |');
|
||||
expect(markdown).not.toContain('| Metric | @ Base | @ Head | Δ | MAD | Result |');
|
||||
expect(markdown).toContain('| Metric | @ Base | @ Head | Δ | MAD |');
|
||||
expect(markdown).not.toContain('| Metric | @ Base | @ Head | Δ | MAD | Result |');
|
||||
});
|
||||
|
||||
@@ -131,7 +131,7 @@ export function chunkMarkdownTable(
|
||||
if (rows.length === 0 && total == null && !hasGenerated && !hasOther) return '_No data_';
|
||||
|
||||
const lines = [
|
||||
'| Chunk | Base | Head | Δ | Δ (%) |',
|
||||
'| Chunk | Base | Head | Δ | Δ (%) |', // nbspにすること
|
||||
'| --- | ---: | ---: | ---: | ---: |',
|
||||
];
|
||||
if (total != null) {
|
||||
@@ -150,7 +150,8 @@ export function chunkMarkdownTable(
|
||||
}
|
||||
}
|
||||
if (hasGenerated) {
|
||||
lines.push(`| (other generated chunks) | ${formatBytes(generated.baseSize)} | ${formatBytes(generated.headSize)} | ${calcAndFormatDeltaBytes(generated.baseSize, generated.headSize, 1000)} | ${calcAndFormatDeltaPercentInMdTable(generated.baseSize, generated.headSize, 0.1)} |`);
|
||||
// eslint-disable-next-line no-irregular-whitespace
|
||||
lines.push(`| (other generated chunks) | ${formatBytes(generated.baseSize)} | ${formatBytes(generated.headSize)} | ${calcAndFormatDeltaBytes(generated.baseSize, generated.headSize, 1000)} | ${calcAndFormatDeltaPercentInMdTable(generated.baseSize, generated.headSize, 0.1)} |`); // nbspにすること
|
||||
}
|
||||
if (hasOther) {
|
||||
lines.push(`| (other) | ${formatBytes(other.baseSize)} | ${formatBytes(other.headSize)} | ${calcAndFormatDeltaBytes(other.baseSize, other.headSize, 1000)} | ${calcAndFormatDeltaPercentInMdTable(other.baseSize, other.headSize, 0.1)} |`);
|
||||
|
||||
@@ -194,7 +194,7 @@ export function renderVisualizerSummaryTable(base: ReturnType<typeof collectVisu
|
||||
...metrics.map((key) => `<td>${calcAndFormatDeltaBytes(base.metrics[key], head.metrics[key], 1000)}</td>`),
|
||||
'</tr>',
|
||||
'<tr>',
|
||||
'<th><b>Δ (%)</b></th>',
|
||||
'<th><b>Δ (%)</b></th>',
|
||||
...summary.map((key) => `<td>${calcAndFormatDeltaPercent(base.summary[key], head.summary[key], 0.1)}</td>`),
|
||||
...metrics.map((key) => `<td>${calcAndFormatDeltaPercent(base.metrics[key], head.metrics[key], 0.1)}</td>`),
|
||||
'</tr>',
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
## 🖥 Frontend Diagnostics Report
|
||||
|
||||
| Metric | @ Base | @ Head | Δ | MAD |
|
||||
| Metric | @ Base | @ Head | Δ | MAD |
|
||||
| --- | ---: | ---: | ---: | ---: |
|
||||
| **Encoded network** | 1 MB <br> ± 10 KB | 1.1 MB <br> ± 11 KB | $\color{orange}{\text{+83 KB}}$<br>$\color{orange}{\text{+8\\%}}$ | 15 KB |
|
||||
| **Decoded body** | 3.5 MB <br> ± 34 KB | 3.7 MB <br> ± 37 KB | $\color{orange}{\text{+277 KB}}$<br>$\color{orange}{\text{+8\\%}}$ | 50 KB |
|
||||
| **Same-origin encoded** | 1 MB <br> ± 10 KB | 1.1 MB <br> ± 11 KB | $\color{orange}{\text{+82 KB}}$<br>$\color{orange}{\text{+8\\%}}$ | 15 KB |
|
||||
| **Script encoded** | 918 KB <br> ± 9 KB | 991 KB <br> ± 9.7 KB | $\color{orange}{\text{+73 KB}}$<br>$\color{orange}{\text{+8\\%}}$ | 13 KB |
|
||||
| **Page-attributed memory** | 92 MB <br> ± 900 KB | 99 MB <br> ± 972 KB | $\color{orange}{\text{+7.3 MB}}$<br>$\color{orange}{\text{+8\\%}}$ | 1.3 MB |
|
||||
| **Encoded network** | 1 MB <br> ± 10 KB | 1.1 MB <br> ± 11 KB | $\color{orange}{\text{+83 KB}}$<br>$\color{orange}{\text{+8\\%}}$ | 15 KB |
|
||||
| **Decoded body** | 3.5 MB <br> ± 34 KB | 3.7 MB <br> ± 37 KB | $\color{orange}{\text{+277 KB}}$<br>$\color{orange}{\text{+8\\%}}$ | 50 KB |
|
||||
| **Same-origin encoded** | 1 MB <br> ± 10 KB | 1.1 MB <br> ± 11 KB | $\color{orange}{\text{+82 KB}}$<br>$\color{orange}{\text{+8\\%}}$ | 15 KB |
|
||||
| **Script encoded** | 918 KB <br> ± 9 KB | 991 KB <br> ± 9.7 KB | $\color{orange}{\text{+73 KB}}$<br>$\color{orange}{\text{+8\\%}}$ | 13 KB |
|
||||
| **Page-attributed memory** | 92 MB <br> ± 900 KB | 99 MB <br> ± 972 KB | $\color{orange}{\text{+7.3 MB}}$<br>$\color{orange}{\text{+8\\%}}$ | 1.3 MB |
|
||||
|
||||
<small><i>Only metrics showing significant changes are displayed.</i></small>
|
||||
|
||||
@@ -37,27 +37,27 @@
|
||||
<td align="right">10</td>
|
||||
<td align="right">10</td>
|
||||
<td align="right">0</td>
|
||||
<td align="right">918 KB</td>
|
||||
<td align="right">991 KB</td>
|
||||
<td align="right">$\color{orange}{\text{+73 KB}}$</td>
|
||||
<td align="right">918 KB</td>
|
||||
<td align="right">991 KB</td>
|
||||
<td align="right">$\color{orange}{\text{+73 KB}}$</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><b>Stylesheet</b></td>
|
||||
<td align="right">2</td>
|
||||
<td align="right">2</td>
|
||||
<td align="right">0</td>
|
||||
<td align="right">82 KB</td>
|
||||
<td align="right">88 KB</td>
|
||||
<td align="right">$\color{orange}{\text{+6.5 KB}}$</td>
|
||||
<td align="right">82 KB</td>
|
||||
<td align="right">88 KB</td>
|
||||
<td align="right">$\color{orange}{\text{+6.5 KB}}$</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><b>Fetch</b></td>
|
||||
<td align="right">6</td>
|
||||
<td align="right">6</td>
|
||||
<td align="right">0</td>
|
||||
<td align="right">41 KB</td>
|
||||
<td align="right">44 KB</td>
|
||||
<td align="right">$\color{orange}{\text{+3.3 KB}}$</td>
|
||||
<td align="right">41 KB</td>
|
||||
<td align="right">44 KB</td>
|
||||
<td align="right">$\color{orange}{\text{+3.3 KB}}$</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
@@ -67,17 +67,17 @@
|
||||
<details>
|
||||
<summary>V8 heap snapshot statistics</summary>
|
||||
|
||||
| Metric | @ Base | @ Head | Δ | MAD |
|
||||
| Metric | @ Base | @ Head | Δ | MAD |
|
||||
| --- | ---: | ---: | ---: | ---: |
|
||||
| $\color{gray}{\rule{8pt}{8pt}}$ **Total** | 1 MB <br> ± 10 KB | 1.1 MB <br> ± 11 KB | $\text{+82 KB}$<br>$\text{+8\\%}$ | 15 KB |
|
||||
| $\color{gray}{\rule{8pt}{8pt}}$ **Total** | 1 MB <br> ± 10 KB | 1.1 MB <br> ± 11 KB | $\text{+82 KB}$<br>$\text{+8\\%}$ | 15 KB |
|
||||
| | | | | |
|
||||
| $\color{orange}{\rule{8pt}{8pt}}$ **Code** | 2 MB | 2.2 MB | $\color{orange}{\text{+163 KB}}$ | 29 KB |
|
||||
| $\color{red}{\rule{8pt}{8pt}}$ **Strings** | 3.1 MB | 3.3 MB | $\color{orange}{\text{+245 KB}}$ | 44 KB |
|
||||
| $\color{cyan}{\rule{8pt}{8pt}}$ **JS arrays** | 4.1 MB | 4.4 MB | $\color{orange}{\text{+326 KB}}$ | 59 KB |
|
||||
| $\color{green}{\rule{8pt}{8pt}}$ **Typed arrays** | 5.1 MB | 5.5 MB | $\color{orange}{\text{+408 KB}}$ | 74 KB |
|
||||
| $\color{yellow}{\rule{8pt}{8pt}}$ **System objects** | 6.1 MB | 6.6 MB | $\color{orange}{\text{+490 KB}}$ | 88 KB |
|
||||
| $\color{violet}{\rule{8pt}{8pt}}$ **Other JS objs** | 7.1 MB | 7.7 MB | $\color{orange}{\text{+571 KB}}$ | 103 KB |
|
||||
| $\color{pink}{\rule{8pt}{8pt}}$ **Other non-JS objs** | 8.2 MB | 8.8 MB | $\color{orange}{\text{+653 KB}}$ | 118 KB |
|
||||
| $\color{orange}{\rule{8pt}{8pt}}$ **Code** | 2 MB | 2.2 MB | $\color{orange}{\text{+163 KB}}$ | 29 KB |
|
||||
| $\color{red}{\rule{8pt}{8pt}}$ **Strings** | 3.1 MB | 3.3 MB | $\color{orange}{\text{+245 KB}}$ | 44 KB |
|
||||
| $\color{cyan}{\rule{8pt}{8pt}}$ **JS arrays** | 4.1 MB | 4.4 MB | $\color{orange}{\text{+326 KB}}$ | 59 KB |
|
||||
| $\color{green}{\rule{8pt}{8pt}}$ **Typed arrays** | 5.1 MB | 5.5 MB | $\color{orange}{\text{+408 KB}}$ | 74 KB |
|
||||
| $\color{yellow}{\rule{8pt}{8pt}}$ **System objects** | 6.1 MB | 6.6 MB | $\color{orange}{\text{+490 KB}}$ | 88 KB |
|
||||
| $\color{violet}{\rule{8pt}{8pt}}$ **Other JS objs** | 7.1 MB | 7.7 MB | $\color{orange}{\text{+571 KB}}$ | 103 KB |
|
||||
| $\color{pink}{\rule{8pt}{8pt}}$ **Other non-JS objs** | 8.2 MB | 8.8 MB | $\color{orange}{\text{+653 KB}}$ | 118 KB |
|
||||
|
||||
Download representative heap snapshot: [base](https://example.invalid/base) / [head](https://example.invalid/head)
|
||||
</details>
|
||||
@@ -87,25 +87,25 @@ Download representative heap snapshot: [base](https://example.invalid/base) / [h
|
||||
<details>
|
||||
<summary>Chunk size diff (2 updated, 0 added, 0 removed)</summary>
|
||||
|
||||
| Chunk | Base | Head | Δ | Δ (%) |
|
||||
| Chunk | Base | Head | Δ | Δ (%) |
|
||||
| --- | ---: | ---: | ---: | ---: |
|
||||
| (total) | 120 KB | 127 KB | $\color{orange}{\text{+6.3 KB}}$ | $\color{orange}{\text{+5.2\\%}}$ |
|
||||
| (total) | 120 KB | 127 KB | $\color{orange}{\text{+6.3 KB}}$ | $\color{orange}{\text{+5.2\\%}}$ |
|
||||
| | | | | |
|
||||
| <details><summary>`vue`</summary> `assets/vue-b2.js` </details> | 90 KB | 96 KB | $\color{orange}{\text{+6 KB}}$ | $\color{orange}{\text{+6.7\\%}}$ |
|
||||
| (other generated chunks) | 1.2 KB | 1.5 KB | $\text{+300 B}$ | $\color{orange}{\text{+25\\%}}$ |
|
||||
| (other) | 20 KB | 20 KB | $\text{+3 B}$ | $\text{+0\\%}$ |
|
||||
| <details><summary>`vue`</summary> `assets/vue-b2.js` </details> | 90 KB | 96 KB | $\color{orange}{\text{+6 KB}}$ | $\color{orange}{\text{+6.7\\%}}$ |
|
||||
| (other generated chunks) | 1.2 KB | 1.5 KB | $\text{+300 B}$ | $\color{orange}{\text{+25\\%}}$ |
|
||||
| (other) | 20 KB | 20 KB | $\text{+3 B}$ | $\text{+0\\%}$ |
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary>Startup chunk size (2 updated, 0 added, 0 removed)</summary>
|
||||
|
||||
| Chunk | Base | Head | Δ | Δ (%) |
|
||||
| Chunk | Base | Head | Δ | Δ (%) |
|
||||
| --- | ---: | ---: | ---: | ---: |
|
||||
| (total) | 114 KB | 120 KB | $\color{orange}{\text{+6 KB}}$ | $\color{orange}{\text{+5.3\\%}}$ |
|
||||
| (total) | 114 KB | 120 KB | $\color{orange}{\text{+6 KB}}$ | $\color{orange}{\text{+5.3\\%}}$ |
|
||||
| | | | | |
|
||||
| <details><summary>`vue`</summary> `assets/vue-b2.js` </details> | 90 KB | 96 KB | $\color{orange}{\text{+6 KB}}$ | $\color{orange}{\text{+6.7\\%}}$ |
|
||||
| (other) | 24 KB | 24 KB | $\text{+3 B}$ | $\text{+0\\%}$ |
|
||||
| <details><summary>`vue`</summary> `assets/vue-b2.js` </details> | 90 KB | 96 KB | $\color{orange}{\text{+6 KB}}$ | $\color{orange}{\text{+6.7\\%}}$ |
|
||||
| (other) | 24 KB | 24 KB | $\text{+3 B}$ | $\text{+0\\%}$ |
|
||||
|
||||
_Startup chunks are the Vite entry for `src/_boot_.ts` and its static imports._
|
||||
|
||||
@@ -138,9 +138,9 @@ _Startup chunks are the Vite entry for `src/_boot_.ts` and its static imports._
|
||||
<td>1</td>
|
||||
<td>2</td>
|
||||
<td>3</td>
|
||||
<td>21 KB</td>
|
||||
<td>6.3 KB</td>
|
||||
<td>5.3 KB</td>
|
||||
<td>21 KB</td>
|
||||
<td>6.3 KB</td>
|
||||
<td>5.3 KB</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th><b>Head</b></th>
|
||||
@@ -149,9 +149,9 @@ _Startup chunks are the Vite entry for `src/_boot_.ts` and its static imports._
|
||||
<td>1</td>
|
||||
<td>3</td>
|
||||
<td>3</td>
|
||||
<td>31 KB</td>
|
||||
<td>8.4 KB</td>
|
||||
<td>7 KB</td>
|
||||
<td>31 KB</td>
|
||||
<td>8.4 KB</td>
|
||||
<td>7 KB</td>
|
||||
</tr>
|
||||
<tr><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td></tr>
|
||||
<tr>
|
||||
@@ -161,12 +161,12 @@ _Startup chunks are the Vite entry for `src/_boot_.ts` and its static imports._
|
||||
<td>0</td>
|
||||
<td>$\color{orange}{\text{+1}}$</td>
|
||||
<td>0</td>
|
||||
<td>$\color{orange}{\text{+9.8 KB}}$</td>
|
||||
<td>$\color{orange}{\text{+2.1 KB}}$</td>
|
||||
<td>$\color{orange}{\text{+1.8 KB}}$</td>
|
||||
<td>$\color{orange}{\text{+9.8 KB}}$</td>
|
||||
<td>$\color{orange}{\text{+2.1 KB}}$</td>
|
||||
<td>$\color{orange}{\text{+1.8 KB}}$</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th><b>Δ (%)</b></th>
|
||||
<th><b>Δ (%)</b></th>
|
||||
<td>0%</td>
|
||||
<td>$\color{orange}{\text{+16.7\%}}$</td>
|
||||
<td>0%</td>
|
||||
|
||||
@@ -232,8 +232,8 @@
|
||||
<div><dt>Round</dt><dd>1</dd></div>
|
||||
<div><dt>Base count</dt><dd>0</dd></div>
|
||||
<div><dt>Head count</dt><dd>1</dd></div>
|
||||
<div><dt>Encoded</dt><dd>50 KB</dd></div>
|
||||
<div><dt>Decoded body</dt><dd>120 KB</dd></div>
|
||||
<div><dt>Encoded</dt><dd>50 KB</dd></div>
|
||||
<div><dt>Decoded body</dt><dd>120 KB</dd></div>
|
||||
<div><dt>MIME</dt><dd>text/javascript</dd></div>
|
||||
<div><dt>Protocol</dt><dd>h2</dd></div>
|
||||
<div><dt>Remote</dt><dd>-</dd></div>
|
||||
@@ -276,8 +276,8 @@
|
||||
<div><dt>Round</dt><dd>2</dd></div>
|
||||
<div><dt>Base count</dt><dd>0</dd></div>
|
||||
<div><dt>Head count</dt><dd>1</dd></div>
|
||||
<div><dt>Encoded</dt><dd>50 KB</dd></div>
|
||||
<div><dt>Decoded body</dt><dd>120 KB</dd></div>
|
||||
<div><dt>Encoded</dt><dd>50 KB</dd></div>
|
||||
<div><dt>Decoded body</dt><dd>120 KB</dd></div>
|
||||
<div><dt>MIME</dt><dd>text/javascript</dd></div>
|
||||
<div><dt>Protocol</dt><dd>h2</dd></div>
|
||||
<div><dt>Remote</dt><dd>-</dd></div>
|
||||
@@ -320,8 +320,8 @@
|
||||
<div><dt>Round</dt><dd>3</dd></div>
|
||||
<div><dt>Base count</dt><dd>0</dd></div>
|
||||
<div><dt>Head count</dt><dd>1</dd></div>
|
||||
<div><dt>Encoded</dt><dd>50 KB</dd></div>
|
||||
<div><dt>Decoded body</dt><dd>120 KB</dd></div>
|
||||
<div><dt>Encoded</dt><dd>50 KB</dd></div>
|
||||
<div><dt>Decoded body</dt><dd>120 KB</dd></div>
|
||||
<div><dt>MIME</dt><dd>text/javascript</dd></div>
|
||||
<div><dt>Protocol</dt><dd>h2</dd></div>
|
||||
<div><dt>Remote</dt><dd>-</dd></div>
|
||||
|
||||
@@ -151,8 +151,8 @@ test('renders one frontend diagnostics markdown report from bundle and browser d
|
||||
const markdown = await renderReport('https://example.invalid/html');
|
||||
|
||||
await expect(markdown).toMatchFileSnapshot('./__snapshots__/report.md');
|
||||
expect(markdown).toContain('| Metric | @ Base | @ Head | Δ | MAD |');
|
||||
expect(markdown).not.toContain('| Metric | @ Base | @ Head | Δ | MAD | Result |');
|
||||
expect(markdown).toContain('| Metric | @ Base | @ Head | Δ | MAD |');
|
||||
expect(markdown).not.toContain('| Metric | @ Base | @ Head | Δ | MAD | Result |');
|
||||
expect(markdown).toContain('<summary>Requests by resource type</summary>');
|
||||
expect(markdown).toContain('## 📦 Bundle Stats');
|
||||
});
|
||||
@@ -177,8 +177,8 @@ test('renders the difference of independent medians instead of the paired median
|
||||
|
||||
const row = requireMetricRow(await renderReport(null, { base, head }), 'Requests');
|
||||
|
||||
expect(row).toContain('100 <br> ± 0');
|
||||
expect(row).toContain('200 <br> ± 0');
|
||||
expect(row).toContain('100 <br> ± 0');
|
||||
expect(row).toContain('200 <br> ± 0');
|
||||
expect(row).toContain('$\\color{orange}{\\text{+100}}$<br>$\\color{orange}{\\text{+100\\\\%}}$');
|
||||
expect(row).toContain('| 0 |');
|
||||
expect(row.split('|')).toHaveLength(7);
|
||||
@@ -251,7 +251,7 @@ test('renders a directional encoded byte delta at the absolute threshold', async
|
||||
|
||||
const row = requireMetricRow(await renderReport(null, { base, head }), 'Encoded network');
|
||||
|
||||
expect(row).toContain('$\\color{orange}{\\text{+10 KB}}$');
|
||||
expect(row).toContain('$\\color{orange}{\\text{+10 KB}}$');
|
||||
});
|
||||
|
||||
test('colours absolute and relative deltas together when the row is significant', async () => {
|
||||
@@ -268,9 +268,9 @@ test('colours absolute and relative deltas together when the row is significant'
|
||||
|
||||
const row = requireMetricRow(await renderReport(null, { base, head }), 'Encoded network');
|
||||
|
||||
expect(row).toContain('100 MB <br> ± 0 B');
|
||||
expect(row).toContain('$\\color{orange}{\\text{+20 KB}}$<br>$\\color{orange}{\\text{+0\\\\%}}$');
|
||||
expect(row).toContain('| 0 B |');
|
||||
expect(row).toContain('100 MB <br> ± 0 B');
|
||||
expect(row).toContain('$\\color{orange}{\\text{+20 KB}}$<br>$\\color{orange}{\\text{+0\\\\%}}$');
|
||||
expect(row).toContain('| 0 B |');
|
||||
expect(row.split('|')).toHaveLength(7);
|
||||
});
|
||||
|
||||
|
||||
@@ -33,7 +33,7 @@ export function formatNumber(value: number) {
|
||||
}
|
||||
|
||||
export function formatBytes(value: number) {
|
||||
if (value === 0) return '0 B';
|
||||
if (value === 0) return '0 B'; // nbspにすること
|
||||
const units = ['B', 'KB', 'MB', 'GB'];
|
||||
let unitIndex = 0;
|
||||
let size = value;
|
||||
@@ -43,7 +43,8 @@ export function formatBytes(value: number) {
|
||||
}
|
||||
|
||||
const maximumFractionDigits = size >= 10 || unitIndex === 0 ? 0 : 1;
|
||||
return `${numberFormatter.format(Number(size.toFixed(maximumFractionDigits)))} ${units[unitIndex]}`;
|
||||
// eslint-disable-next-line no-irregular-whitespace
|
||||
return `${numberFormatter.format(Number(size.toFixed(maximumFractionDigits)))} ${units[unitIndex]}`; // nbspにすること
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -75,9 +76,9 @@ export function formatSecondsAsMs(value: number | null | undefined) {
|
||||
export function formatColoredDelta(delta: number, text: (value: number) => string, colorThreshold = 0) {
|
||||
if (delta === 0) return text(0);
|
||||
const sign = delta > 0 ? '+' : '-';
|
||||
if (Math.abs(delta) < colorThreshold) return `$\\text{${sign}${escapeLatex(text(Math.abs(delta)))}}$`;
|
||||
if (Math.abs(delta) < colorThreshold) return `$\\text{${sign}${escapeLatex(text(Math.abs(delta))).replaceAll(' ', '~')}}$`; // "1 B"とかで1とBの間で自動改行させないためnbsp(~)にする
|
||||
const color = delta > 0 ? 'orange' : 'green';
|
||||
return `$\\color{${color}}{\\text{${sign}${escapeLatex(text(Math.abs(delta)))}}}$`;
|
||||
return `$\\color{${color}}{\\text{${sign}${escapeLatex(text(Math.abs(delta))).replaceAll(' ', '~')}}}$`; // "1 B"とかで1とBの間で自動改行させないためnbsp(~)にする
|
||||
}
|
||||
|
||||
export function formatDeltaBytes(deltaBytes: number, colorThreshold = 0) {
|
||||
|
||||
@@ -20,7 +20,8 @@ function categoryValue(report: HeapSnapshotReport, category: HeapSnapshotCategor
|
||||
}
|
||||
|
||||
function swatch(category: HeapSnapshotCategory) {
|
||||
return `$\\color{${heapSnapshotCategory[category].color}}{\\rule{8pt}{8pt}}$ **${heapSnapshotCategory[category].label}**`;
|
||||
// eslint-disable-next-line no-irregular-whitespace
|
||||
return `$\\color{${heapSnapshotCategory[category].color}}{\\rule{8pt}{8pt}}$ **${heapSnapshotCategory[category].label.replaceAll(' ', ' ')}**`; // nbspにすること
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -35,7 +35,8 @@ function formatMedian<T>(
|
||||
) {
|
||||
const formatted = row.formatValue(value);
|
||||
if (row.showMedianMad === false) return formatted;
|
||||
return `${formatted} <br> ± ${row.formatValue(spread)}`;
|
||||
// eslint-disable-next-line no-irregular-whitespace
|
||||
return `${formatted} <br> ± ${row.formatValue(spread)}`; // nbspにすること
|
||||
}
|
||||
|
||||
function formatDelta<T>(
|
||||
@@ -77,7 +78,7 @@ export function renderMetricComparisonTable<T>(
|
||||
if (lines.length === 0) return '**(No significant changes)**';
|
||||
|
||||
return [
|
||||
'| Metric | @ Base | @ Head | Δ | MAD |',
|
||||
'| Metric | @ Base | @ Head | Δ | MAD |',
|
||||
'| --- | ---: | ---: | ---: | ---: |',
|
||||
...lines,
|
||||
...(omitted ? ['', '<small><i>Only metrics showing significant changes are displayed.</i></small>'] : []),
|
||||
|
||||
@@ -20,15 +20,15 @@ import {
|
||||
describe('formatBytes', () => {
|
||||
// 1024ではなく1000区切り。単位が2桁以上なら小数を落とす
|
||||
test.each([
|
||||
[0, '0 B'],
|
||||
[999, '999 B'],
|
||||
[1_000, '1 KB'],
|
||||
[1_500, '1.5 KB'],
|
||||
[15_000, '15 KB'],
|
||||
[1_234_567, '1.2 MB'],
|
||||
[12_345_678, '12 MB'],
|
||||
[1_500_000_000, '1.5 GB'],
|
||||
[1_500_000_000_000, '1,500 GB'],
|
||||
[0, '0 B'],
|
||||
[999, '999 B'],
|
||||
[1_000, '1 KB'],
|
||||
[1_500, '1.5 KB'],
|
||||
[15_000, '15 KB'],
|
||||
[1_234_567, '1.2 MB'],
|
||||
[12_345_678, '12 MB'],
|
||||
[1_500_000_000, '1.5 GB'],
|
||||
[1_500_000_000_000, '1,500 GB'],
|
||||
])('formats %i as %s', (input, expected) => {
|
||||
expect(formatBytes(input)).toBe(expected);
|
||||
});
|
||||
|
||||
@@ -45,7 +45,7 @@ describe('renderHeapSnapshotTable', () => {
|
||||
report([1_100_000, 1_200_000, 1_300_000]),
|
||||
));
|
||||
|
||||
expect(row).toContain('$\\text{+100 KB}$');
|
||||
expect(row).toContain('$\\text{+100 KB}$');
|
||||
expect(row).not.toContain('within noise');
|
||||
expect(row).not.toContain('\\color{orange}');
|
||||
});
|
||||
@@ -56,7 +56,7 @@ describe('renderHeapSnapshotTable', () => {
|
||||
report([1_200_000, 1_200_000, 1_200_000]),
|
||||
));
|
||||
|
||||
expect(row).toContain('$\\color{orange}{\\text{+200 KB}}$');
|
||||
expect(row).toContain('$\\color{orange}{\\text{+200 KB}}$');
|
||||
expect(row).toContain('$\\color{orange}{\\text{+20\\\\%}}$');
|
||||
expect(row).not.toContain('increase');
|
||||
});
|
||||
@@ -67,7 +67,7 @@ describe('renderHeapSnapshotTable', () => {
|
||||
report([1_050_000, 1_050_000, 1_050_000]),
|
||||
));
|
||||
|
||||
expect(row).toContain('$\\text{+50 KB}$<br>$\\text{+5\\\\%}$');
|
||||
expect(row).toContain('$\\text{+50 KB}$<br>$\\text{+5\\\\%}$');
|
||||
expect(row.split('|')[4]).not.toContain('\\color{');
|
||||
});
|
||||
|
||||
@@ -88,7 +88,7 @@ describe('renderHeapSnapshotTable', () => {
|
||||
const categoryRow = lines.find(line => line.includes('**Code**'));
|
||||
|
||||
expect(lines.slice(0, 2)).toStrictEqual([
|
||||
'| Metric | @ Base | @ Head | Δ | MAD |',
|
||||
'| Metric | @ Base | @ Head | Δ | MAD |',
|
||||
'| --- | ---: | ---: | ---: | ---: |',
|
||||
]);
|
||||
expect(table).not.toContain('Result');
|
||||
|
||||
@@ -31,9 +31,9 @@ describe('renderMetricComparisonTable', () => {
|
||||
);
|
||||
|
||||
expect(table.split('\n')).toStrictEqual([
|
||||
'| Metric | @ Base | @ Head | Δ | MAD |',
|
||||
'| Metric | @ Base | @ Head | Δ | MAD |',
|
||||
'| --- | ---: | ---: | ---: | ---: |',
|
||||
'| <strong>Metric</strong> | 100 units <br> ± 0 units | 120 units <br> ± 0 units | $\\color{orange}{\\text{+20 units}}$<br>$\\color{orange}{\\text{+20\\\\%}}$ | 0 units |',
|
||||
'| <strong>Metric</strong> | 100 units <br> ± 0 units | 120 units <br> ± 0 units | $\\color{orange}{\\text{+20~units}}$<br>$\\color{orange}{\\text{+20\\\\%}}$ | 0 units |',
|
||||
]);
|
||||
});
|
||||
|
||||
@@ -50,9 +50,9 @@ describe('renderMetricComparisonTable', () => {
|
||||
);
|
||||
|
||||
expect(table.split('\n')).toStrictEqual([
|
||||
'| Metric | @ Base | @ Head | Δ | MAD |',
|
||||
'| Metric | @ Base | @ Head | Δ | MAD |',
|
||||
'| --- | ---: | ---: | ---: | ---: |',
|
||||
'| <strong>Metric</strong> | 100 units | 120 units | $\\color{orange}{\\text{+20 units}}$ | 0 units |',
|
||||
'| <strong>Metric</strong> | 100 units | 120 units | $\\color{orange}{\\text{+20~units}}$ | 0 units |',
|
||||
'| | | | | |',
|
||||
]);
|
||||
});
|
||||
@@ -62,7 +62,7 @@ describe('renderMetricComparisonTable', () => {
|
||||
const head = samples(109, 109, 109);
|
||||
const table = renderMetricComparisonTable(base, head, [defaultRow]);
|
||||
|
||||
expect(table).toContain('$\\text{+9 units}$<br>$\\text{+9\\\\%}$');
|
||||
expect(table).toContain('$\\text{+9~units}$<br>$\\text{+9\\\\%}$');
|
||||
expect(table).not.toContain('\\color{');
|
||||
expect(renderMetricComparisonTable(base, head, [defaultRow], {
|
||||
onlySignificantChanges: true,
|
||||
@@ -85,7 +85,7 @@ describe('renderMetricComparisonTable', () => {
|
||||
{ onlySignificantChanges: true },
|
||||
);
|
||||
|
||||
expect(table).toContain('$\\color{orange}{\\text{+10 units}}$');
|
||||
expect(table).toContain('$\\color{orange}{\\text{+10~units}}$');
|
||||
expect(table).toContain('$\\color{orange}{\\text{+10\\\\%}}$');
|
||||
});
|
||||
|
||||
@@ -106,11 +106,11 @@ describe('renderMetricComparisonTable', () => {
|
||||
[defaultRow],
|
||||
);
|
||||
|
||||
expect(inside).toContain('$\\text{+9 units}$');
|
||||
expect(inside).toContain('$\\text{+9~units}$');
|
||||
expect(inside).not.toContain('\\color{');
|
||||
expect(boundary).toContain('$\\text{+30 units}$');
|
||||
expect(boundary).toContain('$\\text{+30~units}$');
|
||||
expect(boundary).not.toContain('\\color{');
|
||||
expect(outside).toContain('$\\color{orange}{\\text{+31 units}}$');
|
||||
expect(outside).toContain('$\\color{orange}{\\text{+31~units}}$');
|
||||
expect(outside).toContain('$\\color{orange}{\\text{+31\\\\%}}$');
|
||||
});
|
||||
|
||||
@@ -121,7 +121,7 @@ describe('renderMetricComparisonTable', () => {
|
||||
[defaultRow],
|
||||
);
|
||||
|
||||
expect(table).toContain('$\\color{green}{\\text{-20 units}}$');
|
||||
expect(table).toContain('$\\color{green}{\\text{-20~units}}$');
|
||||
expect(table).toContain('$\\color{green}{\\text{-16.7\\\\%}}$');
|
||||
});
|
||||
|
||||
@@ -132,7 +132,7 @@ describe('renderMetricComparisonTable', () => {
|
||||
[defaultRow],
|
||||
);
|
||||
|
||||
expect(table).toContain('$\\color{orange}{\\text{+20 units}}$<br>-');
|
||||
expect(table).toContain('$\\color{orange}{\\text{+20~units}}$<br>-');
|
||||
});
|
||||
|
||||
test('propagates the minimum sample contract', () => {
|
||||
|
||||
@@ -5,9 +5,12 @@
|
||||
|
||||
import { bindThis } from '@/decorators.js';
|
||||
import { logManager } from './logging/logging-runtime.js';
|
||||
import type { LogLevel, LoggerContext, LogWriteInput } from './logging/types.js';
|
||||
import type { LogEntryInput, LogLevel, LoggerContext, LogWriteInput } from './logging/types.js';
|
||||
import type { Keyword } from 'color-convert';
|
||||
|
||||
// 旧APIのdataは表示用の任意値を受け取り、Errorや配列も既存呼び出しで使用されています。
|
||||
type LegacyData = Record<string, any> | null;
|
||||
|
||||
/**
|
||||
* ロガー名の階層と従来の公開APIを提供する薄い窓口です。
|
||||
* 出力条件の判断や整形はLogManagerとLogBackendへ委譲します。
|
||||
@@ -52,6 +55,15 @@ export default class Logger {
|
||||
});
|
||||
}
|
||||
|
||||
/** level別メソッドの構造化入力にlevelとLoggerのcontextを付けて渡します。 */
|
||||
@bindThis
|
||||
private logStructured(level: LogLevel, input: LogEntryInput): void {
|
||||
this.write({
|
||||
...input,
|
||||
level,
|
||||
});
|
||||
}
|
||||
|
||||
/** 構造化ログをLoggerのcontext付きでLogManagerへ渡します。 */
|
||||
@bindThis
|
||||
public write(input: LogWriteInput): void {
|
||||
@@ -62,24 +74,35 @@ export default class Logger {
|
||||
}
|
||||
|
||||
/** 処理を継続できない状況を記録します。 */
|
||||
public error(input: LogEntryInput): void;
|
||||
public error(error: Error, data?: LegacyData, important?: boolean): void;
|
||||
public error(message: string, data?: LegacyData, important?: boolean): void;
|
||||
public error(errorOrMessage: string | Error, data?: LegacyData, important?: boolean): void;
|
||||
@bindThis
|
||||
public error(x: string | Error, data?: Record<string, any> | null, important = false): void {
|
||||
public error(x: LogEntryInput | string | Error, data?: LegacyData, important = false): void {
|
||||
if (x instanceof Error) {
|
||||
// エラー本体も第2引数へ残し、従来どおりスタックなどを確認できるようにします。
|
||||
data = data ?? {};
|
||||
data.e = x;
|
||||
this.log('error', x.toString(), data, important, undefined, x);
|
||||
} else if (typeof x === 'object') {
|
||||
this.log('error', `${(x as any).message ?? (x as any).name ?? x}`, data, important);
|
||||
} else if (typeof x === 'string') {
|
||||
this.log('error', x, data, important);
|
||||
} else {
|
||||
this.log('error', `${x}`, data, important);
|
||||
this.logStructured('error', x);
|
||||
}
|
||||
}
|
||||
|
||||
/** 処理は継続できるものの、改善が必要な状況を記録します。 */
|
||||
public warn(input: LogEntryInput): void;
|
||||
public warn(message: string): void;
|
||||
public warn(message: string, data?: LegacyData, important?: boolean): void;
|
||||
@bindThis
|
||||
public warn(message: string, data?: Record<string, any> | null, important = false): void {
|
||||
this.log('warn', message, data, important);
|
||||
public warn(inputOrMessage: LogEntryInput | string, data?: LegacyData, important = false): void {
|
||||
if (typeof inputOrMessage === 'string') {
|
||||
this.log('warn', inputOrMessage, data, important);
|
||||
} else {
|
||||
this.logStructured('warn', inputOrMessage);
|
||||
}
|
||||
}
|
||||
|
||||
/** 処理が成功したことを、従来のDONE表示で記録します。 */
|
||||
@@ -89,14 +112,40 @@ export default class Logger {
|
||||
}
|
||||
|
||||
/** 開発者向けの調査情報を記録します。 */
|
||||
public debug(input: LogEntryInput): void;
|
||||
public debug(message: string): void;
|
||||
public debug(message: string, data?: LegacyData, important?: boolean): void;
|
||||
@bindThis
|
||||
public debug(message: string, data?: Record<string, any> | null, important = false): void {
|
||||
this.log('debug', message, data, important);
|
||||
public debug(inputOrMessage: LogEntryInput | string, data?: LegacyData, important = false): void {
|
||||
if (typeof inputOrMessage === 'string') {
|
||||
this.log('debug', inputOrMessage, data, important);
|
||||
} else {
|
||||
this.logStructured('debug', inputOrMessage);
|
||||
}
|
||||
}
|
||||
|
||||
/** 通常の動作状況を記録します。 */
|
||||
public info(input: LogEntryInput): void;
|
||||
public info(message: string): void;
|
||||
public info(message: string, data?: LegacyData, important?: boolean): void;
|
||||
@bindThis
|
||||
public info(message: string, data?: Record<string, any> | null, important = false): void {
|
||||
this.log('info', message, data, important);
|
||||
public info(inputOrMessage: LogEntryInput | string, data?: LegacyData, important = false): void {
|
||||
if (typeof inputOrMessage === 'string') {
|
||||
this.log('info', inputOrMessage, data, important);
|
||||
} else {
|
||||
this.logStructured('info', inputOrMessage);
|
||||
}
|
||||
}
|
||||
|
||||
/** 致命的な状況を構造化ログとして記録します。 */
|
||||
public fatal(input: LogEntryInput): void;
|
||||
public fatal(message: string): void;
|
||||
@bindThis
|
||||
public fatal(inputOrMessage: LogEntryInput | string): void {
|
||||
if (typeof inputOrMessage === 'string') {
|
||||
this.logStructured('fatal', { message: inputOrMessage });
|
||||
} else {
|
||||
this.logStructured('fatal', inputOrMessage);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -52,15 +52,19 @@ export type LogTraceContext = {
|
||||
/** LogManagerが出力直前に呼び出すTrace Context取得処理です。 */
|
||||
export type LogTraceContextProvider = () => LogTraceContext | undefined;
|
||||
|
||||
/** ロガーの呼び出し側が構造化ログとして指定する入力です。 */
|
||||
export type LogWriteInput = {
|
||||
readonly level: LogLevel;
|
||||
/** level別のLoggerメソッドが受け取る構造化ログの共通入力です。 */
|
||||
export type LogEntryInput = {
|
||||
readonly message: string;
|
||||
readonly eventName?: string;
|
||||
readonly attributes?: Readonly<Record<string, unknown>>;
|
||||
readonly error?: unknown;
|
||||
};
|
||||
|
||||
/** 動的なlevelを含めてLogManagerへ渡す構造化ログの入力です。 */
|
||||
export type LogWriteInput = LogEntryInput & {
|
||||
readonly level: LogLevel;
|
||||
};
|
||||
|
||||
/**
|
||||
* ロガー名を構成する一要素です。
|
||||
* 色は見やすい形式での表示だけに使い、ログの意味には影響させません。
|
||||
|
||||
@@ -7,6 +7,7 @@ import { Inject, Injectable } from '@nestjs/common';
|
||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||
import type { MiMeta, UsersRepository } from '@/models/_.js';
|
||||
import { SignupService } from '@/core/SignupService.js';
|
||||
import { RoleService } from '@/core/RoleService.js';
|
||||
import { UserEntityService } from '@/core/entities/UserEntityService.js';
|
||||
import { localUsernameSchema, passwordSchema } from '@/models/User.js';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
@@ -77,6 +78,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||
|
||||
private userEntityService: UserEntityService,
|
||||
private signupService: SignupService,
|
||||
private roleService: RoleService,
|
||||
) {
|
||||
super(meta, paramDef, async (ps, _me, token) => {
|
||||
const me = _me ? await this.usersRepository.findOneByOrFail({ id: _me.id }) : null;
|
||||
@@ -93,7 +95,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||
// 初期パスワードが設定されていないのに初期パスワードが入力された場合
|
||||
throw new ApiError(meta.errors.wrongInitialPassword);
|
||||
}
|
||||
} else if ((this.serverSettings.rootUserId != null && (this.serverSettings.rootUserId !== me?.id)) || token !== null) {
|
||||
} else if (token !== null || !(await this.roleService.isAdministrator(me))) {
|
||||
// 初回セットアップではなく、管理者でない場合 or 外部トークンを使用している場合
|
||||
throw new ApiError(meta.errors.accessDenied);
|
||||
}
|
||||
|
||||
@@ -76,6 +76,91 @@ describe('Logger', () => {
|
||||
}));
|
||||
});
|
||||
|
||||
test('supports structured input through every level-specific method', () => {
|
||||
const logger = new Logger('root').createSubLogger('child');
|
||||
const error = new Error('broken');
|
||||
const input = {
|
||||
eventName: 'example.failed',
|
||||
message: 'failed',
|
||||
attributes: { id: 'id' },
|
||||
error,
|
||||
};
|
||||
|
||||
logger.debug(input);
|
||||
logger.info(input);
|
||||
logger.warn(input);
|
||||
logger.error(input);
|
||||
logger.fatal(input);
|
||||
|
||||
expect(mocks.write.mock.calls.map(([entry]) => entry.level)).toEqual([
|
||||
'debug',
|
||||
'info',
|
||||
'warn',
|
||||
'error',
|
||||
'fatal',
|
||||
]);
|
||||
for (const [entry] of mocks.write.mock.calls) {
|
||||
expect(entry).toMatchObject({
|
||||
...input,
|
||||
context: [
|
||||
{ name: 'root', color: undefined },
|
||||
{ name: 'child', color: undefined },
|
||||
],
|
||||
});
|
||||
expect(entry).not.toHaveProperty('compatibility');
|
||||
}
|
||||
});
|
||||
|
||||
test('level-specific methods own the level even for runtime-invalid input', () => {
|
||||
const logger = new Logger('root');
|
||||
|
||||
// @ts-expect-error level is selected by the method rather than the input object
|
||||
logger.warn({ level: 'error', message: 'warning' });
|
||||
|
||||
expect(mocks.write.mock.calls[0][0]).toMatchObject({
|
||||
level: 'warn',
|
||||
message: 'warning',
|
||||
});
|
||||
});
|
||||
|
||||
test('preserves the legacy string signatures for non-error levels', () => {
|
||||
const logger = new Logger('root');
|
||||
logger.debug('debug', { source: 'debug' }, true);
|
||||
logger.info('info', null, true);
|
||||
logger.warn('warn', { source: 'warn' });
|
||||
|
||||
expect(mocks.write.mock.calls.map(([entry]) => entry.compatibility)).toEqual([
|
||||
{ legacyLevel: undefined, important: true, data: { source: 'debug' } },
|
||||
{ legacyLevel: undefined, important: true, data: null },
|
||||
{ legacyLevel: undefined, important: false, data: { source: 'warn' } },
|
||||
]);
|
||||
});
|
||||
|
||||
test('records a fatal string through the structured API', () => {
|
||||
new Logger('root').fatal('fatal message');
|
||||
|
||||
expect(mocks.write).toHaveBeenCalledWith({
|
||||
level: 'fatal',
|
||||
message: 'fatal message',
|
||||
context: [{ name: 'root', color: undefined }],
|
||||
});
|
||||
});
|
||||
|
||||
test('preserves the legacy error string signature', () => {
|
||||
new Logger('root').error('failed', { requestId: 'request' }, true);
|
||||
|
||||
expect(mocks.write).toHaveBeenCalledWith({
|
||||
level: 'error',
|
||||
message: 'failed',
|
||||
context: [{ name: 'root', color: undefined }],
|
||||
compatibility: {
|
||||
legacyLevel: undefined,
|
||||
important: true,
|
||||
data: { requestId: 'request' },
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
test('uses Error.toString and adds the Error to existing data', () => {
|
||||
const logger = new Logger('root');
|
||||
const error = new TypeError('broken');
|
||||
|
||||
-5
@@ -13,8 +13,3 @@ declare const _PERF_PREFIX_: string;
|
||||
|
||||
// for dev-mode
|
||||
declare const _LANGS_FULL_: string[][];
|
||||
|
||||
// TagCanvas
|
||||
interface Window {
|
||||
TagCanvas: any;
|
||||
}
|
||||
|
||||
-6
@@ -14,9 +14,3 @@ declare const _PERF_PREFIX_: string;
|
||||
|
||||
// for dev-mode
|
||||
declare const _LANGS_FULL_: string[][];
|
||||
|
||||
// TagCanvas
|
||||
interface Window {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
TagCanvas: any;
|
||||
}
|
||||
|
||||
Vendored
-5
@@ -13,8 +13,3 @@ declare const _PERF_PREFIX_: string;
|
||||
|
||||
// for dev-mode
|
||||
declare const _LANGS_FULL_: string[][];
|
||||
|
||||
// TagCanvas
|
||||
interface Window {
|
||||
TagCanvas: any;
|
||||
}
|
||||
|
||||
-21
File diff suppressed because one or more lines are too long
@@ -22,6 +22,7 @@
|
||||
"@mcaptcha/core-glue": "0.1.0-alpha-5",
|
||||
"@misskey-dev/browser-image-resizer": "2024.1.0",
|
||||
"@misskey-dev/emoji-data": "17.0.3",
|
||||
"@misskey-dev/tagcanvas-es": "0.1.2",
|
||||
"@sentry/vue": "10.65.0",
|
||||
"@simplewebauthn/browser": "13.3.0",
|
||||
"@syuilo/aiscript": "1.2.1",
|
||||
|
||||
@@ -16,7 +16,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
@pointerup.passive="onPointerup"
|
||||
@pointercancel.passive="cancelPointerGesture"
|
||||
@touchstart.passive="onTouchstart"
|
||||
@touchmove.passive="onTouchmove"
|
||||
@touchmove="onTouchmove"
|
||||
@touchcancel.passive="cancelPointerGesture"
|
||||
@contextmenu="cancelPointerGesture"
|
||||
@wheel="onWheel"
|
||||
@@ -284,7 +284,7 @@ function shouldHideInGallery(content: Content): boolean {
|
||||
if (!hiddenByDefault) return false;
|
||||
|
||||
// ギャラリー起動時に最初に開いたセンシティブ画像だけは初期表示で隠さない
|
||||
if (content.file.isSensitive && prefer.s.nsfw !== 'force' && props.initiallyOpened) {
|
||||
if (content.file.isSensitive && props.initiallyOpened) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -721,6 +721,13 @@ function onTouchstart(ev: TouchEvent) {
|
||||
|
||||
function onTouchmove(ev: TouchEvent) {
|
||||
doubleTapDetector.onTouchmove(ev);
|
||||
|
||||
// スワイプ操作中は、ブラウザがタッチを慣性スクロールとして認識するのを防ぐためにpreventDefaultする必要がある
|
||||
// touch-action: noneが指定されているが、それだけではブラウザによっては慣性スクロールが発生した扱いになり、
|
||||
// その後のタップが慣性スクロールを止めるためのタップという扱いで握りつぶされてしまうことがあるので必要
|
||||
if (isVerticalSwiping || isHorizontalSwiping || isZooming.value || pointerEventCache.size > 1) {
|
||||
ev.preventDefault();
|
||||
}
|
||||
}
|
||||
|
||||
//#region inertia
|
||||
|
||||
@@ -4,7 +4,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
-->
|
||||
|
||||
<template>
|
||||
<div :class="[hide ? $style.hidden : $style.visible, (image.isSensitive && prefer.s.highlightSensitiveMedia) && $style.sensitive]" @pointerup="onClick" @contextmenu.stop="onContextmenu">
|
||||
<div :class="[hide ? $style.hidden : $style.visible, (image.isSensitive && prefer.s.highlightSensitiveMedia) && $style.sensitive]" @click="onClick" @contextmenu.stop="onContextmenu">
|
||||
<component
|
||||
:is="disableImageLink ? 'div' : 'a'"
|
||||
v-bind="disableImageLink ? {
|
||||
@@ -61,8 +61,8 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
<div v-if="image.comment" :class="$style.indicator">ALT</div>
|
||||
<div v-if="image.isSensitive" :class="$style.indicator" style="color: var(--MI_THEME-warn);" :title="i18n.ts.sensitive"><i class="ti ti-eye-exclamation"></i></div>
|
||||
</div>
|
||||
<button :class="[$style.menu, $style.menuBottom]" class="_button" @click.stop="showMenu" @pointerup.stop><i class="ti ti-dots" style="vertical-align: middle;" aria-hidden="true"></i></button>
|
||||
<button :class="[$style.menu, $style.menuTop]" class="_button" @click.stop="hide = true" @pointerup.stop><i class="ti ti-eye-off" style="vertical-align: middle;" aria-hidden="true"></i></button>
|
||||
<button :class="[$style.menu, $style.menuBottom]" class="_button" @click.stop="showMenu"><i class="ti ti-dots" style="vertical-align: middle;" aria-hidden="true"></i></button>
|
||||
<button :class="[$style.menu, $style.menuTop]" class="_button" @click.stop="hide = true"><i class="ti ti-eye-off" style="vertical-align: middle;" aria-hidden="true"></i></button>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -21,7 +21,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
</div>
|
||||
</button>
|
||||
|
||||
<div v-else :class="$style.videoRoot" @pointerup="emit('mediaClick', $event)">
|
||||
<div v-else :class="$style.videoRoot" @click="emit('mediaClick', $event)">
|
||||
<img
|
||||
v-if="video.thumbnailUrl"
|
||||
:class="$style.video"
|
||||
@@ -41,8 +41,8 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
<i class="ti ti-player-play"></i>
|
||||
</div>
|
||||
</div>
|
||||
<button :class="[$style.menu, $style.menuBottom]" class="_button" @click.stop="showMenu" @pointerup.stop><i class="ti ti-dots" style="vertical-align: middle;" aria-hidden="true"></i></button>
|
||||
<button :class="[$style.menu, $style.menuTop]" class="_button" @click.stop="hide = true" @pointerup.stop><i class="ti ti-eye-off" style="vertical-align: middle;" aria-hidden="true"></i></button>
|
||||
<button :class="[$style.menu, $style.menuBottom]" class="_button" @click.stop="showMenu"><i class="ti ti-dots" style="vertical-align: middle;" aria-hidden="true"></i></button>
|
||||
<button :class="[$style.menu, $style.menuTop]" class="_button" @click.stop="hide = true"><i class="ti ti-eye-off" style="vertical-align: middle;" aria-hidden="true"></i></button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -5,8 +5,8 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
<template>
|
||||
<div ref="rootEl" :class="$style.root">
|
||||
<canvas :id="idForCanvas" ref="canvasEl" style="display: block;" :width="width" height="300" @contextmenu.prevent="() => {}"></canvas>
|
||||
<div :id="idForTags" ref="tagsEl" :class="$style.tags">
|
||||
<canvas ref="canvasEl" style="display: block;" :width="width" height="300" @contextmenu.prevent="() => {}"></canvas>
|
||||
<div ref="tagsEl" :class="$style.tags">
|
||||
<ul>
|
||||
<slot></slot>
|
||||
</ul>
|
||||
@@ -15,62 +15,68 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { onMounted, watch, onBeforeUnmount, ref, useTemplateRef } from 'vue';
|
||||
import { onMounted, onBeforeUnmount, nextTick, ref, shallowRef, useTemplateRef } from 'vue';
|
||||
import { themeManager } from '@/theme.js';
|
||||
import tinycolor from 'tinycolor2';
|
||||
import { TagCanvas } from '@misskey-dev/tagcanvas-es';
|
||||
|
||||
const loaded = !!window.TagCanvas;
|
||||
const SAFE_FOR_HTML_ID = 'abcdefghijklmnopqrstuvwxyz';
|
||||
const idForCanvas = Array.from({ length: 16 }, () => SAFE_FOR_HTML_ID[Math.floor(Math.random() * SAFE_FOR_HTML_ID.length)]).join('');
|
||||
const idForTags = Array.from({ length: 16 }, () => SAFE_FOR_HTML_ID[Math.floor(Math.random() * SAFE_FOR_HTML_ID.length)]).join('');
|
||||
const available = ref(false);
|
||||
const rootEl = useTemplateRef('rootEl');
|
||||
const canvasEl = useTemplateRef('canvasEl');
|
||||
const tagsEl = useTemplateRef('tagsEl');
|
||||
const tagCanvas = shallowRef<TagCanvas | null>(null);
|
||||
const width = ref(300);
|
||||
|
||||
watch(available, () => {
|
||||
try {
|
||||
window.TagCanvas.Start(idForCanvas, idForTags, {
|
||||
textColour: '#ffffff',
|
||||
outlineColour: tinycolor(themeManager.currentCompiledTheme!.accent).toHexString(),
|
||||
outlineRadius: 10,
|
||||
initial: [-0.030, -0.010],
|
||||
frontSelect: true,
|
||||
imageRadius: 8,
|
||||
//dragControl: true,
|
||||
dragThreshold: 3,
|
||||
wheelZoom: false,
|
||||
reverse: true,
|
||||
depth: 0.5,
|
||||
maxSpeed: 0.2,
|
||||
minSpeed: 0.003,
|
||||
stretchX: 0.8,
|
||||
stretchY: 0.8,
|
||||
});
|
||||
} catch (err) {}
|
||||
});
|
||||
function createTagCanvas() {
|
||||
if (tagCanvas.value) {
|
||||
tagCanvas.value.destroy();
|
||||
tagCanvas.value = null;
|
||||
}
|
||||
|
||||
if (tagsEl.value == null || canvasEl.value == null) return;
|
||||
if (tagsEl.value.children[0].children.length === 0) return;
|
||||
|
||||
tagCanvas.value = new TagCanvas(canvasEl.value, {
|
||||
tagContainer: tagsEl.value,
|
||||
textColor: '#ffffff',
|
||||
outlineColor: tinycolor(themeManager.currentCompiledTheme!.accent).toHexString(),
|
||||
outlineRadius: 10,
|
||||
initial: [-0.030, -0.010],
|
||||
frontSelect: true,
|
||||
imageRadius: 8,
|
||||
// dragControl: true,
|
||||
dragThreshold: 3,
|
||||
wheelZoom: false,
|
||||
reverse: true,
|
||||
depth: 0.5,
|
||||
maxSpeed: 0.2,
|
||||
minSpeed: 0.003,
|
||||
stretchX: 0.8,
|
||||
stretchY: 0.8,
|
||||
});
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
if (rootEl.value) width.value = rootEl.value.offsetWidth;
|
||||
|
||||
if (loaded) {
|
||||
available.value = true;
|
||||
} else {
|
||||
window.document.head.appendChild(Object.assign(window.document.createElement('script'), {
|
||||
async: true,
|
||||
src: '/client-assets/tagcanvas.min.js',
|
||||
})).addEventListener('load', () => available.value = true);
|
||||
}
|
||||
nextTick(() => {
|
||||
createTagCanvas();
|
||||
});
|
||||
});
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
if (window.TagCanvas) window.TagCanvas.Delete(idForCanvas);
|
||||
if (tagCanvas.value) {
|
||||
tagCanvas.value.destroy();
|
||||
tagCanvas.value = null;
|
||||
}
|
||||
});
|
||||
|
||||
defineExpose({
|
||||
update: () => {
|
||||
window.TagCanvas.Update(idForCanvas);
|
||||
if (tagCanvas.value) {
|
||||
tagCanvas.value.update();
|
||||
} else {
|
||||
createTagCanvas();
|
||||
}
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -411,7 +411,8 @@ export class Paginator<
|
||||
public releaseQueue(): void {
|
||||
if (this.aheadQueue.length === 0) return; // これやらないと余計なre-renderが走る
|
||||
for (const item of this.aheadQueue) {
|
||||
item._shouldAnimateIn_ = true;
|
||||
item._shouldAnimateIn_ = false; // 一気に入るときは挿入アニメーションさせない
|
||||
item._shouldAnimateOut_ = false;
|
||||
}
|
||||
this.unshiftItems(this.aheadQueue);
|
||||
this.aheadQueue = [];
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"type": "module",
|
||||
"name": "misskey-js",
|
||||
"version": "2026.7.0-beta.3",
|
||||
"version": "2026.7.0-beta.5",
|
||||
"description": "Misskey SDK for JavaScript",
|
||||
"license": "MIT",
|
||||
"main": "./built/index.js",
|
||||
|
||||
Generated
+8
@@ -673,6 +673,9 @@ importers:
|
||||
'@misskey-dev/emoji-data':
|
||||
specifier: 17.0.3
|
||||
version: 17.0.3
|
||||
'@misskey-dev/tagcanvas-es':
|
||||
specifier: 0.1.2
|
||||
version: 0.1.2
|
||||
'@sentry/vue':
|
||||
specifier: 10.65.0
|
||||
version: 10.65.0(vue@3.5.39(typescript@6.0.2))
|
||||
@@ -2447,6 +2450,9 @@ packages:
|
||||
'@misskey-dev/summaly@5.5.1':
|
||||
resolution: {integrity: sha512-CfpVtmFgltjNS1FAS5+bOluKAF/irZuQnUxAw66ydnp/uesiNNu62ltf9gEgwutT5S+itGtlnP87AOx6DDn/1A==}
|
||||
|
||||
'@misskey-dev/tagcanvas-es@0.1.2':
|
||||
resolution: {integrity: sha512-SGqyu30/CCQyIC95Zcmj3k1vYXwfX66/arsxpl1UUulUqvlRpTpuhB7kEAAps1lqXin0rQzXknb31EeveNKsaw==}
|
||||
|
||||
'@msgpackr-extract/msgpackr-extract-darwin-arm64@3.0.4':
|
||||
resolution: {integrity: sha512-LCkGo6JDfaBhgST7UpPWgNgLINpcpabaHfyz5OBx75nUYxBsaEPxjnyNjWpeb/xBup/682QnBfRBy2/LvPutZQ==}
|
||||
cpu: [arm64]
|
||||
@@ -10505,6 +10511,8 @@ snapshots:
|
||||
optionalDependencies:
|
||||
fastify: 5.8.5
|
||||
|
||||
'@misskey-dev/tagcanvas-es@0.1.2': {}
|
||||
|
||||
'@msgpackr-extract/msgpackr-extract-darwin-arm64@3.0.4':
|
||||
optional: true
|
||||
|
||||
|
||||
Reference in New Issue
Block a user