Merge branch 'develop' into deps-update-node

This commit is contained in:
syuilo 2026-01-22 19:10:15 +09:00 committed by GitHub
commit 09b719e371
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
32 changed files with 307 additions and 155 deletions

View File

@ -54,55 +54,110 @@ jobs:
BASE_MEMORY=$(cat ./artifacts/memory-base.json) BASE_MEMORY=$(cat ./artifacts/memory-base.json)
HEAD_MEMORY=$(cat ./artifacts/memory-head.json) HEAD_MEMORY=$(cat ./artifacts/memory-head.json)
BASE_RSS=$(echo "$BASE_MEMORY" | jq -r '.memory.rss // 0') variation() {
HEAD_RSS=$(echo "$HEAD_MEMORY" | jq -r '.memory.rss // 0') calc() {
BASE=$(echo "$BASE_MEMORY" | jq -r ".${1}.${2} // 0")
HEAD=$(echo "$HEAD_MEMORY" | jq -r ".${1}.${2} // 0")
# Calculate difference DIFF=$((HEAD - BASE))
if [ "$BASE_RSS" -gt 0 ] && [ "$HEAD_RSS" -gt 0 ]; then if [ "$BASE" -gt 0 ]; then
DIFF=$((HEAD_RSS - BASE_RSS)) DIFF_PERCENT=$(echo "scale=2; ($DIFF * 100) / $BASE" | bc)
DIFF_PERCENT=$(echo "scale=2; ($DIFF * 100) / $BASE_RSS" | bc) else
DIFF_PERCENT=0
fi
# Convert to MB for readability # Convert KB to MB for readability
BASE_MB=$(echo "scale=2; $BASE_RSS / 1048576" | bc) BASE_MB=$(echo "scale=2; $BASE / 1024" | bc)
HEAD_MB=$(echo "scale=2; $HEAD_RSS / 1048576" | bc) HEAD_MB=$(echo "scale=2; $HEAD / 1024" | bc)
DIFF_MB=$(echo "scale=2; $DIFF / 1048576" | bc) DIFF_MB=$(echo "scale=2; $DIFF / 1024" | bc)
echo "base_mb=$BASE_MB" >> "$GITHUB_OUTPUT" JSON=$(jq -c -n \
echo "head_mb=$HEAD_MB" >> "$GITHUB_OUTPUT" --argjson base "$BASE_MB" \
echo "diff_mb=$DIFF_MB" >> "$GITHUB_OUTPUT" --argjson head "$HEAD_MB" \
echo "diff_percent=$DIFF_PERCENT" >> "$GITHUB_OUTPUT" --argjson diff "$DIFF_MB" \
echo "has_data=true" >> "$GITHUB_OUTPUT" --argjson diff_percent "$DIFF_PERCENT" \
'{base: $base, head: $head, diff: $diff, diff_percent: $diff_percent}')
# Determine if this is a significant change (more than 5% increase) echo "$JSON"
if [ "$(echo "$DIFF_PERCENT > 5" | bc)" -eq 1 ]; then }
echo "significant_increase=true" >> "$GITHUB_OUTPUT"
else JSON=$(jq -c -n \
echo "significant_increase=false" >> "$GITHUB_OUTPUT" --argjson VmRSS "$(calc $1 VmRSS)" \
fi --argjson VmHWM "$(calc $1 VmHWM)" \
else --argjson VmSize "$(calc $1 VmSize)" \
echo "has_data=false" >> "$GITHUB_OUTPUT" --argjson VmData "$(calc $1 VmData)" \
fi '{VmRSS: $VmRSS, VmHWM: $VmHWM, VmSize: $VmSize, VmData: $VmData}')
echo "$JSON"
}
JSON=$(jq -c -n \
--argjson beforeGc "$(variation beforeGc)" \
--argjson afterGc "$(variation afterGc)" \
--argjson afterRequest "$(variation afterRequest)" \
'{beforeGc: $beforeGc, afterGc: $afterGc, afterRequest: $afterRequest}')
echo "res=$JSON" >> "$GITHUB_OUTPUT"
- id: build-comment - id: build-comment
name: Build memory comment name: Build memory comment
env:
RES: ${{ steps.compare.outputs.res }}
run: | run: |
HEADER="## Backend Memory Usage Comparison" HEADER="## Backend memory usage comparison"
FOOTER="[See workflow logs for details](https://github.com/${GITHUB_REPOSITORY}/actions/runs/${GITHUB_RUN_ID})" FOOTER="[See workflow logs for details](https://github.com/${GITHUB_REPOSITORY}/actions/runs/${GITHUB_RUN_ID})"
echo "$HEADER" > ./output.md echo "$HEADER" > ./output.md
echo >> ./output.md echo >> ./output.md
if [ "${{ steps.compare.outputs.has_data }}" == "true" ]; then table() {
echo "| Metric | base | head | Diff |" >> ./output.md echo "| Metric | base (MB) | head (MB) | Diff (MB) | Diff (%) |" >> ./output.md
echo "|--------|------|------|------|" >> ./output.md echo "|--------|------:|------:|------:|------:|" >> ./output.md
echo "| RSS | ${{ steps.compare.outputs.base_mb }} MB | ${{ steps.compare.outputs.head_mb }} MB | ${{ steps.compare.outputs.diff_mb }} MB (${{ steps.compare.outputs.diff_percent }}%) |" >> ./output.md
echo >> ./output.md
if [ "${{ steps.compare.outputs.significant_increase }}" == "true" ]; then line() {
echo "⚠️ **Warning**: Memory usage has increased by more than 5%. Please verify this is not an unintended change." >> ./output.md METRIC=$2
echo >> ./output.md BASE=$(echo "$RES" | jq -r ".${1}.${2}.base")
fi HEAD=$(echo "$RES" | jq -r ".${1}.${2}.head")
else DIFF=$(echo "$RES" | jq -r ".${1}.${2}.diff")
echo "Could not retrieve memory usage data." >> ./output.md DIFF_PERCENT=$(echo "$RES" | jq -r ".${1}.${2}.diff_percent")
if (( $(echo "$DIFF_PERCENT > 0" | bc -l) )); then
DIFF="+$DIFF"
DIFF_PERCENT="+$DIFF_PERCENT"
fi
# highlight VmRSS
if [ "$2" = "VmRSS" ]; then
METRIC="**${METRIC}**"
BASE="**${BASE}**"
HEAD="**${HEAD}**"
DIFF="**${DIFF}**"
DIFF_PERCENT="**${DIFF_PERCENT}**"
fi
echo "| ${METRIC} | ${BASE} MB | ${HEAD} MB | ${DIFF} MB | ${DIFF_PERCENT}% |" >> ./output.md
}
line $1 VmRSS
line $1 VmHWM
line $1 VmSize
line $1 VmData
}
echo "### Before GC" >> ./output.md
table beforeGc
echo >> ./output.md
echo "### After GC" >> ./output.md
table afterGc
echo >> ./output.md
echo "### After Request" >> ./output.md
table afterRequest
echo >> ./output.md
# Determine if this is a significant change (more than 5% increase)
if [ "$(echo "$RES" | jq -r '.afterGc.VmRSS.diff_percent | tonumber > 5')" = "true" ]; then
echo "⚠️ **Warning**: Memory usage has increased by more than 5%. Please verify this is not an unintended change." >> ./output.md
echo >> ./output.md echo >> ./output.md
fi fi

