This commit is contained in:
samunohito 2025-10-26 21:05:33 +09:00
parent 471497b21b
commit 220ce85fbf
6 changed files with 583 additions and 207 deletions

View File

@ -12,7 +12,7 @@ import * as assert from 'assert';
import { setTimeout } from 'node:timers/promises';
import { entities } from 'misskey-js';
import { Redis } from 'ioredis';
import { SignupResponse, Note, UserList } from 'misskey-js/entities.js';
import { SignupResponse, Note } from 'misskey-js/entities.js';
import { api, initTestDb, post, randomString, sendEnvUpdateRequest, signup, uploadUrl, UserToken } from '../utils.js';
import { loadConfig } from '@/config.js';
@ -81,13 +81,13 @@ describe('Timelines', () => {
root = await signup({ username: 'root' });
}, 1000 * 60 * 2);
afterEach(async () => {
// テスト中に作ったノートをきれいにする。
// ユーザも作っているが、時間差で動く通知系処理などがあり、このタイミングで消すとエラー落ちするので消さない(ノートさえ消えていれば支障はない)
const db = await initTestDb(true);
await db.query('DELETE FROM "note"');
await db.query('DELETE FROM "channel"');
});
// afterEach(async () => {
// // テスト中に作ったノートをきれいにする。
// // ユーザも作っているが、時間差で動く通知系処理などがあり、このタイミングで消すとエラー落ちするので消さない(ノートさえ消えていれば支障はない)
// const db = await initTestDb(true);
// await db.query('DELETE FROM "note"');
// await db.query('DELETE FROM "channel"');
// });
describe.each([
{ enableFanoutTimeline: true },
@ -101,7 +101,6 @@ describe('Timelines', () => {
await api('admin/update-meta', { enableFanoutTimeline }, root);
}, 1000 * 60 * 2);
describe('Home TL', () => {
describe('Home TL', () => {
test('自分の visibility: followers なノートが含まれる', async () => {
const [alice] = await Promise.all([signup()]);
@ -1625,8 +1624,6 @@ describe('Timelines', () => {
});
});
});
describe('Social TL', () => {
test('ローカルユーザーのノートが含まれる', async () => {
const [alice, bob] = await Promise.all([signup(), signup()]);
@ -1667,7 +1664,8 @@ describe('Timelines', () => {
});
test('withReplies: false でフォローしているユーザーからの自分への返信が含まれる', async () => {
/* FIXME: https://github.com/misskey-dev/misskey/issues/12065 */ if (!enableFanoutTimeline) return;
/* FIXME: https://github.com/misskey-dev/misskey/issues/12065 */
if (!enableFanoutTimeline) return;
const [alice, bob] = await Promise.all([signup(), signup()]);
@ -1703,7 +1701,8 @@ describe('Timelines', () => {
});
test('withReplies: true でフォローしているユーザーの行った別のフォローしているユーザーの visibility: followers な投稿への返信が含まれる', async () => {
/* FIXME: https://github.com/misskey-dev/misskey/issues/12065 */ if (!enableFanoutTimeline) return;
/* FIXME: https://github.com/misskey-dev/misskey/issues/12065 */
if (!enableFanoutTimeline) return;
const [alice, bob, carol] = await Promise.all([signup(), signup(), signup()]);
@ -1725,7 +1724,8 @@ describe('Timelines', () => {
});
test('withReplies: true でフォローしているユーザーの自分の visibility: followers な投稿への返信が含まれる', async () => {
/* FIXME: https://github.com/misskey-dev/misskey/issues/12065 */ if (!enableFanoutTimeline) return;
/* FIXME: https://github.com/misskey-dev/misskey/issues/12065 */
if (!enableFanoutTimeline) return;
const [alice, bob] = await Promise.all([signup(), signup()]);
@ -1801,7 +1801,8 @@ describe('Timelines', () => {
});
test('withReplies: false でフォローしていないユーザーからの自分への返信が含まれる', async () => {
/* FIXME: https://github.com/misskey-dev/misskey/issues/12065 */ if (!enableFanoutTimeline) return;
/* FIXME: https://github.com/misskey-dev/misskey/issues/12065 */
if (!enableFanoutTimeline) return;
const [alice, bob] = await Promise.all([signup(), signup()]);
@ -2211,6 +2212,7 @@ describe('Timelines', () => {
assert.strictEqual(res.body.some(note => note.id === elleNote.id), true);
});
});
});
describe('User List TL', () => {
test('リスインしているフォローしていないユーザーのノートが含まれる', async () => {
@ -2761,7 +2763,7 @@ describe('Timelines', () => {
assert.strictEqual(res.body.some((note: any) => note.id === bobRenote.id), false);
});
});
});
});
describe('User TL', () => {
test('ノートが含まれる', async () => {
@ -3118,192 +3120,192 @@ describe('Timelines', () => {
});
});
describe('Channel TL', () => {
test('閲覧中チャンネルのノートが含まれる', async () => {
const [alice, bob] = await Promise.all([signup(), signup()]);
describe('Channel TL', () => {
test('閲覧中チャンネルのノートが含まれる', async () => {
const [alice, bob] = await Promise.all([signup(), signup()]);
const channel = await createChannel('channel', bob);
const channel = await createChannel('channel', bob);
const aliceNote = await post(alice, { text: 'hi' });
const bobNote = await post(bob, { text: 'ok', channelId: channel.id });
const aliceNote = await post(alice, { text: 'hi' });
const bobNote = await post(bob, { text: 'ok', channelId: channel.id });
await waitForPushToTl();
await waitForPushToTl();
const res = await api('channels/timeline', { channelId: channel.id }, alice);
const res = await api('channels/timeline', { channelId: channel.id }, alice);
assert.strictEqual(res.body.some((note: any) => note.id === aliceNote.id), false);
assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), true);
});
test('閲覧中チャンネルとは別チャンネルのノートは含まれない', async() => {
const [alice, bob] = await Promise.all([signup(), signup()]);
const channel = await createChannel('channel', bob);
const channel2 = await createChannel('channel', bob);
const aliceNote = await post(alice, { text: 'hi' });
const bobNote = await post(bob, { text: 'ok', channelId: channel2.id });
await waitForPushToTl();
const res = await api('channels/timeline', { channelId: channel.id }, alice);
assert.strictEqual(res.body.some((note: any) => note.id === aliceNote.id), false);
assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), false);
});
test('閲覧中チャンネルのノートにリノートが含まれる', async() => {
const [alice, bob] = await Promise.all([signup(), signup()]);
const channel = await createChannel('channel', bob);
const aliceNote = await post(alice, { text: 'hi' });
const bobNote = await post(bob, { text: 'ok', channelId: channel.id });
const bobRenote = await post(bob, { channelId: channel.id, renoteId: bobNote.id });
await waitForPushToTl();
const res = await api('channels/timeline', { channelId: channel.id }, alice);
assert.strictEqual(res.body.some((note: any) => note.id === bobRenote.id), true);
});
test('閲覧中チャンネルとは別チャンネルからのリノートが含まれる', async() => {
const [alice, bob] = await Promise.all([signup(), signup()]);
const channel = await createChannel('channel', bob);
const channel2 = await createChannel('channel', bob);
const aliceNote = await post(alice, { text: 'hi' });
const bobNote = await post(bob, { text: 'ok', channelId: channel2.id });
const bobRenote = await post(bob, { channelId: channel.id, renoteId: bobNote.id });
await waitForPushToTl();
const res = await api('channels/timeline', { channelId: channel.id }, alice);
assert.strictEqual(res.body.some((note: any) => note.id === bobRenote.id), true);
});
test('閲覧中チャンネルに自分の他人への返信が含まれる', async () => {
const [alice, bob] = await Promise.all([signup(), signup()]);
const channel = await createChannel('channel', bob);
const bobNote = await post(bob, { text: 'ok', channelId: channel.id });
const aliceNote = await post(alice, { text: 'hi', replyId: bobNote.id, channelId: channel.id });
await waitForPushToTl();
const res = await api('channels/timeline', { channelId: channel.id }, alice);
assert.strictEqual(res.body.some((note: any) => note.id === aliceNote.id), true);
});
test('閲覧中チャンネルに他人の自分への返信が含まれる', async () => {
const [alice, bob] = await Promise.all([signup(), signup()]);
const channel = await createChannel('channel', bob);
const aliceNote = await post(alice, { text: 'hi', channelId: channel.id });
const bobNote = await post(bob, { text: 'ok', replyId: aliceNote.id, channelId: channel.id });
await waitForPushToTl();
const res = await api('channels/timeline', { channelId: channel.id }, alice);
assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), true);
});
test('閲覧中チャンネルにミュートしているユーザのノートは含まれない', async () => {
const [alice, bob] = await Promise.all([signup(), signup()]);
await api('mute/create', { userId: bob.id }, alice);
const channel = await createChannel('channel', bob);
const aliceNote = await post(alice, { text: 'hi' });
const bobNote = await post(bob, { text: 'ok', channelId: channel.id });
await waitForPushToTl();
const res = await api('channels/timeline', { channelId: channel.id }, alice);
assert.strictEqual(res.body.some((note: any) => note.id === aliceNote.id), false);
assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), false);
});
test('閲覧中チャンネルにこちらをブロックしているユーザのノートは含まれない', async () => {
const [alice, bob] = await Promise.all([signup(), signup()]);
await api('blocking/create', { userId: alice.id }, bob);
const channel = await createChannel('channel', bob);
const aliceNote = await post(alice, { text: 'hi' });
const bobNote = await post(bob, { text: 'ok', channelId: channel.id });
await waitForPushToTl();
const res = await api('channels/timeline', { channelId: channel.id }, alice);
assert.strictEqual(res.body.some((note: any) => note.id === aliceNote.id), false);
assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), false);
});
test('閲覧中チャンネルをミュートしていてもノートが含まれる', async () => {
const [alice, bob] = await Promise.all([signup(), signup()]);
const channel = await createChannel('channel', bob);
await muteChannel(channel.id, alice);
const aliceNote = await post(alice, { text: 'hi' });
const bobNote = await post(bob, { text: 'ok', channelId: channel.id });
await waitForPushToTl();
const res = await api('channels/timeline', { channelId: channel.id }, alice);
assert.strictEqual(res.body.some((note: any) => note.id === aliceNote.id), false);
assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), true);
});
test('閲覧中チャンネルをミュートしていてもリノートが含まれる', async () => {
const [alice, bob] = await Promise.all([signup(), signup()]);
const channel = await createChannel('channel', bob);
await muteChannel(channel.id, alice);
const aliceNote = await post(alice, { text: 'hi' });
const bobNote = await post(bob, { text: 'ok', channelId: channel.id });
const bobRenote = await post(bob, { channelId: channel.id, renoteId: bobNote.id });
await waitForPushToTl();
const res = await api('channels/timeline', { channelId: channel.id }, alice);
assert.strictEqual(res.body.some((note: any) => note.id === bobRenote.id), true);
});
test('閲覧中チャンネルとは別チャンネルをミュートしているとき、そのチャンネルからのリノートは含まれない', async() => {
const [alice, bob] = await Promise.all([signup(), signup()]);
const channel = await createChannel('channel', bob);
const channel2 = await createChannel('channel', bob);
await muteChannel(channel2.id, alice);
const aliceNote = await post(alice, { text: 'hi' });
const bobNote = await post(bob, { text: 'ok', channelId: channel2.id });
const bobRenote = await post(bob, { channelId: channel.id, renoteId: bobNote.id });
await waitForPushToTl();
const res = await api('channels/timeline', { channelId: channel.id }, alice);
assert.strictEqual(res.body.some((note: any) => note.id === bobRenote.id), false);
});
assert.strictEqual(res.body.some((note: any) => note.id === aliceNote.id), false);
assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), true);
});
test('閲覧中チャンネルとは別チャンネルのノートは含まれない', async() => {
const [alice, bob] = await Promise.all([signup(), signup()]);
const channel = await createChannel('channel', bob);
const channel2 = await createChannel('channel', bob);
const aliceNote = await post(alice, { text: 'hi' });
const bobNote = await post(bob, { text: 'ok', channelId: channel2.id });
await waitForPushToTl();
const res = await api('channels/timeline', { channelId: channel.id }, alice);
assert.strictEqual(res.body.some((note: any) => note.id === aliceNote.id), false);
assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), false);
});
test('閲覧中チャンネルのノートにリノートが含まれる', async() => {
const [alice, bob] = await Promise.all([signup(), signup()]);
const channel = await createChannel('channel', bob);
const aliceNote = await post(alice, { text: 'hi' });
const bobNote = await post(bob, { text: 'ok', channelId: channel.id });
const bobRenote = await post(bob, { channelId: channel.id, renoteId: bobNote.id });
await waitForPushToTl();
const res = await api('channels/timeline', { channelId: channel.id }, alice);
assert.strictEqual(res.body.some((note: any) => note.id === bobRenote.id), true);
});
test('閲覧中チャンネルとは別チャンネルからのリノートが含まれる', async() => {
const [alice, bob] = await Promise.all([signup(), signup()]);
const channel = await createChannel('channel', bob);
const channel2 = await createChannel('channel', bob);
const aliceNote = await post(alice, { text: 'hi' });
const bobNote = await post(bob, { text: 'ok', channelId: channel2.id });
const bobRenote = await post(bob, { channelId: channel.id, renoteId: bobNote.id });
await waitForPushToTl();
const res = await api('channels/timeline', { channelId: channel.id }, alice);
assert.strictEqual(res.body.some((note: any) => note.id === bobRenote.id), true);
});
test('閲覧中チャンネルに自分の他人への返信が含まれる', async () => {
const [alice, bob] = await Promise.all([signup(), signup()]);
const channel = await createChannel('channel', bob);
const bobNote = await post(bob, { text: 'ok', channelId: channel.id });
const aliceNote = await post(alice, { text: 'hi', replyId: bobNote.id, channelId: channel.id });
await waitForPushToTl();
const res = await api('channels/timeline', { channelId: channel.id }, alice);
assert.strictEqual(res.body.some((note: any) => note.id === aliceNote.id), true);
});
test('閲覧中チャンネルに他人の自分への返信が含まれる', async () => {
const [alice, bob] = await Promise.all([signup(), signup()]);
const channel = await createChannel('channel', bob);
const aliceNote = await post(alice, { text: 'hi', channelId: channel.id });
const bobNote = await post(bob, { text: 'ok', replyId: aliceNote.id, channelId: channel.id });
await waitForPushToTl();
const res = await api('channels/timeline', { channelId: channel.id }, alice);
assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), true);
});
test('閲覧中チャンネルにミュートしているユーザのノートは含まれない', async () => {
const [alice, bob] = await Promise.all([signup(), signup()]);
await api('mute/create', { userId: bob.id }, alice);
const channel = await createChannel('channel', bob);
const aliceNote = await post(alice, { text: 'hi' });
const bobNote = await post(bob, { text: 'ok', channelId: channel.id });
await waitForPushToTl();
const res = await api('channels/timeline', { channelId: channel.id }, alice);
assert.strictEqual(res.body.some((note: any) => note.id === aliceNote.id), false);
assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), false);
});
test('閲覧中チャンネルにこちらをブロックしているユーザのノートは含まれない', async () => {
const [alice, bob] = await Promise.all([signup(), signup()]);
await api('blocking/create', { userId: alice.id }, bob);
const channel = await createChannel('channel', bob);
const aliceNote = await post(alice, { text: 'hi' });
const bobNote = await post(bob, { text: 'ok', channelId: channel.id });
await waitForPushToTl();
const res = await api('channels/timeline', { channelId: channel.id }, alice);
assert.strictEqual(res.body.some((note: any) => note.id === aliceNote.id), false);
assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), false);
});
test('閲覧中チャンネルをミュートしていてもノートが含まれる', async () => {
const [alice, bob] = await Promise.all([signup(), signup()]);
const channel = await createChannel('channel', bob);
await muteChannel(channel.id, alice);
const aliceNote = await post(alice, { text: 'hi' });
const bobNote = await post(bob, { text: 'ok', channelId: channel.id });
await waitForPushToTl();
const res = await api('channels/timeline', { channelId: channel.id }, alice);
assert.strictEqual(res.body.some((note: any) => note.id === aliceNote.id), false);
assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), true);
});
test('閲覧中チャンネルをミュートしていてもリノートが含まれる', async () => {
const [alice, bob] = await Promise.all([signup(), signup()]);
const channel = await createChannel('channel', bob);
await muteChannel(channel.id, alice);
const aliceNote = await post(alice, { text: 'hi' });
const bobNote = await post(bob, { text: 'ok', channelId: channel.id });
const bobRenote = await post(bob, { channelId: channel.id, renoteId: bobNote.id });
await waitForPushToTl();
const res = await api('channels/timeline', { channelId: channel.id }, alice);
assert.strictEqual(res.body.some((note: any) => note.id === bobRenote.id), true);
});
test('閲覧中チャンネルとは別チャンネルをミュートしているとき、そのチャンネルからのリノートは含まれない', async() => {
const [alice, bob] = await Promise.all([signup(), signup()]);
const channel = await createChannel('channel', bob);
const channel2 = await createChannel('channel', bob);
await muteChannel(channel2.id, alice);
const aliceNote = await post(alice, { text: 'hi' });
const bobNote = await post(bob, { text: 'ok', channelId: channel2.id });
const bobRenote = await post(bob, { channelId: channel.id, renoteId: bobNote.id });
await waitForPushToTl();
const res = await api('channels/timeline', { channelId: channel.id }, alice);
assert.strictEqual(res.body.some((note: any) => note.id === bobRenote.id), false);
});
});
// TODO: リノートミュート済みユーザーのテスト
// TODO: ページネーションのテスト
});
});
});