View File

@ -96,7 +96,6 @@
"@swc/cli": "0.7.9", "@swc/cli": "0.7.9",
"@swc/core": "1.15.7", "@swc/core": "1.15.7",
"@twemoji/parser": "16.0.0", "@twemoji/parser": "16.0.0",
"@types/redis-info": "3.0.3",
"accepts": "1.3.8", "accepts": "1.3.8",
"ajv": "8.17.1", "ajv": "8.17.1",
"archiver": "7.0.1", "archiver": "7.0.1",
@ -154,7 +153,6 @@
"random-seed": "0.3.0", "random-seed": "0.3.0",
"ratelimiter": "3.4.1", "ratelimiter": "3.4.1",
"re2": "1.23.0", "re2": "1.23.0",
"redis-info": "3.1.0",
"reflect-metadata": "0.2.2", "reflect-metadata": "0.2.2",
"rename": "1.0.4", "rename": "1.0.4",
"rss-parser": "3.13.0", "rss-parser": "3.13.0",

View File

@ -14,16 +14,46 @@ import { fork } from 'node:child_process';
import { setTimeout } from 'node:timers/promises'; import { setTimeout } from 'node:timers/promises';
import { fileURLToPath } from 'node:url'; import { fileURLToPath } from 'node:url';
import { dirname, join } from 'node:path'; import { dirname, join } from 'node:path';
import * as http from 'node:http';
import * as fs from 'node:fs/promises';
const __filename = fileURLToPath(import.meta.url); const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename); const __dirname = dirname(__filename);
const SAMPLE_COUNT = 3; // Number of samples to measure
const STARTUP_TIMEOUT = 120000; // 120 seconds timeout for server startup const STARTUP_TIMEOUT = 120000; // 120 seconds timeout for server startup
const MEMORY_SETTLE_TIME = 10000; // Wait 10 seconds after startup for memory to settle const MEMORY_SETTLE_TIME = 10000; // Wait 10 seconds after startup for memory to settle
async function measureMemory() { const keys = {
const startTime = Date.now(); VmPeak: 0,
VmSize: 0,
VmHWM: 0,
VmRSS: 0,
VmData: 0,
VmStk: 0,
VmExe: 0,
VmLib: 0,
VmPTE: 0,
VmSwap: 0,
};
async function getMemoryUsage(pid) {
const status = await fs.readFile(`/proc/${pid}/status`, 'utf-8');
const result = {};
for (const key of Object.keys(keys)) {
const match = status.match(new RegExp(`${key}:\\s+(\\d+)\\s+kB`));
if (match) {
result[key] = parseInt(match[1], 10);
} else {
throw new Error(`Failed to parse ${key} from /proc/${pid}/status`);
}
}
return result;
}
async function measureMemory() {
// Start the Misskey backend server using fork to enable IPC // 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/boot/entry.js'), ['expose-gc'], {
cwd: join(__dirname, '..'), cwd: join(__dirname, '..'),
@ -31,9 +61,9 @@ async function measureMemory() {
...process.env, ...process.env,
NODE_ENV: 'production', NODE_ENV: 'production',
MK_DISABLE_CLUSTERING: '1', MK_DISABLE_CLUSTERING: '1',
MK_FORCE_GC: '1',
}, },
stdio: ['pipe', 'pipe', 'pipe', 'ipc'], stdio: ['pipe', 'pipe', 'pipe', 'ipc'],
execArgv: [...process.execArgv, '--expose-gc'],
}); });
let serverReady = false; let serverReady = false;
@ -59,6 +89,40 @@ async function measureMemory() {
process.stderr.write(`[server error] ${err}\n`); process.stderr.write(`[server error] ${err}\n`);
}); });
async function triggerGc() {
const ok = new Promise((resolve) => {
serverProcess.once('message', (message) => {
if (message === 'gc ok') resolve();
});
});
serverProcess.send('gc');
await ok;
await setTimeout(1000);
}
function createRequest() {
return new Promise((resolve, reject) => {
const req = http.request({
host: 'localhost',
port: 61812,
path: '/api/meta',
method: 'POST',
}, (res) => {
res.on('data', () => { });
res.on('end', () => {
resolve();
});
});
req.on('error', (err) => {
reject(err);
});
req.end();
});
}
// Wait for server to be ready or timeout // Wait for server to be ready or timeout
const startupStartTime = Date.now(); const startupStartTime = Date.now();
while (!serverReady) { while (!serverReady) {
@ -75,46 +139,23 @@ async function measureMemory() {
// Wait for memory to settle // Wait for memory to settle
await setTimeout(MEMORY_SETTLE_TIME); await setTimeout(MEMORY_SETTLE_TIME);
// Get memory usage from the server process via /proc
const pid = serverProcess.pid; const pid = serverProcess.pid;
let memoryInfo;
try { const beforeGc = await getMemoryUsage(pid);
const fs = await import('node:fs/promises');
// Read /proc/[pid]/status for detailed memory info await triggerGc();
const status = await fs.readFile(`/proc/${pid}/status`, 'utf-8');
const vmRssMatch = status.match(/VmRSS:\s+(\d+)\s+kB/);
const vmDataMatch = status.match(/VmData:\s+(\d+)\s+kB/);
const vmSizeMatch = status.match(/VmSize:\s+(\d+)\s+kB/);
memoryInfo = { const afterGc = await getMemoryUsage(pid);
rss: vmRssMatch ? parseInt(vmRssMatch[1], 10) * 1024 : null,
heapUsed: vmDataMatch ? parseInt(vmDataMatch[1], 10) * 1024 : null,
vmSize: vmSizeMatch ? parseInt(vmSizeMatch[1], 10) * 1024 : null,
};
} catch (err) {
// Fallback: use ps command
process.stderr.write(`Warning: Could not read /proc/${pid}/status: ${err}\n`);
const { execSync } = await import('node:child_process'); // create some http requests to simulate load
try { const REQUEST_COUNT = 10;
const ps = execSync(`ps -o rss= -p ${pid}`, { encoding: 'utf-8' }); await Promise.all(
const rssKb = parseInt(ps.trim(), 10); Array.from({ length: REQUEST_COUNT }).map(() => createRequest()),
memoryInfo = { );
rss: rssKb * 1024,
heapUsed: null, await triggerGc();
vmSize: null,
}; const afterRequest = await getMemoryUsage(pid);
} catch {
memoryInfo = {
rss: null,
heapUsed: null,
vmSize: null,
error: 'Could not measure memory',
};
}
}
// Stop the server // Stop the server
serverProcess.kill('SIGTERM'); serverProcess.kill('SIGTERM');
@ -137,15 +178,51 @@ async function measureMemory() {
const result = { const result = {
timestamp: new Date().toISOString(), timestamp: new Date().toISOString(),
startupTimeMs: startupTime, beforeGc,
memory: memoryInfo, afterGc,
afterRequest,
};
return result;
}
async function main() {
// 直列の方が時間的に分散されて正確そうだから直列でやる
const results = [];
for (let i = 0; i < SAMPLE_COUNT; i++) {
const res = await measureMemory();
results.push(res);
}
// Calculate averages
const beforeGc = structuredClone(keys);
const afterGc = structuredClone(keys);
const afterRequest = structuredClone(keys);
for (const res of results) {
for (const key of Object.keys(keys)) {
beforeGc[key] += res.beforeGc[key];
afterGc[key] += res.afterGc[key];
afterRequest[key] += res.afterRequest[key];
}
}
for (const key of Object.keys(keys)) {
beforeGc[key] = Math.round(beforeGc[key] / SAMPLE_COUNT);
afterGc[key] = Math.round(afterGc[key] / SAMPLE_COUNT);
afterRequest[key] = Math.round(afterRequest[key] / SAMPLE_COUNT);
}
const result = {
timestamp: new Date().toISOString(),
beforeGc,
afterGc,
afterRequest,
}; };
// Output as JSON to stdout // Output as JSON to stdout
console.log(JSON.stringify(result, null, 2)); console.log(JSON.stringify(result, null, 2));
} }
measureMemory().catch((err) => { main().catch((err) => {
console.error(JSON.stringify({ console.error(JSON.stringify({
error: err.message, error: err.message,
timestamp: new Date().toISOString(), timestamp: new Date().toISOString(),

View File

@ -86,9 +86,17 @@ if (!envOption.disableClustering) {
ev.mount(); ev.mount();
} }
if (envOption.forceGc && global.gc != null) { process.on('message', msg => {
global.gc(); if (msg === 'gc') {
} if (global.gc != null) {
logger.info('Manual GC triggered');
global.gc();
if (process.send != null) process.send('gc ok');
} else {
logger.warn('Manual GC requested but gc is not available. Start the process with --expose-gc to enable this feature.');
}
}
});
readyRef.value = true; readyRef.value = true;

View File

@ -6,7 +6,6 @@
import { randomUUID } from 'node:crypto'; import { randomUUID } from 'node:crypto';
import { Inject, Injectable } from '@nestjs/common'; import { Inject, Injectable } from '@nestjs/common';
import { MetricsTime, type JobType } from 'bullmq'; import { MetricsTime, type JobType } from 'bullmq';
import { parse as parseRedisInfo } from 'redis-info';
import type { IActivity } from '@/core/activitypub/type.js'; import type { IActivity } from '@/core/activitypub/type.js';
import type { MiDriveFile } from '@/models/DriveFile.js'; import type { MiDriveFile } from '@/models/DriveFile.js';
import type { MiWebhook, WebhookEventTypes } from '@/models/Webhook.js'; import type { MiWebhook, WebhookEventTypes } from '@/models/Webhook.js';
@ -86,6 +85,19 @@ const REPEATABLE_SYSTEM_JOB_DEF = [{
pattern: '0 4 * * *', pattern: '0 4 * * *',
}]; }];
function parseRedisInfo(infoText: string): Record<string, string> {
const fields = infoText
.split('\n')
.filter(line => line.length > 0 && !line.startsWith('#'))
.map(line => line.trim().split(':'));
const result: Record<string, string> = {};
for (const [key, value] of fields) {
result[key] = value;
}
return result;
}
@Injectable() @Injectable()
export class QueueService { export class QueueService {
constructor( constructor(
@ -890,7 +902,7 @@ export class QueueService {
}, },
db: { db: {
version: db.redis_version, version: db.redis_version,
mode: db.redis_mode, mode: db.redis_mode as 'cluster' | 'standalone' | 'sentinel',
runId: db.run_id, runId: db.run_id,
processId: db.process_id, processId: db.process_id,
port: parseInt(db.tcp_port), port: parseInt(db.tcp_port),

View File

@ -11,7 +11,6 @@ const envOption = {
verbose: false, verbose: false,
withLogTime: false, withLogTime: false,
quiet: false, quiet: false,
forceGc: false,
}; };
for (const key of Object.keys(envOption) as (keyof typeof envOption)[]) { for (const key of Object.keys(envOption) as (keyof typeof envOption)[]) {

View File

@ -144,7 +144,15 @@ export default [
'vue/return-in-computed-property': 'warn', 'vue/return-in-computed-property': 'warn',
'vue/no-setup-props-reactivity-loss': 'warn', 'vue/no-setup-props-reactivity-loss': 'warn',
'vue/max-attributes-per-line': 'off', 'vue/max-attributes-per-line': 'off',
'vue/html-self-closing': 'off', 'vue/html-self-closing': ['error', {
html: {
void: 'any',
normal: 'never',
component: 'any',
},
svg: 'any',
math: 'any',
}],
'vue/singleline-html-element-content-newline': 'off', 'vue/singleline-html-element-content-newline': 'off',
'vue/v-on-event-hyphenation': ['error', 'never', { 'vue/v-on-event-hyphenation': ['error', 'never', {
autofix: true, autofix: true,

View File

@ -9,16 +9,16 @@ SPDX-License-Identifier: AGPL-3.0-only
<div v-if="user.isCat" :class="[$style.ears]"> <div v-if="user.isCat" :class="[$style.ears]">
<div :class="$style.earLeft"> <div :class="$style.earLeft">
<div v-if="false" :class="$style.layer"> <div v-if="false" :class="$style.layer">
<div :class="$style.plot" :style="{ backgroundImage: `url(${JSON.stringify(url)})` }"/> <div :class="$style.plot" :style="{ backgroundImage: `url(${JSON.stringify(url)})` }"></div>
<div :class="$style.plot" :style="{ backgroundImage: `url(${JSON.stringify(url)})` }"/> <div :class="$style.plot" :style="{ backgroundImage: `url(${JSON.stringify(url)})` }"></div>
<div :class="$style.plot" :style="{ backgroundImage: `url(${JSON.stringify(url)})` }"/> <div :class="$style.plot" :style="{ backgroundImage: `url(${JSON.stringify(url)})` }"></div>
</div> </div>
</div> </div>
<div :class="$style.earRight"> <div :class="$style.earRight">
<div v-if="false" :class="$style.layer"> <div v-if="false" :class="$style.layer">
<div :class="$style.plot" :style="{ backgroundImage: `url(${JSON.stringify(url)})` }"/> <div :class="$style.plot" :style="{ backgroundImage: `url(${JSON.stringify(url)})` }"></div>
<div :class="$style.plot" :style="{ backgroundImage: `url(${JSON.stringify(url)})` }"/> <div :class="$style.plot" :style="{ backgroundImage: `url(${JSON.stringify(url)})` }"></div>
<div :class="$style.plot" :style="{ backgroundImage: `url(${JSON.stringify(url)})` }"/> <div :class="$style.plot" :style="{ backgroundImage: `url(${JSON.stringify(url)})` }"></div>
</div> </div>
</div> </div>
</div> </div>

View File

@ -5,7 +5,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<template> <template>
<div ref="root" :class="['chromatic-ignore', $style.root, { [$style.cover]: cover }]" :title="title ?? ''"> <div ref="root" :class="['chromatic-ignore', $style.root, { [$style.cover]: cover }]" :title="title ?? ''">
<canvas v-show="hide" key="canvas" ref="canvas" :class="$style.canvas" :width="canvasWidth" :height="canvasHeight" :title="title ?? undefined" tabindex="-1"/> <canvas v-show="hide" key="canvas" ref="canvas" :class="$style.canvas" :width="canvasWidth" :height="canvasHeight" :title="title ?? undefined" tabindex="-1"></canvas>
<img v-show="!hide" key="img" ref="img" :height="imgHeight ?? undefined" :width="imgWidth ?? undefined" :class="$style.img" :src="src ?? undefined" :title="title ?? undefined" :alt="alt ?? undefined" loading="eager" decoding="async" tabindex="-1"/> <img v-show="!hide" key="img" ref="img" :height="imgHeight ?? undefined" :width="imgWidth ?? undefined" :class="$style.img" :src="src ?? undefined" :title="title ?? undefined" :alt="alt ?? undefined" loading="eager" decoding="async" tabindex="-1"/>
</div> </div>
</template> </template>

View File

@ -147,7 +147,15 @@ export default [
'vue/return-in-computed-property': 'warn', 'vue/return-in-computed-property': 'warn',
'vue/no-setup-props-reactivity-loss': 'warn', 'vue/no-setup-props-reactivity-loss': 'warn',
'vue/max-attributes-per-line': 'off', 'vue/max-attributes-per-line': 'off',
'vue/html-self-closing': 'off', 'vue/html-self-closing': ['error', {
html: {
void: 'any',
normal: 'never',
component: 'any',
},
svg: 'any',
math: 'any',
}],
'vue/singleline-html-element-content-newline': 'off', 'vue/singleline-html-element-content-newline': 'off',
'vue/v-on-event-hyphenation': ['error', 'never', { 'vue/v-on-event-hyphenation': ['error', 'never', {
autofix: true, autofix: true,

View File

@ -25,8 +25,8 @@ SPDX-License-Identifier: AGPL-3.0-only
<MkInput v-if="input" v-model="inputValue" autofocus :type="input.type || 'text'" :placeholder="input.placeholder || undefined" :autocomplete="input.autocomplete" @keydown="onInputKeydown"> <MkInput v-if="input" v-model="inputValue" autofocus :type="input.type || 'text'" :placeholder="input.placeholder || undefined" :autocomplete="input.autocomplete" @keydown="onInputKeydown">
<template v-if="input.type === 'password'" #prefix><i class="ti ti-lock"></i></template> <template v-if="input.type === 'password'" #prefix><i class="ti ti-lock"></i></template>
<template #caption> <template #caption>
<span v-if="okButtonDisabledReason === 'charactersExceeded'" v-text="i18n.tsx._dialog.charactersExceeded({ current: (inputValue as string)?.length ?? 0, max: input.maxLength ?? 'NaN' })"/> <span v-if="okButtonDisabledReason === 'charactersExceeded'" v-text="i18n.tsx._dialog.charactersExceeded({ current: (inputValue as string)?.length ?? 0, max: input.maxLength ?? 'NaN' })"></span>
<span v-else-if="okButtonDisabledReason === 'charactersBelow'" v-text="i18n.tsx._dialog.charactersBelow({ current: (inputValue as string)?.length ?? 0, min: input.minLength ?? 'NaN' })"/> <span v-else-if="okButtonDisabledReason === 'charactersBelow'" v-text="i18n.tsx._dialog.charactersBelow({ current: (inputValue as string)?.length ?? 0, min: input.minLength ?? 'NaN' })"></span>
</template> </template>
</MkInput> </MkInput>
<MkSelect v-if="select" v-model="selectedValue" :items="selectDef" autofocus></MkSelect> <MkSelect v-if="select" v-model="selectedValue" :items="selectDef" autofocus></MkSelect>

View File

@ -12,7 +12,7 @@ SPDX-License-Identifier: AGPL-3.0-only
borderWidth ? { borderWidth: borderWidth } : {}, borderWidth ? { borderWidth: borderWidth } : {},
borderColor ? { borderColor: borderColor } : {}, borderColor ? { borderColor: borderColor } : {},
]" ]"
/> ></div>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">

View File

@ -25,7 +25,7 @@ SPDX-License-Identifier: AGPL-3.0-only
draggable="false" draggable="false"
tabindex="-1" tabindex="-1"
style="-webkit-user-drag: none;" style="-webkit-user-drag: none;"
/> ></canvas>
<img <img
v-show="!hide" v-show="!hide"
key="img" key="img"

View File

@ -33,7 +33,7 @@ SPDX-License-Identifier: AGPL-3.0-only
@input="onInput" @input="onInput"
> >
<datalist v-if="datalist" :id="id"> <datalist v-if="datalist" :id="id">
<option v-for="data in datalist" :key="data" :value="data"/> <option v-for="data in datalist" :key="data" :value="data"></option>
</datalist> </datalist>
<div ref="suffixEl" :class="$style.suffix"><slot name="suffix"></slot></div> <div ref="suffixEl" :class="$style.suffix"><slot name="suffix"></slot></div>
</div> </div>

View File

@ -143,8 +143,8 @@ SPDX-License-Identifier: AGPL-3.0-only
<template v-else-if="notification.type === 'receiveFollowRequest'"> <template v-else-if="notification.type === 'receiveFollowRequest'">
<span :class="$style.text" style="opacity: 0.6;">{{ i18n.ts.receiveFollowRequest }}</span> <span :class="$style.text" style="opacity: 0.6;">{{ i18n.ts.receiveFollowRequest }}</span>
<div v-if="full && !followRequestDone" :class="$style.followRequestCommands"> <div v-if="full && !followRequestDone" :class="$style.followRequestCommands">
<MkButton :class="$style.followRequestCommandButton" rounded primary @click="acceptFollowRequest()"><i class="ti ti-check"/> {{ i18n.ts.accept }}</MkButton> <MkButton :class="$style.followRequestCommandButton" rounded primary @click="acceptFollowRequest()"><i class="ti ti-check"></i> {{ i18n.ts.accept }}</MkButton>
<MkButton :class="$style.followRequestCommandButton" rounded danger @click="rejectFollowRequest()"><i class="ti ti-x"/> {{ i18n.ts.reject }}</MkButton> <MkButton :class="$style.followRequestCommandButton" rounded danger @click="rejectFollowRequest()"><i class="ti ti-x"></i> {{ i18n.ts.reject }}</MkButton>
</div> </div>
</template> </template>
<span v-else-if="notification.type === 'test'" :class="$style.text">{{ i18n.ts._notification.notificationWillBeDisplayedLikeThis }}</span> <span v-else-if="notification.type === 'test'" :class="$style.text">{{ i18n.ts._notification.notificationWillBeDisplayedLikeThis }}</span>

View File

@ -10,7 +10,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<div :class="$style.buttons"> <div :class="$style.buttons">
<div v-if="prevDotVisible" :class="$style.headTailButtons"> <div v-if="prevDotVisible" :class="$style.headTailButtons">
<MkButton @click="onToHeadButtonClicked">{{ min }}</MkButton> <MkButton @click="onToHeadButtonClicked">{{ min }}</MkButton>
<span class="ti ti-dots"/> <span class="ti ti-dots"></span>
</div> </div>
<MkButton <MkButton
@ -22,7 +22,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</MkButton> </MkButton>
<div v-if="nextDotVisible" :class="$style.headTailButtons"> <div v-if="nextDotVisible" :class="$style.headTailButtons">
<span class="ti ti-dots"/> <span class="ti ti-dots"></span>
<MkButton @click="onToTailButtonClicked">{{ max }}</MkButton> <MkButton @click="onToTailButtonClicked">{{ max }}</MkButton>
</div> </div>
</div> </div>

View File

@ -4,7 +4,7 @@ SPDX-License-Identifier: AGPL-3.0-only
--> -->
<template> <template>
<div :class="[$style.root, accented ? $style.accented : null, revered ? $style.revered : null]"/> <div :class="[$style.root, accented ? $style.accented : null, revered ? $style.revered : null]"></div>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>

View File

@ -77,7 +77,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</div> </div>
<div :class="[$style.textOuter, { [$style.withCw]: useCw }]"> <div :class="[$style.textOuter, { [$style.withCw]: useCw }]">
<div v-if="targetChannel" :class="$style.colorBar" :style="{ background: targetChannel.color }"></div> <div v-if="targetChannel" :class="$style.colorBar" :style="{ background: targetChannel.color }"></div>
<textarea ref="textareaEl" v-model="text" :class="[$style.text]" :disabled="posting || posted" :readonly="textAreaReadOnly" :placeholder="placeholder" data-cy-post-form-text @keydown="onKeydown" @keyup="onKeyup" @paste="onPaste" @compositionupdate="onCompositionUpdate" @compositionend="onCompositionEnd"/> <textarea ref="textareaEl" v-model="text" :class="[$style.text]" :disabled="posting || posted" :readonly="textAreaReadOnly" :placeholder="placeholder" data-cy-post-form-text @keydown="onKeydown" @keyup="onKeyup" @paste="onPaste" @compositionupdate="onCompositionUpdate" @compositionend="onCompositionEnd"></textarea>
<div v-if="maxTextLength - textLength < 100" :class="['_acrylic', $style.textCount, { [$style.textOver]: textLength > maxTextLength }]">{{ maxTextLength - textLength }}</div> <div v-if="maxTextLength - textLength < 100" :class="['_acrylic', $style.textCount, { [$style.textOver]: textLength > maxTextLength }]">{{ maxTextLength - textLength }}</div>
</div> </div>
<input v-show="withHashtags" ref="hashtagsInputEl" v-model="hashtags" :class="$style.hashtags" :placeholder="i18n.ts.hashtags" list="hashtags"> <input v-show="withHashtags" ref="hashtagsInputEl" v-model="hashtags" :class="$style.hashtags" :placeholder="i18n.ts.hashtags" list="hashtags">
@ -108,7 +108,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</div> </div>
</footer> </footer>
<datalist id="hashtags"> <datalist id="hashtags">
<option v-for="hashtag in recentHashtags" :key="hashtag" :value="hashtag"/> <option v-for="hashtag in recentHashtags" :key="hashtag" :value="hashtag"></option>
</datalist> </datalist>
</div> </div>
</template> </template>

View File

@ -18,7 +18,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</div> </div>
</div> </div>
<slot/> <slot></slot>
</div> </div>
</template> </template>

View File

@ -24,7 +24,7 @@ SPDX-License-Identifier: AGPL-3.0-only
:myReaction="props.myReaction" :myReaction="props.myReaction"
@reactionToggled="onMockToggleReaction" @reactionToggled="onMockToggleReaction"
/> />
<slot v-if="hasMoreReactions" name="more"/> <slot v-if="hasMoreReactions" name="more"></slot>
</component> </component>
</template> </template>

View File

@ -11,16 +11,16 @@ SPDX-License-Identifier: AGPL-3.0-only
<div v-if="user.isCat" :class="[$style.ears]"> <div v-if="user.isCat" :class="[$style.ears]">
<div :class="$style.earLeft"> <div :class="$style.earLeft">
<div v-if="false" :class="$style.layer"> <div v-if="false" :class="$style.layer">
<div :class="$style.plot" :style="{ backgroundImage: `url(${JSON.stringify(url)})` }"/> <div :class="$style.plot" :style="{ backgroundImage: `url(${JSON.stringify(url)})` }"></div>
<div :class="$style.plot" :style="{ backgroundImage: `url(${JSON.stringify(url)})` }"/> <div :class="$style.plot" :style="{ backgroundImage: `url(${JSON.stringify(url)})` }"></div>
<div :class="$style.plot" :style="{ backgroundImage: `url(${JSON.stringify(url)})` }"/> <div :class="$style.plot" :style="{ backgroundImage: `url(${JSON.stringify(url)})` }"></div>
</div> </div>
</div> </div>
<div :class="$style.earRight"> <div :class="$style.earRight">
<div v-if="false" :class="$style.layer"> <div v-if="false" :class="$style.layer">
<div :class="$style.plot" :style="{ backgroundImage: `url(${JSON.stringify(url)})` }"/> <div :class="$style.plot" :style="{ backgroundImage: `url(${JSON.stringify(url)})` }"></div>
<div :class="$style.plot" :style="{ backgroundImage: `url(${JSON.stringify(url)})` }"/> <div :class="$style.plot" :style="{ backgroundImage: `url(${JSON.stringify(url)})` }"></div>
<div :class="$style.plot" :style="{ backgroundImage: `url(${JSON.stringify(url)})` }"/> <div :class="$style.plot" :style="{ backgroundImage: `url(${JSON.stringify(url)})` }"></div>
</div> </div>
</div> </div>
</div> </div>

View File

@ -37,8 +37,8 @@ SPDX-License-Identifier: AGPL-3.0-only
<template #label>{{ i18n.ts._abuseReport._notificationRecipient.notifiedWebhook }}</template> <template #label>{{ i18n.ts._abuseReport._notificationRecipient.notifiedWebhook }}</template>
</MkSelect> </MkSelect>
<MkButton rounded :class="$style.systemWebhookEditButton" @click="onEditSystemWebhookClicked"> <MkButton rounded :class="$style.systemWebhookEditButton" @click="onEditSystemWebhookClicked">
<span v-if="systemWebhookId === null" class="ti ti-plus" style="line-height: normal"/> <span v-if="systemWebhookId === null" class="ti ti-plus" style="line-height: normal"></span>
<span v-else class="ti ti-settings" style="line-height: normal"/> <span v-else class="ti ti-settings" style="line-height: normal"></span>
</MkButton> </MkButton>
</div> </div>
</div> </div>

View File

@ -5,7 +5,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<template> <template>
<div :class="$style.root" class="_panel _gaps_s"> <div :class="$style.root" class="_panel _gaps_s">
<div :class="$style.rightDivider" style="width: 80px;"><span :class="`ti ${methodIcon}`"/> {{ methodName }}</div> <div :class="$style.rightDivider" style="width: 80px;"><span :class="`ti ${methodIcon}`"></span> {{ methodName }}</div>
<div :class="$style.rightDivider" style="flex: 0.5">{{ entity.name }}</div> <div :class="$style.rightDivider" style="flex: 0.5">{{ entity.name }}</div>
<div :class="$style.rightDivider" style="flex: 1"> <div :class="$style.rightDivider" style="flex: 1">
<div v-if="method === 'email' && user"> <div v-if="method === 'email' && user">
@ -19,10 +19,10 @@ SPDX-License-Identifier: AGPL-3.0-only
</div> </div>
<div :class="$style.recipientButtons" style="margin-left: auto"> <div :class="$style.recipientButtons" style="margin-left: auto">
<button :class="$style.recipientButton" @click="onEditButtonClicked()"> <button :class="$style.recipientButton" @click="onEditButtonClicked()">
<span class="ti ti-settings"/> <span class="ti ti-settings"></span>
</button> </button>
<button :class="$style.recipientButton" @click="onDeleteButtonClicked()"> <button :class="$style.recipientButton" @click="onDeleteButtonClicked()">
<span class="ti ti-trash"/> <span class="ti ti-trash"></span>
</button> </button>
</div> </div>
</div> </div>

View File

@ -9,7 +9,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<div :class="$style.root" class="_gaps_m"> <div :class="$style.root" class="_gaps_m">
<div :class="$style.addButton"> <div :class="$style.addButton">
<MkButton primary @click="onAddButtonClicked"> <MkButton primary @click="onAddButtonClicked">
<span class="ti ti-plus"/> {{ i18n.ts._abuseReport._notificationRecipient.createRecipient }} <span class="ti ti-plus"></span> {{ i18n.ts._abuseReport._notificationRecipient.createRecipient }}
</MkButton> </MkButton>
</div> </div>
<div :class="$style.subMenus" class="_gaps_s"> <div :class="$style.subMenus" class="_gaps_s">

View File

@ -21,8 +21,8 @@ SPDX-License-Identifier: AGPL-3.0-only
<template #default="{ item, index, dragStart }"> <template #default="{ item, index, dragStart }">
<div :class="$style.item"> <div :class="$style.item">
<div :class="$style.itemHeader"> <div :class="$style.itemHeader">
<div :class="$style.itemNumber" v-text="String(index + 1)"/> <div :class="$style.itemNumber">{{ index + 1 }}</div>
<span :class="$style.itemHandle" :draggable="true" @dragstart.stop="dragStart"><i class="ti ti-menu"/></span> <span :class="$style.itemHandle" :draggable="true" @dragstart.stop="dragStart"><i class="ti ti-menu"></i></span>
<button class="_button" :class="$style.itemRemove" @click="remove(item.id)"><i class="ti ti-x"></i></button> <button class="_button" :class="$style.itemRemove" @click="remove(item.id)"><i class="ti ti-x"></i></button>
</div> </div>
<MkInput :modelValue="item.text" @update:modelValue="serverRules[index].text = $event"/> <MkInput :modelValue="item.text" @update:modelValue="serverRules[index].text = $event"/>

View File

@ -8,14 +8,14 @@ SPDX-License-Identifier: AGPL-3.0-only
<template #label>{{ entity.name || entity.url }}</template> <template #label>{{ entity.name || entity.url }}</template>
<template v-if="entity.name != null && entity.name != ''" #caption>{{ entity.url }}</template> <template v-if="entity.name != null && entity.name != ''" #caption>{{ entity.url }}</template>
<template #icon> <template #icon>
<i v-if="!entity.isActive" class="ti ti-player-pause"/> <i v-if="!entity.isActive" class="ti ti-player-pause"></i>
<i v-else-if="entity.latestStatus === null" class="ti ti-circle"/> <i v-else-if="entity.latestStatus === null" class="ti ti-circle"></i>
<i <i
v-else-if="[200, 201, 204].includes(entity.latestStatus)" v-else-if="[200, 201, 204].includes(entity.latestStatus)"
class="ti ti-check" class="ti ti-check"
:style="{ color: 'var(--MI_THEME-success)' }" :style="{ color: 'var(--MI_THEME-success)' }"
/> ></i>
<i v-else class="ti ti-alert-triangle" :style="{ color: 'var(--MI_THEME-error)' }"/> <i v-else class="ti ti-alert-triangle" :style="{ color: 'var(--MI_THEME-error)' }"></i>
</template> </template>
<template #suffix> <template #suffix>
<MkTime v-if="entity.latestSentAt" :time="entity.latestSentAt" style="margin-right: 8px"/> <MkTime v-if="entity.latestSentAt" :time="entity.latestSentAt" style="margin-right: 8px"/>

View File

@ -58,7 +58,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<div ref="containerEl" :class="[$style.gameContainer, { [$style.gameOver]: isGameOver && !replaying }]" @contextmenu.stop.prevent @click.stop.prevent="onClick" @touchmove.stop.prevent="onTouchmove" @touchend="onTouchend" @mousemove="onMousemove"> <div ref="containerEl" :class="[$style.gameContainer, { [$style.gameOver]: isGameOver && !replaying }]" @contextmenu.stop.prevent @click.stop.prevent="onClick" @touchmove.stop.prevent="onTouchmove" @touchend="onTouchend" @mousemove="onMousemove">
<img v-if="store.s.darkMode" src="/client-assets/drop-and-fusion/frame-dark.svg" :class="$style.mainFrameImg"/> <img v-if="store.s.darkMode" src="/client-assets/drop-and-fusion/frame-dark.svg" :class="$style.mainFrameImg"/>
<img v-else src="/client-assets/drop-and-fusion/frame-light.svg" :class="$style.mainFrameImg"/> <img v-else src="/client-assets/drop-and-fusion/frame-light.svg" :class="$style.mainFrameImg"/>
<canvas ref="canvasEl" :class="$style.canvas"/> <canvas ref="canvasEl" :class="$style.canvas"></canvas>
<Transition <Transition
:enterActiveClass="$style.transition_combo_enterActive" :enterActiveClass="$style.transition_combo_enterActive"
:leaveActiveClass="$style.transition_combo_leaveActive" :leaveActiveClass="$style.transition_combo_leaveActive"
@ -82,7 +82,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</Transition> </Transition>
<template v-if="dropReady && currentPick"> <template v-if="dropReady && currentPick">
<img src="/client-assets/drop-and-fusion/drop-arrow.svg" :class="$style.currentMonoArrow"/> <img src="/client-assets/drop-and-fusion/drop-arrow.svg" :class="$style.currentMonoArrow"/>
<div :class="$style.dropGuide"/> <div :class="$style.dropGuide"></div>
</template> </template>
</div> </div>
<div v-if="isGameOver && !replaying" :class="$style.gameOverLabel"> <div v-if="isGameOver && !replaying" :class="$style.gameOverLabel">

View File

@ -18,11 +18,11 @@ SPDX-License-Identifier: AGPL-3.0-only
<p class="acct">@{{ acct(displayUser(req)) }}</p> <p class="acct">@{{ acct(displayUser(req)) }}</p>
</div> </div>
<div v-if="tab === 'list'" class="commands"> <div v-if="tab === 'list'" class="commands">
<MkButton class="command" rounded primary @click="accept(displayUser(req))"><i class="ti ti-check"/> {{ i18n.ts.accept }}</MkButton> <MkButton class="command" rounded primary @click="accept(displayUser(req))"><i class="ti ti-check"></i> {{ i18n.ts.accept }}</MkButton>
<MkButton class="command" rounded danger @click="reject(displayUser(req))"><i class="ti ti-x"/> {{ i18n.ts.reject }}</MkButton> <MkButton class="command" rounded danger @click="reject(displayUser(req))"><i class="ti ti-x"></i> {{ i18n.ts.reject }}</MkButton>
</div> </div>
<div v-else class="commands"> <div v-else class="commands">
<MkButton class="command" rounded danger @click="cancel(displayUser(req))"><i class="ti ti-x"/> {{ i18n.ts.cancel }}</MkButton> <MkButton class="command" rounded danger @click="cancel(displayUser(req))"><i class="ti ti-x"></i> {{ i18n.ts.cancel }}</MkButton>
</div> </div>
</div> </div>
</div> </div>

View File

@ -24,7 +24,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<template #suffix><i v-if="$i.twoFactorEnabled" class="ti ti-check" style="color: var(--MI_THEME-success)"></i></template> <template #suffix><i v-if="$i.twoFactorEnabled" class="ti ti-check" style="color: var(--MI_THEME-success)"></i></template>
<div v-if="$i.twoFactorEnabled" class="_gaps_s"> <div v-if="$i.twoFactorEnabled" class="_gaps_s">
<div v-text="i18n.ts._2fa.alreadyRegistered"/> <div>{{ i18n.ts._2fa.alreadyRegistered }}</div>
<template v-if="$i.securityKeysList!.length > 0"> <template v-if="$i.securityKeysList!.length > 0">
<MkButton @click="renewTOTP">{{ i18n.ts._2fa.renewTOTP }}</MkButton> <MkButton @click="renewTOTP">{{ i18n.ts._2fa.renewTOTP }}</MkButton>
<MkInfo>{{ i18n.ts._2fa.whyTOTPOnlyRenew }}</MkInfo> <MkInfo>{{ i18n.ts._2fa.whyTOTPOnlyRenew }}</MkInfo>

View File

@ -28,7 +28,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<span v-if="user.isLocked"><i class="ti ti-lock"></i></span> <span v-if="user.isLocked"><i class="ti ti-lock"></i></span>
<span v-if="user.isBot"><i class="ti ti-robot"></i></span> <span v-if="user.isBot"><i class="ti ti-robot"></i></span>
<button v-if="$i && !isEditingMemo && !memoDraft" class="_button add-note-button" @click="showMemoTextarea"> <button v-if="$i && !isEditingMemo && !memoDraft" class="_button add-note-button" @click="showMemoTextarea">
<i class="ti ti-edit"/> {{ i18n.ts.addMemo }} <i class="ti ti-edit"></i> {{ i18n.ts.addMemo }}
</button> </button>
</div> </div>
</div> </div>
@ -71,7 +71,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</div> </div>
</div> </div>
<div v-if="isEditingMemo || memoDraft" class="memo" :class="{'no-memo': !memoDraft}"> <div v-if="isEditingMemo || memoDraft" class="memo" :class="{'no-memo': !memoDraft}">
<div class="heading" v-text="i18n.ts.memo"/> <div class="heading">{{ i18n.ts.memo }}</div>
<textarea <textarea
ref="memoTextareaEl" ref="memoTextareaEl"
v-model="memoDraft" v-model="memoDraft"
@ -79,7 +79,7 @@ SPDX-License-Identifier: AGPL-3.0-only
@focus="isEditingMemo = true" @focus="isEditingMemo = true"
@blur="updateMemo" @blur="updateMemo"
@input="adjustMemoTextarea" @input="adjustMemoTextarea"
/> ></textarea>
</div> </div>
<div class="description"> <div class="description">
<MkOmit> <MkOmit>

View File

@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<template> <template>
<XColumn :menu="menu" :column="column" :isStacked="isStacked" :refresher="async () => { await timeline?.reloadTimeline() }"> <XColumn :menu="menu" :column="column" :isStacked="isStacked" :refresher="async () => { await timeline?.reloadTimeline() }">
<template #header> <template #header>
<i v-if="column.tl != null" :class="basicTimelineIconClass(column.tl)"/> <i v-if="column.tl != null" :class="basicTimelineIconClass(column.tl)"></i>
<span style="margin-left: 8px;">{{ column.name || (column.tl ? i18n.ts._timelines[column.tl] : null) || i18n.ts._deck._columns.tl }}</span> <span style="margin-left: 8px;">{{ column.name || (column.tl ? i18n.ts._timelines[column.tl] : null) || i18n.ts._deck._columns.tl }}</span>
</template> </template>

View File

@ -165,9 +165,6 @@ importers:
'@twemoji/parser': '@twemoji/parser':
specifier: 16.0.0 specifier: 16.0.0
version: 16.0.0 version: 16.0.0
'@types/redis-info':
specifier: 3.0.3
version: 3.0.3
accepts: accepts:
specifier: 1.3.8 specifier: 1.3.8
version: 1.3.8 version: 1.3.8
@ -339,9 +336,6 @@ importers:
re2: re2:
specifier: 1.23.0 specifier: 1.23.0
version: 1.23.0 version: 1.23.0
redis-info:
specifier: 3.1.0
version: 3.1.0
reflect-metadata: reflect-metadata:
specifier: 0.2.2 specifier: 0.2.2
version: 0.2.2 version: 0.2.2
@ -4684,9 +4678,6 @@ packages:
'@types/readdir-glob@1.1.5': '@types/readdir-glob@1.1.5':
resolution: {integrity: sha512-raiuEPUYqXu+nvtY2Pe8s8FEmZ3x5yAH4VkLdihcPdalvsHltomrRC9BzuStrJ9yk06470hS0Crw0f1pXqD+Hg==} resolution: {integrity: sha512-raiuEPUYqXu+nvtY2Pe8s8FEmZ3x5yAH4VkLdihcPdalvsHltomrRC9BzuStrJ9yk06470hS0Crw0f1pXqD+Hg==}
'@types/redis-info@3.0.3':
resolution: {integrity: sha512-VIkNy6JbYI/RLdbPHdm9JQvv6RVld2uE2/6Hdid38Qdq+zvDli2FTpImI8pC5zwp8xS8qVqfzlfyAub8xZEd5g==}
'@types/rename@1.0.7': '@types/rename@1.0.7':
resolution: {integrity: sha512-E9qapfghUGfBMi3jNhsmCKPIp3f2zvNKpaX1BDGLGJNjzpgsZ/RTx7NaNksFjGoJ+r9NvWF1NSM5vVecnNjVmw==} resolution: {integrity: sha512-E9qapfghUGfBMi3jNhsmCKPIp3f2zvNKpaX1BDGLGJNjzpgsZ/RTx7NaNksFjGoJ+r9NvWF1NSM5vVecnNjVmw==}
@ -9522,9 +9513,6 @@ packages:
resolution: {integrity: sha512-1qny3OExCf0UvUV/5wpYKf2YwPcOqXzkwKKSmKHiE6ZMQs5heeE/c8eXK+PNllPvmjgAbfnsbpkGZWy8cBpn9w==} resolution: {integrity: sha512-1qny3OExCf0UvUV/5wpYKf2YwPcOqXzkwKKSmKHiE6ZMQs5heeE/c8eXK+PNllPvmjgAbfnsbpkGZWy8cBpn9w==}
engines: {node: '>=4'} engines: {node: '>=4'}
redis-info@3.1.0:
resolution: {integrity: sha512-ER4L9Sh/vm63DkIE0bkSjxluQlioBiBgf5w1UuldaW/3vPcecdljVDisZhmnCMvsxHNiARTTDDHGg9cGwTfrKg==}
redis-parser@3.0.0: redis-parser@3.0.0:
resolution: {integrity: sha512-DJnGAeenTdpMEH6uAJRK/uiyEIH9WVsUmoLwzudwGJUwZPp80PDBWPHXSAGNPwNvIXAbe7MSUB1zQFugFml66A==} resolution: {integrity: sha512-DJnGAeenTdpMEH6uAJRK/uiyEIH9WVsUmoLwzudwGJUwZPp80PDBWPHXSAGNPwNvIXAbe7MSUB1zQFugFml66A==}
engines: {node: '>=4'} engines: {node: '>=4'}
@ -10947,6 +10935,9 @@ packages:
vue-component-type-helpers@3.2.1: vue-component-type-helpers@3.2.1:
resolution: {integrity: sha512-gKV7XOkQl4urSuLHNY1tnVQf7wVgtb/mKbRyxSLWGZUY9RK7aDPhBenTjm+i8ZFe0zC2PZeHMPtOZXZfyaFOzQ==} resolution: {integrity: sha512-gKV7XOkQl4urSuLHNY1tnVQf7wVgtb/mKbRyxSLWGZUY9RK7aDPhBenTjm+i8ZFe0zC2PZeHMPtOZXZfyaFOzQ==}
vue-component-type-helpers@3.2.2:
resolution: {integrity: sha512-x8C2nx5XlUNM0WirgfTkHjJGO/ABBxlANZDtHw2HclHtQnn+RFPTnbjMJn8jHZW4TlUam0asHcA14lf1C6Jb+A==}
vue-demi@0.14.10: vue-demi@0.14.10:
resolution: {integrity: sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg==} resolution: {integrity: sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg==}
engines: {node: '>=12'} engines: {node: '>=12'}
@ -14941,7 +14932,7 @@ snapshots:
storybook: 10.1.10(@testing-library/dom@10.4.0)(bufferutil@4.1.0)(prettier@3.7.4)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(utf-8-validate@6.0.6) storybook: 10.1.10(@testing-library/dom@10.4.0)(bufferutil@4.1.0)(prettier@3.7.4)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(utf-8-validate@6.0.6)
type-fest: 2.19.0 type-fest: 2.19.0
vue: 3.5.26(typescript@5.9.3) vue: 3.5.26(typescript@5.9.3)
vue-component-type-helpers: 3.2.1 vue-component-type-helpers: 3.2.2
'@stylistic/eslint-plugin@5.5.0(eslint@9.39.2)': '@stylistic/eslint-plugin@5.5.0(eslint@9.39.2)':
dependencies: dependencies:
@ -15478,8 +15469,6 @@ snapshots:
dependencies: dependencies:
'@types/node': 24.10.4 '@types/node': 24.10.4
'@types/redis-info@3.0.3': {}
'@types/rename@1.0.7': {} '@types/rename@1.0.7': {}
'@types/resolve@1.20.6': {} '@types/resolve@1.20.6': {}
@ -21352,10 +21341,6 @@ snapshots:
redis-errors@1.2.0: {} redis-errors@1.2.0: {}
redis-info@3.1.0:
dependencies:
lodash: 4.17.21
redis-parser@3.0.0: redis-parser@3.0.0:
dependencies: dependencies:
redis-errors: 1.2.0 redis-errors: 1.2.0
@ -22855,6 +22840,8 @@ snapshots:
vue-component-type-helpers@3.2.1: {} vue-component-type-helpers@3.2.1: {}
vue-component-type-helpers@3.2.2: {}
vue-demi@0.14.10(vue@3.5.26(typescript@5.9.3)): vue-demi@0.14.10(vue@3.5.26(typescript@5.9.3)):
dependencies: dependencies:
vue: 3.5.26(typescript@5.9.3) vue: 3.5.26(typescript@5.9.3)