View File

@ -137,10 +137,10 @@ describe('ChannelFollowingService', () => {
});
afterEach(async () => {
await channelFollowingsRepository.delete({});
await channelsRepository.delete({});
await userProfilesRepository.delete({});
await usersRepository.delete({});
await channelFollowingsRepository.deleteAll();
await channelsRepository.deleteAll();
await userProfilesRepository.deleteAll();
await usersRepository.deleteAll();
});
describe('list', () => {

View File

@ -137,10 +137,10 @@ describe('ChannelMutingService', () => {
});
afterEach(async () => {
await channelMutingRepository.delete({});
await channelsRepository.delete({});
await userProfilesRepository.delete({});
await usersRepository.delete({});
await channelMutingRepository.deleteAll();
await channelsRepository.deleteAll();
await userProfilesRepository.deleteAll();
await usersRepository.deleteAll();
});
describe('list', () => {

View File

@ -15,7 +15,8 @@
"openapi-typescript": "7.9.1",
"ts-case-convert": "2.1.0",
"tsx": "4.20.6",
"typescript": "5.9.3"
"typescript": "5.9.3",
"eslint": "9.27.0"
},
"files": [
"built"

View File

@ -1140,6 +1140,33 @@ export type paths = {
*/
post: operations['channels___followed'];
};
'/channels/mute/create': {
/**
* channels/mute/create
* @description No description provided.
*
* **Credential required**: *Yes* / **Permission**: *write:channels*
*/
post: operations['channels___mute___create'];
};
'/channels/mute/delete': {
/**
* channels/mute/delete
* @description No description provided.
*
* **Credential required**: *Yes* / **Permission**: *write:channels*
*/
post: operations['channels___mute___delete'];
};
'/channels/mute/list': {
/**
* channels/mute/list
* @description No description provided.
*
* **Credential required**: *Yes* / **Permission**: *read:channels*
*/
post: operations['channels___mute___list'];
};
'/channels/my-favorites': {
/**
* channels/my-favorites
@ -4929,6 +4956,7 @@ export type components = {
allowRenoteToExternal: boolean;
isFollowing?: boolean;
isFavorited?: boolean;
isMuting?: boolean;
pinnedNotes?: components['schemas']['Note'][];
};
QueueCount: {
@ -14790,6 +14818,192 @@ export interface operations {
};
};
};
channels___mute___create: {
requestBody: {
content: {
'application/json': {
/** Format: misskey:id */
channelId: string;
/** @description A Unix Epoch timestamp that must lie in the future. `null` means an indefinite mute. */
expiresAt?: number | null;
};
};
};
responses: {
/** @description OK (without any results) */
204: {
headers: {
[name: string]: unknown;
};
};
/** @description Client error */
400: {
headers: {
[name: string]: unknown;
};
content: {
'application/json': components['schemas']['Error'];
};
};
/** @description Authentication error */
401: {
headers: {
[name: string]: unknown;
};
content: {
'application/json': components['schemas']['Error'];
};
};
/** @description Forbidden error */
403: {
headers: {
[name: string]: unknown;
};
content: {
'application/json': components['schemas']['Error'];
};
};
/** @description I'm Ai */
418: {
headers: {
[name: string]: unknown;
};
content: {
'application/json': components['schemas']['Error'];
};
};
/** @description Internal server error */
500: {
headers: {
[name: string]: unknown;
};
content: {
'application/json': components['schemas']['Error'];
};
};
};
};
channels___mute___delete: {
requestBody: {
content: {
'application/json': {
/** Format: misskey:id */
channelId: string;
};
};
};
responses: {
/** @description OK (without any results) */
204: {
headers: {
[name: string]: unknown;
};
};
/** @description Client error */
400: {
headers: {
[name: string]: unknown;
};
content: {
'application/json': components['schemas']['Error'];
};
};
/** @description Authentication error */
401: {
headers: {
[name: string]: unknown;
};
content: {
'application/json': components['schemas']['Error'];
};
};
/** @description Forbidden error */
403: {
headers: {
[name: string]: unknown;
};
content: {
'application/json': components['schemas']['Error'];
};
};
/** @description I'm Ai */
418: {
headers: {
[name: string]: unknown;
};
content: {
'application/json': components['schemas']['Error'];
};
};
/** @description Internal server error */
500: {
headers: {
[name: string]: unknown;
};
content: {
'application/json': components['schemas']['Error'];
};
};
};
};
channels___mute___list: {
responses: {
/** @description OK (with results) */
200: {
headers: {
[name: string]: unknown;
};
content: {
'application/json': components['schemas']['Channel'][];
};
};
/** @description Client error */
400: {
headers: {
[name: string]: unknown;
};
content: {
'application/json': components['schemas']['Error'];
};
};
/** @description Authentication error */
401: {
headers: {
[name: string]: unknown;
};
content: {
'application/json': components['schemas']['Error'];
};
};
/** @description Forbidden error */
403: {
headers: {
[name: string]: unknown;
};
content: {
'application/json': components['schemas']['Error'];
};
};
/** @description I'm Ai */
418: {
headers: {
[name: string]: unknown;
};
content: {
'application/json': components['schemas']['Error'];
};
};
/** @description Internal server error */
500: {
headers: {
[name: string]: unknown;
};
content: {
'application/json': components['schemas']['Error'];
};
};
};
};
'channels___my-favorites': {
responses: {
/** @description OK (with results) */

View File

@ -1475,10 +1475,13 @@ importers:
version: 22.18.10
'@typescript-eslint/eslint-plugin':
specifier: 8.46.1
version: 8.46.1(@typescript-eslint/parser@8.46.1(eslint@9.37.0)(typescript@5.9.3))(eslint@9.37.0)(typescript@5.9.3)
version: 8.46.1(@typescript-eslint/parser@8.46.1(eslint@9.27.0)(typescript@5.9.3))(eslint@9.27.0)(typescript@5.9.3)
'@typescript-eslint/parser':
specifier: 8.46.1
version: 8.46.1(eslint@9.37.0)(typescript@5.9.3)
version: 8.46.1(eslint@9.27.0)(typescript@5.9.3)
eslint:
specifier: 9.27.0
version: 9.27.0
openapi-types:
specifier: 12.1.3
version: 12.1.3
@ -2265,14 +2268,30 @@ packages:
resolution: {integrity: sha512-lpHyRyplhGPL5mGEh6M9O5nnKk0Gz4bFI+Zu6tKlPpDUN7XshWvH9C/px4UVm87IAANE0W81CEsNGbS1KlzXpA==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
'@eslint/config-array@0.20.1':
resolution: {integrity: sha512-OL0RJzC/CBzli0DrrR31qzj6d6i6Mm3HByuhflhl4LOBiWxN+3i6/t/ZQQNii4tjksXi8r2CRW1wMpWA2ULUEw==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
'@eslint/config-array@0.21.0':
resolution: {integrity: sha512-ENIdc4iLu0d93HeYirvKmrzshzofPw6VkZRKQGe9Nv46ZnWUzcF1xV01dcvEg/1wXUR61OmmlSfyeyO7EvjLxQ==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
'@eslint/config-helpers@0.2.3':
resolution: {integrity: sha512-u180qk2Um1le4yf0ruXH3PYFeEZeYC3p/4wCTKrr2U1CmGdzGi3KtY0nuPDH48UJxlKCC5RDzbcbh4X0XlqgHg==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
'@eslint/config-helpers@0.4.0':
resolution: {integrity: sha512-WUFvV4WoIwW8Bv0KeKCIIEgdSiFOsulyN0xrMu+7z43q/hkOLXjvb5u7UC9jDxvRzcrbEmuZBX5yJZz1741jog==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
'@eslint/core@0.14.0':
resolution: {integrity: sha512-qIbV0/JZr7iSDjqAc60IqbLdsj9GDt16xQtWD+B78d/HAlvysGdZZ6rpJHGAc2T0FQx1X6thsSPdnoiGKdNtdg==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
'@eslint/core@0.15.2':
resolution: {integrity: sha512-78Md3/Rrxh83gCxoUc0EiciuOHsIITzLy53m3d9UyiW8y9Dj2D29FeETqyKA+BRK76tnTp6RXWb3pCay8Oyomg==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
'@eslint/core@0.16.0':
resolution: {integrity: sha512-nmC8/totwobIiFcGkDza3GIKfAw1+hLiYVrh3I1nIomQ8PEr5cxg34jnkmGawul/ep52wGRAcyeDCNtWKSOj4Q==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
@ -2281,6 +2300,10 @@ packages:
resolution: {integrity: sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
'@eslint/js@9.27.0':
resolution: {integrity: sha512-G5JD9Tu5HJEu4z2Uo4aHY2sLV64B7CDMXxFzqzjl3NKd6RVzSXNoE80jk7Y0lJkTTkjiIhBAqmlYwjuBY3tvpA==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
'@eslint/js@9.37.0':
resolution: {integrity: sha512-jaS+NJ+hximswBG6pjNX0uEJZkrT0zwpVi3BA3vX22aFGjJjmgSTSmPpZCRKmoBL5VY/M6p0xsSJx7rk7sy5gg==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
@ -2289,6 +2312,10 @@ packages:
resolution: {integrity: sha512-RBMg5FRL0I0gs51M/guSAj5/e14VQ4tpZnQNWwuDT66P14I43ItmPfIZRhO9fUVIPOAQXU47atlywZ/czoqFPA==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
'@eslint/plugin-kit@0.3.5':
resolution: {integrity: sha512-Z5kJ+wU3oA7MMIqVR9tyZRtjYPr4OC004Q4Rw7pgOKUOKkJfZ3O24nz3WYfGRpMDNmcOi3TwQOmgm7B7Tpii0w==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
'@eslint/plugin-kit@0.4.0':
resolution: {integrity: sha512-sB5uyeq+dwCWyPi31B2gQlVlo+j5brPlWx4yZBrEaRo/nhdDE8Xke1gsGgtiBdaBTxuTkceLVuVt/pclrasb0A==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
@ -6457,6 +6484,16 @@ packages:
resolution: {integrity: sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
eslint@9.27.0:
resolution: {integrity: sha512-ixRawFQuMB9DZ7fjU3iGGganFDp3+45bPOdaRurcFHSXO1e/sYwUX/FtQZpLZJR6SjMoJH8hR2pPEAfDyCoU2Q==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
hasBin: true
peerDependencies:
jiti: '*'
peerDependenciesMeta:
jiti:
optional: true
eslint@9.37.0:
resolution: {integrity: sha512-XyLmROnACWqSxiGYArdef1fItQd47weqB7iwtfr9JHwRrqIXZdcFMvvEcL9xHCmL0SNsOvF0c42lWyM1U5dgig==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
@ -12414,6 +12451,11 @@ snapshots:
'@esbuild/win32-x64@0.25.10':
optional: true
'@eslint-community/eslint-utils@4.9.0(eslint@9.27.0)':
dependencies:
eslint: 9.27.0
eslint-visitor-keys: 3.4.3
'@eslint-community/eslint-utils@4.9.0(eslint@9.37.0)':
dependencies:
eslint: 9.37.0
@ -12423,6 +12465,14 @@ snapshots:
'@eslint/compat@1.1.1': {}
'@eslint/config-array@0.20.1':
dependencies:
'@eslint/object-schema': 2.1.6
debug: 4.4.3(supports-color@10.2.0)
minimatch: 3.1.2
transitivePeerDependencies:
- supports-color
'@eslint/config-array@0.21.0':
dependencies:
'@eslint/object-schema': 2.1.6
@ -12431,10 +12481,20 @@ snapshots:
transitivePeerDependencies:
- supports-color
'@eslint/config-helpers@0.2.3': {}
'@eslint/config-helpers@0.4.0':
dependencies:
'@eslint/core': 0.16.0
'@eslint/core@0.14.0':
dependencies:
'@types/json-schema': 7.0.15
'@eslint/core@0.15.2':
dependencies:
'@types/json-schema': 7.0.15
'@eslint/core@0.16.0':
dependencies:
'@types/json-schema': 7.0.15
@ -12453,10 +12513,17 @@ snapshots:
transitivePeerDependencies:
- supports-color
'@eslint/js@9.27.0': {}
'@eslint/js@9.37.0': {}
'@eslint/object-schema@2.1.6': {}
'@eslint/plugin-kit@0.3.5':
dependencies:
'@eslint/core': 0.15.2
levn: 0.4.1
'@eslint/plugin-kit@0.4.0':
dependencies:
'@eslint/core': 0.16.0
@ -15334,6 +15401,23 @@ snapshots:
'@types/node': 22.18.10
optional: true
'@typescript-eslint/eslint-plugin@8.46.1(@typescript-eslint/parser@8.46.1(eslint@9.27.0)(typescript@5.9.3))(eslint@9.27.0)(typescript@5.9.3)':
dependencies:
'@eslint-community/regexpp': 4.12.1
'@typescript-eslint/parser': 8.46.1(eslint@9.27.0)(typescript@5.9.3)
'@typescript-eslint/scope-manager': 8.46.1
'@typescript-eslint/type-utils': 8.46.1(eslint@9.27.0)(typescript@5.9.3)
'@typescript-eslint/utils': 8.46.1(eslint@9.27.0)(typescript@5.9.3)
'@typescript-eslint/visitor-keys': 8.46.1
eslint: 9.27.0
graphemer: 1.4.0
ignore: 7.0.5
natural-compare: 1.4.0
ts-api-utils: 2.1.0(typescript@5.9.3)
typescript: 5.9.3
transitivePeerDependencies:
- supports-color
'@typescript-eslint/eslint-plugin@8.46.1(@typescript-eslint/parser@8.46.1(eslint@9.37.0)(typescript@5.9.3))(eslint@9.37.0)(typescript@5.9.3)':
dependencies:
'@eslint-community/regexpp': 4.12.1
@ -15351,6 +15435,18 @@ snapshots:
transitivePeerDependencies:
- supports-color
'@typescript-eslint/parser@8.46.1(eslint@9.27.0)(typescript@5.9.3)':
dependencies:
'@typescript-eslint/scope-manager': 8.46.1
'@typescript-eslint/types': 8.46.1
'@typescript-eslint/typescript-estree': 8.46.1(typescript@5.9.3)
'@typescript-eslint/visitor-keys': 8.46.1
debug: 4.4.3(supports-color@10.2.0)
eslint: 9.27.0
typescript: 5.9.3
transitivePeerDependencies:
- supports-color
'@typescript-eslint/parser@8.46.1(eslint@9.37.0)(typescript@5.9.3)':
dependencies:
'@typescript-eslint/scope-manager': 8.46.1
@ -15381,6 +15477,18 @@ snapshots:
dependencies:
typescript: 5.9.3
'@typescript-eslint/type-utils@8.46.1(eslint@9.27.0)(typescript@5.9.3)':
dependencies:
'@typescript-eslint/types': 8.46.1
'@typescript-eslint/typescript-estree': 8.46.1(typescript@5.9.3)
'@typescript-eslint/utils': 8.46.1(eslint@9.27.0)(typescript@5.9.3)
debug: 4.4.3(supports-color@10.2.0)
eslint: 9.27.0
ts-api-utils: 2.1.0(typescript@5.9.3)
typescript: 5.9.3
transitivePeerDependencies:
- supports-color
'@typescript-eslint/type-utils@8.46.1(eslint@9.37.0)(typescript@5.9.3)':
dependencies:
'@typescript-eslint/types': 8.46.1
@ -15411,6 +15519,17 @@ snapshots:
transitivePeerDependencies:
- supports-color
'@typescript-eslint/utils@8.46.1(eslint@9.27.0)(typescript@5.9.3)':
dependencies:
'@eslint-community/eslint-utils': 4.9.0(eslint@9.27.0)
'@typescript-eslint/scope-manager': 8.46.1
'@typescript-eslint/types': 8.46.1
'@typescript-eslint/typescript-estree': 8.46.1(typescript@5.9.3)
eslint: 9.27.0
typescript: 5.9.3
transitivePeerDependencies:
- supports-color
'@typescript-eslint/utils@8.46.1(eslint@9.37.0)(typescript@5.9.3)':
dependencies:
'@eslint-community/eslint-utils': 4.9.0(eslint@9.37.0)
@ -17402,6 +17521,46 @@ snapshots:
eslint-visitor-keys@4.2.1: {}
eslint@9.27.0:
dependencies:
'@eslint-community/eslint-utils': 4.9.0(eslint@9.27.0)
'@eslint-community/regexpp': 4.12.1
'@eslint/config-array': 0.20.1
'@eslint/config-helpers': 0.2.3
'@eslint/core': 0.14.0
'@eslint/eslintrc': 3.3.1
'@eslint/js': 9.27.0
'@eslint/plugin-kit': 0.3.5
'@humanfs/node': 0.16.7
'@humanwhocodes/module-importer': 1.0.1
'@humanwhocodes/retry': 0.4.3
'@types/estree': 1.0.8
'@types/json-schema': 7.0.15
ajv: 6.12.6
chalk: 4.1.2
cross-spawn: 7.0.6
debug: 4.4.3(supports-color@10.2.0)
escape-string-regexp: 4.0.0
eslint-scope: 8.4.0
eslint-visitor-keys: 4.2.1
espree: 10.4.0
esquery: 1.6.0
esutils: 2.0.3
fast-deep-equal: 3.1.3
file-entry-cache: 8.0.0
find-up: 5.0.0
glob-parent: 6.0.2
ignore: 5.3.2
imurmurhash: 0.1.4
is-glob: 4.0.3
json-stable-stringify-without-jsonify: 1.0.1
lodash.merge: 4.6.2
minimatch: 3.1.2
natural-compare: 1.4.0
optionator: 0.9.4
transitivePeerDependencies:
- supports-color
eslint@9.37.0:
dependencies:
'@eslint-community/eslint-utils': 4.9.0(eslint@9.37.0)