merge: 최신 main의 NPC 정책과 운영 보완을 오픈 요청에 통합한다

This commit is contained in:
2026-10-23 00:18:45 +09:00
24 changed files with 2112 additions and 680 deletions
+3 -3
View File
@@ -40,10 +40,10 @@
"@trpc/server": "^11.8.1",
"date-fns": "^4.1.0",
"es-toolkit": "^1.43.0",
"fastify": "^5.6.2",
"fastify": "^5.12.5",
"redis": "^5.10.0",
"sanitize-html": "2.17.6",
"sharp": "^0.35.0",
"sanitize-html": "2.17.7",
"sharp": "^0.35.5",
"zod": "^4.3.5"
}
}
+91
View File
@@ -0,0 +1,91 @@
import { TRPCError } from '@trpc/server';
import { z } from 'zod';
import type { GamePrisma } from '@sammo-ts/infra';
import type { GameApiContext } from '../../context.js';
import { authedProcedure } from '../../trpc.js';
import { getAuthenticatedUserId } from '../shared/general.js';
import { resolveSecretPermission } from '../shared/secretPermission.js';
import { projectPolicyConfiguration, projectPolicySummary } from '../playAudit/policies.js';
import { readAudit, readAuditWorld } from '../playAudit/shared.js';
const area = z.enum(['NPC_VALUES', 'NPC_NATION_PRIORITY', 'NPC_GENERAL_PRIORITY']);
const summarySelect = {
id: true,
schemaVersion: true,
nationId: true,
area: true,
revision: true,
previousId: true,
source: true,
year: true,
month: true,
actor: true,
createdAt: true,
} satisfies GamePrisma.PlayAuditPolicySelect;
// 소속·기밀 권한과 이력을 같은 snapshot에서 읽어 이적/권한 변경 경합을 막는다.
const readNationId = async (ctx: GameApiContext, tx: GamePrisma.TransactionClient): Promise<number> => {
const general = await tx.general.findFirst({ where: { userId: getAuthenticatedUserId(ctx) } });
if (!general) throw new TRPCError({ code: 'NOT_FOUND', message: '장수 정보를 찾을 수 없습니다.' });
if (general.nationId <= 0)
throw new TRPCError({ code: 'PRECONDITION_FAILED', message: '국가에 소속되어있지 않습니다.' });
const nation = await tx.nation.findUnique({ where: { id: general.nationId }, select: { meta: true } });
if (!nation) throw new TRPCError({ code: 'NOT_FOUND', message: '국가 정보를 찾을 수 없습니다.' });
if (resolveSecretPermission(general, nation.meta) < 1)
throw new TRPCError({ code: 'FORBIDDEN', message: '권한이 부족합니다.' });
return general.nationId;
};
export const policyHistory = authedProcedure
.input(
z
.object({
area,
cursor: z.number().int().positive().optional(),
limit: z.number().int().min(1).max(50).default(20),
})
.strict()
)
.query(({ ctx, input }) =>
readAudit(ctx, async (tx) => {
const nationId = await readNationId(ctx, tx);
const world = await readAuditWorld(tx);
const rows = world.serverId
? await tx.playAuditPolicy.findMany({
where: {
serverId: world.serverId,
nationId,
area: input.area,
revision: input.cursor === undefined ? undefined : { lt: input.cursor },
},
orderBy: { revision: 'desc' },
take: input.limit + 1,
select: summarySelect,
})
: [];
return {
coverage: world.serverId ? ('RECORDED_VERSIONS_ONLY' as const) : ('IDENTITY_MISSING' as const),
historyGap: world.historyGap,
items: rows.slice(0, input.limit).map(projectPolicySummary),
nextCursor: rows.length > input.limit ? rows[input.limit - 1]!.revision : null,
};
})
);
export const policyVersion = authedProcedure
.input(z.object({ id: z.string().regex(/^[a-f0-9]{64}$/) }).strict())
.query(({ ctx, input }) =>
readAudit(ctx, async (tx) => {
const nationId = await readNationId(ctx, tx);
const world = await readAuditWorld(tx);
const row = world.serverId
? await tx.playAuditPolicy.findFirst({
where: { id: input.id, serverId: world.serverId, nationId, area: { in: area.options } },
select: { ...summarySelect, before: true, after: true },
})
: null;
if (!row)
throw new TRPCError({ code: 'NOT_FOUND', message: '자국의 현재 기수 정책 기록을 찾을 수 없습니다.' });
return projectPolicyConfiguration(row);
})
);
+3
View File
@@ -15,6 +15,7 @@ import { loadUnitSetDefinitionByName } from '@sammo-ts/game-engine/scenario/unit
import type { GameApiContext } from '../../context.js';
import { getMyGeneral } from '../shared/general.js';
import { resolveSecretPermission } from '../shared/secretPermission.js';
import { policyHistory, policyVersion } from './history.js';
type SetterInfo = {
setter: string | null;
@@ -356,6 +357,8 @@ const requestNpcPolicyMutation = async (
};
export const npcRouter = router({
policyHistory,
policyVersion,
getPolicy: authedProcedure.query(async ({ ctx }) => {
const general = await getMyGeneral(ctx);
if (general.nationId <= 0) {
@@ -0,0 +1,372 @@
import { afterAll, beforeAll, expect, it } from 'vitest';
import fastify from 'fastify';
import { fastifyTRPCPlugin } from '@trpc/server/adapters/fastify';
import { asRecord } from '@sammo-ts/common';
import { createGamePostgresConnector, createRedisConnector, GamePrisma } from '@sammo-ts/infra';
import { createTurnDaemonRuntime, seedScenarioToDatabase, type TurnDaemonRuntime } from '@sammo-ts/game-engine';
import { appRouter } from '../src/router.js';
import { DatabaseTurnDaemonTransport } from '../src/daemon/databaseTransport.js';
import { RedisAccessTokenStore } from '../src/auth/accessTokenStore.js';
import { InMemoryFlushStore } from '../src/auth/flushStore.js';
import type { GameApiContext } from '../src/context.js';
const databaseUrl = process.env.NPC_POLICY_DATABASE_URL;
const integration = it.skipIf(!databaseUrl || !process.env.REDIS_URL);
const connector = databaseUrl ? createGamePostgresConnector({ url: databaseUrl }) : null;
const redis = process.env.REDIS_URL ? createRedisConnector({ url: process.env.REDIS_URL }) : null;
let runtime: TurnDaemonRuntime | undefined;
let loop: Promise<void> | undefined;
const profile = `npc-policy:${databaseUrl ? new URL(databaseUrl).searchParams.get('schema') : 'fixture'}`;
const baseTime = new Date('0185-01-01T00:00:00Z');
beforeAll(async () => {
if (!databaseUrl) return;
if (!new URL(databaseUrl).searchParams.get('schema')?.endsWith('_npc_policy_fixture'))
throw new Error('Dedicated NPC policy fixture schema required');
await connector!.connect();
await redis!.connect();
});
afterAll(async () => {
if (loop) {
await runtime?.lifecycle.stop();
await loop;
}
await runtime?.close();
await connector?.disconnect();
await redis?.disconnect();
});
integration(
'HTTP user edits survive daemon restart and real NPC turns, with readable actor and before/after history',
async () => {
const db = connector!.prisma;
await db.playAuditPolicy.deleteMany({
where: {
OR: [
{ serverId: 'npc-policy-lifecycle' },
{ id: { in: ['c'.repeat(64), 'd'.repeat(64), 'e'.repeat(64)] } },
],
},
});
await db.playAuditMonth.deleteMany({ where: { serverId: 'npc-policy-lifecycle' } });
await db.playAuditDecisionChunk.deleteMany({ where: { decision: { serverId: 'npc-policy-lifecycle' } } });
await db.playAuditDecision.deleteMany({ where: { serverId: 'npc-policy-lifecycle' } });
await db.playAuditDiplomacyEvent.deleteMany({ where: { serverId: 'npc-policy-lifecycle' } });
await db.inputEvent.deleteMany({ where: { requestId: { startsWith: 'npc-policy-http-' } } });
await seedScenarioToDatabase({
scenarioId: 903,
databaseUrl: databaseUrl!,
now: new Date(),
installOptions: {
openAt: new Date(Date.now() + 86_400_000),
turnTermMinutes: 5,
npcMode: 2,
showImgLevel: 3,
serverId: 'npc-policy-lifecycle',
season: 1,
},
});
await db.generalTurn.deleteMany();
await db.nationTurn.deleteMany();
await db.general.deleteMany();
await db.nation.deleteMany();
await db.diplomacy.deleteMany();
// 185년 고정 fixture에 180년 시나리오의 지연 등장 이벤트를 이월하지 않는다.
// 일반턴·월간 재정/도시 갱신은 실제 runtime handler를 그대로 실행한다.
await db.event.deleteMany();
const city = await db.city.findFirstOrThrow({ orderBy: { id: 'asc' } });
const world = await db.worldState.findFirstOrThrow();
await db.worldState.update({
where: { id: world.id },
data: {
currentYear: 185,
currentMonth: 1,
clockBaseTime: baseTime,
clockTick: 0n,
lastTurnTick: 0n,
clockPhase: 'RUNNING',
clockMode: 'realtime',
clockWallAnchor: new Date(),
config: { ...asRecord(world.config), tournamentTrig: false },
},
});
await db.nation.create({
data: {
id: 1,
name: '정책국',
color: '#ffffff',
level: 3,
chiefGeneralId: 1,
capitalCityId: city.id,
gold: 100_000,
rice: 100_000,
tech: 3000,
meta: {},
},
});
await db.city.updateMany({ data: { nationId: 0 } });
await db.city.update({
where: { id: city.id },
data: {
nationId: 1,
population: 100_000,
populationMax: 200_000,
supplyState: 1,
frontState: 1,
meta: { trust: 80, trade: 100 },
},
});
await db.general.createMany({
data: [
{
id: 1,
userId: 'npc-policy-owner',
name: '정책군주',
nationId: 1,
cityId: city.id,
officerLevel: 12,
npcState: 0,
turnTime: new Date('0185-02-01Z'),
turnTick: 100_000_000n,
meta: { belong: 5, killturn: 24 },
},
{
id: 2,
name: '징병검증장',
nationId: 1,
cityId: city.id,
officerLevel: 5,
npcState: 2,
leadership: 80,
strength: 80,
intel: 30,
gold: 100_000,
rice: 100_000,
crew: 0,
turnTime: baseTime,
turnTick: 0n,
meta: { killturn: 24, armType: 1, dex1: 1000 },
},
],
});
const transport = new DatabaseTurnDaemonTransport(db, 10_000);
const auth: NonNullable<GameApiContext['auth']> = {
version: 1,
profile,
sessionId: 'fixture-session',
issuedAt: new Date().toISOString(),
expiresAt: '2999-01-01T00:00:00Z',
user: { id: 'npc-policy-owner', username: 'fixture', displayName: 'fixture', roles: [] },
sanctions: {},
};
const app = fastify();
let request = 0;
await app.register(fastifyTRPCPlugin, {
prefix: '/che/api/trpc',
trpcOptions: {
router: appRouter,
createContext: ({
req,
}: {
req: { headers: Record<string, string | string[] | undefined> };
}): GameApiContext => ({
db,
redis: redis!.client,
profile: { id: 'hwe', scenario: '903', name: profile },
auth: req.headers['x-session-token'] === 'fixture-session' ? auth : null,
requestId: `npc-policy-http-${++request}`,
turnDaemon: transport,
accessTokenStore: new RedisAccessTokenStore(redis!.client, profile),
flushStore: new InMemoryFlushStore(),
battleSim: {} as GameApiContext['battleSim'],
uploadDir: '/tmp',
uploadPath: '/uploads',
uploadPublicUrl: null,
gameTokenSecret: 'fixture-only',
}),
},
});
const actions: Array<{ kind: string; actionKey: string; generalId: number; completed?: boolean }> = [];
const start = async () => {
const instance = await createTurnDaemonRuntime({
profile,
databaseUrl: databaseUrl!,
enableLeaseHeartbeat: false,
onActionResolved: (action) => actions.push(action),
});
// 자연 진행은 정지하고, 사용자 command와 명시한 due 턴만 처리한다.
instance.lifecycle.pause('NPC policy fixture');
return instance;
};
const call = async (endpoint: string, input: unknown, mutation = false) => {
const response = await app.inject({
method: mutation ? 'POST' : 'GET',
url: `/che/api/trpc/npc.${endpoint}${mutation ? '' : `?input=${encodeURIComponent(JSON.stringify(input))}`}`,
headers: {
'x-session-token': 'fixture-session',
...(mutation ? { 'content-type': 'application/json' } : {}),
},
...(mutation ? { payload: JSON.stringify(input) } : {}),
});
expect(response.statusCode, response.body).toBe(200);
return response.json<{ result: { data: unknown } }>().result.data;
};
const configuration = (value: unknown) => {
const data = asRecord(value);
return {
values: data.currentNationPolicy,
nation: data.currentNationPriority,
general: data.currentGeneralActionPriority,
setters: data.lastSetters,
};
};
try {
runtime = await start();
loop = runtime.lifecycle.start();
// 발령과 징병을 목록에서 빼면 실제 턴에서도 수행하지 않아야 한다.
await call('setNationPriority', ['NPC포상'], true);
await call('setGeneralPriority', ['출병', '일반내정'], true);
await call(
'setNationPolicy',
{ minWarCrew: 2000, minNPCRecruitCityPopulation: 30000, safeRecruitCityPopulationRatio: 0 },
true
);
const withoutRecruitment = await call('getPolicy', {});
expect(withoutRecruitment).toMatchObject({
currentNationPriority: ['NPC포상'],
currentGeneralActionPriority: ['출병', '일반내정'],
});
await runtime.lifecycle.stop();
await loop;
loop = undefined;
await runtime.close();
runtime = await start();
expect(configuration(await call('getPolicy', {}))).toEqual(configuration(withoutRecruitment));
let result = await runtime.processor.run(baseTime, { budgetMs: 10_000, maxGenerals: 1, catchUpCap: 1 });
expect(result.processedGenerals).toBe(1);
expect(runtime.world.getGeneralById(2)!.crew).toBe(0);
expect(actions).toContainEqual(expect.objectContaining({ generalId: 2, kind: 'nation' }));
expect(
actions.filter((action) => action.generalId === 2).some((action) => action.actionKey === 'che_징병')
).toBe(false);
await runtime.hooks!.flushChanges!(result);
const nationDecision = await db.playAuditDecision.findFirstOrThrow({
where: { generalId: 2, phase: 'nation' },
include: { chunks: { orderBy: { ordinal: 'asc' } } },
});
const procedures = nationDecision.chunks.flatMap((chunk) =>
Array.isArray(chunk.steps)
? chunk.steps
.map(asRecord)
.filter((step) => step.kind === 'PROCEDURE_START')
.map((step) => step.procedure)
: []
);
expect(procedures).toEqual(['NPC포상']);
expect(configuration(await call('getPolicy', {}))).toEqual(configuration(withoutRecruitment));
// 같은 장수·자원으로 징병 우선순위를 맨 앞에 저장한 뒤 다시 시작한다.
loop = runtime.lifecycle.start();
await call('setGeneralPriority', ['징병', '출병', '일반내정'], true);
const saved = await call('getPolicy', {});
await runtime.lifecycle.stop();
await loop;
loop = undefined;
await runtime.close();
runtime = await start();
expect(configuration(await call('getPolicy', {}))).toEqual(configuration(saved));
const beforeMeta = asRecord(runtime.world.getNationById(1)!.meta);
// 실제 due 턴과 command를 실행한다. 전방의 중립 도시가 전쟁 상태를 만든다.
actions.length = 0;
result = await runtime.processor.run(runtime.world.getGeneralById(2)!.turnTime, {
budgetMs: 10_000,
maxGenerals: 1,
catchUpCap: 1,
});
expect(result.processedGenerals).toBe(1);
expect(runtime.world.getGeneralById(2)!.crew).toBeGreaterThan(0);
expect(actions).toContainEqual(
expect.objectContaining({ generalId: 2, actionKey: 'che_징병', completed: true })
);
await runtime.hooks!.flushChanges!(result);
expect((await db.general.findUniqueOrThrow({ where: { id: 2 } })).crew).toBeGreaterThan(0);
expect(asRecord(runtime.world.getNationById(1)!.meta).npc_nation_policy).toEqual(
beforeMeta.npc_nation_policy
);
expect(configuration(await call('getPolicy', {}))).toEqual(configuration(saved));
// 병력이 소진되고 도시 인구가 정책 하한 미만이면 징병을 거른다.
runtime.world.updateGeneral(2, { crew: 0 });
runtime.world.updateCity(city.id, { population: 30_100 });
actions.length = 0;
result = await runtime.processor.run(runtime.world.getGeneralById(2)!.turnTime, {
budgetMs: 10_000,
maxGenerals: 1,
catchUpCap: 1,
});
expect(result.processedGenerals).toBe(1);
expect(runtime.world.getGeneralById(2)!.crew).toBe(0);
expect(actions.some((action) => action.actionKey === 'che_징병')).toBe(false);
await runtime.hooks!.flushChanges!(result);
expect(configuration(await call('getPolicy', {}))).toEqual(configuration(saved));
// 이후의 개인턴/월간 갱신도 정책을 덮어쓰면 안 된다.
const target = new Date(runtime.world.getGeneralById(2)!.turnTime.getTime() + 30 * 60_000);
result = await runtime.processor.run(target, { budgetMs: 10_000, maxGenerals: 100, catchUpCap: 12 });
expect(result.processedGenerals).toBeGreaterThan(1);
await runtime.hooks!.flushChanges!(result);
expect(await call('getPolicy', {})).toMatchObject({
currentNationPriority: ['NPC포상'],
currentGeneralActionPriority: ['징병', '출병', '일반내정'],
currentNationPolicy: { minWarCrew: 2000, minNPCRecruitCityPopulation: 30000 },
});
expect(runtime.world.getState().meta.playAuditGap).toBeUndefined();
const history = await call('policyHistory', { area: 'NPC_NATION_PRIORITY' });
expect(history).toMatchObject({
items: [
expect.objectContaining({ source: 'CHANGE', actor: expect.objectContaining({ name: '정책군주' }) }),
expect.anything(),
],
});
const row = await db.playAuditPolicy.findFirstOrThrow({
where: { nationId: 1, area: 'NPC_NATION_PRIORITY', source: 'CHANGE' },
});
expect(await call('policyVersion', { id: row.id })).toMatchObject({
actor: { name: '정책군주' },
fields: [{ key: 'priority', beforeJson: 'null', afterJson: '["NPC포상"]', changed: true }],
});
expect(row.inputSequence).not.toBeNull();
expect((await db.inputEvent.findUniqueOrThrow({ where: { sequence: row.inputSequence! } })).status).toBe(
'SUCCEEDED'
);
// 외국/이전 기수/국방 정책 ID를 알아도 자국 NPC 정책으로 조회할 수 없다.
for (const [id, patch] of [
['c'.repeat(64), { nationId: 2 }],
['d'.repeat(64), { serverId: 'previous-season' }],
['e'.repeat(64), { area: 'DEFENCE' }],
] as const) {
await db.playAuditPolicy.create({
data: {
...row,
id,
...patch,
actor: row.actor ?? GamePrisma.DbNull,
before: row.before ?? GamePrisma.DbNull,
after: row.after ?? GamePrisma.JsonNull,
},
});
const response = await app.inject({
url: `/che/api/trpc/npc.policyVersion?input=${encodeURIComponent(JSON.stringify({ id }))}`,
headers: { 'x-session-token': 'fixture-session' },
});
expect(response.statusCode).toBe(404);
}
await db.general.update({ where: { id: 1 }, data: { penalty: { noChief: true } } });
const denied = await app.inject({
url: `/che/api/trpc/npc.policyHistory?input=${encodeURIComponent(JSON.stringify({ area: 'NPC_VALUES' }))}`,
headers: { 'x-session-token': 'fixture-session' },
});
expect(denied.statusCode).toBe(403);
} finally {
await app.close();
}
},
90_000
);
+154 -1
View File
@@ -1,4 +1,6 @@
import { describe, expect, it, vi } from 'vitest';
import fastify from 'fastify';
import { fastifyTRPCPlugin } from '@trpc/server/adapters/fastify';
import type { GameSessionTokenPayload } from '@sammo-ts/common/auth/gameToken';
import type { RedisConnector } from '@sammo-ts/infra';
@@ -96,10 +98,16 @@ const createContext = (
options: {
me?: GeneralRow;
nation?: typeof baseNation;
world?: typeof baseWorld;
world?: {
config: typeof baseWorld.config;
meta: Record<string, unknown>;
currentYear?: number;
currentMonth?: number;
};
requestCommand?: ReturnType<typeof vi.fn>;
troopRows?: Array<{ troopLeaderId: number }>;
cityRows?: Array<{ id: number }>;
historyRows?: Array<Record<string, unknown>>;
} = {}
): { context: GameApiContext; findFirst: ReturnType<typeof vi.fn>; requestCommand: ReturnType<typeof vi.fn> } => {
const requestCommand =
@@ -117,6 +125,11 @@ const createContext = (
worldState: { findFirst: vi.fn(async () => options.world ?? baseWorld) },
troop: { findMany: vi.fn(async () => options.troopRows ?? [{ troopLeaderId: 101 }]) },
city: { findMany: vi.fn(async () => options.cityRows ?? [{ id: 1 }, { id: 2 }]) },
playAuditPolicy: {
findMany: vi.fn(async () => options.historyRows ?? []),
findFirst: vi.fn(async () => options.historyRows?.[0] ?? null),
},
$transaction: async <T>(read: (tx: object) => Promise<T>): Promise<T> => read(db),
};
const redisClient = { get: async () => null, set: async () => null };
return {
@@ -140,6 +153,146 @@ const createContext = (
};
describe('NPC policy router', () => {
const historyRow = {
id: 'a'.repeat(64),
schemaVersion: 1,
nationId: 1,
area: 'NPC_NATION_PRIORITY',
revision: 2,
previousId: 'b'.repeat(64),
source: 'CHANGE',
year: 185,
month: 1,
createdAt: new Date('2026-01-01Z'),
actor: {
userId: 'private-user',
generalId: 22,
name: '정책담당',
nationId: 1,
officerLevel: 12,
npcState: 0,
permission: 4,
},
before: { priority: ['NPC전방발령', 'NPC포상'] },
after: { priority: ['NPC포상'] },
};
const historyContext = (options: Parameters<typeof createContext>[0] = {}) =>
createContext({
...options,
world: {
...baseWorld,
currentYear: 185,
currentMonth: 1,
meta: { ...baseWorld.meta, serverId: 'current-season' },
},
historyRows: options.historyRows ?? [historyRow],
});
it('lets an ordinary nation reader inspect the actor and changes without admin request/account fields', async () => {
const fixture = historyContext({ me: { ...baseGeneral, officerLevel: 2 } });
const caller = appRouter.createCaller(fixture.context);
const page = await caller.npc.policyHistory({ area: 'NPC_NATION_PRIORITY', limit: 1 });
expect(page.items[0]).toMatchObject({ actor: { name: '정책담당' }, revision: 2 });
const detail = await caller.npc.policyVersion({ id: historyRow.id });
expect(detail.fields).toEqual([
{ key: 'priority', beforeJson: '["NPC전방발령","NPC포상"]', afterJson: '["NPC포상"]', changed: true },
]);
expect(JSON.stringify(detail)).not.toMatch(/private-user|permission|requestId|inputSequence/);
});
it.each([
{ nationId: 0 },
{ officerLevel: 0 },
{ officerLevel: 1, meta: { belong: 0 } },
{ penalty: { noChief: true } },
])('denies history when current nation read permission is missing: %j', async (patch) => {
const fixture = historyContext({ me: { ...baseGeneral, ...patch } });
await expect(
appRouter.createCaller(fixture.context).npc.policyHistory({ area: 'NPC_VALUES' })
).rejects.toBeDefined();
});
it('scopes cursor and version lookups to the authenticated nation and current season', async () => {
const fixture = historyContext();
const caller = appRouter.createCaller(fixture.context);
await caller.npc.policyHistory({ area: 'NPC_VALUES', cursor: 7, limit: 1 });
const tx = await fixture.context.db.$transaction!(async (db) => db);
expect(tx.playAuditPolicy.findMany).toHaveBeenCalledWith(
expect.objectContaining({
where: { serverId: 'current-season', nationId: 1, area: 'NPC_VALUES', revision: { lt: 7 } },
take: 2,
})
);
await caller.npc.policyVersion({ id: historyRow.id });
expect(tx.playAuditPolicy.findFirst).toHaveBeenCalledWith(
expect.objectContaining({
where: {
id: historyRow.id,
serverId: 'current-season',
nationId: 1,
area: { in: ['NPC_VALUES', 'NPC_NATION_PRIORITY', 'NPC_GENERAL_PRIORITY'] },
},
})
);
await expect(caller.npc.policyHistory({ area: 'NPC_VALUES', limit: 51 })).rejects.toMatchObject({
code: 'BAD_REQUEST',
});
await expect(caller.npc.policyVersion({ id: 'bad' })).rejects.toMatchObject({ code: 'BAD_REQUEST' });
});
it('requires authentication and rejects caller-supplied nation or defence history over HTTP', async () => {
const fixture = historyContext();
const app = fastify();
await app.register(fastifyTRPCPlugin, {
prefix: '/che/api/trpc',
trpcOptions: {
router: appRouter,
createContext: ({ req }: { req: { headers: Record<string, string | string[] | undefined> } }) => ({
...fixture.context,
auth: req.headers['x-session-token'] === 'fixture-session' ? auth : null,
}),
},
});
try {
const query = (input: object, authenticated = true) =>
app.inject({
url: `/che/api/trpc/npc.policyHistory?input=${encodeURIComponent(JSON.stringify(input))}`,
headers: authenticated ? { 'x-session-token': 'fixture-session' } : {},
});
expect((await query({ area: 'NPC_VALUES' }, false)).statusCode).toBe(401);
expect((await query({ area: 'NPC_VALUES' })).statusCode).toBe(200);
expect((await query({ area: 'DEFENCE' })).statusCode).toBe(400);
expect((await query({ area: 'NPC_VALUES', nationId: 2 })).statusCode).toBe(400);
} finally {
await app.close();
}
});
it('denies sanctioned sessions before reading history', async () => {
const fixture = historyContext();
fixture.context.auth = { ...auth, sanctions: { suspendedUntil: '2999-01-01T00:00:00Z' } };
await expect(
appRouter.createCaller(fixture.context).npc.policyHistory({ area: 'NPC_VALUES' })
).rejects.toMatchObject({ code: 'FORBIDDEN' });
expect(fixture.findFirst).not.toHaveBeenCalled();
});
it('paginates revisions without exposing a missing or unavailable version', async () => {
const fixture = historyContext({ historyRows: [historyRow, { ...historyRow, revision: 1 }] });
const page = await appRouter
.createCaller(fixture.context)
.npc.policyHistory({ area: 'NPC_NATION_PRIORITY', limit: 1 });
expect(page.items).toHaveLength(1);
expect(page.nextCursor).toBe(2);
const missing = historyContext({ historyRows: [] });
await expect(
appRouter.createCaller(missing.context).npc.policyVersion({ id: historyRow.id })
).rejects.toMatchObject({ code: 'NOT_FOUND' });
const noIdentity = createContext();
await expect(
appRouter.createCaller(noIdentity.context).npc.policyHistory({ area: 'NPC_VALUES' })
).resolves.toMatchObject({ coverage: 'IDENTITY_MISSING', items: [], nextCursor: null });
});
it('loads server and nation overrides while calculating legacy zero-value hints from nation tech', async () => {
const fixture = createContext();
const result = await appRouter.createCaller(fixture.context).npc.getPolicy();
+141 -2
View File
@@ -1,6 +1,6 @@
import { verifyVerticalAlignment } from '../../../tools/frontend-legacy-parity/verticalAlignment.js';
import { devices, expect, test, type Page, type Route } from '@playwright/test';
import { mkdir, readFile } from 'node:fs/promises';
import { mkdir, readFile, writeFile } from 'node:fs/promises';
import { dirname, resolve } from 'node:path';
import { fileURLToPath } from 'node:url';
import { gameProfile, gameTrpcRoute } from './gameTestPaths.js';
@@ -14,6 +14,8 @@ type FixtureState = {
mutations: string[];
nationPriorityInput?: string[];
currentNationPriority?: string[];
currentGeneralPriority?: string[];
historyVersion?: number;
};
const repositoryRoot = resolve(dirname(fileURLToPath(import.meta.url)), '../../..');
@@ -131,7 +133,7 @@ const policyFixture = (state: FixtureState) => ({
currentNationPriority: state.currentNationPriority ?? nationPriority,
availableNationPriorityItems: nationPriority,
defaultGeneralActionPriority: generalPriority,
currentGeneralActionPriority: generalPriority,
currentGeneralActionPriority: state.currentGeneralPriority ?? generalPriority,
availableGeneralActionPriorityItems: generalPriority,
lastSetters: {
policy: { setter: null, date: null },
@@ -175,6 +177,41 @@ const installFixture = async (page: Page, state: FixtureState) => {
? errorResponse(operation, '권한이 부족합니다.', 'FORBIDDEN')
: response(policyFixture(state));
}
if (operation === 'npc.policyHistory') {
return response({
coverage: 'RECORDED_VERSIONS_ONLY',
nextCursor: null,
items: [
{
id: 'a'.repeat(64),
revision: state.historyVersion ?? 2,
year: 185,
month: 1,
source: 'CHANGE',
actor: { name: '정책담당', generalId: 22 },
},
],
});
}
if (operation === 'npc.policyVersion') {
return response({
id: 'a'.repeat(64),
revision: state.historyVersion ?? 2,
year: 185,
month: 1,
source: 'CHANGE',
createdAt: '2026-10-22T00:00:00Z',
actor: { name: '정책담당', generalId: 22 },
fields: [
{
key: 'priority',
beforeJson: '["NPC전방발령","NPC포상"]',
afterJson: '["NPC포상"]',
changed: true,
},
],
});
}
if (
operation === 'npc.setNationPolicy' ||
operation === 'npc.setNationPriority' ||
@@ -188,6 +225,11 @@ const installFixture = async (page: Page, state: FixtureState) => {
state.failNextMutation = false;
return errorResponse(operation, '권한이 부족합니다.', 'FORBIDDEN');
}
state.historyVersion = (state.historyVersion ?? 2) + 1;
if (operation === 'npc.setNationPriority' && Array.isArray(jsonInput))
state.currentNationPriority = jsonInput.map(String);
if (operation === 'npc.setGeneralPriority' && Array.isArray(jsonInput))
state.currentGeneralPriority = jsonInput.map(String);
return response({ ok: true });
}
return errorResponse(operation, `Unhandled fixture operation: ${operation}`);
@@ -206,6 +248,103 @@ const screenshot = async (page: Page, name: string) => {
await page.screenshot({ path: resolve(artifactRoot, name), fullPage: true });
};
for (const width of [500, 1000]) {
test(`일반 사용자가 정책 변경자와 전후 이력을 조회하고 새로고침한다 ${width}px`, async ({ page }, testInfo) => {
const state: FixtureState = { permissionLevel: 1, mutations: [], currentNationPriority: ['NPC포상'] };
await installFixture(page, state);
await page.setViewportSize({ width, height: 900 });
await gotoPolicy(page);
await page.getByRole('button', { name: '정책 변경 이력 조회' }).click();
const history = page.getByRole('region', { name: 'NPC 정책 변경 이력', exact: true });
await expect(history).toContainText('정책담당 (#22)');
await history.getByLabel('이력 종류').selectOption('NPC_NATION_PRIORITY');
await history.getByRole('button', { name: '버전 2', exact: true }).click();
const detail = page.getByRole('region', { name: '정책 변경 내용', exact: true });
await expect(detail).toContainText('["NPC전방발령","NPC포상"]');
await expect(detail).toContainText('["NPC포상"]');
await history.getByLabel('이력 종류').focus();
await expect(history.getByLabel('이력 종류')).toBeFocused();
const measurements = await history.evaluate((element) => {
const rect = element.getBoundingClientRect();
const cell = element.querySelector('td')!;
return {
rect: rect.toJSON(),
style: {
verticalAlign: getComputedStyle(cell).verticalAlign,
whiteSpace: getComputedStyle(cell.querySelector('pre')!).whiteSpace,
},
html: element.outerHTML,
overflow: document.documentElement.scrollWidth > innerWidth,
};
});
expect(measurements.style).toEqual({ verticalAlign: 'middle', whiteSpace: 'pre-wrap' });
expect(measurements.overflow).toBe(false);
await testInfo.attach('history-dom-geometry-style', {
body: JSON.stringify(measurements, null, 2),
contentType: 'application/json',
});
await writeFile(testInfo.outputPath('history-dom-geometry-style.json'), JSON.stringify(measurements, null, 2));
await page.screenshot({ path: testInfo.outputPath(`npc-history-${width}.png`), fullPage: true });
state.historyVersion = 3;
await history.getByRole('button', { name: '이력 새로고침' }).click();
await expect(history.getByRole('button', { name: '버전 3', exact: true })).toBeVisible();
await page.reload();
const nationPanel = page.locator('.priority-panel').first();
await expect(
nationPanel.locator('.priority-column').nth(1).getByText('NPC전방발령', { exact: true })
).toHaveCount(0);
await expect(
nationPanel.locator('.priority-column').first().getByText('NPC전방발령', { exact: true })
).toBeVisible();
});
}
test('발령 비활성화와 징병 우선순위를 저장한 뒤 새로고침해도 유지되고 열린 이력이 갱신된다', async ({ page }) => {
const state: FixtureState = {
permissionLevel: 4,
mutations: [],
currentNationPriority: ['NPC전방발령', 'NPC포상'],
currentGeneralPriority: ['출병', '일반내정', '징병'],
};
await installFixture(page, state);
await page.setViewportSize({ width: 1000, height: 900 });
await gotoPolicy(page);
await page.getByRole('button', { name: '정책 변경 이력 조회' }).click();
const history = page.getByRole('region', { name: 'NPC 정책 변경 이력', exact: true });
await expect(history.getByRole('button', { name: '버전 2', exact: true })).toBeVisible();
const nationPanel = page.locator('.priority-panel').first();
await nationPanel
.locator('.priority-column')
.nth(1)
.getByText('NPC전방발령', { exact: true })
.dragTo(nationPanel.locator('.priority-column').first().locator('.priority-list'));
await nationPanel.locator('.submit_btn').click();
await acceptAppConfirmation(page, '저장할까요?');
await expect(history.getByRole('button', { name: '버전 3', exact: true })).toBeVisible();
const generalPanel = page.locator('.priority-panel').nth(1);
const active = generalPanel.locator('.priority-column').nth(1);
// 목록을 화면 중앙에 놓아 창 가장자리의 Sortable auto-scroll을 피한다.
await active
.locator('.priority-list')
.evaluate((element) => element.scrollIntoView({ block: 'center', behavior: 'instant' }));
await touchDrag(page, active.getByText('징병', { exact: true }), active.getByText('출병', { exact: true }), {
targetYRatio: 0.1,
});
await expect(active.locator('.priority_info > span:nth-child(2)').first()).toHaveText('징병');
await generalPanel.locator('.submit_btn').click();
await acceptAppConfirmation(page, '저장할까요?');
await expect(history.getByRole('button', { name: '버전 4', exact: true })).toBeVisible();
expect(state.currentNationPriority).toEqual(['NPC포상']);
expect(state.currentGeneralPriority).toEqual(['징병', '출병', '일반내정']);
await page.reload();
await expect(
nationPanel.locator('.priority-column').first().getByText('NPC전방발령', { exact: true })
).toBeVisible();
await expect(
generalPanel.locator('.priority-column').nth(1).locator('.priority_info > span:nth-child(2)').first()
).toHaveText('징병');
});
test('desktop geometry, typography, textures, drag, focus, tooltip, and successful save match the reference', async ({
page,
}) => {
+9 -9
View File
@@ -35,14 +35,14 @@
"dependencies": {
"@sammo-ts/common": "workspace:*",
"@sammo-ts/logic": "workspace:*",
"@tiptap/extension-image": "^3.5.0",
"@tiptap/extension-link": "^3.5.0",
"@tiptap/extension-placeholder": "^3.5.0",
"@tiptap/extension-text-align": "^3.5.0",
"@tiptap/extension-text-style": "^3.5.0",
"@tiptap/extension-underline": "^3.5.0",
"@tiptap/starter-kit": "^3.5.0",
"@tiptap/vue-3": "^3.5.0",
"@tiptap/extension-image": "^3.30.5",
"@tiptap/extension-link": "^3.30.5",
"@tiptap/extension-placeholder": "^3.30.5",
"@tiptap/extension-text-align": "^3.30.5",
"@tiptap/extension-text-style": "^3.30.5",
"@tiptap/extension-underline": "^3.30.5",
"@tiptap/starter-kit": "^3.30.5",
"@tiptap/vue-3": "^3.30.5",
"@trpc/client": "^11.8.1",
"@trpc/server": "^11.8.1",
"@vueuse/core": "^14.1.0",
@@ -52,7 +52,7 @@
"mitt": "^3.0.1",
"pinia": "^3.0.4",
"tippy.js": "6.3.7",
"vue": "^3.5.26",
"vue": "^3.5.42",
"vue-draggable-plus": "0.6.1",
"vue-router": "^4.6.4",
"zod": "^4.3.5"
@@ -0,0 +1,191 @@
<script setup lang="ts">
import { ref, watch } from 'vue';
import { trpc } from '../utils/trpc';
import { fieldLabels } from './playAudit/policyLabels';
const props = defineProps<{ revision: number }>();
type History = Awaited<ReturnType<typeof trpc.npc.policyHistory.query>>;
type Version = Awaited<ReturnType<typeof trpc.npc.policyVersion.query>>;
const open = ref(false);
const area = ref<'NPC_VALUES' | 'NPC_NATION_PRIORITY' | 'NPC_GENERAL_PRIORITY'>('NPC_VALUES');
const history = ref<History | null>(null);
const version = ref<Version | null>(null);
const loading = ref(false);
const error = ref('');
const labels = { BASELINE: '최초 관측', CHANGE: '변경', OBSERVED_GAP: '관측 누락 이후 기준' };
let generation = 0;
const load = async (append = false) => {
const request = ++generation;
loading.value = true;
error.value = '';
if (!append) {
history.value = null;
version.value = null;
}
try {
const response = await trpc.npc.policyHistory.query({
area: area.value,
cursor: append ? (history.value?.nextCursor ?? undefined) : undefined,
});
if (request === generation)
history.value = {
...response,
items: append ? [...(history.value?.items ?? []), ...response.items] : response.items,
};
} catch (cause) {
if (request === generation)
error.value = cause instanceof Error ? cause.message : '이력을 조회하지 못했습니다.';
} finally {
if (request === generation) loading.value = false;
}
};
const select = async (id: string) => {
const request = ++generation;
loading.value = true;
error.value = '';
version.value = null;
try {
const response = await trpc.npc.policyVersion.query({ id });
if (request === generation) version.value = response;
} catch (cause) {
if (request === generation)
error.value = cause instanceof Error ? cause.message : '변경 내용을 조회하지 못했습니다.';
} finally {
if (request === generation) loading.value = false;
}
};
watch([open, area, () => props.revision], () => {
if (open.value) void load();
else {
++generation;
loading.value = false;
history.value = null;
version.value = null;
}
});
</script>
<template>
<section class="npc-policy-history" aria-label="NPC 정책 변경 이력">
<button class="legacy-button" :aria-expanded="open" @click="open = !open">
정책 변경 이력 {{ open ? '닫기' : '조회' }}
</button>
<div v-if="open">
<p>자국의 현재 기수 기록입니다. 최초 관측 이전의 변경은 복원하지 않습니다.</p>
<p>우선순위는 실행을 시도하는 순서입니다. 징병은 전쟁 상태·병력·인구·자원 조건을 만족해야 합니다.</p>
<p>0이면 자동 계산하는 항목은 저장값이 같아도 기술·내정 비용에 따라 기준이 달라질 수 있습니다.</p>
<label
>이력 종류
<select v-model="area">
<option value="NPC_VALUES">국가 정책</option>
<option value="NPC_NATION_PRIORITY">국가턴 우선순위</option>
<option value="NPC_GENERAL_PRIORITY">일반턴 우선순위</option>
</select>
</label>
<button class="legacy-button" :disabled="loading" @click="load()">이력 새로고침</button>
<p v-if="loading" role="status">조회 중…</p>
<p v-if="error" role="alert">{{ error }}</p>
<template v-if="history">
<p v-if="history.historyGap" role="status">
{{ history.historyGap.firstYear }}년 {{ history.historyGap.firstMonth }}월 이후 일부 기록에 누락이
있습니다. 이력이 없다는 사실만으로 변경이 없었다고 판단할 수 없습니다.
</p>
<p v-if="history.coverage === 'IDENTITY_MISSING'">기수 식별자가 없어 이력을 조회할 수 없습니다.</p>
<p v-else-if="!history.items.length">기록된 정책 이력이 없습니다.</p>
<ol v-else>
<li v-for="item in history.items" :key="item.id">
<button class="legacy-button" :disabled="loading" @click="select(item.id)">
버전 {{ item.revision }}
</button>
{{ item.year }}년 {{ item.month }}월 · {{ labels[item.source] }} ·
{{ item.actor ? `${item.actor.name} (#${item.actor.generalId})` : '변경 주체 미상' }}
</li>
</ol>
<button
v-if="history.nextCursor !== null"
class="legacy-button"
:disabled="loading"
@click="load(true)"
>
이전 기록 더 보기
</button>
</template>
<section v-if="version" aria-label="정책 변경 내용">
<h3>버전 {{ version.revision }} · {{ version.actor?.name ?? '변경 주체 미상' }}</h3>
<p>{{ version.year }}년 {{ version.month }}월 · 기록 시각 {{ version.createdAt }}</p>
<p v-if="version.source !== 'CHANGE'">관측 기준입니다. 이전 값과 변경 주체를 추정하지 않습니다.</p>
<p>null은 개별 설정 없이 기본값을 상속함을 뜻합니다.</p>
<div class="table-scroll" tabindex="0" aria-label="정책 변경 전후 값">
<table>
<thead>
<tr>
<th>설정</th>
<th>변경 전</th>
<th>변경 후</th>
</tr>
</thead>
<tbody>
<tr v-for="field in version.fields" :key="field.key" :class="{ changed: field.changed }">
<th scope="row">
{{ fieldLabels[field.key] ?? field.key }}{{ field.changed ? ' (변경)' : '' }}
</th>
<td>
<pre>{{ field.beforeJson ?? '관측하지 않음' }}</pre>
</td>
<td>
<pre>{{ field.afterJson }}</pre>
</td>
</tr>
</tbody>
</table>
</div>
</section>
</div>
</section>
</template>
<style scoped>
.npc-policy-history {
margin-top: 16px;
padding: 8px;
border: 1px solid gray;
}
select,
button {
margin: 4px;
vertical-align: middle;
}
li {
margin-block: 6px;
}
.table-scroll {
overflow-x: auto;
}
table {
width: 100%;
border-collapse: collapse;
}
th,
td {
border: 1px solid gray;
padding: 6px;
text-align: left;
vertical-align: middle;
}
pre {
white-space: pre-wrap;
overflow-wrap: anywhere;
min-width: 100px;
margin: 0;
font: inherit;
}
.changed {
background: #343425;
}
[role='alert'] {
color: #ffb9b9;
}
h3 {
font-size: var(--sammo-font-size-normal);
}
</style>
@@ -3,12 +3,14 @@ import { usePageExit } from '../composables/usePageExit';
import { computed, onMounted, ref, watch } from 'vue';
import NpcPolicyHistory from '../components/NpcPolicyHistory.vue';
import SortableStringList from '../components/ui/SortableStringList';
import { useGameFeedback } from '../composables/useGameFeedback';
import { npcPriorityHelp } from '../utils/npcPriorityHelp';
import { trpc } from '../utils/trpc';
const { pageExitLabel, exitPage } = usePageExit();
const historyRevision = ref(0);
type NpcPolicyResponse = Awaited<ReturnType<typeof trpc.npc.getPolicy.query>>;
type NationPolicy = NpcPolicyResponse['currentNationPolicy'];
@@ -308,6 +310,7 @@ const submitPolicy = async () => {
try {
await trpc.npc.setNationPolicy.mutate(policyDraft.value);
lastSavedPolicy.value = clonePolicy(policyDraft.value);
historyRevision.value += 1;
showSuccessToast('NPC 정책이 반영되었습니다.');
} catch (caught) {
showErrorToast(`설정하지 못했습니다: ${resolveErrorMessage(caught)}`);
@@ -357,6 +360,7 @@ const submitPriority = async (section: PrioritySectionKey) => {
await trpc.npc.setGeneralPriority.mutate(state.active);
lastSavedGeneralPriority.value = [...state.active];
}
historyRevision.value += 1;
showSuccessToast('NPC 정책이 반영되었습니다.');
} catch (caught) {
showErrorToast(`설정하지 못했습니다: ${resolveErrorMessage(caught)}`);
@@ -547,6 +551,7 @@ const submitPriority = async (section: PrioritySectionKey) => {
</section>
</div>
</section>
<NpcPolicyHistory v-if="data" :key="data.nationId" :revision="historyRevision" />
<div class="sortable-compat-controls" aria-hidden="true">
<button type="button" tabindex="-1" />
<input v-for="index in 20" :key="index" type="hidden" />
+4 -3
View File
@@ -40,11 +40,12 @@
"@trpc/server": "^11.8.1",
"date-fns": "^4.1.0",
"es-toolkit": "^1.43.0",
"fastify": "^5.6.2",
"fastify": "^5.12.5",
"ipaddr.js": "2.5.0",
"pm2": "^7.0.3",
"redis": "^5.10.0",
"sanitize-html": "2.17.6",
"sharp": "^0.35.0",
"sanitize-html": "2.17.7",
"sharp": "^0.35.5",
"web-push": "3.6.7",
"zod": "^4.3.5"
}
@@ -148,12 +148,9 @@ export class RedisOAuthSessionStore implements OAuthSessionStore {
async consumePendingState(state: string): Promise<OAuthPendingState | null> {
const key = this.stateKey(state);
const raw = await this.client.get(key);
if (!raw) {
return null;
}
await this.client.del(key);
return parseJson<OAuthPendingState>(raw);
// Callback 재사용 방지는 읽기와 삭제를 같은 Redis 실행에서 완료해야 한다.
const raw = await this.client.eval(consumeOnceScript, { keys: [key], arguments: [] });
return typeof raw === 'string' ? parseJson<OAuthPendingState>(raw) : null;
}
async createSession(session: Omit<OAuthSession, 'id'>): Promise<OAuthSession> {
@@ -54,6 +54,8 @@ export const buildPm2StartOptions = (definition: ProcessDefinition) => ({
args: definition.args,
env: sanitizePm2IdentityEnv(definition.env ?? {}),
autorestart: true,
// PM2의 chokidar/braces glob parser에 사용자 입력을 전달하는 watch 경로는 사용하지 않는다.
watch: false,
max_restarts: 5,
min_uptime: 10_000,
restart_delay: 2_000,
+10 -7
View File
@@ -12,6 +12,7 @@ import { GatewayPrisma, type GatewayPrismaClient } from '@sammo-ts/infra';
import webPush from 'web-push';
import { resolveGatewayProfileDisplayName } from '../profileOrder.js';
import { parsePushEndpoint, publicPushAgent, PushEndpointPolicyError, validatePushEndpoint } from './endpointPolicy.js';
export interface WebPushCoordinatorConfig {
enabled: boolean;
@@ -200,8 +201,7 @@ export class WebPushCoordinator {
async subscribe(userId: string, subscription: WebPushClientSubscription, userAgent?: string): Promise<void> {
if (!this.configured) throw new Error('웹 알림 전송이 아직 활성화되지 않았습니다.');
const endpointUrl = new URL(subscription.endpoint);
if (endpointUrl.protocol !== 'https:') throw new Error('보안 연결의 Push 구독만 저장할 수 있습니다.');
await validatePushEndpoint(subscription.endpoint);
const expirationTime = subscription.expirationTime ? new Date(subscription.expirationTime) : null;
await this.prisma.webPushSubscription.upsert({
where: { endpoint: subscription.endpoint },
@@ -428,7 +428,7 @@ export class WebPushCoordinator {
const expired = await this.prisma.$transaction(async (tx) => {
const count = await tx.$executeRaw(GatewayPrisma.sql`
UPDATE web_push_delivery AS delivery
SET status = 'FAILED'::"WebPushDeliveryStatus",
SET status = 'FAILED',
locked_at = NULL,
lock_owner = NULL,
last_error = 'Push subscription expired.'
@@ -453,6 +453,8 @@ export class WebPushCoordinator {
continue;
}
try {
// 기존 DB 구독도 검사한다. DNS 검사는 실제 socket lookup에서 다시 수행한다.
parsePushEndpoint(delivery.subscription.endpoint);
await webPush.sendNotification(
{
endpoint: delivery.subscription.endpoint,
@@ -464,11 +466,11 @@ export class WebPushCoordinator {
url: delivery.notification.url,
tag: delivery.notification.tag,
}),
{ TTL: 60 * 60 }
{ TTL: 60 * 60, timeout: 10_000, agent: publicPushAgent }
);
await this.prisma.$executeRaw(GatewayPrisma.sql`
UPDATE web_push_delivery
SET status = 'DELIVERED'::"WebPushDeliveryStatus",
SET status = 'DELIVERED',
delivered_at = CURRENT_TIMESTAMP,
locked_at = NULL,
lock_owner = NULL,
@@ -481,6 +483,7 @@ export class WebPushCoordinator {
? Number((error as { statusCode?: unknown }).statusCode)
: 0;
const terminal =
error instanceof PushEndpointPolicyError ||
statusCode === 404 ||
statusCode === 410 ||
(statusCode >= 400 && statusCode < 500 && statusCode !== 429);
@@ -493,7 +496,7 @@ export class WebPushCoordinator {
const nextStatus = terminal || exhausted ? 'FAILED' : 'PENDING';
await tx.$executeRaw(GatewayPrisma.sql`
UPDATE web_push_delivery
SET status = ${nextStatus}::"WebPushDeliveryStatus",
SET status = ${nextStatus},
available_at = CURRENT_TIMESTAMP
+ ${delaySeconds * 1_000} * INTERVAL '1 millisecond',
locked_at = NULL,
@@ -501,7 +504,7 @@ export class WebPushCoordinator {
last_error = ${safeError}
WHERE id = ${delivery.id} AND lock_owner = ${this.owner}
`);
if (statusCode === 404 || statusCode === 410) {
if (error instanceof PushEndpointPolicyError || statusCode === 404 || statusCode === 410) {
await tx.$executeRaw(GatewayPrisma.sql`
UPDATE web_push_subscription
SET disabled_at = CURRENT_TIMESTAMP,
@@ -0,0 +1,108 @@
import { lookup } from 'node:dns/promises';
import type { LookupAddress } from 'node:dns';
import { Agent } from 'node:https';
import { isIP, type LookupFunction } from 'node:net';
import ipaddr from 'ipaddr.js';
export class PushEndpointPolicyError extends Error {
constructor() {
// URL, DNS 응답과 구독 capability는 API 오류나 로그에 포함하지 않는다.
super('Push endpoint must use HTTPS on port 443 and a public destination.');
this.name = 'PushEndpointPolicyError';
}
}
export type PushAddressResolver = (hostname: string) => Promise<LookupAddress[]>;
const resolveAddresses: PushAddressResolver = (hostname) => lookup(hostname, { all: true });
export const isPublicPushAddress = (address: string): boolean => {
try {
// 사설, loopback, link-local, multicast, 문서용·전환·예약 주소를 모두 거부한다.
return ipaddr.process(address).range() === 'unicast';
} catch {
return false;
}
};
export const parsePushEndpoint = (endpoint: string): URL => {
let url: URL;
try {
url = new URL(endpoint);
} catch {
throw new PushEndpointPolicyError();
}
const hostname = url.hostname.replace(/^\[|\]$/gu, '');
if (
url.protocol !== 'https:' ||
(url.port !== '' && url.port !== '443') ||
url.username ||
url.password ||
url.hash ||
!hostname ||
isIP(hostname) !== 0
) {
// IP literal은 Node가 custom lookup을 건너뛰므로 구독으로 받지 않는다.
throw new PushEndpointPolicyError();
}
return url;
};
export const resolvePublicPushAddresses = async (
hostname: string,
resolver: PushAddressResolver = resolveAddresses
): Promise<LookupAddress[]> => {
let timer: NodeJS.Timeout | undefined;
try {
const addresses = await Promise.race([
resolver(hostname),
new Promise<never>((_, reject) => {
timer = setTimeout(() => reject(new Error('Push endpoint resolution failed.')), 5_000);
}),
]);
if (
addresses.length === 0 ||
addresses.some(({ address, family }) => !isPublicPushAddress(address) || isIP(address) !== family)
) {
throw new PushEndpointPolicyError();
}
return addresses;
} catch (error) {
if (error instanceof PushEndpointPolicyError) throw error;
// 일시 DNS 장애는 전송 재시도 대상으로 남기고 구독을 영구 비활성화하지 않는다.
// eslint-disable-next-line preserve-caught-error -- DNS 오류의 hostname 등 비공개 목적지는 cause에도 복제하지 않는다.
throw new Error('Push endpoint resolution failed.');
} finally {
clearTimeout(timer);
}
};
export const validatePushEndpoint = async (endpoint: string): Promise<void> => {
const url = parsePushEndpoint(endpoint);
await resolvePublicPushAddresses(url.hostname);
};
export const createPublicPushLookup =
(resolver: PushAddressResolver = resolveAddresses): LookupFunction =>
(hostname, options, callback) => {
// 저장 시 검사만으로는 DNS rebinding을 막지 못한다. socket이 실제 연결할
// DNS 응답을 검사하고 바로 그 주소만 반환하여 재조회 경로를 없앤다.
void resolvePublicPushAddresses(hostname, resolver).then(
(addresses) => {
const requestedFamily = typeof options.family === 'number' ? options.family : 0;
const matching = requestedFamily
? addresses.filter(({ family }) => family === requestedFamily)
: addresses;
if (matching.length === 0) {
callback(new PushEndpointPolicyError(), []);
} else if (options.all) {
callback(null, matching);
} else {
callback(null, matching[0].address, matching[0].family);
}
},
(error: Error) => callback(error, [])
);
};
// web-push는 https.request를 직접 호출하고 redirect를 따라가지 않는다.
export const publicPushAgent = new Agent({ lookup: createPublicPushLookup(), maxSockets: 10 });
@@ -14,6 +14,7 @@ describe.skipIf(!redisUrl)('RedisOAuthSessionStore Kakao state', () => {
const userIds = new Set<string>();
const challengeIds = new Set<string>();
const sessionIds = new Set<string>();
const stateIds = new Set<string>();
beforeAll(async () => {
await client.connect();
@@ -24,6 +25,7 @@ describe.skipIf(!redisUrl)('RedisOAuthSessionStore Kakao state', () => {
...[...challengeIds].map((id) => `${prefix}:kakao-login-challenge:${id}`),
...[...userIds].map((id) => `${prefix}:kakao-login-challenge-user:${id}`),
...[...sessionIds].map((id) => `${prefix}:oauth-session:${id}`),
...[...stateIds].map((id) => `${prefix}:oauth-state:${id}`),
];
if (keys.length > 0) {
await client.del(keys);
@@ -45,6 +47,14 @@ describe.skipIf(!redisUrl)('RedisOAuthSessionStore Kakao state', () => {
return challenge;
};
it('consumes a pending OAuth state once under concurrent callbacks', async () => {
const pending = await store.createPendingState('verify', ['account_email'], randomUUID());
stateIds.add(pending.state);
const results = await Promise.all(Array.from({ length: 8 }, () => store.consumePendingState(pending.state)));
expect(results.filter((result) => result !== null)).toEqual([pending]);
await expect(store.consumePendingState(pending.state)).resolves.toBeNull();
});
it('preserves and consumes a retained-email recovery target once', async () => {
const targetUserId = randomUUID();
const session = await store.createSession({
@@ -26,6 +26,7 @@ describe('buildPm2StartOptions', () => {
expect(options).toMatchObject({
name: 'sammo:che:2:game-api',
autorestart: true,
watch: false,
max_restarts: 5,
min_uptime: 10_000,
restart_delay: 2_000,
@@ -1,4 +1,4 @@
import { afterAll, beforeAll, describe, expect, it } from 'vitest';
import { afterAll, beforeAll, describe, expect, it, vi } from 'vitest';
import webPush from 'web-push';
import { createGatewayPostgresConnector, type GatewayPrismaClient } from '@sammo-ts/infra';
@@ -22,6 +22,7 @@ integration('web push Gateway persistence boundary', () => {
let db: GatewayPrismaClient;
let closeDb: (() => Promise<void>) | undefined;
let coordinator: WebPushCoordinator;
const dispatchErrors: string[] = [];
beforeAll(async () => {
assertDedicatedSchema();
@@ -64,12 +65,16 @@ integration('web push Gateway persistence boundary', () => {
},
});
const vapid = webPush.generateVAPIDKeys();
coordinator = new WebPushCoordinator(db, {
coordinator = new WebPushCoordinator(
db,
{
enabled: true,
vapidSubject: 'mailto:web-push-test@example.invalid',
vapidPublicKey: vapid.publicKey,
vapidPrivateKey: vapid.privateKey,
});
},
(error) => dispatchErrors.push(error instanceof Error ? error.message : 'Unknown dispatcher error')
);
});
afterAll(async () => {
@@ -166,4 +171,72 @@ integration('web push Gateway persistence boundary', () => {
db.webPushEventReceipt.findUnique({ where: { eventId: 'integration:disabled:1' } })
).resolves.toBeNull();
});
it('rejects a private endpoint before persisting a subscription', async () => {
const before = await db.webPushSubscription.count({ where: { userId } });
await expect(
coordinator.subscribe(userId, {
endpoint: 'https://127.0.0.1/internal',
expirationTime: null,
keys: { p256dh: 'unused', auth: 'unused' },
})
).rejects.toThrow('public destination');
expect(await db.webPushSubscription.count({ where: { userId } })).toBe(before);
});
it('quarantines an existing unsafe subscription without sending a request', async () => {
await coordinator.stop();
await db.webPushDelivery.deleteMany({ where: { notification: { userId } } });
dispatchErrors.length = 0;
const subscription = await db.webPushSubscription.create({
data: {
userId,
endpoint: 'https://[::1]/internal',
p256dh: 'unused',
auth: 'unused',
},
});
const notification = await db.webPushNotification.create({
data: {
userId,
profileName,
dedupeKey: 'integration:unsafe-endpoint',
eventType: 'PRIVATE_MESSAGE_RECEIVED',
title: 'synthetic notification',
body: 'synthetic body',
url: '/hwe/',
tag: 'synthetic-tag',
},
});
const delivery = await db.webPushDelivery.create({
data: {
subscriptionId: subscription.id,
notificationId: notification.id,
},
});
const sender = vi.spyOn(webPush, 'sendNotification').mockRejectedValue({ statusCode: 410 });
try {
coordinator.start();
await vi.waitFor(
async () => {
expect(dispatchErrors).toEqual([]);
expect((await db.webPushDelivery.findUniqueOrThrow({ where: { id: delivery.id } })).status).toBe(
'FAILED'
);
},
{ timeout: 5_000 }
);
expect(await db.webPushSubscription.findUnique({ where: { id: subscription.id } })).toMatchObject({
disabledAt: expect.any(Date),
});
expect(sender).not.toHaveBeenCalledWith(
expect.objectContaining({ endpoint: subscription.endpoint }),
expect.anything(),
expect.anything()
);
} finally {
await coordinator.stop();
sender.mockRestore();
}
});
});
@@ -0,0 +1,131 @@
import { createServer, request, Agent } from 'node:https';
import type { LookupAddress, LookupOptions } from 'node:dns';
import type { AddressInfo } from 'node:net';
import { describe, expect, it, vi } from 'vitest';
import {
createPublicPushLookup,
isPublicPushAddress,
parsePushEndpoint,
PushEndpointPolicyError,
resolvePublicPushAddresses,
} from '../src/webPush/endpointPolicy.js';
describe('Web Push outbound destination policy', () => {
it.each([
'127.0.0.1',
'0.0.0.0',
'10.0.0.1',
'172.16.0.1',
'192.168.1.1',
'169.254.169.254',
'100.64.0.1',
'192.0.2.1',
'198.18.0.1',
'224.0.0.1',
'255.255.255.255',
'::1',
'::',
'fc00::1',
'fe80::1',
'ff02::1',
'2001:db8::1',
'::ffff:127.0.0.1',
'64:ff9b::a00:1',
'2002:0a00:0001::1',
'not-an-ip',
])('rejects non-public address %s', (address) => {
expect(isPublicPushAddress(address)).toBe(false);
});
it.each(['8.8.8.8', '1.1.1.1', '2606:4700:4700::1111'])('accepts public address %s', (address) => {
expect(isPublicPushAddress(address)).toBe(true);
});
it.each([
'http://push.example/subscription',
'https://push.example:8443/subscription',
'https://user:password@push.example/subscription',
'https://push.example/subscription#secret',
'https://127.0.0.1/subscription',
'https://2130706433/subscription',
'https://0x7f000001/subscription',
'https://[::1]/subscription',
'https://[::ffff:127.0.0.1]/subscription',
'not-a-url',
])('rejects endpoint %s without DNS or network I/O', (endpoint) => {
expect(() => parsePushEndpoint(endpoint)).toThrow(PushEndpointPolicyError);
});
it('preserves provider capability paths and query parameters', () => {
const endpoint = 'https://fcm.googleapis.com:443/fcm/send/synthetic-capability?version=1';
expect(parsePushEndpoint(endpoint).hostname).toBe('fcm.googleapis.com');
expect(parsePushEndpoint(endpoint).search).toBe('?version=1');
});
it('rejects mixed public/private answers, empty answers and resolution errors without leaking values', async () => {
for (const addresses of [
[],
[
{ address: '8.8.8.8', family: 4 },
{ address: '10.0.0.1', family: 4 },
],
]) {
await expect(resolvePublicPushAddresses('push.example', async () => addresses)).rejects.toThrow(
PushEndpointPolicyError
);
}
await expect(
resolvePublicPushAddresses('push.example', async () => {
throw new Error('sensitive resolver details');
})
).rejects.toThrow('Push endpoint resolution failed.');
});
it('returns only the validated DNS answer to the socket and respects family/all', async () => {
const resolver = vi.fn(async () => [
{ address: '8.8.8.8', family: 4 },
{ address: '2606:4700:4700::1111', family: 6 },
]);
const lookup = createPublicPushLookup(resolver);
const runLookup = (options: LookupOptions) =>
new Promise<string | LookupAddress[]>((resolve, reject) => {
lookup('push.example', options, (error, address) => (error ? reject(error) : resolve(address)));
});
await expect(runLookup({ family: 6 })).resolves.toBe('2606:4700:4700::1111');
await expect(runLookup({ all: true, family: 4 })).resolves.toEqual([{ address: '8.8.8.8', family: 4 }]);
expect(resolver).toHaveBeenCalledTimes(2);
});
it('blocks a rebound hostname at the actual HTTPS socket before contacting a local listener', async () => {
let connections = 0;
const server = createServer();
server.on('connection', () => {
connections += 1;
});
await new Promise<void>((resolve) => server.listen(0, '127.0.0.1', resolve));
const port = (server.address() as AddressInfo).port;
const resolver = vi
.fn()
.mockResolvedValueOnce([{ address: '8.8.8.8', family: 4 }])
.mockResolvedValueOnce([{ address: '127.0.0.1', family: 4 }]);
const agent = new Agent({ lookup: createPublicPushLookup(resolver) });
try {
await resolvePublicPushAddresses('push.example', resolver);
const failure = await new Promise<Error>((resolve, reject) => {
const req = request({ hostname: 'push.example', port, agent, timeout: 1_000 }, () =>
reject(new Error('Unexpected connection'))
);
req.on('error', resolve);
req.on('timeout', () => req.destroy(new Error('Unexpected timeout')));
req.end();
});
expect(failure).toBeInstanceOf(PushEndpointPolicyError);
expect(connections).toBe(0);
expect(resolver).toHaveBeenCalledTimes(2);
} finally {
agent.destroy();
await new Promise<void>((resolve, reject) => server.close((error) => (error ? reject(error) : resolve())));
}
});
});
+1 -1
View File
@@ -26,7 +26,7 @@
"es-toolkit": "^1.43.0",
"mitt": "^3.0.1",
"pinia": "^3.0.4",
"vue": "^3.5.26",
"vue": "^3.5.42",
"vue-router": "^4.6.4",
"zod": "^4.3.5"
},
+747 -638
View File
@@ -164,8 +164,17 @@ settings:
overrides:
deepmerge-ts: 8.0.1
fast-uri: 3.1.5
js-yaml: 4.3.1
mysql2: 3.23.1
basic-ftp: 6.2.1
ip-address: 10.7.1
source-map-js: 1.2.2
prosemirror-view: 1.42.3
postcss-selector-parser: 7.1.6
vitepress>vite: 6.4.3
vue@>=3.5.0 <3.5.42: 3.5.43
brace-expansion@>=4.0.0 <6.0.0: 5.0.12
fast-uri: 3.1.8
js-yaml: 4.3.2
lodash: 4.18.1
postcss: 8.5.26
rolldown-plugin-dts: 0.27.13
@@ -219,7 +228,7 @@ importers:
version: 3.9.8
prisma:
specifier: ^7.10.0
version: 7.10.0(@types/react@19.2.18)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(typescript@6.0.3)
version: 7.10.0(@types/node@26.2.0)(@types/react@19.2.18)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(typescript@6.0.3)
tsdown:
specifier: ^0.23.0
version: 0.23.0(@volar/typescript@2.4.28(typescript@6.0.3))(tsx@4.23.12)(typescript@6.0.3)(unrun@0.2.22(@emnapi/core@1.11.3)(@emnapi/runtime@1.11.3)(synckit@0.11.13))(vue-tsc@3.3.10(typescript@6.0.3))
@@ -234,7 +243,7 @@ importers:
version: 8.67.0(eslint@10.8.1(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3)
vitepress:
specifier: 1.6.4
version: 1.6.4(@algolia/client-search@5.56.0)(@types/node@26.2.0)(@types/react@19.2.18)(lightningcss@1.33.0)(postcss@8.5.26)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(search-insights@2.17.3)(sortablejs@1.14.0)(typescript@6.0.3)
version: 1.6.4(@algolia/client-search@5.56.0)(@types/node@26.2.0)(@types/react@19.2.18)(jiti@2.7.0)(lightningcss@1.33.0)(postcss@8.5.26)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(search-insights@2.17.3)(sortablejs@1.14.0)(tsx@4.23.12)(typescript@6.0.3)
vue-eslint-parser:
specifier: ^10.4.1
version: 10.4.1(eslint@10.8.1(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)
@@ -269,17 +278,17 @@ importers:
specifier: ^1.43.0
version: 1.51.0
fastify:
specifier: ^5.6.2
version: 5.12.0
specifier: ^5.12.5
version: 5.12.5
redis:
specifier: ^5.10.0
version: 5.12.1
sanitize-html:
specifier: 2.17.6
version: 2.17.6
specifier: 2.17.7
version: 2.17.7
sharp:
specifier: ^0.35.0
version: 0.35.3(@types/node@26.2.0)
specifier: ^0.35.5
version: 0.35.5(@types/node@26.2.0)
zod:
specifier: ^4.3.5
version: 4.4.3
@@ -298,7 +307,7 @@ importers:
dependencies:
'@prisma/client':
specifier: ^7.10.0
version: 7.10.0(prisma@7.10.0(@types/react@19.2.18)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(typescript@6.0.3))(typescript@6.0.3)
version: 7.10.0(prisma@7.10.0(@types/node@26.2.0)(@types/react@19.2.18)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(typescript@6.0.3))(typescript@6.0.3)
'@sammo-ts/common':
specifier: workspace:*
version: link:../../packages/common
@@ -331,29 +340,29 @@ importers:
specifier: workspace:*
version: link:../../packages/logic
'@tiptap/extension-image':
specifier: ^3.5.0
version: 3.30.1(@tiptap/core@3.30.1(@tiptap/pm@3.30.1))
specifier: ^3.30.5
version: 3.31.4(@tiptap/core@3.31.4(@tiptap/pm@3.31.4))
'@tiptap/extension-link':
specifier: ^3.5.0
version: 3.30.1(@tiptap/core@3.30.1(@tiptap/pm@3.30.1))(@tiptap/pm@3.30.1)
specifier: ^3.30.5
version: 3.31.4(@tiptap/core@3.31.4(@tiptap/pm@3.31.4))(@tiptap/pm@3.31.4)
'@tiptap/extension-placeholder':
specifier: ^3.5.0
version: 3.30.1(@tiptap/extensions@3.30.1(@tiptap/core@3.30.1(@tiptap/pm@3.30.1))(@tiptap/pm@3.30.1))
specifier: ^3.30.5
version: 3.31.4(@tiptap/extensions@3.31.4(@tiptap/core@3.31.4(@tiptap/pm@3.31.4))(@tiptap/pm@3.31.4))
'@tiptap/extension-text-align':
specifier: ^3.5.0
version: 3.30.1(@tiptap/core@3.30.1(@tiptap/pm@3.30.1))
specifier: ^3.30.5
version: 3.31.4(@tiptap/core@3.31.4(@tiptap/pm@3.31.4))
'@tiptap/extension-text-style':
specifier: ^3.5.0
version: 3.30.1(@tiptap/core@3.30.1(@tiptap/pm@3.30.1))
specifier: ^3.30.5
version: 3.31.4(@tiptap/core@3.31.4(@tiptap/pm@3.31.4))
'@tiptap/extension-underline':
specifier: ^3.5.0
version: 3.30.1(@tiptap/core@3.30.1(@tiptap/pm@3.30.1))
specifier: ^3.30.5
version: 3.31.4(@tiptap/core@3.31.4(@tiptap/pm@3.31.4))
'@tiptap/starter-kit':
specifier: ^3.5.0
version: 3.30.1
specifier: ^3.30.5
version: 3.31.4
'@tiptap/vue-3':
specifier: ^3.5.0
version: 3.30.1(@floating-ui/dom@1.8.0)(@tiptap/core@3.30.1(@tiptap/pm@3.30.1))(@tiptap/pm@3.30.1)(vue@3.5.41(typescript@6.0.3))
specifier: ^3.30.5
version: 3.31.4(@floating-ui/dom@1.8.0)(@tiptap/core@3.31.4(@tiptap/pm@3.31.4))(@tiptap/pm@3.31.4)(vue@3.5.43(typescript@6.0.3))
'@trpc/client':
specifier: ^11.8.1
version: 11.18.0(@trpc/server@11.18.0(typescript@6.0.3))(typescript@6.0.3)
@@ -362,7 +371,7 @@ importers:
version: 11.18.0(typescript@6.0.3)
'@vueuse/core':
specifier: ^14.1.0
version: 14.4.0(vue@3.5.41(typescript@6.0.3))
version: 14.4.0(vue@3.5.43(typescript@6.0.3))
chart.js:
specifier: 4.5.1
version: 4.5.1
@@ -377,19 +386,19 @@ importers:
version: 3.0.1
pinia:
specifier: ^3.0.4
version: 3.0.4(typescript@6.0.3)(vue@3.5.41(typescript@6.0.3))
version: 3.0.4(typescript@6.0.3)(vue@3.5.43(typescript@6.0.3))
tippy.js:
specifier: 6.3.7
version: 6.3.7
vue:
specifier: ^3.5.26
version: 3.5.41(typescript@6.0.3)
specifier: ^3.5.42
version: 3.5.43(typescript@6.0.3)
vue-draggable-plus:
specifier: 0.6.1
version: 0.6.1(@types/sortablejs@1.15.9)
vue-router:
specifier: ^4.6.4
version: 4.6.4(vue@3.5.41(typescript@6.0.3))
version: 4.6.4(vue@3.5.43(typescript@6.0.3))
zod:
specifier: ^4.3.5
version: 4.4.3
@@ -411,7 +420,7 @@ importers:
version: 4.3.3(vite@8.3.0(@types/node@26.2.0)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.12))
'@vitejs/plugin-vue':
specifier: ^6.0.9
version: 6.0.9(vite@8.3.0(@types/node@26.2.0)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.12))(vue@3.5.41(typescript@6.0.3))
version: 6.0.9(vite@8.3.0(@types/node@26.2.0)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.12))(vue@3.5.43(typescript@6.0.3))
autoprefixer:
specifier: ^10.5.4
version: 10.5.4(postcss@8.5.26)
@@ -441,7 +450,7 @@ importers:
version: 10.1.3
'@prisma/client':
specifier: ^7.10.0
version: 7.10.0(prisma@7.10.0(@types/react@19.2.18)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(typescript@6.0.3))(typescript@6.0.3)
version: 7.10.0(prisma@7.10.0(@types/node@26.2.0)(@types/react@19.2.18)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(typescript@6.0.3))(typescript@6.0.3)
'@sammo-ts/common':
specifier: workspace:*
version: link:../../packages/common
@@ -464,8 +473,11 @@ importers:
specifier: ^1.43.0
version: 1.51.0
fastify:
specifier: ^5.6.2
version: 5.12.0
specifier: ^5.12.5
version: 5.12.5
ipaddr.js:
specifier: 2.5.0
version: 2.5.0
pm2:
specifier: ^7.0.3
version: 7.0.3(supports-color@7.2.0)
@@ -473,11 +485,11 @@ importers:
specifier: ^5.10.0
version: 5.12.1
sanitize-html:
specifier: 2.17.6
version: 2.17.6
specifier: 2.17.7
version: 2.17.7
sharp:
specifier: ^0.35.0
version: 0.35.3(@types/node@26.2.0)
specifier: ^0.35.5
version: 0.35.5(@types/node@26.2.0)
web-push:
specifier: 3.6.7
version: 3.6.7(supports-color@7.2.0)
@@ -511,7 +523,7 @@ importers:
version: 11.18.0(typescript@6.0.3)
'@vueuse/core':
specifier: ^13.9.0
version: 13.9.0(vue@3.5.41(typescript@6.0.3))
version: 13.9.0(vue@3.5.43(typescript@6.0.3))
date-fns:
specifier: ^4.1.0
version: 4.4.0
@@ -523,13 +535,13 @@ importers:
version: 3.0.1
pinia:
specifier: ^3.0.4
version: 3.0.4(typescript@6.0.3)(vue@3.5.41(typescript@6.0.3))
version: 3.0.4(typescript@6.0.3)(vue@3.5.43(typescript@6.0.3))
vue:
specifier: ^3.5.26
version: 3.5.41(typescript@6.0.3)
specifier: ^3.5.42
version: 3.5.43(typescript@6.0.3)
vue-router:
specifier: ^4.6.4
version: 4.6.4(vue@3.5.41(typescript@6.0.3))
version: 4.6.4(vue@3.5.43(typescript@6.0.3))
zod:
specifier: ^4.3.5
version: 4.4.3
@@ -557,7 +569,7 @@ importers:
version: 8.23.1
'@vitejs/plugin-vue':
specifier: ^6.0.9
version: 6.0.9(vite@8.3.0(@types/node@26.2.0)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.12))(vue@3.5.41(typescript@6.0.3))
version: 6.0.9(vite@8.3.0(@types/node@26.2.0)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.12))(vue@3.5.43(typescript@6.0.3))
autoprefixer:
specifier: ^10.5.4
version: 10.5.4(postcss@8.5.26)
@@ -622,7 +634,7 @@ importers:
version: 7.10.0
'@prisma/client':
specifier: ^7.10.0
version: 7.10.0(prisma@7.10.0(@types/react@19.2.18)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(typescript@6.0.3))(typescript@6.0.3)
version: 7.10.0(prisma@7.10.0(@types/node@26.2.0)(@types/react@19.2.18)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(typescript@6.0.3))(typescript@6.0.3)
'@prisma/client-runtime-utils':
specifier: ^7.10.0
version: 7.10.0
@@ -650,7 +662,7 @@ importers:
version: 17.4.2
prisma:
specifier: ^7.10.0
version: 7.10.0(@types/react@19.2.18)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(typescript@6.0.3)
version: 7.10.0(@types/node@26.2.0)(@types/react@19.2.18)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(typescript@6.0.3)
tsdown:
specifier: ^0.23.0
version: 0.23.0(@volar/typescript@2.4.28(typescript@6.0.3))(tsx@4.23.12)(typescript@6.0.3)(unrun@0.2.22(@emnapi/core@1.11.3)(@emnapi/runtime@1.11.3)(synckit@0.11.13))(vue-tsc@3.3.10(typescript@6.0.3))
@@ -738,8 +750,8 @@ importers:
specifier: ^8.16.3
version: 8.23.0
sharp:
specifier: ^0.35.0
version: 0.35.3(@types/node@26.2.0)
specifier: ^0.35.5
version: 0.35.5(@types/node@26.2.0)
devDependencies:
'@types/node':
specifier: ^26.2.0
@@ -907,9 +919,9 @@ packages:
'@emnapi/wasi-threads@1.2.3':
resolution: {integrity: sha512-ELEBe8PsLvvJ6QMr0zLt8ffvOHW/dc1m3CEzNMg7aJUv3bMaoDtw2TXyDAwkYBuroxxuHEwhRTLJSe5sya547g==}
'@esbuild/aix-ppc64@0.21.5':
resolution: {integrity: sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==}
engines: {node: '>=12'}
'@esbuild/aix-ppc64@0.25.12':
resolution: {integrity: sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==}
engines: {node: '>=18'}
cpu: [ppc64]
os: [aix]
@@ -919,9 +931,9 @@ packages:
cpu: [ppc64]
os: [aix]
'@esbuild/android-arm64@0.21.5':
resolution: {integrity: sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==}
engines: {node: '>=12'}
'@esbuild/android-arm64@0.25.12':
resolution: {integrity: sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==}
engines: {node: '>=18'}
cpu: [arm64]
os: [android]
@@ -931,9 +943,9 @@ packages:
cpu: [arm64]
os: [android]
'@esbuild/android-arm@0.21.5':
resolution: {integrity: sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==}
engines: {node: '>=12'}
'@esbuild/android-arm@0.25.12':
resolution: {integrity: sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==}
engines: {node: '>=18'}
cpu: [arm]
os: [android]
@@ -943,9 +955,9 @@ packages:
cpu: [arm]
os: [android]
'@esbuild/android-x64@0.21.5':
resolution: {integrity: sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==}
engines: {node: '>=12'}
'@esbuild/android-x64@0.25.12':
resolution: {integrity: sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==}
engines: {node: '>=18'}
cpu: [x64]
os: [android]
@@ -955,9 +967,9 @@ packages:
cpu: [x64]
os: [android]
'@esbuild/darwin-arm64@0.21.5':
resolution: {integrity: sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==}
engines: {node: '>=12'}
'@esbuild/darwin-arm64@0.25.12':
resolution: {integrity: sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==}
engines: {node: '>=18'}
cpu: [arm64]
os: [darwin]
@@ -967,9 +979,9 @@ packages:
cpu: [arm64]
os: [darwin]
'@esbuild/darwin-x64@0.21.5':
resolution: {integrity: sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==}
engines: {node: '>=12'}
'@esbuild/darwin-x64@0.25.12':
resolution: {integrity: sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==}
engines: {node: '>=18'}
cpu: [x64]
os: [darwin]
@@ -979,9 +991,9 @@ packages:
cpu: [x64]
os: [darwin]
'@esbuild/freebsd-arm64@0.21.5':
resolution: {integrity: sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==}
engines: {node: '>=12'}
'@esbuild/freebsd-arm64@0.25.12':
resolution: {integrity: sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==}
engines: {node: '>=18'}
cpu: [arm64]
os: [freebsd]
@@ -991,9 +1003,9 @@ packages:
cpu: [arm64]
os: [freebsd]
'@esbuild/freebsd-x64@0.21.5':
resolution: {integrity: sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==}
engines: {node: '>=12'}
'@esbuild/freebsd-x64@0.25.12':
resolution: {integrity: sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==}
engines: {node: '>=18'}
cpu: [x64]
os: [freebsd]
@@ -1003,9 +1015,9 @@ packages:
cpu: [x64]
os: [freebsd]
'@esbuild/linux-arm64@0.21.5':
resolution: {integrity: sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==}
engines: {node: '>=12'}
'@esbuild/linux-arm64@0.25.12':
resolution: {integrity: sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==}
engines: {node: '>=18'}
cpu: [arm64]
os: [linux]
@@ -1015,9 +1027,9 @@ packages:
cpu: [arm64]
os: [linux]
'@esbuild/linux-arm@0.21.5':
resolution: {integrity: sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==}
engines: {node: '>=12'}
'@esbuild/linux-arm@0.25.12':
resolution: {integrity: sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==}
engines: {node: '>=18'}
cpu: [arm]
os: [linux]
@@ -1027,9 +1039,9 @@ packages:
cpu: [arm]
os: [linux]
'@esbuild/linux-ia32@0.21.5':
resolution: {integrity: sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==}
engines: {node: '>=12'}
'@esbuild/linux-ia32@0.25.12':
resolution: {integrity: sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==}
engines: {node: '>=18'}
cpu: [ia32]
os: [linux]
@@ -1039,9 +1051,9 @@ packages:
cpu: [ia32]
os: [linux]
'@esbuild/linux-loong64@0.21.5':
resolution: {integrity: sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==}
engines: {node: '>=12'}
'@esbuild/linux-loong64@0.25.12':
resolution: {integrity: sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==}
engines: {node: '>=18'}
cpu: [loong64]
os: [linux]
@@ -1051,9 +1063,9 @@ packages:
cpu: [loong64]
os: [linux]
'@esbuild/linux-mips64el@0.21.5':
resolution: {integrity: sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==}
engines: {node: '>=12'}
'@esbuild/linux-mips64el@0.25.12':
resolution: {integrity: sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==}
engines: {node: '>=18'}
cpu: [mips64el]
os: [linux]
@@ -1063,9 +1075,9 @@ packages:
cpu: [mips64el]
os: [linux]
'@esbuild/linux-ppc64@0.21.5':
resolution: {integrity: sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==}
engines: {node: '>=12'}
'@esbuild/linux-ppc64@0.25.12':
resolution: {integrity: sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==}
engines: {node: '>=18'}
cpu: [ppc64]
os: [linux]
@@ -1075,9 +1087,9 @@ packages:
cpu: [ppc64]
os: [linux]
'@esbuild/linux-riscv64@0.21.5':
resolution: {integrity: sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==}
engines: {node: '>=12'}
'@esbuild/linux-riscv64@0.25.12':
resolution: {integrity: sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==}
engines: {node: '>=18'}
cpu: [riscv64]
os: [linux]
@@ -1087,9 +1099,9 @@ packages:
cpu: [riscv64]
os: [linux]
'@esbuild/linux-s390x@0.21.5':
resolution: {integrity: sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==}
engines: {node: '>=12'}
'@esbuild/linux-s390x@0.25.12':
resolution: {integrity: sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==}
engines: {node: '>=18'}
cpu: [s390x]
os: [linux]
@@ -1099,9 +1111,9 @@ packages:
cpu: [s390x]
os: [linux]
'@esbuild/linux-x64@0.21.5':
resolution: {integrity: sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==}
engines: {node: '>=12'}
'@esbuild/linux-x64@0.25.12':
resolution: {integrity: sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==}
engines: {node: '>=18'}
cpu: [x64]
os: [linux]
@@ -1111,15 +1123,21 @@ packages:
cpu: [x64]
os: [linux]
'@esbuild/netbsd-arm64@0.25.12':
resolution: {integrity: sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==}
engines: {node: '>=18'}
cpu: [arm64]
os: [netbsd]
'@esbuild/netbsd-arm64@0.28.2':
resolution: {integrity: sha512-sSATRjPeDBg3pdgHoQfoYBob11Kk1FGa9lui5RIHZCoCkJa9QKlvl3/vKz2usCmYYjs7ymJR/2Nnsqe+Hjt5nw==}
engines: {node: '>=18'}
cpu: [arm64]
os: [netbsd]
'@esbuild/netbsd-x64@0.21.5':
resolution: {integrity: sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==}
engines: {node: '>=12'}
'@esbuild/netbsd-x64@0.25.12':
resolution: {integrity: sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==}
engines: {node: '>=18'}
cpu: [x64]
os: [netbsd]
@@ -1129,15 +1147,21 @@ packages:
cpu: [x64]
os: [netbsd]
'@esbuild/openbsd-arm64@0.25.12':
resolution: {integrity: sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==}
engines: {node: '>=18'}
cpu: [arm64]
os: [openbsd]
'@esbuild/openbsd-arm64@0.28.2':
resolution: {integrity: sha512-AL2qJILH7lNjrDmCQDvdxMfAUIv8KMNZOvrwAQ8i8//ntL9FflhOyMJ8OZSMBb8/AWXe3/5v5S20y3zCoZWKoQ==}
engines: {node: '>=18'}
cpu: [arm64]
os: [openbsd]
'@esbuild/openbsd-x64@0.21.5':
resolution: {integrity: sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==}
engines: {node: '>=12'}
'@esbuild/openbsd-x64@0.25.12':
resolution: {integrity: sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==}
engines: {node: '>=18'}
cpu: [x64]
os: [openbsd]
@@ -1147,15 +1171,21 @@ packages:
cpu: [x64]
os: [openbsd]
'@esbuild/openharmony-arm64@0.25.12':
resolution: {integrity: sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==}
engines: {node: '>=18'}
cpu: [arm64]
os: [openharmony]
'@esbuild/openharmony-arm64@0.28.2':
resolution: {integrity: sha512-WkhYDmpTjLvGlScA1rwjRUmhl4k8oXR3cIbtqWmELgU/dFeHHlEllxDvdWcNJV9rbzCexB5vz8gtNewWLgCT7Q==}
engines: {node: '>=18'}
cpu: [arm64]
os: [openharmony]
'@esbuild/sunos-x64@0.21.5':
resolution: {integrity: sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==}
engines: {node: '>=12'}
'@esbuild/sunos-x64@0.25.12':
resolution: {integrity: sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==}
engines: {node: '>=18'}
cpu: [x64]
os: [sunos]
@@ -1165,9 +1195,9 @@ packages:
cpu: [x64]
os: [sunos]
'@esbuild/win32-arm64@0.21.5':
resolution: {integrity: sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==}
engines: {node: '>=12'}
'@esbuild/win32-arm64@0.25.12':
resolution: {integrity: sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==}
engines: {node: '>=18'}
cpu: [arm64]
os: [win32]
@@ -1177,9 +1207,9 @@ packages:
cpu: [arm64]
os: [win32]
'@esbuild/win32-ia32@0.21.5':
resolution: {integrity: sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==}
engines: {node: '>=12'}
'@esbuild/win32-ia32@0.25.12':
resolution: {integrity: sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==}
engines: {node: '>=18'}
cpu: [ia32]
os: [win32]
@@ -1189,9 +1219,9 @@ packages:
cpu: [ia32]
os: [win32]
'@esbuild/win32-x64@0.21.5':
resolution: {integrity: sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==}
engines: {node: '>=12'}
'@esbuild/win32-x64@0.25.12':
resolution: {integrity: sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==}
engines: {node: '>=18'}
cpu: [x64]
os: [win32]
@@ -1309,160 +1339,160 @@ packages:
resolution: {integrity: sha512-Td76q7j57o/tLVdgS746cYARfSyxk8iEfRxewL9h4OMzYhbW4TAcppl0mT4eyqXddh6L/jwoM75mo7ixa/pCeQ==}
engines: {node: '>=18'}
'@img/sharp-darwin-arm64@0.35.3':
resolution: {integrity: sha512-RMnFX7YQsMoh7lWfcM4NEHHymBX/rLuKNPVM84XE9ONPcaSCDgE7CHIHpSgPcO2xcRthgBy1HfNO319mwhIAkg==}
'@img/sharp-darwin-arm64@0.35.5':
resolution: {integrity: sha512-QRUlFQ0WxvdWyqqG/WtI3iupfD5rBzmCHXSdPsY91sAtVtTo7Q4cb6zOccZ3gqEqkr0f1As1ehLqmEpDsRf+lg==}
engines: {node: '>=20.9.0'}
cpu: [arm64]
os: [darwin]
'@img/sharp-darwin-x64@0.35.3':
resolution: {integrity: sha512-Xo+5uFBtLN0BKqieTxiFzFPQAUlBbbH5iBKyRX/z1JrbnYsHTfKJnUfL8+p2TPXr1pXqao4eeL4Rl144uDpK9w==}
'@img/sharp-darwin-x64@0.35.5':
resolution: {integrity: sha512-+BR255RhDlpygUpOc/Jdt1nT6DQ3XG/ERo5wbcdOf5Q320dKtPCKPLR1LJs9VGXRaMa8l1uUa0tkCNOXiAxZUw==}
engines: {node: '>=20.9.0'}
cpu: [x64]
os: [darwin]
'@img/sharp-freebsd-wasm32@0.35.3':
resolution: {integrity: sha512-lUxcqWIj2wMQ9BrwNjngcr1gWUr5xgaGThBRqPPalIC2n67Cqj1uPh8NnA/ZhAg8hUbKl+kVHKwgUIwe6ZYPrg==}
'@img/sharp-freebsd-wasm32@0.35.5':
resolution: {integrity: sha512-Y/z91nEZ4uIBX5X3nfTovjU9lHNKFYbL2lpHCLVNmXQK03VIZvXBBt0KxbPGp2SdGSF+2mQU4e+hQaWOt86iAw==}
engines: {node: '>=20.9.0'}
os: [freebsd]
'@img/sharp-libvips-darwin-arm64@1.3.2':
resolution: {integrity: sha512-9J6ypZFpQBj4YnePGoq/S38w6nz+vqg5WZLrLGY4YuSemdMq47GMLBPO42MzwdGwpg/agZ7xzZcFHa48xlywfg==}
'@img/sharp-libvips-darwin-arm64@1.3.4':
resolution: {integrity: sha512-5R89nBYiRdUlSWJxPhO+GVtaXzXSxKnRu/xqMn3KTA3L9EB9Oy/P+Nn2f2vlhPuUdy/Zusb2DarbyTpGCfEDuw==}
cpu: [arm64]
os: [darwin]
'@img/sharp-libvips-darwin-x64@1.3.2':
resolution: {integrity: sha512-m2pW1n6cns9VaubNwsZ+c3CRYjxNQWgJ5gPlnL1nbBcpkBvFm6SCFN5o0psFHI8w9n11NKhFkeEDns98tiqbEw==}
'@img/sharp-libvips-darwin-x64@1.3.4':
resolution: {integrity: sha512-iR2OKH80yi0U+dUplyh3/xdpFvps6YkCwsXenIJxqxR1v9o+xtKTGbS9H7cps+2Vxjc8B1j96p75NmTGjIhtpQ==}
cpu: [x64]
os: [darwin]
'@img/sharp-libvips-linux-arm64@1.3.2':
resolution: {integrity: sha512-dqVSFynCox4C/J8kT16V7SIFAns0IjgLwkvYT7p8LQVmJ5OS5b6tI9IGflxTeuBS//zXeFIUbwt5dwxyZ17cnA==}
'@img/sharp-libvips-linux-arm64@1.3.4':
resolution: {integrity: sha512-Y3dgX/6lE2QhQb+Gxy0WZxfg9MEm/JBjamZpS2IklP7xIQoKN4hzAm7KcMVGtaVDt3neE9OKBC7vAfonA/Lr1A==}
cpu: [arm64]
os: [linux]
libc: [glibc]
'@img/sharp-libvips-linux-arm@1.3.2':
resolution: {integrity: sha512-1eMLzy92I4J6rmi4mAT8yC3HxOtniyGELlzGbNMLLeqe052ahFQ0h6LFq+lh5DsDIdYViIDst08abvSbcEdLXQ==}
'@img/sharp-libvips-linux-arm@1.3.4':
resolution: {integrity: sha512-LmRtTsOHuvM2+wlO2Db37dx5MiZhB0FvSunciw48YjdOkZz9KAiRbm8ujeMOA1INqmei5NapFxYEK1D1ZSidmw==}
cpu: [arm]
os: [linux]
libc: [glibc]
'@img/sharp-libvips-linux-ppc64@1.3.2':
resolution: {integrity: sha512-3z0NHDxD6n5I9gc05U1eW1AyRm+Gznzq3naMrthPNqE6oYykcogW0l/jfpJdjYnuNl8R7yI9pNbE1XiUeyq0Aw==}
'@img/sharp-libvips-linux-ppc64@1.3.4':
resolution: {integrity: sha512-Le6boB8Tai0Nis+gIxIpKx68UDVVIqdR8Tin5Yf1z2LJJQLDJvCDRqRu+jC2qCoD+eIomonmOwB4smBRxfVpYQ==}
cpu: [ppc64]
os: [linux]
libc: [glibc]
'@img/sharp-libvips-linux-riscv64@1.3.2':
resolution: {integrity: sha512-bsb4rI+NldGOsXuej2r8OdSS8+zXDVaCWxyWrcv6kneTOlgAHtZABRzBBCwdsPiD90J4myNJuHpg6kA20ImW/w==}
'@img/sharp-libvips-linux-riscv64@1.3.4':
resolution: {integrity: sha512-aHkkIEHPRdQEegJN20MLmGtxYD9R2wQr3Cwpddnu5+YKMt6Uzax7S9h5gpZTo8wyrGuZSlfQ63OevL5mTyOC7Q==}
cpu: [riscv64]
os: [linux]
libc: [glibc]
'@img/sharp-libvips-linux-s390x@1.3.2':
resolution: {integrity: sha512-/ABshyj8gCpyIrNXnHn4LorDJ0HHm1VhXPBlxZ8zAtfVPAaSafXPGn+sUSIRiwaSBy0mmFjSjiXI5mkcwdChKQ==}
'@img/sharp-libvips-linux-s390x@1.3.4':
resolution: {integrity: sha512-ra/mB6MikESDUO7Yg+Mi95bFBb9GsObURuhnOv3OqknjGe9sZrG8tCe9q0xSIGrtLgvgw0gKnFWcK4blSgQOuQ==}
cpu: [s390x]
os: [linux]
libc: [glibc]
'@img/sharp-libvips-linux-x64@1.3.2':
resolution: {integrity: sha512-ITPEtgffGJ0S6G9dRyw/366tJQqFRcHWPHhC+Stpg3Z8AEMrDrTr2lhdz4f/Y/HMbRh//7Z5mBzEpVdi62Oc3w==}
'@img/sharp-libvips-linux-x64@1.3.4':
resolution: {integrity: sha512-GJ//SSXbnwSDes02umB3nDJLFcQzw8a18V8fyhqr6tV515tOEMdImjjxj1AoafMRz56F3PHgftnj1QEKSU1zkw==}
cpu: [x64]
os: [linux]
libc: [glibc]
'@img/sharp-libvips-linuxmusl-arm64@1.3.2':
resolution: {integrity: sha512-zE9EdiUzUmg5mDT5a1rk5fYJ6GWPloTwWBYDS14naqHsL+EaMpDj1AWnpLgh3u0YCORv2Tt50wrcrpYqkP97Kw==}
'@img/sharp-libvips-linuxmusl-arm64@1.3.4':
resolution: {integrity: sha512-hvulFwtjUcagsis6BBxHwGFwWoNZjgYmULGVrZcyfNbjA8hKILbRxGg15/7w5HDyXHXUos/j6baAWqnCyQ2DWA==}
cpu: [arm64]
os: [linux]
libc: [musl]
'@img/sharp-libvips-linuxmusl-x64@1.3.2':
resolution: {integrity: sha512-m0lrLiUt+lBYnCFr8qV/65yMR4E/c7/wf78I5eKTdkEakFAlZ9QlzEM3QIhhAwVeUhLAHLcCq7a7Vszq/oFNZQ==}
'@img/sharp-libvips-linuxmusl-x64@1.3.4':
resolution: {integrity: sha512-6zXKeE/p39I1AmA3cJG35eyBGNqNddLnUXjhwBnsGjFPWqf5VKkDBEqaEkPDoTEtkxwi2vv8Tcr2mDyP4So7Fg==}
cpu: [x64]
os: [linux]
libc: [musl]
'@img/sharp-linux-arm64@0.35.3':
resolution: {integrity: sha512-QgKDspHPnrU+GQ55XPhGwyhC8acLVOOSyAvo1oVfFmrIXLkDNmGWzAfDZ4xK8oSA1qBQrALcHX0G5UZni/SuFQ==}
'@img/sharp-linux-arm64@0.35.5':
resolution: {integrity: sha512-LYVx5JTsOM2CBzmxreh+nl64/3H6Xb09iSLknqH47z2T2DFFxDeFLP5y4dJwe6H7uGQlHPyEEtIqyo3DYsRwdQ==}
engines: {node: '>=20.9.0'}
cpu: [arm64]
os: [linux]
libc: [glibc]
'@img/sharp-linux-arm@0.35.3':
resolution: {integrity: sha512-affVWCTLooy8TSxbDx2qkzuDeaWLNVBA+P//FNBirHsXpP2fuBhk5AuboYUnrDnzoXes8GFjpTx0SBFOCRg+FA==}
'@img/sharp-linux-arm@0.35.5':
resolution: {integrity: sha512-LEaXK2WdXVK5ykcw0buWyPMsmLLL2vpHLD6yrNSW+JGEL3BZPA4tpKN6iaMc4AxTTAoaX/sU1rOL51lcIz48ZQ==}
engines: {node: '>=20.9.0'}
cpu: [arm]
os: [linux]
libc: [glibc]
'@img/sharp-linux-ppc64@0.35.3':
resolution: {integrity: sha512-sMd8rDxmpLOwv/7N44klFjOD5DUO7FLdjiXDI0hoxYaf7Ar262dQIEkosE98bps+5HPLtp/EvNqeqQtOycP/IA==}
'@img/sharp-linux-ppc64@0.35.5':
resolution: {integrity: sha512-QVxAAq8evVRI9ia2vqgwrmWucn5Dfv+JdWzj75pD8omHLPSP7f8p20O8jxzjCcuCEQEOtYOZUmX1hkiZ0kdevA==}
engines: {node: '>=20.9.0'}
cpu: [ppc64]
os: [linux]
libc: [glibc]
'@img/sharp-linux-riscv64@0.35.3':
resolution: {integrity: sha512-0Eob78yjlYPfL5vMNWAW55l3R9Y6BQS/gOfe0ZcP9mEz9ohhKSt4im1hayiknXgf8AWrFqMvJcKIdmLmEe7yeQ==}
'@img/sharp-linux-riscv64@0.35.5':
resolution: {integrity: sha512-LtdreXguaavKODPIfzJ4kffx7UNt1omwtK0rch4EBbbSTXPnxWmYSayXdLJw0fJzQ97kHt1gL/yh4tvU+nCyRQ==}
engines: {node: '>=20.9.0'}
cpu: [riscv64]
os: [linux]
libc: [glibc]
'@img/sharp-linux-s390x@0.35.3':
resolution: {integrity: sha512-KgAxQ0DxpNOq1rG2t5cgTgShJFGSuU7XO45cqC+1NVOuZnP6tlgZRuSYOfNupGkHID0o3cJOsw4DVeJpMovcGw==}
'@img/sharp-linux-s390x@0.35.5':
resolution: {integrity: sha512-UZasTOFiYzotTsGOCu42BfUzP6Tu6Do/947iRm1RsLKvlllxwGcn4RN27LibGWceix4Y+Pmw3jsnTcCQIgWjqA==}
engines: {node: '>=20.9.0'}
cpu: [s390x]
os: [linux]
libc: [glibc]
'@img/sharp-linux-x64@0.35.3':
resolution: {integrity: sha512-8pqvxubL2PGdhlPy6GLqzDYMUjyRmKAwKHYKixpdJYBUK7PJ0C029XdsnpFIdgRZG68fZiGdHVWcKPvtiPB4cA==}
'@img/sharp-linux-x64@0.35.5':
resolution: {integrity: sha512-SxFtLTeJInhAA9Q836kux2vZNeOBQEx658qvbboZScr0wIARym3IcGmW7KpVD5sbVg0Ojy+udFQdayYIZyoNog==}
engines: {node: '>=20.9.0'}
cpu: [x64]
os: [linux]
libc: [glibc]
'@img/sharp-linuxmusl-arm64@0.35.3':
resolution: {integrity: sha512-Vz0iQjzzcSX3HCbfwFfCSG/9SCIqyO0mH2sXyiHaAYfBk0cRsCWXRyQYX0ovCK/PAQBbTzQ0dsPQHh5MAFL59w==}
'@img/sharp-linuxmusl-arm64@0.35.5':
resolution: {integrity: sha512-9HbMclmI1zlNkFRs3z9/eBtDjfD0sGlrX1z6b1qwmiFY5ElDLh4BC0LPBdVp7z1DXFiKlIcznf+ZlsuZzLxQqg==}
engines: {node: '>=20.9.0'}
cpu: [arm64]
os: [linux]
libc: [musl]
'@img/sharp-linuxmusl-x64@0.35.3':
resolution: {integrity: sha512-6O1NPKcDVj9QEdg7Hx549EX8U0rp6yXQERqru6yRN7fGBn32UvIRJUlWnk+8xDCiG76hXVBbX82NZ/ZKr0euIg==}
'@img/sharp-linuxmusl-x64@0.35.5':
resolution: {integrity: sha512-4KOphqB035HrVdqLZfCgMzzERrQkkzOwRhl4OAkRO1YCldbaFjySXMaK534Mo0V+LndnlJk+sbUyLeU0ULyD1A==}
engines: {node: '>=20.9.0'}
cpu: [x64]
os: [linux]
libc: [musl]
'@img/sharp-wasm32@0.35.3':
resolution: {integrity: sha512-cZ0XkcYGpHZkqW6iCkqTcmUC0CD9DhD5d/qeZlZkfRBn6GnHniZXLUo5+9xw8Iv76YE6LQFN9YNBlKREcCG76w==}
'@img/sharp-wasm32@0.35.5':
resolution: {integrity: sha512-Ptsga1su4tQx+LLF1ECS9U6nz5kmrXKo6XVbtR48Ke3ZRxxgaWBu7IDtEe1quo8hiupwm6WFqxVlXaSf7IINGQ==}
engines: {node: '>=20.9.0'}
'@img/sharp-webcontainers-wasm32@0.35.3':
resolution: {integrity: sha512-2rnq7bX3NzeR2T4YWgz8qiG4h3TSdMe+vN1iQXpJleSJ3SM5zQ8Fy2SyyXAWlbxpEZ2Y+Z4u1BePgJEYbSy80Q==}
'@img/sharp-webcontainers-wasm32@0.35.5':
resolution: {integrity: sha512-hfhF/FmoQyTUkA0bIKFOtw536BQSeBMe6BF6QyWlrPxT754+TFLaZ7sKKTfvvM0yJgKgaYTwnFCIZ/GuDw5SUA==}
engines: {node: '>=20.9.0'}
cpu: [wasm32]
'@img/sharp-win32-arm64@0.35.3':
resolution: {integrity: sha512-4bPwFdMbeC4JQ8L8LOyWp6nsHcboP5fxkp6iPOXz2Vg49R42TuMs2whkJ5OAP4/Ul035qOzy0AecOF9VOscn4w==}
'@img/sharp-win32-arm64@0.35.5':
resolution: {integrity: sha512-X4t7g+7ZA5DKblCBEXGjUqqemj4vczING/5viFwAL8h4N3qYeyjwdCvRLHi4EdOUI+2Z7UFlp1VM+p/AuEtm6Q==}
engines: {node: '>=20.9.0'}
cpu: [arm64]
os: [win32]
'@img/sharp-win32-ia32@0.35.3':
resolution: {integrity: sha512-r53mXsBN6lFUDiST764SvgwUdHAqM4rPAiDzAmf4fLoB6X/rkfyTrLCg6+g17wJJiCmB3JYgHuUldCWUIRFSXw==}
'@img/sharp-win32-ia32@0.35.5':
resolution: {integrity: sha512-5Zm82LoBc43nhwNybZlG7Y1KO//Zhsn306fQl29ZOuStHLGTo3BWL83q3cznX0poxSAMuYL1On/BHBxkBeKr6A==}
engines: {node: ^20.9.0}
cpu: [ia32]
os: [win32]
'@img/sharp-win32-x64@0.35.3':
resolution: {integrity: sha512-D4y1vNeZrIIJCN+uHaWVtH86B+aCrdMYYjicy9pXHvbGZeGYLLSd3wdVuC37FxVXlU1ARsk84eKWfWMXGYEqvA==}
'@img/sharp-win32-x64@0.35.5':
resolution: {integrity: sha512-x76eH0vEiHlcMQu8Y8IenntaACtddpT6W0wmXtWrnKcnKI7ME5DdgqhAD6SEWOEl1v2zDvkZDhFA9KnURwpfqg==}
engines: {node: '>=20.9.0'}
cpu: [x64]
os: [win32]
@@ -2311,173 +2341,173 @@ packages:
peerDependencies:
vite: ^5.2.0 || ^6 || ^7 || ^8
'@tiptap/core@3.30.1':
resolution: {integrity: sha512-HWEO6Qi8fI8Zt8X4atVV6GxthJx7Vrjr6L5YFq4OkjYJ7HG2/bFw6IKsDA3jOYgY4DM9lv8IadaiWUQ2JJRIBA==}
'@tiptap/core@3.31.4':
resolution: {integrity: sha512-cIVUQYBycighzsk6GbmCef8ZiEDVxoT5EuAe4xg2q9dNkDqCXVqI4YGr9aavCQEiAS4uHiRwqvcsPqJHipFDfg==}
peerDependencies:
'@tiptap/pm': 3.30.1
'@tiptap/pm': 3.31.4
'@tiptap/extension-blockquote@3.30.1':
resolution: {integrity: sha512-nUBIPmf9fKfBzeMpQsexsHw3/ICzDfBK8DN9uLf5wO3I/gWycAByynvfNIqfHyl6Q77QgTIHrdjihEXUSYzeJg==}
'@tiptap/extension-blockquote@3.31.4':
resolution: {integrity: sha512-FHnbvCIW0eDKgUUFjmvEB0ax9YUBW7/Unm+38qpuQROvU872yRqhnDtycKT9IKk46jNdjM4A4fAGUbge10CrAA==}
peerDependencies:
'@tiptap/core': 3.30.1
'@tiptap/pm': 3.30.1
'@tiptap/core': 3.31.4
'@tiptap/pm': 3.31.4
'@tiptap/extension-bold@3.30.1':
resolution: {integrity: sha512-xnEGv7evFQquT0r/byjBt5iqDYQi6bF/W7DPO2rAG0z33mShDEUHhE407jakMiOSYNUilFjuQj1naLvwpQC3Bw==}
'@tiptap/extension-bold@3.31.4':
resolution: {integrity: sha512-QX1xoXvO0gz6uVsoLegumqQ4MJXL7lcUDQZ4J7la7LBqiIh4efrCmp2EXbhpr37M/YQL7w8q3g7H/VovSh7xIg==}
peerDependencies:
'@tiptap/core': 3.30.1
'@tiptap/core': 3.31.4
'@tiptap/extension-bubble-menu@3.30.1':
resolution: {integrity: sha512-6asFH7ZW0gTh0aX7qrABYRVqiU/IOSjk4PKt6qdZM5YS455bjVLZDM0PcIccMDaYiomuCNjfuCSj5dxmPDoWiQ==}
'@tiptap/extension-bubble-menu@3.31.4':
resolution: {integrity: sha512-Ml/yswuys+3QkOMajfXyAGlUKCxEbT3dMyF8vHwQ5mDk9MDm+dqp10It5CrDBkUT/KkVTa6AJI3kb0TXZJZiAA==}
peerDependencies:
'@tiptap/core': 3.30.1
'@tiptap/pm': 3.30.1
'@tiptap/core': 3.31.4
'@tiptap/pm': 3.31.4
'@tiptap/extension-bullet-list@3.30.1':
resolution: {integrity: sha512-9bf4VgIxaOe+c7W20WZGUU5d2SFNGSpVwDGU/7jGlnjobzR7ZhDK/THk9JQr8OpvZrVN8Nmf84ZD3axjt+h/hQ==}
'@tiptap/extension-bullet-list@3.31.4':
resolution: {integrity: sha512-gHn1RDR4olCnmKpH+G9x+f8R+iKPNVdLlvbFkIpR/stjabZi7fB4Rr5GiHWsulbtxAjdQrNjnVeNdGM4rBxApw==}
peerDependencies:
'@tiptap/extension-list': 3.30.1
'@tiptap/extension-list': 3.31.4
'@tiptap/extension-code-block@3.30.1':
resolution: {integrity: sha512-j19Ml9BpvHgTMSN4SfkJ26B5xSuHaxPXvMoXyAX1iOoSc4J92sCqShLGgpmtce42sIroYFRs4sBv0ndBRDcxtA==}
'@tiptap/extension-code-block@3.31.4':
resolution: {integrity: sha512-su/B/cfQ1HpiHl6JfJ6wa+RVEptiYsV4dCntE8XQNj2dUcII4y6w6PQbbswOoDrU79uXHsAvpy44215rghG4LA==}
peerDependencies:
'@tiptap/core': 3.30.1
'@tiptap/pm': 3.30.1
'@tiptap/core': 3.31.4
'@tiptap/pm': 3.31.4
'@tiptap/extension-code@3.30.1':
resolution: {integrity: sha512-oqLEOLZel3lrLhACP4vMhH5RNbV9yxFsJYiOmQjjEFEoCEWWxVfJuhZ+8NFS3mUCdgy77G62XimjEvqC3+7V7Q==}
'@tiptap/extension-code@3.31.4':
resolution: {integrity: sha512-pzi8GNDs5J1DEoO1HNYiJ5WZpgOZzlc/2VIa77mMpEuD4hZxuK0BQzlF/O6Ps/O5P1lGJT4xS0Yowpzb9mD9RA==}
peerDependencies:
'@tiptap/core': 3.30.1
'@tiptap/core': 3.31.4
'@tiptap/extension-document@3.30.1':
resolution: {integrity: sha512-aeMhO7EDoJyl5AGkF9hDQ9e52s18L8oYdbYjzcmXeo0YuXeaxbZCuUueVH4DnTfV7/vSkDRpDkk4TFF5LfKQpg==}
'@tiptap/extension-document@3.31.4':
resolution: {integrity: sha512-vT20u62RWR+BLIyNJ54e24MPUY4zm8VF7tcgJnxQXi7BgVJWcNMtMWWDIkPJ4JNXwfoIznKh3jzYsaYmbCJgQQ==}
peerDependencies:
'@tiptap/core': 3.30.1
'@tiptap/core': 3.31.4
'@tiptap/extension-dropcursor@3.30.1':
resolution: {integrity: sha512-N3R5rA01WX/brGm1Qcnf2KLu0xkyGbRPMsHzzl1YG2sOLQM9t+FQi1T8WvSJiOpfYBVaWQkILUoCymADSpgUAg==}
'@tiptap/extension-dropcursor@3.31.4':
resolution: {integrity: sha512-5TbCPCC4RL+QT1NYHjXCYDBHBtVqYOFvFEnk3v98lvXCM2PNL13ofXUfJPuykAVy1OdTAx4DlZOQAD9WKy/IdQ==}
peerDependencies:
'@tiptap/extensions': 3.30.1
'@tiptap/extensions': 3.31.4
'@tiptap/extension-floating-menu@3.30.1':
resolution: {integrity: sha512-/Rlqsn7OXThZNzD0Qq/Ru4rZCFj3YnxL8Vf01cez+pvcyxgbwf4b9Wir+1JWJmaEMYsmWprSgEBW0l9ctbq97w==}
'@tiptap/extension-floating-menu@3.31.4':
resolution: {integrity: sha512-pKzulkDmolpVt7IP9FSPsC5pKbXQ5pyeqkaAYr8cyOVW+KUObpPRIzAuXvEso8PCjWgwJ1f2qyishOEMw8MbfQ==}
peerDependencies:
'@floating-ui/dom': ^1.0.0
'@tiptap/core': 3.30.1
'@tiptap/pm': 3.30.1
'@tiptap/core': 3.31.4
'@tiptap/pm': 3.31.4
'@tiptap/extension-gapcursor@3.30.1':
resolution: {integrity: sha512-dp13WaPNj32SkDN9tun0OtwtfdO/y52BJWigwXPgpUFLEyr8hQ84TxXL3rDshGHE4Eyjdd+erdBHisjBEV9vwQ==}
'@tiptap/extension-gapcursor@3.31.4':
resolution: {integrity: sha512-hBogUAepQH6IQUhaePhPWzQ1Oeu+yqlJVMKO06Eue5m36uRy1PZZAQ780pCGT0SC4R5MH2td1N8EUQ7bupQdDA==}
peerDependencies:
'@tiptap/extensions': 3.30.1
'@tiptap/extensions': 3.31.4
'@tiptap/extension-hard-break@3.30.1':
resolution: {integrity: sha512-2emcEkSSj+Y3dXXEugKQZDNeWCum0LBuKFmun7SiJyLOdxNuRnxwm2l4/LLiEHGDJBchDti4Oo8jZGLTRt4uog==}
'@tiptap/extension-hard-break@3.31.4':
resolution: {integrity: sha512-oLNajLyUvRMg1qtzFgKGduygTlpaHFB9PU+M+pUWdA4sa1bRnxS2Y++Ao4lHYopJ7nxatLndrYoCIHkotqkPDQ==}
peerDependencies:
'@tiptap/core': 3.30.1
'@tiptap/core': 3.31.4
'@tiptap/extension-heading@3.30.1':
resolution: {integrity: sha512-TGM1ZNeH/D8HQK59vFMZql2gesMoHy+6nLq3mFsYpiRT2TShMGoq+K7SC+ty6N5a/k4M82ZQrDq8t4zjiiS5xA==}
'@tiptap/extension-heading@3.31.4':
resolution: {integrity: sha512-KqlSeLathpdxCc3kVHBCA/RkHvb+V7u5vgiSEu1Cv3L70r7F19COMGODav/k1AGYBgEXKL6lM9cy8cloE5/28g==}
peerDependencies:
'@tiptap/core': 3.30.1
'@tiptap/core': 3.31.4
'@tiptap/extension-horizontal-rule@3.30.1':
resolution: {integrity: sha512-fTaSDapNV3cRgsek94z/Z4+Fyr0UpkE8/9PdSvt9MHYo/2yx3mmuamEJwUhUCpe7gDfzGdrMMsRpDOcd0b1ZEQ==}
'@tiptap/extension-horizontal-rule@3.31.4':
resolution: {integrity: sha512-ws0Kh8kfHL7ESILSXj0HJAXs8G10ewlW1sedXKm8dm0op9L8il+xl8bEJVoqG/jMf77900Fc2CaQldcCGRpF1g==}
peerDependencies:
'@tiptap/core': 3.30.1
'@tiptap/pm': 3.30.1
'@tiptap/core': 3.31.4
'@tiptap/pm': 3.31.4
'@tiptap/extension-image@3.30.1':
resolution: {integrity: sha512-LChYqHC0u7dq9/zj9R+SYgT04tuT549tzFeu8Ymo1TJ46Y9Iy3BfwlETZRvyV8NkC/eoGqUevJeD3HtG/vMaog==}
'@tiptap/extension-image@3.31.4':
resolution: {integrity: sha512-vtD+rK73abJnPRKOjU37sQTCa5Auyg451yhqZD/3lZKiFJDBHAZKI7lQARkXBQYK289hBhK6yMIwofoqCBvi3g==}
peerDependencies:
'@tiptap/core': 3.30.1
'@tiptap/core': 3.31.4
'@tiptap/extension-italic@3.30.1':
resolution: {integrity: sha512-KldMtozKk8OBHexo6akCQpJCWMgJ5OwPYuEOJO+vFjgw6VS+9N16peIo5dY+vXAiVIZL+swEsBp4barwz7NiLw==}
'@tiptap/extension-italic@3.31.4':
resolution: {integrity: sha512-pftTQBPYZocNDAj/u6OyDIp7YxmN0+N895cQPogJQ6qMHdLiRhzgawiEewJkniyN1w8k3Wx/Etp8pTKrxaOOBQ==}
peerDependencies:
'@tiptap/core': 3.30.1
'@tiptap/core': 3.31.4
'@tiptap/extension-link@3.30.1':
resolution: {integrity: sha512-krq5yHDT4K+u2r7rS0CGk/yafyAKY1gbxexfbMmq+jyHtUUrr0UuCT1nOTa61bV3zX1nrObM1lKSpZ5FgbfdhQ==}
'@tiptap/extension-link@3.31.4':
resolution: {integrity: sha512-LLxW3qfeL9gvDdMPVdxYFYZ0zZSjsoIDhOqjJ5U2UfaK3U2iwkKTBgV2qT+FWf3uUHmBRjBZ08q3dRPmWBAD5g==}
peerDependencies:
'@tiptap/core': 3.30.1
'@tiptap/pm': 3.30.1
'@tiptap/core': 3.31.4
'@tiptap/pm': 3.31.4
'@tiptap/extension-list-item@3.30.1':
resolution: {integrity: sha512-8QqIKvEqXKlT4Cv3ZySHAq1jzKimT/UnFxNBCnkBg+aZBgImvPlRFdN5nFs0elxiSQ7Jtt6k29aOyrkjxgyeew==}
'@tiptap/extension-list-item@3.31.4':
resolution: {integrity: sha512-ao8XAPvhOrtGFILs+fKtOM91omDSpUC8X19m44gbaq5xJDVpIMZ0rP+NAVUS72G7LskhcHa0bu/Pm022bUVjVw==}
peerDependencies:
'@tiptap/extension-list': 3.30.1
'@tiptap/extension-list': 3.31.4
'@tiptap/extension-list-keymap@3.30.1':
resolution: {integrity: sha512-ju39AnbRyWr1vG4GnMM7BritV/9heGMeH103wccXgJU9hOEqzwmN9zpf5CZqeRnXOjzwQiojrEL+nwm5MnuboA==}
'@tiptap/extension-list-keymap@3.31.4':
resolution: {integrity: sha512-etdkl/YnYfwtALDxecJ+4WYgJAez1fn1vzzZqJklZBpgqiZtny3NxpvWS7/JhsltNG84Wo/oovpfJgVmzhddrw==}
peerDependencies:
'@tiptap/extension-list': 3.30.1
'@tiptap/extension-list': 3.31.4
'@tiptap/extension-list@3.30.1':
resolution: {integrity: sha512-LizBu3fWrjifsL7QxKt+NhnHJDVGW/WWL+5asfqyznMqCZTy6tvPJ2W39pUHH2dBaVtzRIkAlsgWjDcNX/ep/Q==}
'@tiptap/extension-list@3.31.4':
resolution: {integrity: sha512-Wd4YRYxOdf+TtF33/QsRo1RK1CecVxjYGjUBIKNGHAywfh8tghgV4YOUCiV1iJqYt2IBs+RWlIqTmdIPzBrd4A==}
peerDependencies:
'@tiptap/core': 3.30.1
'@tiptap/pm': 3.30.1
'@tiptap/core': 3.31.4
'@tiptap/pm': 3.31.4
'@tiptap/extension-ordered-list@3.30.1':
resolution: {integrity: sha512-kJ4+4I1n4b5jV8OpWH+dbKOlIWTWVPv/fxDQKZuAT/a0DRoowpRoAuGJ0Nk9/Azeqel6c8Ocb4n1J7xLR6Xl2Q==}
'@tiptap/extension-ordered-list@3.31.4':
resolution: {integrity: sha512-UNWyF+Dhm08znszIMnT8Y2kJ1lvtiCvUGs1aG7m9cPHxDa7VycWnOqdtXWkc+6xRLNiC0zgWCFWgLLHduV8pRQ==}
peerDependencies:
'@tiptap/extension-list': 3.30.1
'@tiptap/extension-list': 3.31.4
'@tiptap/extension-paragraph@3.30.1':
resolution: {integrity: sha512-1kAYoyIF88l2qPgHi9ZeS5D5N7TzdJg3FvVCOJvUplQtBIwrsNAx6zaH/16NKN7kRiW7zETMKXMKkhL6MIvnDg==}
'@tiptap/extension-paragraph@3.31.4':
resolution: {integrity: sha512-WZlBgw+XVKXerRlmEsoQ7x1PwSU+dh3X+b70+ViZsLebdfsmmYBIaNDrvIGwG9UQr5eLnddJnZplMSTI2YKs+g==}
peerDependencies:
'@tiptap/core': 3.30.1
'@tiptap/core': 3.31.4
'@tiptap/extension-placeholder@3.30.1':
resolution: {integrity: sha512-z0WAMWLF6Hh3C1kuhcYVA3srK0J7d7UNdrY8hw4LlrhmJv1xIoZSUIwH+c8WkMJ5c79idL2zpeWVCtGEyH8UHw==}
'@tiptap/extension-placeholder@3.31.4':
resolution: {integrity: sha512-3SoJopaOzaA9IZHSk9ZXHiO6ib8ErPh7ssRh3bIaxfD+BqRyXcm4hV4yfFXTqrDCnl5FE+DbBRGmeVAVkDko3g==}
peerDependencies:
'@tiptap/extensions': 3.30.1
'@tiptap/extensions': 3.31.4
'@tiptap/extension-strike@3.30.1':
resolution: {integrity: sha512-vyF8TPTARaN5coGrFwqVREPXDONE4SXJMIxo9qMaBJWl7723m+Enpy7qmFscnwifW5XTpOA8jKl/QJ7qud/ljw==}
'@tiptap/extension-strike@3.31.4':
resolution: {integrity: sha512-4MBr63TJIMqxCjE+gAPdlY1IdELgvbAP516K3Ntn/BkcpEbEoPMEYVz+XTxULyHQEuARDO6jsXPPxRXpA74jaw==}
peerDependencies:
'@tiptap/core': 3.30.1
'@tiptap/core': 3.31.4
'@tiptap/extension-text-align@3.30.1':
resolution: {integrity: sha512-oYAYySpwcgcHSdZw8xgMqF+sCNUgM7v/2UQj0amsBOLbk5VMfiLdJp5Ku+z5aqCwBBUjb6sInh1Amzd3q57w4A==}
'@tiptap/extension-text-align@3.31.4':
resolution: {integrity: sha512-Z8x/x2N4BiFwu7+UfJABT/OQbwZPUHbmvAH6Fo5lefgeIlTwAXFBzz4x7Eeed7iJAcXUzOnyd6bmkhK36gYJ2g==}
peerDependencies:
'@tiptap/core': 3.30.1
'@tiptap/core': 3.31.4
'@tiptap/extension-text-style@3.30.1':
resolution: {integrity: sha512-VLci8TnjAxBGbNlailFabGAv8ayPR8roGeNhS25UG90XJxiO2FZUHmOv+vqrciQ7Zfs5BXyrGQY+Yb3dAtHgIw==}
'@tiptap/extension-text-style@3.31.4':
resolution: {integrity: sha512-djeemliEWmqs3cJrUyMG+17HbjHyf6D/0NkIHl/RkVHYOCX1AYMZ7r+XLefApSNBCAx/aFh1hJzaMlnGbnTaAw==}
peerDependencies:
'@tiptap/core': 3.30.1
'@tiptap/core': 3.31.4
'@tiptap/extension-text@3.30.1':
resolution: {integrity: sha512-Zt3Ik95ZKJx5YNzC6TUXWTNBHUfRH/qVENmqfPjA7x7q4cqNdOEU+tX9DrlFjgxu/x0k48dlQS0Kaop24/vihA==}
'@tiptap/extension-text@3.31.4':
resolution: {integrity: sha512-WL2ZRpFksgQcXJN05/UQH09YCH9LlhdLrSEcviASvEH/aAfsKiJlDrouN8PET+YAWK/F5xFc5/Q1z0xj+I23jw==}
peerDependencies:
'@tiptap/core': 3.30.1
'@tiptap/core': 3.31.4
'@tiptap/extension-underline@3.30.1':
resolution: {integrity: sha512-Xro/EzEgWW+46ztrw5f4epDWQvcGpD+HUykzigj30bpIS8Vv4XDXFt+89dAcyDC7zRUMQULlWZV0UCyRLFEKBw==}
'@tiptap/extension-underline@3.31.4':
resolution: {integrity: sha512-0iFGPIqmqGkCrOFh1OPo7Dzv5s86dVN3bZaugsz0jK04otkb8bn04ZDKP2/6Wq+lcK0M9DfJ0QqfW5hC2nBZxQ==}
peerDependencies:
'@tiptap/core': 3.30.1
'@tiptap/core': 3.31.4
'@tiptap/extensions@3.30.1':
resolution: {integrity: sha512-prNrvF1oMj+tngCLmZgXzQfuUuvYMbCe2sEPXXKJPqJNNffHd2wAuUZ0h6UQS5aRILaxB2kN1/yfwpxnhRpwYA==}
'@tiptap/extensions@3.31.4':
resolution: {integrity: sha512-nIupIne/sHpV2Kw1M4MOmr4+G454KI8BXM5+LWKOU2EpzfALZWVSy7koF9lVRna4Zp5Gv8hfg5Z8kvmrYQvhkw==}
peerDependencies:
'@tiptap/core': 3.30.1
'@tiptap/pm': 3.30.1
'@tiptap/core': 3.31.4
'@tiptap/pm': 3.31.4
'@tiptap/pm@3.30.1':
resolution: {integrity: sha512-2bHY+ihexKpfBURbAD+ahY0+lWle0TueTK80GAGYqZ/G7ErAPOmzN5RVAGMyZJI4wDkcFycRNLi3k0/3VT9a1A==}
'@tiptap/pm@3.31.4':
resolution: {integrity: sha512-x7dwth79cQp4QGae66/RJqMDJQv0+n7b7dD7cyFQBWqzPFLJxN/4paHY4qbWedZjNLhRyIOAD9a5q4AYM1zmPA==}
'@tiptap/starter-kit@3.30.1':
resolution: {integrity: sha512-6P2WEp2ZHsx8LV6hQ3K/MB3n39oiVhKESlzh0v+KhK8RR/iKSKGxvTFbi1D/1fo4j3fZf+43PGHjyKXskn9ZmQ==}
'@tiptap/starter-kit@3.31.4':
resolution: {integrity: sha512-u7HIJ9vK6kFe4S4J2CIcDLrhRZzoTMSyU0XxjQ6lUNF7pWW3rMi//pvi8RcsZmxGjyb8CpXAqYgNl7f9WyaUeA==}
'@tiptap/vue-3@3.30.1':
resolution: {integrity: sha512-Y7YUkC+LpHcnraN0GwJtySh7XZnveqde5SsHV5X7h1erflp+WT7rG52sE0WKyFOSc3t9ZKSJ/4IBUtB3+ZQHrA==}
'@tiptap/vue-3@3.31.4':
resolution: {integrity: sha512-xi/t4EfSX9QEbOSC0RhkzhT78GJSeJ2Jmz0EBV860lcxDdNd+Xw1pfOjFseNz2E+r3zhTcKDBsLblhCao7eF5Q==}
peerDependencies:
'@floating-ui/dom': ^1.0.0
'@tiptap/core': 3.30.1
'@tiptap/pm': 3.30.1
vue: ^3.0.0
'@tiptap/core': 3.31.4
'@tiptap/pm': 3.31.4
vue: 3.5.43
'@tootallnate/quickjs-emscripten@0.23.0':
resolution: {integrity: sha512-C5Mc6rdnsaJDjO3UpGW/CQTHtCKaYlScZTly4JIu97Jxo/odCiH0ITnDXSJPTOrEKk/ycSZ0AOgTmkDtkOsvIA==}
@@ -2843,14 +2873,14 @@ packages:
engines: {node: ^18.0.0 || >=20.0.0}
peerDependencies:
vite: ^5.0.0 || ^6.0.0
vue: ^3.2.25
vue: 3.5.43
'@vitejs/plugin-vue@6.0.9':
resolution: {integrity: sha512-rD/MORlhaZMlXWW0rEn4FB4wMinWC0z/D7Ye160S5+Rs1mC8ZDAdcDp4SjKfUhG5t8q1ktcPVw4xuTkPRlzrSA==}
engines: {node: ^20.19.0 || >=22.12.0}
peerDependencies:
vite: ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0
vue: ^3.2.25
vue: 3.5.43
'@vitest/mocker@5.0.1':
resolution: {integrity: sha512-6K1DoBNAPGvuOcSsGA4D6x+5zEEff/KmOOP3uetT2TrGpVfI+HRHRnJJfKi5ib/g1vx8IYHQD8s0pbJz8WQI7Q==}
@@ -2883,14 +2913,20 @@ packages:
'@vue/compiler-core@3.5.41':
resolution: {integrity: sha512-q0Xtv/F9w2YO/7htQhtiL+Ev2WCJbe5N2hc+XfgyKkEKqWpSxknmT8QOuGdEKNdjPq0c3F7rNpFkTo3Kfrm7pg==}
'@vue/compiler-core@3.5.43':
resolution: {integrity: sha512-zdiLhnbe1QQqgDT8xZMpNmyqZ3qlI+/Q/FHQco57Kwl/b05HhCzN6eVGN9QU9rbga4CrS0H5SYY8VGHZCt/1Hg==}
'@vue/compiler-dom@3.5.41':
resolution: {integrity: sha512-oKacVfNglLvGjnS6BXOlGL7EyG2h8X03pqXCjzotRZUaXGjbrTJUnVAQjrCqUnS+lyu31nwQjZY/d817GmCnfw==}
'@vue/compiler-sfc@3.5.41':
resolution: {integrity: sha512-XJhip7R2wy6vX3knCxdZN4KracFaZUef58s1KYewqluedHIJaPIVfXoYT7MF1F8nCvv6k8bWWxDC8opMkg1VTQ==}
'@vue/compiler-dom@3.5.43':
resolution: {integrity: sha512-PEZoAk3NQmsn/ejMzSOCyTYqwGqczrWm70PuhBKjjv1+TCoQAaO/zOqNwjV+honlNstT5ILxtc+8r8UUfj+iEQ==}
'@vue/compiler-ssr@3.5.41':
resolution: {integrity: sha512-U3v5OejKEGqOI0Wy0+Sz7hGuIFZHA4LSXzrNM3IMIeDyJEBBfTpX26n3SDgToRpP2bLc9FfI2j/kSgcJ8Emq5A==}
'@vue/compiler-sfc@3.5.43':
resolution: {integrity: sha512-FCbrG3XNCRl+js3huuKx4IVHBLTvMkJhVepjbxSPu1gn4yWLaYtGNQdjJGZaMytXB6qb76qQDDmDSLy/vkmleQ==}
'@vue/compiler-ssr@3.5.43':
resolution: {integrity: sha512-GF62orf7KiJX9RqrHNGrYBudsQGD0OhJ5nDs90O8UiDuS40+YMYomiXu6w6EuvtXuRDc3MSNis3EaaSKAVSWpg==}
'@vue/devtools-api@6.6.4':
resolution: {integrity: sha512-sGhTPMuXqZ1rVOk32RylztWkfXTRhuS7vgAKv0zjqk8gbsHkJ7xfFf+jbySxt7tWObEJwyKaHMikV/WGDiQm8g==}
@@ -2907,33 +2943,36 @@ packages:
'@vue/language-core@3.3.10':
resolution: {integrity: sha512-CR7ByBbgPHqhxrioKPOcZBqttaozzLNwtkCzXQ+uF8gLPHnUe03srPnGpdtHD3zp+bq5iyVkZ1WNx7W564RPwg==}
'@vue/reactivity@3.5.41':
resolution: {integrity: sha512-rznsqKM0np0x18EjzF8x88MpEhdNsffbvFbckLL5+oUKz1BxAImEmO7J1ArRYSyo6aQaVoBDp7jEkT91OOxydA==}
'@vue/reactivity@3.5.43':
resolution: {integrity: sha512-G/c9GyOZNI2jVaaS6OX1EF1SSFSv7H0ERqNTl4+DTFMlZmB5eVAB53aLQNam/7NL2NPtaDD7RdVrzf8uJzMuOA==}
'@vue/runtime-core@3.5.41':
resolution: {integrity: sha512-Vcry58hiAKwGen9Z1jUZE0feFsNArPCMOImYI8el48A9Idf6DuQYD0U05zZIF2Iad1hGhPSvcbBbAOhNr55fhg==}
'@vue/runtime-core@3.5.43':
resolution: {integrity: sha512-hU6U6VnVhBGQDpvlnnDlIB8ZGJBiOcgk2lh/0InltHiz3D8oSkluvuvY+do1G2H3+udeKFsmaBlgVYP7gXQzEw==}
'@vue/runtime-dom@3.5.41':
resolution: {integrity: sha512-3vVBahVBS9+U6cmXBLyb8nE6/yYo4J/CGI9eVFs3KiMc0YHuudwKyShTD65jtJy/L9PUUxNAFu4cj4LiJ0UFbw==}
'@vue/runtime-dom@3.5.43':
resolution: {integrity: sha512-Bb2Jc0YjjJdMt1SJmb9b2L/IWd3I8lIT9x9eS/xvvP9CiVgna0ffua74xKRmt4/uSJ+0r4iN8ex1jrqkhQGWQw==}
'@vue/server-renderer@3.5.41':
resolution: {integrity: sha512-n6hx/pNFfbD6SuyeuMVkvqox8bwf/ET9JlA/kAz/imw8sw++wkqKe2mHX5KutjPpbKE4Z56yTHszoOjGMI9igQ==}
'@vue/server-renderer@3.5.43':
resolution: {integrity: sha512-l2Ygjv9NehV94PSBxNWsAHC0j/eIIKbn92mBuWXAPGLnn6HfJ6MH5ubsd+Nk0YoZ5FRuxWI1P2VSoh+dbPQhCQ==}
'@vue/shared@3.5.41':
resolution: {integrity: sha512-IOnwSCma8j+9xJT6b8H0dEYidC80NsYmNMlZxRsukYcSoGaDBohog5hDxzeUXdFeGWFA++vWvxqOmrr96VlqMA==}
'@vue/shared@3.5.43':
resolution: {integrity: sha512-uksS7YGMR5NZyr4JNq0Rp+QyLns0ueaz20KwzIPW9R0LH1Vnt4E+XUM29PNseEbf1www2gOuhuDi5AKOIXag9Q==}
'@vueuse/core@12.8.2':
resolution: {integrity: sha512-HbvCmZdzAu3VGi/pWYm5Ut+Kd9mn1ZHnn4L5G8kOQTPs/IwIAmJoBrmYk2ckLArgMXZj0AW3n5CAejLUO+PhdQ==}
'@vueuse/core@13.9.0':
resolution: {integrity: sha512-ts3regBQyURfCE2BcytLqzm8+MmLlo5Ln/KLoxDVcsZ2gzIwVNnQpQOL/UKV8alUqjSZOlpFZcRNsLRqj+OzyA==}
peerDependencies:
vue: ^3.5.0
vue: 3.5.43
'@vueuse/core@14.4.0':
resolution: {integrity: sha512-X4WHz1HlCzCBoYXesUkifzzWBAcZgXG8Fi5iNPQg/epdzOB3gu8Fawj3hvuwYR1nGcXGnvxwYYcUC/71++svtQ==}
peerDependencies:
vue: ^3.5.0
vue: 3.5.43
'@vueuse/integrations@12.8.2':
resolution: {integrity: sha512-fbGYivgK5uBTRt7p5F3zy6VrETlV9RtZjBqd1/HxGdjdckBgBM4ugP8LHpjolqTj14TXTxSK1ZfgPbHYyGuH7g==}
@@ -2991,134 +3030,156 @@ packages:
'@vueuse/shared@13.9.0':
resolution: {integrity: sha512-e89uuTLMh0U5cZ9iDpEI2senqPGfbPRTHM/0AaQkcxnpqjkZqDYP8rpfm7edOz8s+pOCOROEy1PIveSW8+fL5g==}
peerDependencies:
vue: ^3.5.0
vue: 3.5.43
'@vueuse/shared@14.4.0':
resolution: {integrity: sha512-JRgY90Sz8DDtPMsaDflvPMp9xYk69JZAmbuDvAquUVXKr2gEjqtzGNTTthLfckH0BzBqvnu31gb4a8TGLRe79g==}
peerDependencies:
vue: ^3.5.0
vue: 3.5.43
'@yuku-codegen/binding-darwin-arm64@0.7.4':
resolution: {integrity: sha512-fjkATm+fg4r6Ss8o82u3j33PfIqhSs4A0WEEw9kOwhMx/ui/RQ0ZAsCtF6e7UG2CWGOGXAJspLlfk2tr3rjjKQ==}
cpu: [arm64]
os: [darwin]
deprecated: yuku-codegen is pure JavaScript since 0.14
'@yuku-codegen/binding-darwin-x64@0.7.4':
resolution: {integrity: sha512-Hj0KvHpS1RJY/bgM3BADzmXXkKO3+bq3M8bMq4b0j0LsVi1SeWYpD/hJLJFwHeNMS+jO4vlZ343yjb4DEb9XXQ==}
cpu: [x64]
os: [darwin]
deprecated: yuku-codegen is pure JavaScript since 0.14
'@yuku-codegen/binding-freebsd-x64@0.7.4':
resolution: {integrity: sha512-GLKBZvqVFvC1bvNPoPZRj0UxUaAZZKCzb8IPNyvRhXesOk+Af9UbhbVPwx9Do4o2AVf6R5FPzyRWWIihQdCp+A==}
cpu: [x64]
os: [freebsd]
deprecated: yuku-codegen is pure JavaScript since 0.14
'@yuku-codegen/binding-linux-arm-gnu@0.7.4':
resolution: {integrity: sha512-CAjbJexBJUqHgIPgOCJO7EYRnFluNLt5VD3jNS40wmsNZqa04HbewVVcgfmzfzuBhjX6ookntLDG3lWieyTAVw==}
cpu: [arm]
os: [linux]
libc: [glibc]
deprecated: yuku-codegen is pure JavaScript since 0.14
'@yuku-codegen/binding-linux-arm-musl@0.7.4':
resolution: {integrity: sha512-HQMuKBltnKFUqhUh8wNiivd76coRwDngdCxbcAULlatAlc2OXo8L6jLTkVnNeUuOw9JYzSq9LY2/7zvrg63Ddg==}
cpu: [arm]
os: [linux]
libc: [musl]
deprecated: yuku-codegen is pure JavaScript since 0.14
'@yuku-codegen/binding-linux-arm64-gnu@0.7.4':
resolution: {integrity: sha512-cnU7ZVxK/Oq/TIS2iq56rouy1dqnLYgWR2puWcrxGCtmbdxiMISANYhI5t2tMLzTGZMyhawM2zYlyI+gnpBupQ==}
cpu: [arm64]
os: [linux]
libc: [glibc]
deprecated: yuku-codegen is pure JavaScript since 0.14
'@yuku-codegen/binding-linux-arm64-musl@0.7.4':
resolution: {integrity: sha512-NyuabTumcxPtZv/Q+pMVvrcKxLn1SPcpdBGtekVJz7JwI36SuExTq4IG4EZsk7YDmFKP8wDEvmKYOpV/a8Lwhw==}
cpu: [arm64]
os: [linux]
libc: [musl]
deprecated: yuku-codegen is pure JavaScript since 0.14
'@yuku-codegen/binding-linux-x64-gnu@0.7.4':
resolution: {integrity: sha512-w3EnCLPD2vpJw0F+0qVV/1KSOAw4SgzjbGUbbwXUh9w5Kxo1Hdc3mN+/Nvk50oA6cCbSOCEaILnPHXPCauArvg==}
cpu: [x64]
os: [linux]
libc: [glibc]
deprecated: yuku-codegen is pure JavaScript since 0.14
'@yuku-codegen/binding-linux-x64-musl@0.7.4':
resolution: {integrity: sha512-FXKQlFjDM8FxqJ/TncAni7e7JyaXIB3Hv6boApxSs5ZUlvqP4TbrjYVk3mYDQt6xQAE/kEHplxM6m5SKx5sfRg==}
cpu: [x64]
os: [linux]
libc: [musl]
deprecated: yuku-codegen is pure JavaScript since 0.14
'@yuku-codegen/binding-win32-arm64@0.7.4':
resolution: {integrity: sha512-rWr899KEvZIWMx9yUXQl4i90OGIs4gaw4X1UsS2rxsI3qnp8acLIVKI3N5WDqaertkW10crDRZvIxxyw7kTMTQ==}
cpu: [arm64]
os: [win32]
deprecated: yuku-codegen is pure JavaScript since 0.14
'@yuku-codegen/binding-win32-x64@0.7.4':
resolution: {integrity: sha512-3Olgmkd5rDrIN9g7wJFMRRC9jub5zAwiQOtwOVhvNe/nZE/rSufFRa7vrFupqSCQml+Zxbcy9npzo3Qne3rA2A==}
cpu: [x64]
os: [win32]
deprecated: yuku-codegen is pure JavaScript since 0.14
'@yuku-parser/binding-darwin-arm64@0.7.4':
resolution: {integrity: sha512-rUetRGukIlPOkDCy+Fo0YTee66n9A04XRQMONptbemWSU27CCV6RDj56+4ne4eSE4gJ0139RWUfbqMtt744pWQ==}
cpu: [arm64]
os: [darwin]
deprecated: yuku-parser runs on yuku-core since 0.14
'@yuku-parser/binding-darwin-x64@0.7.4':
resolution: {integrity: sha512-9bHrGQUot2vWTg0YTdyIBHGd38fy2BSQH1WaqUDVuNqSn7HffrTUzWOgbaWRNd5GTOvDdrt9SDZIG7nfqzeBtg==}
cpu: [x64]
os: [darwin]
deprecated: yuku-parser runs on yuku-core since 0.14
'@yuku-parser/binding-freebsd-x64@0.7.4':
resolution: {integrity: sha512-159565OJ/LR6cCP781nH8DxB5GqlqqWk3uyOLZIznQhsj9zeht4X7+jz9IQH1l/TUio+ojEXf8yt2+DJrN+QVw==}
cpu: [x64]
os: [freebsd]
deprecated: yuku-parser runs on yuku-core since 0.14
'@yuku-parser/binding-linux-arm-gnu@0.7.4':
resolution: {integrity: sha512-j3s3LJxEbOyP58hmzuXpJlKzgxaswuOTu8nAbDpE119b5W3gP1SW+HndLscGUOACpyMdH5WJ6gBHRxq0HCRVBw==}
cpu: [arm]
os: [linux]
libc: [glibc]
deprecated: yuku-parser runs on yuku-core since 0.14
'@yuku-parser/binding-linux-arm-musl@0.7.4':
resolution: {integrity: sha512-PUpHPmvWIDxhYTS5oah08RQ28t6dlFBxRPJcftS6HgquiPsm/e0gL5vKwPJpyjaPBHzRH61IAUDDfrRe8iFs8g==}
cpu: [arm]
os: [linux]
libc: [musl]
deprecated: yuku-parser runs on yuku-core since 0.14
'@yuku-parser/binding-linux-arm64-gnu@0.7.4':
resolution: {integrity: sha512-Hi3w0et5mu3i7S+qE0UgOev4RqJ/U9DW8xn79t4nttiICYCx3NveC0Goo78uqzxttsDI3hfRY43lrrF26svqcw==}
cpu: [arm64]
os: [linux]
libc: [glibc]
deprecated: yuku-parser runs on yuku-core since 0.14
'@yuku-parser/binding-linux-arm64-musl@0.7.4':
resolution: {integrity: sha512-i073u4ENL9DJeWBRKioRCz8i5hg6EmUcH89eH1lts9f9AFPA1GphODwK6OXOXUbpi2AwZ1deFIUWgLGP2mdmmA==}
cpu: [arm64]
os: [linux]
libc: [musl]
deprecated: yuku-parser runs on yuku-core since 0.14
'@yuku-parser/binding-linux-x64-gnu@0.7.4':
resolution: {integrity: sha512-j5pt0LyDGxCzfoqRTR3cmMG21+bgSxIufAzJXFOWXkbg/22Ih51eGEsbInnSD5Q9FvJ3ooIn/jfok0dBdhudnA==}
cpu: [x64]
os: [linux]
libc: [glibc]
deprecated: yuku-parser runs on yuku-core since 0.14
'@yuku-parser/binding-linux-x64-musl@0.7.4':
resolution: {integrity: sha512-RR1hNhrSpv3FanLM6u5uD+9CYA9IM8U3uGscgvKKV77gtj7ttO4UubpwN7vcx1KknoEIQHKJFPVKoeVy+avf6w==}
cpu: [x64]
os: [linux]
libc: [musl]
deprecated: yuku-parser runs on yuku-core since 0.14
'@yuku-parser/binding-win32-arm64@0.7.4':
resolution: {integrity: sha512-1xhYwOLo9TjppHHwNYi3X/dGgOvnj9xh62jpgP4U8nEtTGA70NtJDCkN45pRQdU3B6/U7oQj1T4esP3aFJg6BA==}
cpu: [arm64]
os: [win32]
deprecated: yuku-parser runs on yuku-core since 0.14
'@yuku-parser/binding-win32-x64@0.7.4':
resolution: {integrity: sha512-HonZAapmSKusxLZPnU9WrMzAUdPSBJliGw3CSkA9Er/aq15STEOEy9SOsVGvj4maBv95EmWxt2LThHXnFnGfNg==}
cpu: [x64]
os: [win32]
deprecated: yuku-parser runs on yuku-core since 0.14
'@yuku-toolchain/types@0.7.4':
resolution: {integrity: sha512-iUFXr+UnUJjzVLNI6GIv07poi9NwcG5hTBJSheJh3SdpkYpIjCl9kAGe7dbJMHn5sXeceCL4H12pKa0b6pkouQ==}
@@ -3226,8 +3287,8 @@ packages:
engines: {node: '>=6.0.0'}
hasBin: true
basic-ftp@5.3.1:
resolution: {integrity: sha512-bopVNp6ugyA150DDuZfPFdt1KZ5a94ZDiwX4hMgZDzF+GttD80lEy8kj98kbyhLXnPvhtIo93mdnLIjpCAeeOw==}
basic-ftp@6.2.1:
resolution: {integrity: sha512-bK67isD+lKq46AU8vNtjvMaT2ZqAOAmNCbxUHlFBRD4k15NWxyEjmaKtZPlgce58So4BNTjITGQOVTjL9y0ECA==}
engines: {node: '>=10.0.0'}
better-result@2.10.0:
@@ -3246,8 +3307,8 @@ packages:
boolbase@1.0.0:
resolution: {integrity: sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==}
brace-expansion@5.0.9:
resolution: {integrity: sha512-ScQ4IuvIEF1TMlP7Zt+vjJ//9zlPb2SDcxWxM3bk8s6t6GGdJ7KO1dCcTidOPJKePW30LE/2cT7wCyPho9/Wxg==}
brace-expansion@5.0.12:
resolution: {integrity: sha512-YovQ3rzhaLMIrDjNDMkNS01tea93qhEhG5xy8f6+R0l+dw3Ki+5sCoIoI942iuLZTHWogWktgwVDhU09iNEimQ==}
engines: {node: 20 || >=22}
braces@3.0.3:
@@ -3573,9 +3634,9 @@ packages:
es-toolkit@1.51.0:
resolution: {integrity: sha512-zC2lQGkM7QX+Gm6iM3+WIdZJzthsEd14LvRNJneSO2hzyz/zNBENR8+YXWo1cKxgPBtV6ksPYHELbcwBRzmdCw==}
esbuild@0.21.5:
resolution: {integrity: sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==}
engines: {node: '>=12'}
esbuild@0.25.12:
resolution: {integrity: sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==}
engines: {node: '>=18'}
hasBin: true
esbuild@0.28.2:
@@ -3727,14 +3788,14 @@ packages:
fast-querystring@1.1.2:
resolution: {integrity: sha512-g6KuKWmFXc0fID8WWH0jit4g0AGBoJhCkJMb1RmbsSEUNvQ+ZC8D6CUZ+GtF8nMzSPXnhiePyyqqipzNNEnHjg==}
fast-uri@3.1.5:
resolution: {integrity: sha512-gHwA1O9LDIcKunMKhObS/HimwtehO1nPUECKAu5TpKgaO19fcWEl4bliWe1jWxVFvIXztJjjQ4L8XQ1EU9f7Jw==}
fast-uri@3.1.8:
resolution: {integrity: sha512-GZMtZUTNRpOVIECoXwLNZS5xUGE+mVNbTB8h/7Rwh2TFWcBQiPzTgyZi05BF9UMZKkLJv8XBRJTlU7zg8+ZfMg==}
fastify-plugin@6.0.0:
resolution: {integrity: sha512-fZOty7z3O7vOliF6d8bHE3wiEh1KcNnKEQensSgTk9C1DvN6nRLS++XVd86v33Hw/8u9Un8A1zDrQ8ujcQDHEg==}
fastify@5.12.0:
resolution: {integrity: sha512-A3RNEaDIHWaxFW8n8rNJaW1wQ+XAXuoU71llfUQJjuh5WaYLmKfRhhanaJBOx8m2EBPQkR6sDBovYdHNe9F6rA==}
fastify@5.12.5:
resolution: {integrity: sha512-OB2k1dlxs5/NAABqeKV2FUHkSD2BbENsCak8yULVcymn3fHIPDVa9TI3SDnJSWYSllZmSYuZXy2gTnsT+Sut1A==}
fastq@1.20.1:
resolution: {integrity: sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==}
@@ -3919,8 +3980,8 @@ packages:
resolution: {integrity: sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==}
engines: {node: '>=12'}
ip-address@10.5.0:
resolution: {integrity: sha512-R5SnVLJmgYYvf2F2ZgwSBnelz5G4q5AxIC277GDfUaNbrZKNANcBC7RHqYYePlszf4kBolVkJauG0ZjHHFh55g==}
ip-address@10.7.1:
resolution: {integrity: sha512-4OUAqU9Z1i3vCnS05hzGiFnEMDpQ+62pAD/MVQOp83fYyNC8GleCqaS0QikQBmcWCrKFiUs/B8ztRRiYOAXuCA==}
engines: {node: '>= 12'}
ipaddr.js@2.5.0:
@@ -3961,8 +4022,8 @@ packages:
resolution: {integrity: sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ==}
hasBin: true
js-yaml@4.3.1:
resolution: {integrity: sha512-CY6crGq313MX8GkwvB7tzgp99vjQxY1++5y10/BKN/GUfHqWaOGQMNZkBvqSzsZKWk/ijwHlWzzkLulsGHhjWQ==}
js-yaml@4.3.2:
resolution: {integrity: sha512-SFNOvSJ+Dgf/9An904Yx+CgSlIPCkIpao4qo51lpee25TIRejdH3rhR4EZMGoNx3/TP3O+wzWuiTFl4sqbltzA==}
hasBin: true
json-buffer@3.0.1:
@@ -4175,6 +4236,10 @@ packages:
resolution: {integrity: sha512-Lkk/vx6ak3rYkRR0Nhu4lFUT2VDnQSxBe8Hbl7f36358p6ow8Bnvr8lrLt98H8J1aGxfhbX4Fs5tYg2+FTwr5Q==}
engines: {bun: '>=1.0.0', deno: '>=1.30.0', node: '>=8.0.0'}
lru.min@1.1.5:
resolution: {integrity: sha512-5J9ysMYUpYIg9RF2vJpy9SinEmSviFSe0GyPpCQ4L5QSkLAgeLXlTAOu2ZwWUU5m+0SBl6gUU1R1ZQB3aKypfA==}
engines: {bun: '>=1.0.0', deno: '>=1.30.0', node: '>=8.0.0'}
magic-string@0.30.21:
resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==}
@@ -4237,9 +4302,11 @@ packages:
muggle-string@0.4.1:
resolution: {integrity: sha512-VNTrAak/KhO2i8dqqnqnAHOa3cYBwXEZe9h+D5h/1ZqFSTEFHdM65lR7RoIqq3tBBYavsOXV84NoHXZ0AkPyqQ==}
mysql2@3.15.3:
resolution: {integrity: sha512-FBrGau0IXmuqg4haEZRBfHNWB5mUARw6hNwPDXXGg0XzVJ50mr/9hb267lvpVMnhZ1FON3qNd4Xfcez1rbFwSg==}
mysql2@3.23.1:
resolution: {integrity: sha512-tTuRnC7qCet2IOfSNMYZ5SwXuBnfvBPAcIA28P0gtruXyZlU1LMxA6uha32kYypoFgyYklMqhLWwt4laYwXR/Q==}
engines: {node: '>= 8.0'}
peerDependencies:
'@types/node': '>= 8'
named-placeholders@1.1.6:
resolution: {integrity: sha512-Tz09sEL2EEuv5fFowm419c1+a/jSMiBjI9gHxVLrVdbUkkNUUfjsVYs9pVZu5oCon/kmRh9TfLEObFtkVxmY0w==}
@@ -4402,7 +4469,7 @@ packages:
resolution: {integrity: sha512-l7pqLUFTI/+ESXn6k3nu30ZIzW5E2WZF/LaHJEpoq6ElcLD+wduZoB2kBN19du6K/4FDpPMazY2wJr+IndBtQw==}
peerDependencies:
typescript: 6.0.3
vue: ^3.5.11
vue: 3.5.43
peerDependenciesMeta:
typescript:
optional: true
@@ -4439,8 +4506,8 @@ packages:
engines: {node: '>=18.0.0'}
hasBin: true
postcss-selector-parser@7.1.5:
resolution: {integrity: sha512-KvvtD7SrlBP7dlgkBghEE3r84CABm5SmV2aNcG4oCA+qDnJ/tvKonFVvwWAyyWUEwxuNawdfEAZKP9zM3oZ2Uw==}
postcss-selector-parser@7.1.6:
resolution: {integrity: sha512-7qASPzhKF2l2KLboRZux8CCTRMdGiV08vWmyKzPz22qZ7ZjQBOeY7rNzNoCLSUiftJ7HUq0GERHmxw/t0dCdMw==}
engines: {node: '>=4'}
postcss-value-parser@4.2.0:
@@ -4556,8 +4623,8 @@ packages:
prosemirror-transform@1.12.0:
resolution: {integrity: sha512-GxboyN4AMIsoHNtz5uf2r2Ru551i5hWeCMD6E2Ib4Eogqoub0NflniaBPVQ4MrGE5yZ8JV9tUHg9qcZTTrcN4w==}
prosemirror-view@1.42.2:
resolution: {integrity: sha512-Pdg0l5kXm8aLDquFAnQFTCITg0q44sLqBlHlpsVLD9segdOao8TOfQdAhCrCXyVgPSRr6UDDROOIWA3bIrN9YQ==}
prosemirror-view@1.42.3:
resolution: {integrity: sha512-oTN7EtH+CpwxU9NrwEYWd0UZ4JUx7l048l5A2Xppm4p/60isZYLnth9QVQmC3VRIvdrIWCxwZSd+Uz791G31/w==}
proxy-agent@6.5.0:
resolution: {integrity: sha512-TmatMXdr2KlRiA2CyDu8GqR8EjahTG3aY3nXjdzFyoZbmB8hrBsTyMezhULIXKnC0jpfjlmiZ3+EaCzoInSu/A==}
@@ -4704,8 +4771,8 @@ packages:
safer-buffer@2.1.2:
resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==}
sanitize-html@2.17.6:
resolution: {integrity: sha512-M4bo9tfv1yfhQZZKkc6dL07ALrGJtfvNOuhX3hU9AVPR/uPQ+nKOJBqTYc7LfMQblTW04mtSWDJWEyLvygJsLA==}
sanitize-html@2.17.7:
resolution: {integrity: sha512-PGtEkc9cbnedU3s9TmzDbpsZ8w086g/0Q8k8/oIO1NLNU3i5k9yn835CrjJSajp1KMmkisbO1qPXxNKO3welAg==}
engines: {node: '>=22.12.0'}
scheduler@0.27.0:
@@ -4727,17 +4794,14 @@ packages:
engines: {node: '>=10'}
hasBin: true
seq-queue@0.0.5:
resolution: {integrity: sha512-hr3Wtp/GZIc/6DAGPDcV4/9WoZhjrkXsi5B/07QgX8tsdc6ilr7BFM6PM6rbdAX1kFSDYeZGLipIZZKyQP0O5Q==}
set-cookie-parser@2.7.2:
resolution: {integrity: sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw==}
setprototypeof@1.2.0:
resolution: {integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==}
sharp@0.35.3:
resolution: {integrity: sha512-ej0zVHuZGHCiABXcNxeYhpRnPNPAcvbG8RMdBAhDAxLKkCRVSpK3Iyu7qbqw3JMzoj0REeM6f3tJLtVwl0023Q==}
sharp@0.35.5:
resolution: {integrity: sha512-Ywn4OnzGukp7CDMrp08RQ50YKmuwG47brZgIVPTvBaaAfQlRlygrRqSrxdCiL9M+LlzLBiJ68IR1QqvzHyjC7g==}
engines: {node: '>=20.9.0'}
peerDependencies:
'@types/node': '*'
@@ -4784,8 +4848,8 @@ packages:
sortablejs@1.14.0:
resolution: {integrity: sha512-pBXvQCs5/33fdN1/39pPL0NZF20LeRbLQ5jtnheIPN9JQAaufGjKdWduZn4U7wCtVuzKhmRkI0DFYHYRbB2H1w==}
source-map-js@1.2.1:
resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==}
source-map-js@1.2.2:
resolution: {integrity: sha512-KGj/8Y43x35aZVDtt+J4mK1hoLGHULMYfSkODJNQjNDC3oW1PqPoxMwo0pLUsWM/UEGzON/NxeHywEfNXNP3Vw==}
engines: {node: '>=0.10.0'}
source-map@0.6.1:
@@ -4803,9 +4867,9 @@ packages:
resolution: {integrity: sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==}
engines: {node: '>= 10.x'}
sqlstring@2.3.3:
resolution: {integrity: sha512-qC9iz2FlN7DQl3+wjwn3802RTyjCx7sDvfQEXchwa6CWOx07/WVfh91gBmQ9fahw8snwGEWU3xGzOt4tFyHLxg==}
engines: {node: '>= 0.6'}
sql-escaper@1.5.2:
resolution: {integrity: sha512-6CKD38c31SENivxOADeMNLdukOnUxUcflKtzVWzace7Riv1v7cAEym5Cx9Q7gYZ3ezIJI7ZpqecQq8cqNeSSFg==}
engines: {bun: '>=1.0.0', deno: '>=2.0.0', node: '>=12.0.0'}
stackback@0.0.2:
resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==}
@@ -5028,22 +5092,27 @@ packages:
vfile@6.0.3:
resolution: {integrity: sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==}
vite@5.4.21:
resolution: {integrity: sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==}
engines: {node: ^18.0.0 || >=20.0.0}
vite@6.4.3:
resolution: {integrity: sha512-NTKlcQjlAK7MlQoyb6LgaqHc8sso/pVyUJYWMws3jg21uTJw/LddqIFPcPqP6PzpgbIcZyKI85sFE4HBrQDA8A==}
engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0}
hasBin: true
peerDependencies:
'@types/node': ^18.0.0 || >=20.0.0
'@types/node': ^18.0.0 || ^20.0.0 || >=22.0.0
jiti: '>=1.21.0'
less: '*'
lightningcss: ^1.21.0
sass: '*'
sass-embedded: '*'
stylus: '*'
sugarss: '*'
terser: ^5.4.0
terser: ^5.16.0
tsx: ^4.8.1
yaml: ^2.4.2
peerDependenciesMeta:
'@types/node':
optional: true
jiti:
optional: true
less:
optional: true
lightningcss:
@@ -5058,6 +5127,10 @@ packages:
optional: true
terser:
optional: true
tsx:
optional: true
yaml:
optional: true
vite@8.3.0:
resolution: {integrity: sha512-lhZBVvEHefgE+HQZC9O7EBJgCU/nVzFNl7vkS4RE0APtWLP02/8QVIkQtzBxPquh7lq5/78NHipTj7ODQ6XuyQ==}
@@ -5176,7 +5249,7 @@ packages:
vue-router@4.6.4:
resolution: {integrity: sha512-Hz9q5sa33Yhduglwz6g9skT8OBPii+4bFn88w6J+J4MfEo4KRRpmiNG/hHHkdbRFlLBOqxN8y8gf2Fb0MTUgVg==}
peerDependencies:
vue: ^3.5.0
vue: 3.5.43
vue-tsc@3.3.10:
resolution: {integrity: sha512-YaDVxcW+CGtaOt3pZahMG5jYPx0hsUTxyEoPOTSMebcGUXP9lIBabQ14vfKMORb2CqqK5CxsNo/d1d+4IQwiKg==}
@@ -5184,8 +5257,8 @@ packages:
peerDependencies:
typescript: 6.0.3
vue@3.5.41:
resolution: {integrity: sha512-2laE0p+aK+/AOPG/XL/WepOs/GlK755LJ1XECi9kDUrz1FKNw8rb2Xzlw9JS1rqEV55nb0ttsKxVlTCcd+R5cg==}
vue@3.5.43:
resolution: {integrity: sha512-o5qZoksdnjIKvW1srZ3ab7pcDNYAerBjRe54D0LBLfRdCYFrSgBHVXokMas35czQc0//lmx4/tuY4ZNQ+Rf2Ng==}
peerDependencies:
typescript: 6.0.3
peerDependenciesMeta:
@@ -5449,148 +5522,157 @@ snapshots:
tslib: 2.8.1
optional: true
'@esbuild/aix-ppc64@0.21.5':
'@esbuild/aix-ppc64@0.25.12':
optional: true
'@esbuild/aix-ppc64@0.28.2':
optional: true
'@esbuild/android-arm64@0.21.5':
'@esbuild/android-arm64@0.25.12':
optional: true
'@esbuild/android-arm64@0.28.2':
optional: true
'@esbuild/android-arm@0.21.5':
'@esbuild/android-arm@0.25.12':
optional: true
'@esbuild/android-arm@0.28.2':
optional: true
'@esbuild/android-x64@0.21.5':
'@esbuild/android-x64@0.25.12':
optional: true
'@esbuild/android-x64@0.28.2':
optional: true
'@esbuild/darwin-arm64@0.21.5':
'@esbuild/darwin-arm64@0.25.12':
optional: true
'@esbuild/darwin-arm64@0.28.2':
optional: true
'@esbuild/darwin-x64@0.21.5':
'@esbuild/darwin-x64@0.25.12':
optional: true
'@esbuild/darwin-x64@0.28.2':
optional: true
'@esbuild/freebsd-arm64@0.21.5':
'@esbuild/freebsd-arm64@0.25.12':
optional: true
'@esbuild/freebsd-arm64@0.28.2':
optional: true
'@esbuild/freebsd-x64@0.21.5':
'@esbuild/freebsd-x64@0.25.12':
optional: true
'@esbuild/freebsd-x64@0.28.2':
optional: true
'@esbuild/linux-arm64@0.21.5':
'@esbuild/linux-arm64@0.25.12':
optional: true
'@esbuild/linux-arm64@0.28.2':
optional: true
'@esbuild/linux-arm@0.21.5':
'@esbuild/linux-arm@0.25.12':
optional: true
'@esbuild/linux-arm@0.28.2':
optional: true
'@esbuild/linux-ia32@0.21.5':
'@esbuild/linux-ia32@0.25.12':
optional: true
'@esbuild/linux-ia32@0.28.2':
optional: true
'@esbuild/linux-loong64@0.21.5':
'@esbuild/linux-loong64@0.25.12':
optional: true
'@esbuild/linux-loong64@0.28.2':
optional: true
'@esbuild/linux-mips64el@0.21.5':
'@esbuild/linux-mips64el@0.25.12':
optional: true
'@esbuild/linux-mips64el@0.28.2':
optional: true
'@esbuild/linux-ppc64@0.21.5':
'@esbuild/linux-ppc64@0.25.12':
optional: true
'@esbuild/linux-ppc64@0.28.2':
optional: true
'@esbuild/linux-riscv64@0.21.5':
'@esbuild/linux-riscv64@0.25.12':
optional: true
'@esbuild/linux-riscv64@0.28.2':
optional: true
'@esbuild/linux-s390x@0.21.5':
'@esbuild/linux-s390x@0.25.12':
optional: true
'@esbuild/linux-s390x@0.28.2':
optional: true
'@esbuild/linux-x64@0.21.5':
'@esbuild/linux-x64@0.25.12':
optional: true
'@esbuild/linux-x64@0.28.2':
optional: true
'@esbuild/netbsd-arm64@0.25.12':
optional: true
'@esbuild/netbsd-arm64@0.28.2':
optional: true
'@esbuild/netbsd-x64@0.21.5':
'@esbuild/netbsd-x64@0.25.12':
optional: true
'@esbuild/netbsd-x64@0.28.2':
optional: true
'@esbuild/openbsd-arm64@0.25.12':
optional: true
'@esbuild/openbsd-arm64@0.28.2':
optional: true
'@esbuild/openbsd-x64@0.21.5':
'@esbuild/openbsd-x64@0.25.12':
optional: true
'@esbuild/openbsd-x64@0.28.2':
optional: true
'@esbuild/openharmony-arm64@0.25.12':
optional: true
'@esbuild/openharmony-arm64@0.28.2':
optional: true
'@esbuild/sunos-x64@0.21.5':
'@esbuild/sunos-x64@0.25.12':
optional: true
'@esbuild/sunos-x64@0.28.2':
optional: true
'@esbuild/win32-arm64@0.21.5':
'@esbuild/win32-arm64@0.25.12':
optional: true
'@esbuild/win32-arm64@0.28.2':
optional: true
'@esbuild/win32-ia32@0.21.5':
'@esbuild/win32-ia32@0.25.12':
optional: true
'@esbuild/win32-ia32@0.28.2':
optional: true
'@esbuild/win32-x64@0.21.5':
'@esbuild/win32-x64@0.25.12':
optional: true
'@esbuild/win32-x64@0.28.2':
@@ -5636,7 +5718,7 @@ snapshots:
dependencies:
ajv: 8.20.0
ajv-formats: 3.0.1(ajv@8.20.0)
fast-uri: 3.1.5
fast-uri: 3.1.8
'@fastify/cors@11.3.0':
dependencies:
@@ -5713,108 +5795,108 @@ snapshots:
'@img/colour@1.1.0': {}
'@img/sharp-darwin-arm64@0.35.3':
'@img/sharp-darwin-arm64@0.35.5':
optionalDependencies:
'@img/sharp-libvips-darwin-arm64': 1.3.2
'@img/sharp-libvips-darwin-arm64': 1.3.4
optional: true
'@img/sharp-darwin-x64@0.35.3':
'@img/sharp-darwin-x64@0.35.5':
optionalDependencies:
'@img/sharp-libvips-darwin-x64': 1.3.2
'@img/sharp-libvips-darwin-x64': 1.3.4
optional: true
'@img/sharp-freebsd-wasm32@0.35.3':
'@img/sharp-freebsd-wasm32@0.35.5':
dependencies:
'@img/sharp-wasm32': 0.35.3
'@img/sharp-wasm32': 0.35.5
optional: true
'@img/sharp-libvips-darwin-arm64@1.3.2':
'@img/sharp-libvips-darwin-arm64@1.3.4':
optional: true
'@img/sharp-libvips-darwin-x64@1.3.2':
'@img/sharp-libvips-darwin-x64@1.3.4':
optional: true
'@img/sharp-libvips-linux-arm64@1.3.2':
'@img/sharp-libvips-linux-arm64@1.3.4':
optional: true
'@img/sharp-libvips-linux-arm@1.3.2':
'@img/sharp-libvips-linux-arm@1.3.4':
optional: true
'@img/sharp-libvips-linux-ppc64@1.3.2':
'@img/sharp-libvips-linux-ppc64@1.3.4':
optional: true
'@img/sharp-libvips-linux-riscv64@1.3.2':
'@img/sharp-libvips-linux-riscv64@1.3.4':
optional: true
'@img/sharp-libvips-linux-s390x@1.3.2':
'@img/sharp-libvips-linux-s390x@1.3.4':
optional: true
'@img/sharp-libvips-linux-x64@1.3.2':
'@img/sharp-libvips-linux-x64@1.3.4':
optional: true
'@img/sharp-libvips-linuxmusl-arm64@1.3.2':
'@img/sharp-libvips-linuxmusl-arm64@1.3.4':
optional: true
'@img/sharp-libvips-linuxmusl-x64@1.3.2':
'@img/sharp-libvips-linuxmusl-x64@1.3.4':
optional: true
'@img/sharp-linux-arm64@0.35.3':
'@img/sharp-linux-arm64@0.35.5':
optionalDependencies:
'@img/sharp-libvips-linux-arm64': 1.3.2
'@img/sharp-libvips-linux-arm64': 1.3.4
optional: true
'@img/sharp-linux-arm@0.35.3':
'@img/sharp-linux-arm@0.35.5':
optionalDependencies:
'@img/sharp-libvips-linux-arm': 1.3.2
'@img/sharp-libvips-linux-arm': 1.3.4
optional: true
'@img/sharp-linux-ppc64@0.35.3':
'@img/sharp-linux-ppc64@0.35.5':
optionalDependencies:
'@img/sharp-libvips-linux-ppc64': 1.3.2
'@img/sharp-libvips-linux-ppc64': 1.3.4
optional: true
'@img/sharp-linux-riscv64@0.35.3':
'@img/sharp-linux-riscv64@0.35.5':
optionalDependencies:
'@img/sharp-libvips-linux-riscv64': 1.3.2
'@img/sharp-libvips-linux-riscv64': 1.3.4
optional: true
'@img/sharp-linux-s390x@0.35.3':
'@img/sharp-linux-s390x@0.35.5':
optionalDependencies:
'@img/sharp-libvips-linux-s390x': 1.3.2
'@img/sharp-libvips-linux-s390x': 1.3.4
optional: true
'@img/sharp-linux-x64@0.35.3':
'@img/sharp-linux-x64@0.35.5':
optionalDependencies:
'@img/sharp-libvips-linux-x64': 1.3.2
'@img/sharp-libvips-linux-x64': 1.3.4
optional: true
'@img/sharp-linuxmusl-arm64@0.35.3':
'@img/sharp-linuxmusl-arm64@0.35.5':
optionalDependencies:
'@img/sharp-libvips-linuxmusl-arm64': 1.3.2
'@img/sharp-libvips-linuxmusl-arm64': 1.3.4
optional: true
'@img/sharp-linuxmusl-x64@0.35.3':
'@img/sharp-linuxmusl-x64@0.35.5':
optionalDependencies:
'@img/sharp-libvips-linuxmusl-x64': 1.3.2
'@img/sharp-libvips-linuxmusl-x64': 1.3.4
optional: true
'@img/sharp-wasm32@0.35.3':
'@img/sharp-wasm32@0.35.5':
dependencies:
'@emnapi/runtime': 1.11.3
optional: true
'@img/sharp-webcontainers-wasm32@0.35.3':
'@img/sharp-webcontainers-wasm32@0.35.5':
dependencies:
'@img/sharp-wasm32': 0.35.3
'@img/sharp-wasm32': 0.35.5
optional: true
'@img/sharp-win32-arm64@0.35.3':
'@img/sharp-win32-arm64@0.35.5':
optional: true
'@img/sharp-win32-ia32@0.35.3':
'@img/sharp-win32-ia32@0.35.5':
optional: true
'@img/sharp-win32-x64@0.35.3':
'@img/sharp-win32-x64@0.35.5':
optional: true
'@jridgewell/gen-mapping@0.3.13':
@@ -5973,11 +6055,11 @@ snapshots:
'@prisma/client-runtime-utils@7.10.0': {}
'@prisma/client@7.10.0(prisma@7.10.0(@types/react@19.2.18)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(typescript@6.0.3))(typescript@6.0.3)':
'@prisma/client@7.10.0(prisma@7.10.0(@types/node@26.2.0)(@types/react@19.2.18)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(typescript@6.0.3))(typescript@6.0.3)':
dependencies:
'@prisma/client-runtime-utils': 7.10.0
optionalDependencies:
prisma: 7.10.0(@types/react@19.2.18)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(typescript@6.0.3)
prisma: 7.10.0(@types/node@26.2.0)(@types/react@19.2.18)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(typescript@6.0.3)
typescript: 6.0.3
'@prisma/config@7.10.0':
@@ -6364,7 +6446,7 @@ snapshots:
jiti: 2.7.0
lightningcss: 1.32.0
magic-string: 0.30.21
source-map-js: 1.2.1
source-map-js: 1.2.2
tailwindcss: 4.3.3
'@tailwindcss/oxide-android-arm64@4.3.3':
@@ -6425,136 +6507,136 @@ snapshots:
tailwindcss: 4.3.3
vite: 8.3.0(@types/node@26.2.0)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.12)
'@tiptap/core@3.30.1(@tiptap/pm@3.30.1)':
'@tiptap/core@3.31.4(@tiptap/pm@3.31.4)':
dependencies:
'@tiptap/pm': 3.30.1
'@tiptap/pm': 3.31.4
'@tiptap/extension-blockquote@3.30.1(@tiptap/core@3.30.1(@tiptap/pm@3.30.1))(@tiptap/pm@3.30.1)':
'@tiptap/extension-blockquote@3.31.4(@tiptap/core@3.31.4(@tiptap/pm@3.31.4))(@tiptap/pm@3.31.4)':
dependencies:
'@tiptap/core': 3.30.1(@tiptap/pm@3.30.1)
'@tiptap/pm': 3.30.1
'@tiptap/core': 3.31.4(@tiptap/pm@3.31.4)
'@tiptap/pm': 3.31.4
'@tiptap/extension-bold@3.30.1(@tiptap/core@3.30.1(@tiptap/pm@3.30.1))':
'@tiptap/extension-bold@3.31.4(@tiptap/core@3.31.4(@tiptap/pm@3.31.4))':
dependencies:
'@tiptap/core': 3.30.1(@tiptap/pm@3.30.1)
'@tiptap/core': 3.31.4(@tiptap/pm@3.31.4)
'@tiptap/extension-bubble-menu@3.30.1(@tiptap/core@3.30.1(@tiptap/pm@3.30.1))(@tiptap/pm@3.30.1)':
'@tiptap/extension-bubble-menu@3.31.4(@tiptap/core@3.31.4(@tiptap/pm@3.31.4))(@tiptap/pm@3.31.4)':
dependencies:
'@floating-ui/dom': 1.8.0
'@tiptap/core': 3.30.1(@tiptap/pm@3.30.1)
'@tiptap/pm': 3.30.1
'@tiptap/core': 3.31.4(@tiptap/pm@3.31.4)
'@tiptap/pm': 3.31.4
optional: true
'@tiptap/extension-bullet-list@3.30.1(@tiptap/extension-list@3.30.1(@tiptap/core@3.30.1(@tiptap/pm@3.30.1))(@tiptap/pm@3.30.1))':
'@tiptap/extension-bullet-list@3.31.4(@tiptap/extension-list@3.31.4(@tiptap/core@3.31.4(@tiptap/pm@3.31.4))(@tiptap/pm@3.31.4))':
dependencies:
'@tiptap/extension-list': 3.30.1(@tiptap/core@3.30.1(@tiptap/pm@3.30.1))(@tiptap/pm@3.30.1)
'@tiptap/extension-list': 3.31.4(@tiptap/core@3.31.4(@tiptap/pm@3.31.4))(@tiptap/pm@3.31.4)
'@tiptap/extension-code-block@3.30.1(@tiptap/core@3.30.1(@tiptap/pm@3.30.1))(@tiptap/pm@3.30.1)':
'@tiptap/extension-code-block@3.31.4(@tiptap/core@3.31.4(@tiptap/pm@3.31.4))(@tiptap/pm@3.31.4)':
dependencies:
'@tiptap/core': 3.30.1(@tiptap/pm@3.30.1)
'@tiptap/pm': 3.30.1
'@tiptap/core': 3.31.4(@tiptap/pm@3.31.4)
'@tiptap/pm': 3.31.4
'@tiptap/extension-code@3.30.1(@tiptap/core@3.30.1(@tiptap/pm@3.30.1))':
'@tiptap/extension-code@3.31.4(@tiptap/core@3.31.4(@tiptap/pm@3.31.4))':
dependencies:
'@tiptap/core': 3.30.1(@tiptap/pm@3.30.1)
'@tiptap/core': 3.31.4(@tiptap/pm@3.31.4)
'@tiptap/extension-document@3.30.1(@tiptap/core@3.30.1(@tiptap/pm@3.30.1))':
'@tiptap/extension-document@3.31.4(@tiptap/core@3.31.4(@tiptap/pm@3.31.4))':
dependencies:
'@tiptap/core': 3.30.1(@tiptap/pm@3.30.1)
'@tiptap/core': 3.31.4(@tiptap/pm@3.31.4)
'@tiptap/extension-dropcursor@3.30.1(@tiptap/extensions@3.30.1(@tiptap/core@3.30.1(@tiptap/pm@3.30.1))(@tiptap/pm@3.30.1))':
'@tiptap/extension-dropcursor@3.31.4(@tiptap/extensions@3.31.4(@tiptap/core@3.31.4(@tiptap/pm@3.31.4))(@tiptap/pm@3.31.4))':
dependencies:
'@tiptap/extensions': 3.30.1(@tiptap/core@3.30.1(@tiptap/pm@3.30.1))(@tiptap/pm@3.30.1)
'@tiptap/extensions': 3.31.4(@tiptap/core@3.31.4(@tiptap/pm@3.31.4))(@tiptap/pm@3.31.4)
'@tiptap/extension-floating-menu@3.30.1(@floating-ui/dom@1.8.0)(@tiptap/core@3.30.1(@tiptap/pm@3.30.1))(@tiptap/pm@3.30.1)':
'@tiptap/extension-floating-menu@3.31.4(@floating-ui/dom@1.8.0)(@tiptap/core@3.31.4(@tiptap/pm@3.31.4))(@tiptap/pm@3.31.4)':
dependencies:
'@floating-ui/dom': 1.8.0
'@tiptap/core': 3.30.1(@tiptap/pm@3.30.1)
'@tiptap/pm': 3.30.1
'@tiptap/core': 3.31.4(@tiptap/pm@3.31.4)
'@tiptap/pm': 3.31.4
optional: true
'@tiptap/extension-gapcursor@3.30.1(@tiptap/extensions@3.30.1(@tiptap/core@3.30.1(@tiptap/pm@3.30.1))(@tiptap/pm@3.30.1))':
'@tiptap/extension-gapcursor@3.31.4(@tiptap/extensions@3.31.4(@tiptap/core@3.31.4(@tiptap/pm@3.31.4))(@tiptap/pm@3.31.4))':
dependencies:
'@tiptap/extensions': 3.30.1(@tiptap/core@3.30.1(@tiptap/pm@3.30.1))(@tiptap/pm@3.30.1)
'@tiptap/extensions': 3.31.4(@tiptap/core@3.31.4(@tiptap/pm@3.31.4))(@tiptap/pm@3.31.4)
'@tiptap/extension-hard-break@3.30.1(@tiptap/core@3.30.1(@tiptap/pm@3.30.1))':
'@tiptap/extension-hard-break@3.31.4(@tiptap/core@3.31.4(@tiptap/pm@3.31.4))':
dependencies:
'@tiptap/core': 3.30.1(@tiptap/pm@3.30.1)
'@tiptap/core': 3.31.4(@tiptap/pm@3.31.4)
'@tiptap/extension-heading@3.30.1(@tiptap/core@3.30.1(@tiptap/pm@3.30.1))':
'@tiptap/extension-heading@3.31.4(@tiptap/core@3.31.4(@tiptap/pm@3.31.4))':
dependencies:
'@tiptap/core': 3.30.1(@tiptap/pm@3.30.1)
'@tiptap/core': 3.31.4(@tiptap/pm@3.31.4)
'@tiptap/extension-horizontal-rule@3.30.1(@tiptap/core@3.30.1(@tiptap/pm@3.30.1))(@tiptap/pm@3.30.1)':
'@tiptap/extension-horizontal-rule@3.31.4(@tiptap/core@3.31.4(@tiptap/pm@3.31.4))(@tiptap/pm@3.31.4)':
dependencies:
'@tiptap/core': 3.30.1(@tiptap/pm@3.30.1)
'@tiptap/pm': 3.30.1
'@tiptap/core': 3.31.4(@tiptap/pm@3.31.4)
'@tiptap/pm': 3.31.4
'@tiptap/extension-image@3.30.1(@tiptap/core@3.30.1(@tiptap/pm@3.30.1))':
'@tiptap/extension-image@3.31.4(@tiptap/core@3.31.4(@tiptap/pm@3.31.4))':
dependencies:
'@tiptap/core': 3.30.1(@tiptap/pm@3.30.1)
'@tiptap/core': 3.31.4(@tiptap/pm@3.31.4)
'@tiptap/extension-italic@3.30.1(@tiptap/core@3.30.1(@tiptap/pm@3.30.1))':
'@tiptap/extension-italic@3.31.4(@tiptap/core@3.31.4(@tiptap/pm@3.31.4))':
dependencies:
'@tiptap/core': 3.30.1(@tiptap/pm@3.30.1)
'@tiptap/core': 3.31.4(@tiptap/pm@3.31.4)
'@tiptap/extension-link@3.30.1(@tiptap/core@3.30.1(@tiptap/pm@3.30.1))(@tiptap/pm@3.30.1)':
'@tiptap/extension-link@3.31.4(@tiptap/core@3.31.4(@tiptap/pm@3.31.4))(@tiptap/pm@3.31.4)':
dependencies:
'@tiptap/core': 3.30.1(@tiptap/pm@3.30.1)
'@tiptap/pm': 3.30.1
'@tiptap/core': 3.31.4(@tiptap/pm@3.31.4)
'@tiptap/pm': 3.31.4
linkifyjs: 4.3.3
'@tiptap/extension-list-item@3.30.1(@tiptap/extension-list@3.30.1(@tiptap/core@3.30.1(@tiptap/pm@3.30.1))(@tiptap/pm@3.30.1))':
'@tiptap/extension-list-item@3.31.4(@tiptap/extension-list@3.31.4(@tiptap/core@3.31.4(@tiptap/pm@3.31.4))(@tiptap/pm@3.31.4))':
dependencies:
'@tiptap/extension-list': 3.30.1(@tiptap/core@3.30.1(@tiptap/pm@3.30.1))(@tiptap/pm@3.30.1)
'@tiptap/extension-list': 3.31.4(@tiptap/core@3.31.4(@tiptap/pm@3.31.4))(@tiptap/pm@3.31.4)
'@tiptap/extension-list-keymap@3.30.1(@tiptap/extension-list@3.30.1(@tiptap/core@3.30.1(@tiptap/pm@3.30.1))(@tiptap/pm@3.30.1))':
'@tiptap/extension-list-keymap@3.31.4(@tiptap/extension-list@3.31.4(@tiptap/core@3.31.4(@tiptap/pm@3.31.4))(@tiptap/pm@3.31.4))':
dependencies:
'@tiptap/extension-list': 3.30.1(@tiptap/core@3.30.1(@tiptap/pm@3.30.1))(@tiptap/pm@3.30.1)
'@tiptap/extension-list': 3.31.4(@tiptap/core@3.31.4(@tiptap/pm@3.31.4))(@tiptap/pm@3.31.4)
'@tiptap/extension-list@3.30.1(@tiptap/core@3.30.1(@tiptap/pm@3.30.1))(@tiptap/pm@3.30.1)':
'@tiptap/extension-list@3.31.4(@tiptap/core@3.31.4(@tiptap/pm@3.31.4))(@tiptap/pm@3.31.4)':
dependencies:
'@tiptap/core': 3.30.1(@tiptap/pm@3.30.1)
'@tiptap/pm': 3.30.1
'@tiptap/core': 3.31.4(@tiptap/pm@3.31.4)
'@tiptap/pm': 3.31.4
'@tiptap/extension-ordered-list@3.30.1(@tiptap/extension-list@3.30.1(@tiptap/core@3.30.1(@tiptap/pm@3.30.1))(@tiptap/pm@3.30.1))':
'@tiptap/extension-ordered-list@3.31.4(@tiptap/extension-list@3.31.4(@tiptap/core@3.31.4(@tiptap/pm@3.31.4))(@tiptap/pm@3.31.4))':
dependencies:
'@tiptap/extension-list': 3.30.1(@tiptap/core@3.30.1(@tiptap/pm@3.30.1))(@tiptap/pm@3.30.1)
'@tiptap/extension-list': 3.31.4(@tiptap/core@3.31.4(@tiptap/pm@3.31.4))(@tiptap/pm@3.31.4)
'@tiptap/extension-paragraph@3.30.1(@tiptap/core@3.30.1(@tiptap/pm@3.30.1))':
'@tiptap/extension-paragraph@3.31.4(@tiptap/core@3.31.4(@tiptap/pm@3.31.4))':
dependencies:
'@tiptap/core': 3.30.1(@tiptap/pm@3.30.1)
'@tiptap/core': 3.31.4(@tiptap/pm@3.31.4)
'@tiptap/extension-placeholder@3.30.1(@tiptap/extensions@3.30.1(@tiptap/core@3.30.1(@tiptap/pm@3.30.1))(@tiptap/pm@3.30.1))':
'@tiptap/extension-placeholder@3.31.4(@tiptap/extensions@3.31.4(@tiptap/core@3.31.4(@tiptap/pm@3.31.4))(@tiptap/pm@3.31.4))':
dependencies:
'@tiptap/extensions': 3.30.1(@tiptap/core@3.30.1(@tiptap/pm@3.30.1))(@tiptap/pm@3.30.1)
'@tiptap/extensions': 3.31.4(@tiptap/core@3.31.4(@tiptap/pm@3.31.4))(@tiptap/pm@3.31.4)
'@tiptap/extension-strike@3.30.1(@tiptap/core@3.30.1(@tiptap/pm@3.30.1))':
'@tiptap/extension-strike@3.31.4(@tiptap/core@3.31.4(@tiptap/pm@3.31.4))':
dependencies:
'@tiptap/core': 3.30.1(@tiptap/pm@3.30.1)
'@tiptap/core': 3.31.4(@tiptap/pm@3.31.4)
'@tiptap/extension-text-align@3.30.1(@tiptap/core@3.30.1(@tiptap/pm@3.30.1))':
'@tiptap/extension-text-align@3.31.4(@tiptap/core@3.31.4(@tiptap/pm@3.31.4))':
dependencies:
'@tiptap/core': 3.30.1(@tiptap/pm@3.30.1)
'@tiptap/core': 3.31.4(@tiptap/pm@3.31.4)
'@tiptap/extension-text-style@3.30.1(@tiptap/core@3.30.1(@tiptap/pm@3.30.1))':
'@tiptap/extension-text-style@3.31.4(@tiptap/core@3.31.4(@tiptap/pm@3.31.4))':
dependencies:
'@tiptap/core': 3.30.1(@tiptap/pm@3.30.1)
'@tiptap/core': 3.31.4(@tiptap/pm@3.31.4)
'@tiptap/extension-text@3.30.1(@tiptap/core@3.30.1(@tiptap/pm@3.30.1))':
'@tiptap/extension-text@3.31.4(@tiptap/core@3.31.4(@tiptap/pm@3.31.4))':
dependencies:
'@tiptap/core': 3.30.1(@tiptap/pm@3.30.1)
'@tiptap/core': 3.31.4(@tiptap/pm@3.31.4)
'@tiptap/extension-underline@3.30.1(@tiptap/core@3.30.1(@tiptap/pm@3.30.1))':
'@tiptap/extension-underline@3.31.4(@tiptap/core@3.31.4(@tiptap/pm@3.31.4))':
dependencies:
'@tiptap/core': 3.30.1(@tiptap/pm@3.30.1)
'@tiptap/core': 3.31.4(@tiptap/pm@3.31.4)
'@tiptap/extensions@3.30.1(@tiptap/core@3.30.1(@tiptap/pm@3.30.1))(@tiptap/pm@3.30.1)':
'@tiptap/extensions@3.31.4(@tiptap/core@3.31.4(@tiptap/pm@3.31.4))(@tiptap/pm@3.31.4)':
dependencies:
'@tiptap/core': 3.30.1(@tiptap/pm@3.30.1)
'@tiptap/pm': 3.30.1
'@tiptap/core': 3.31.4(@tiptap/pm@3.31.4)
'@tiptap/pm': 3.31.4
'@tiptap/pm@3.30.1':
'@tiptap/pm@3.31.4':
dependencies:
prosemirror-changeset: 2.4.1
prosemirror-commands: 1.7.1
@@ -6568,44 +6650,44 @@ snapshots:
prosemirror-state: 1.4.4
prosemirror-tables: 1.8.5
prosemirror-transform: 1.12.0
prosemirror-view: 1.42.2
prosemirror-view: 1.42.3
'@tiptap/starter-kit@3.30.1':
'@tiptap/starter-kit@3.31.4':
dependencies:
'@tiptap/core': 3.30.1(@tiptap/pm@3.30.1)
'@tiptap/extension-blockquote': 3.30.1(@tiptap/core@3.30.1(@tiptap/pm@3.30.1))(@tiptap/pm@3.30.1)
'@tiptap/extension-bold': 3.30.1(@tiptap/core@3.30.1(@tiptap/pm@3.30.1))
'@tiptap/extension-bullet-list': 3.30.1(@tiptap/extension-list@3.30.1(@tiptap/core@3.30.1(@tiptap/pm@3.30.1))(@tiptap/pm@3.30.1))
'@tiptap/extension-code': 3.30.1(@tiptap/core@3.30.1(@tiptap/pm@3.30.1))
'@tiptap/extension-code-block': 3.30.1(@tiptap/core@3.30.1(@tiptap/pm@3.30.1))(@tiptap/pm@3.30.1)
'@tiptap/extension-document': 3.30.1(@tiptap/core@3.30.1(@tiptap/pm@3.30.1))
'@tiptap/extension-dropcursor': 3.30.1(@tiptap/extensions@3.30.1(@tiptap/core@3.30.1(@tiptap/pm@3.30.1))(@tiptap/pm@3.30.1))
'@tiptap/extension-gapcursor': 3.30.1(@tiptap/extensions@3.30.1(@tiptap/core@3.30.1(@tiptap/pm@3.30.1))(@tiptap/pm@3.30.1))
'@tiptap/extension-hard-break': 3.30.1(@tiptap/core@3.30.1(@tiptap/pm@3.30.1))
'@tiptap/extension-heading': 3.30.1(@tiptap/core@3.30.1(@tiptap/pm@3.30.1))
'@tiptap/extension-horizontal-rule': 3.30.1(@tiptap/core@3.30.1(@tiptap/pm@3.30.1))(@tiptap/pm@3.30.1)
'@tiptap/extension-italic': 3.30.1(@tiptap/core@3.30.1(@tiptap/pm@3.30.1))
'@tiptap/extension-link': 3.30.1(@tiptap/core@3.30.1(@tiptap/pm@3.30.1))(@tiptap/pm@3.30.1)
'@tiptap/extension-list': 3.30.1(@tiptap/core@3.30.1(@tiptap/pm@3.30.1))(@tiptap/pm@3.30.1)
'@tiptap/extension-list-item': 3.30.1(@tiptap/extension-list@3.30.1(@tiptap/core@3.30.1(@tiptap/pm@3.30.1))(@tiptap/pm@3.30.1))
'@tiptap/extension-list-keymap': 3.30.1(@tiptap/extension-list@3.30.1(@tiptap/core@3.30.1(@tiptap/pm@3.30.1))(@tiptap/pm@3.30.1))
'@tiptap/extension-ordered-list': 3.30.1(@tiptap/extension-list@3.30.1(@tiptap/core@3.30.1(@tiptap/pm@3.30.1))(@tiptap/pm@3.30.1))
'@tiptap/extension-paragraph': 3.30.1(@tiptap/core@3.30.1(@tiptap/pm@3.30.1))
'@tiptap/extension-strike': 3.30.1(@tiptap/core@3.30.1(@tiptap/pm@3.30.1))
'@tiptap/extension-text': 3.30.1(@tiptap/core@3.30.1(@tiptap/pm@3.30.1))
'@tiptap/extension-underline': 3.30.1(@tiptap/core@3.30.1(@tiptap/pm@3.30.1))
'@tiptap/extensions': 3.30.1(@tiptap/core@3.30.1(@tiptap/pm@3.30.1))(@tiptap/pm@3.30.1)
'@tiptap/pm': 3.30.1
'@tiptap/core': 3.31.4(@tiptap/pm@3.31.4)
'@tiptap/extension-blockquote': 3.31.4(@tiptap/core@3.31.4(@tiptap/pm@3.31.4))(@tiptap/pm@3.31.4)
'@tiptap/extension-bold': 3.31.4(@tiptap/core@3.31.4(@tiptap/pm@3.31.4))
'@tiptap/extension-bullet-list': 3.31.4(@tiptap/extension-list@3.31.4(@tiptap/core@3.31.4(@tiptap/pm@3.31.4))(@tiptap/pm@3.31.4))
'@tiptap/extension-code': 3.31.4(@tiptap/core@3.31.4(@tiptap/pm@3.31.4))
'@tiptap/extension-code-block': 3.31.4(@tiptap/core@3.31.4(@tiptap/pm@3.31.4))(@tiptap/pm@3.31.4)
'@tiptap/extension-document': 3.31.4(@tiptap/core@3.31.4(@tiptap/pm@3.31.4))
'@tiptap/extension-dropcursor': 3.31.4(@tiptap/extensions@3.31.4(@tiptap/core@3.31.4(@tiptap/pm@3.31.4))(@tiptap/pm@3.31.4))
'@tiptap/extension-gapcursor': 3.31.4(@tiptap/extensions@3.31.4(@tiptap/core@3.31.4(@tiptap/pm@3.31.4))(@tiptap/pm@3.31.4))
'@tiptap/extension-hard-break': 3.31.4(@tiptap/core@3.31.4(@tiptap/pm@3.31.4))
'@tiptap/extension-heading': 3.31.4(@tiptap/core@3.31.4(@tiptap/pm@3.31.4))
'@tiptap/extension-horizontal-rule': 3.31.4(@tiptap/core@3.31.4(@tiptap/pm@3.31.4))(@tiptap/pm@3.31.4)
'@tiptap/extension-italic': 3.31.4(@tiptap/core@3.31.4(@tiptap/pm@3.31.4))
'@tiptap/extension-link': 3.31.4(@tiptap/core@3.31.4(@tiptap/pm@3.31.4))(@tiptap/pm@3.31.4)
'@tiptap/extension-list': 3.31.4(@tiptap/core@3.31.4(@tiptap/pm@3.31.4))(@tiptap/pm@3.31.4)
'@tiptap/extension-list-item': 3.31.4(@tiptap/extension-list@3.31.4(@tiptap/core@3.31.4(@tiptap/pm@3.31.4))(@tiptap/pm@3.31.4))
'@tiptap/extension-list-keymap': 3.31.4(@tiptap/extension-list@3.31.4(@tiptap/core@3.31.4(@tiptap/pm@3.31.4))(@tiptap/pm@3.31.4))
'@tiptap/extension-ordered-list': 3.31.4(@tiptap/extension-list@3.31.4(@tiptap/core@3.31.4(@tiptap/pm@3.31.4))(@tiptap/pm@3.31.4))
'@tiptap/extension-paragraph': 3.31.4(@tiptap/core@3.31.4(@tiptap/pm@3.31.4))
'@tiptap/extension-strike': 3.31.4(@tiptap/core@3.31.4(@tiptap/pm@3.31.4))
'@tiptap/extension-text': 3.31.4(@tiptap/core@3.31.4(@tiptap/pm@3.31.4))
'@tiptap/extension-underline': 3.31.4(@tiptap/core@3.31.4(@tiptap/pm@3.31.4))
'@tiptap/extensions': 3.31.4(@tiptap/core@3.31.4(@tiptap/pm@3.31.4))(@tiptap/pm@3.31.4)
'@tiptap/pm': 3.31.4
'@tiptap/vue-3@3.30.1(@floating-ui/dom@1.8.0)(@tiptap/core@3.30.1(@tiptap/pm@3.30.1))(@tiptap/pm@3.30.1)(vue@3.5.41(typescript@6.0.3))':
'@tiptap/vue-3@3.31.4(@floating-ui/dom@1.8.0)(@tiptap/core@3.31.4(@tiptap/pm@3.31.4))(@tiptap/pm@3.31.4)(vue@3.5.43(typescript@6.0.3))':
dependencies:
'@floating-ui/dom': 1.8.0
'@tiptap/core': 3.30.1(@tiptap/pm@3.30.1)
'@tiptap/pm': 3.30.1
vue: 3.5.41(typescript@6.0.3)
'@tiptap/core': 3.31.4(@tiptap/pm@3.31.4)
'@tiptap/pm': 3.31.4
vue: 3.5.43(typescript@6.0.3)
optionalDependencies:
'@tiptap/extension-bubble-menu': 3.30.1(@tiptap/core@3.30.1(@tiptap/pm@3.30.1))(@tiptap/pm@3.30.1)
'@tiptap/extension-floating-menu': 3.30.1(@floating-ui/dom@1.8.0)(@tiptap/core@3.30.1(@tiptap/pm@3.30.1))(@tiptap/pm@3.30.1)
'@tiptap/extension-bubble-menu': 3.31.4(@tiptap/core@3.31.4(@tiptap/pm@3.31.4))(@tiptap/pm@3.31.4)
'@tiptap/extension-floating-menu': 3.31.4(@floating-ui/dom@1.8.0)(@tiptap/core@3.31.4(@tiptap/pm@3.31.4))(@tiptap/pm@3.31.4)
'@tootallnate/quickjs-emscripten@0.23.0': {}
@@ -6963,16 +7045,16 @@ snapshots:
d3-time-format: 4.1.0
internmap: 2.0.3
'@vitejs/plugin-vue@5.2.4(vite@5.4.21(@types/node@26.2.0)(lightningcss@1.33.0))(vue@3.5.41(typescript@6.0.3))':
'@vitejs/plugin-vue@5.2.4(vite@6.4.3(@types/node@26.2.0)(jiti@2.7.0)(lightningcss@1.33.0)(tsx@4.23.12))(vue@3.5.43(typescript@6.0.3))':
dependencies:
vite: 5.4.21(@types/node@26.2.0)(lightningcss@1.33.0)
vue: 3.5.41(typescript@6.0.3)
vite: 6.4.3(@types/node@26.2.0)(jiti@2.7.0)(lightningcss@1.33.0)(tsx@4.23.12)
vue: 3.5.43(typescript@6.0.3)
'@vitejs/plugin-vue@6.0.9(vite@8.3.0(@types/node@26.2.0)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.12))(vue@3.5.41(typescript@6.0.3))':
'@vitejs/plugin-vue@6.0.9(vite@8.3.0(@types/node@26.2.0)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.12))(vue@3.5.43(typescript@6.0.3))':
dependencies:
'@rolldown/pluginutils': 1.0.1
vite: 8.3.0(@types/node@26.2.0)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.12)
vue: 3.5.41(typescript@6.0.3)
vue: 3.5.43(typescript@6.0.3)
'@vitest/mocker@5.0.1(vite@8.3.0(@types/node@26.2.0)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.12))':
dependencies:
@@ -7005,29 +7087,42 @@ snapshots:
'@vue/shared': 3.5.41
entities: 7.0.1
estree-walker: 2.0.2
source-map-js: 1.2.1
source-map-js: 1.2.2
'@vue/compiler-core@3.5.43':
dependencies:
'@babel/parser': 7.29.8
'@vue/shared': 3.5.43
entities: 7.0.1
estree-walker: 2.0.2
source-map-js: 1.2.2
'@vue/compiler-dom@3.5.41':
dependencies:
'@vue/compiler-core': 3.5.41
'@vue/shared': 3.5.41
'@vue/compiler-sfc@3.5.41':
'@vue/compiler-dom@3.5.43':
dependencies:
'@vue/compiler-core': 3.5.43
'@vue/shared': 3.5.43
'@vue/compiler-sfc@3.5.43':
dependencies:
'@babel/parser': 7.29.8
'@vue/compiler-core': 3.5.41
'@vue/compiler-dom': 3.5.41
'@vue/compiler-ssr': 3.5.41
'@vue/shared': 3.5.41
'@vue/compiler-core': 3.5.43
'@vue/compiler-dom': 3.5.43
'@vue/compiler-ssr': 3.5.43
'@vue/shared': 3.5.43
estree-walker: 2.0.2
magic-string: 0.30.21
postcss: 8.5.26
source-map-js: 1.2.1
source-map-js: 1.2.2
'@vue/compiler-ssr@3.5.41':
'@vue/compiler-ssr@3.5.43':
dependencies:
'@vue/compiler-dom': 3.5.41
'@vue/shared': 3.5.41
'@vue/compiler-dom': 3.5.43
'@vue/shared': 3.5.43
'@vue/devtools-api@6.6.4': {}
@@ -7059,58 +7154,60 @@ snapshots:
path-browserify: 1.0.1
picomatch: 4.0.7
'@vue/reactivity@3.5.41':
'@vue/reactivity@3.5.43':
dependencies:
'@vue/shared': 3.5.41
'@vue/shared': 3.5.43
'@vue/runtime-core@3.5.41':
'@vue/runtime-core@3.5.43':
dependencies:
'@vue/reactivity': 3.5.41
'@vue/shared': 3.5.41
'@vue/reactivity': 3.5.43
'@vue/shared': 3.5.43
'@vue/runtime-dom@3.5.41':
'@vue/runtime-dom@3.5.43':
dependencies:
'@vue/reactivity': 3.5.41
'@vue/runtime-core': 3.5.41
'@vue/shared': 3.5.41
'@vue/reactivity': 3.5.43
'@vue/runtime-core': 3.5.43
'@vue/shared': 3.5.43
csstype: 3.2.3
'@vue/server-renderer@3.5.41':
'@vue/server-renderer@3.5.43':
dependencies:
'@vue/compiler-ssr': 3.5.41
'@vue/runtime-dom': 3.5.41
'@vue/shared': 3.5.41
'@vue/compiler-ssr': 3.5.43
'@vue/runtime-dom': 3.5.43
'@vue/shared': 3.5.43
'@vue/shared@3.5.41': {}
'@vue/shared@3.5.43': {}
'@vueuse/core@12.8.2(typescript@6.0.3)':
dependencies:
'@types/web-bluetooth': 0.0.21
'@vueuse/metadata': 12.8.2
'@vueuse/shared': 12.8.2(typescript@6.0.3)
vue: 3.5.41(typescript@6.0.3)
vue: 3.5.43(typescript@6.0.3)
transitivePeerDependencies:
- typescript
'@vueuse/core@13.9.0(vue@3.5.41(typescript@6.0.3))':
'@vueuse/core@13.9.0(vue@3.5.43(typescript@6.0.3))':
dependencies:
'@types/web-bluetooth': 0.0.21
'@vueuse/metadata': 13.9.0
'@vueuse/shared': 13.9.0(vue@3.5.41(typescript@6.0.3))
vue: 3.5.41(typescript@6.0.3)
'@vueuse/shared': 13.9.0(vue@3.5.43(typescript@6.0.3))
vue: 3.5.43(typescript@6.0.3)
'@vueuse/core@14.4.0(vue@3.5.41(typescript@6.0.3))':
'@vueuse/core@14.4.0(vue@3.5.43(typescript@6.0.3))':
dependencies:
'@types/web-bluetooth': 0.0.21
'@vueuse/metadata': 14.4.0
'@vueuse/shared': 14.4.0(vue@3.5.41(typescript@6.0.3))
vue: 3.5.41(typescript@6.0.3)
'@vueuse/shared': 14.4.0(vue@3.5.43(typescript@6.0.3))
vue: 3.5.43(typescript@6.0.3)
'@vueuse/integrations@12.8.2(focus-trap@7.8.0)(sortablejs@1.14.0)(typescript@6.0.3)':
dependencies:
'@vueuse/core': 12.8.2(typescript@6.0.3)
'@vueuse/shared': 12.8.2(typescript@6.0.3)
vue: 3.5.41(typescript@6.0.3)
vue: 3.5.43(typescript@6.0.3)
optionalDependencies:
focus-trap: 7.8.0
sortablejs: 1.14.0
@@ -7125,17 +7222,17 @@ snapshots:
'@vueuse/shared@12.8.2(typescript@6.0.3)':
dependencies:
vue: 3.5.41(typescript@6.0.3)
vue: 3.5.43(typescript@6.0.3)
transitivePeerDependencies:
- typescript
'@vueuse/shared@13.9.0(vue@3.5.41(typescript@6.0.3))':
'@vueuse/shared@13.9.0(vue@3.5.43(typescript@6.0.3))':
dependencies:
vue: 3.5.41(typescript@6.0.3)
vue: 3.5.43(typescript@6.0.3)
'@vueuse/shared@14.4.0(vue@3.5.41(typescript@6.0.3))':
'@vueuse/shared@14.4.0(vue@3.5.43(typescript@6.0.3))':
dependencies:
vue: 3.5.41(typescript@6.0.3)
vue: 3.5.43(typescript@6.0.3)
'@yuku-codegen/binding-darwin-arm64@0.7.4':
optional: true
@@ -7229,7 +7326,7 @@ snapshots:
ajv@8.20.0:
dependencies:
fast-deep-equal: 3.1.3
fast-uri: 3.1.5
fast-uri: 3.1.8
json-schema-traverse: 1.0.0
require-from-string: 2.0.2
@@ -7312,7 +7409,7 @@ snapshots:
baseline-browser-mapping@2.11.15: {}
basic-ftp@5.3.1: {}
basic-ftp@6.2.1: {}
better-result@2.10.0: {}
@@ -7324,7 +7421,7 @@ snapshots:
boolbase@1.0.0: {}
brace-expansion@5.0.9:
brace-expansion@5.0.12:
dependencies:
balanced-match: 4.0.4
@@ -7608,31 +7705,34 @@ snapshots:
es-toolkit@1.51.0: {}
esbuild@0.21.5:
esbuild@0.25.12:
optionalDependencies:
'@esbuild/aix-ppc64': 0.21.5
'@esbuild/android-arm': 0.21.5
'@esbuild/android-arm64': 0.21.5
'@esbuild/android-x64': 0.21.5
'@esbuild/darwin-arm64': 0.21.5
'@esbuild/darwin-x64': 0.21.5
'@esbuild/freebsd-arm64': 0.21.5
'@esbuild/freebsd-x64': 0.21.5
'@esbuild/linux-arm': 0.21.5
'@esbuild/linux-arm64': 0.21.5
'@esbuild/linux-ia32': 0.21.5
'@esbuild/linux-loong64': 0.21.5
'@esbuild/linux-mips64el': 0.21.5
'@esbuild/linux-ppc64': 0.21.5
'@esbuild/linux-riscv64': 0.21.5
'@esbuild/linux-s390x': 0.21.5
'@esbuild/linux-x64': 0.21.5
'@esbuild/netbsd-x64': 0.21.5
'@esbuild/openbsd-x64': 0.21.5
'@esbuild/sunos-x64': 0.21.5
'@esbuild/win32-arm64': 0.21.5
'@esbuild/win32-ia32': 0.21.5
'@esbuild/win32-x64': 0.21.5
'@esbuild/aix-ppc64': 0.25.12
'@esbuild/android-arm': 0.25.12
'@esbuild/android-arm64': 0.25.12
'@esbuild/android-x64': 0.25.12
'@esbuild/darwin-arm64': 0.25.12
'@esbuild/darwin-x64': 0.25.12
'@esbuild/freebsd-arm64': 0.25.12
'@esbuild/freebsd-x64': 0.25.12
'@esbuild/linux-arm': 0.25.12
'@esbuild/linux-arm64': 0.25.12
'@esbuild/linux-ia32': 0.25.12
'@esbuild/linux-loong64': 0.25.12
'@esbuild/linux-mips64el': 0.25.12
'@esbuild/linux-ppc64': 0.25.12
'@esbuild/linux-riscv64': 0.25.12
'@esbuild/linux-s390x': 0.25.12
'@esbuild/linux-x64': 0.25.12
'@esbuild/netbsd-arm64': 0.25.12
'@esbuild/netbsd-x64': 0.25.12
'@esbuild/openbsd-arm64': 0.25.12
'@esbuild/openbsd-x64': 0.25.12
'@esbuild/openharmony-arm64': 0.25.12
'@esbuild/sunos-x64': 0.25.12
'@esbuild/win32-arm64': 0.25.12
'@esbuild/win32-ia32': 0.25.12
'@esbuild/win32-x64': 0.25.12
esbuild@0.28.2:
optionalDependencies:
@@ -7696,7 +7796,7 @@ snapshots:
eslint: 10.8.1(jiti@2.7.0)(supports-color@7.2.0)
natural-compare: 1.4.0
nth-check: 2.1.1
postcss-selector-parser: 7.1.5
postcss-selector-parser: 7.1.6
semver: 7.8.5
vue-eslint-parser: 10.4.1(eslint@10.8.1(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)
xml-name-validator: 5.0.0
@@ -7808,7 +7908,7 @@ snapshots:
'@fastify/merge-json-schemas': 0.2.1
ajv: 8.20.0
ajv-formats: 3.0.1(ajv@8.20.0)
fast-uri: 3.1.5
fast-uri: 3.1.8
json-schema-ref-resolver: 3.0.0
rfdc: 1.4.1
@@ -7818,11 +7918,11 @@ snapshots:
dependencies:
fast-decode-uri-component: 1.0.1
fast-uri@3.1.5: {}
fast-uri@3.1.8: {}
fastify-plugin@6.0.0: {}
fastify@5.12.0:
fastify@5.12.5:
dependencies:
'@fastify/ajv-compiler': 4.0.6
'@fastify/error': 4.2.0
@@ -7913,7 +8013,7 @@ snapshots:
get-uri@6.0.5(supports-color@7.2.0):
dependencies:
basic-ftp: 5.3.1
basic-ftp: 6.2.1
data-uri-to-buffer: 6.0.2
debug: 4.4.3(supports-color@7.2.0)
transitivePeerDependencies:
@@ -8025,7 +8125,7 @@ snapshots:
internmap@2.0.3: {}
ip-address@10.5.0: {}
ip-address@10.7.1: {}
ipaddr.js@2.5.0: {}
@@ -8051,7 +8151,7 @@ snapshots:
jiti@2.7.0: {}
js-yaml@4.3.1:
js-yaml@4.3.2:
dependencies:
argparse: 2.0.1
@@ -8213,6 +8313,8 @@ snapshots:
lru.min@1.1.3: {}
lru.min@1.1.5: {}
magic-string@0.30.21:
dependencies:
'@jridgewell/sourcemap-codec': 1.6.0
@@ -8266,7 +8368,7 @@ snapshots:
minimatch@10.2.6:
dependencies:
brace-expansion: 5.0.9
brace-expansion: 5.0.12
minimist@1.2.8: {}
@@ -8280,17 +8382,17 @@ snapshots:
muggle-string@0.4.1: {}
mysql2@3.15.3:
mysql2@3.23.1(@types/node@26.2.0):
dependencies:
'@types/node': 26.2.0
aws-ssl-profiles: 1.1.2
denque: 2.1.0
generate-function: 2.3.1
iconv-lite: 0.7.3
long: 5.3.2
lru.min: 1.1.3
lru.min: 1.1.5
named-placeholders: 1.1.6
seq-queue: 0.0.5
sqlstring: 2.3.3
sql-escaper: 1.5.2
named-placeholders@1.1.6:
dependencies:
@@ -8455,10 +8557,10 @@ snapshots:
dependencies:
safe-buffer: 5.2.1
pinia@3.0.4(typescript@6.0.3)(vue@3.5.41(typescript@6.0.3)):
pinia@3.0.4(typescript@6.0.3)(vue@3.5.43(typescript@6.0.3)):
dependencies:
'@vue/devtools-api': 7.7.10
vue: 3.5.41(typescript@6.0.3)
vue: 3.5.43(typescript@6.0.3)
optionalDependencies:
typescript: 6.0.3
@@ -8518,7 +8620,7 @@ snapshots:
debug: 4.4.3(supports-color@7.2.0)
eventemitter2: 6.4.9
fast-json-patch: 3.1.1
js-yaml: 4.3.1
js-yaml: 4.3.2
pidusage: 4.0.1
pm2-deploy: 1.0.2
proxy-agent: 6.5.0(supports-color@7.2.0)
@@ -8530,7 +8632,7 @@ snapshots:
- supports-color
- utf-8-validate
postcss-selector-parser@7.1.5:
postcss-selector-parser@7.1.6:
dependencies:
cssesc: 3.0.0
util-deprecate: 1.0.2
@@ -8541,7 +8643,7 @@ snapshots:
dependencies:
nanoid: 3.3.18
picocolors: 1.1.1
source-map-js: 1.2.1
source-map-js: 1.2.2
postgres-array@2.0.0: {}
@@ -8567,17 +8669,18 @@ snapshots:
prettier@3.9.8: {}
prisma@7.10.0(@types/react@19.2.18)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(typescript@6.0.3):
prisma@7.10.0(@types/node@26.2.0)(@types/react@19.2.18)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(typescript@6.0.3):
dependencies:
'@prisma/config': 7.10.0
'@prisma/dev': 0.24.17(typescript@6.0.3)
'@prisma/engines': 7.10.0
'@prisma/studio-core': 0.33.0(@types/react@19.2.18)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
mysql2: 3.15.3
mysql2: 3.23.1(@types/node@26.2.0)
postgres: 3.4.7
optionalDependencies:
typescript: 6.0.3
transitivePeerDependencies:
- '@types/node'
- '@types/react'
- '@types/react-dom'
- magicast
@@ -8610,20 +8713,20 @@ snapshots:
dependencies:
prosemirror-state: 1.4.4
prosemirror-transform: 1.12.0
prosemirror-view: 1.42.2
prosemirror-view: 1.42.3
prosemirror-gapcursor@1.4.1:
dependencies:
prosemirror-keymap: 1.2.3
prosemirror-model: 1.25.11
prosemirror-state: 1.4.4
prosemirror-view: 1.42.2
prosemirror-view: 1.42.3
prosemirror-history@1.5.0:
dependencies:
prosemirror-state: 1.4.4
prosemirror-transform: 1.12.0
prosemirror-view: 1.42.2
prosemirror-view: 1.42.3
rope-sequence: 1.3.4
prosemirror-inputrules@1.5.1:
@@ -8650,7 +8753,7 @@ snapshots:
dependencies:
prosemirror-model: 1.25.11
prosemirror-transform: 1.12.0
prosemirror-view: 1.42.2
prosemirror-view: 1.42.3
prosemirror-tables@1.8.5:
dependencies:
@@ -8658,13 +8761,13 @@ snapshots:
prosemirror-model: 1.25.11
prosemirror-state: 1.4.4
prosemirror-transform: 1.12.0
prosemirror-view: 1.42.2
prosemirror-view: 1.42.3
prosemirror-transform@1.12.0:
dependencies:
prosemirror-model: 1.25.11
prosemirror-view@1.42.2:
prosemirror-view@1.42.3:
dependencies:
prosemirror-model: 1.25.11
prosemirror-state: 1.4.4
@@ -8860,7 +8963,7 @@ snapshots:
safer-buffer@2.1.2: {}
sanitize-html@2.17.6:
sanitize-html@2.17.7:
dependencies:
deepmerge: 4.3.1
escape-string-regexp: 4.0.0
@@ -8880,43 +8983,41 @@ snapshots:
semver@7.8.5: {}
seq-queue@0.0.5: {}
set-cookie-parser@2.7.2: {}
setprototypeof@1.2.0: {}
sharp@0.35.3(@types/node@26.2.0):
sharp@0.35.5(@types/node@26.2.0):
dependencies:
'@img/colour': 1.1.0
detect-libc: 2.1.2
semver: 7.8.5
optionalDependencies:
'@img/sharp-darwin-arm64': 0.35.3
'@img/sharp-darwin-x64': 0.35.3
'@img/sharp-freebsd-wasm32': 0.35.3
'@img/sharp-libvips-darwin-arm64': 1.3.2
'@img/sharp-libvips-darwin-x64': 1.3.2
'@img/sharp-libvips-linux-arm': 1.3.2
'@img/sharp-libvips-linux-arm64': 1.3.2
'@img/sharp-libvips-linux-ppc64': 1.3.2
'@img/sharp-libvips-linux-riscv64': 1.3.2
'@img/sharp-libvips-linux-s390x': 1.3.2
'@img/sharp-libvips-linux-x64': 1.3.2
'@img/sharp-libvips-linuxmusl-arm64': 1.3.2
'@img/sharp-libvips-linuxmusl-x64': 1.3.2
'@img/sharp-linux-arm': 0.35.3
'@img/sharp-linux-arm64': 0.35.3
'@img/sharp-linux-ppc64': 0.35.3
'@img/sharp-linux-riscv64': 0.35.3
'@img/sharp-linux-s390x': 0.35.3
'@img/sharp-linux-x64': 0.35.3
'@img/sharp-linuxmusl-arm64': 0.35.3
'@img/sharp-linuxmusl-x64': 0.35.3
'@img/sharp-webcontainers-wasm32': 0.35.3
'@img/sharp-win32-arm64': 0.35.3
'@img/sharp-win32-ia32': 0.35.3
'@img/sharp-win32-x64': 0.35.3
'@img/sharp-darwin-arm64': 0.35.5
'@img/sharp-darwin-x64': 0.35.5
'@img/sharp-freebsd-wasm32': 0.35.5
'@img/sharp-libvips-darwin-arm64': 1.3.4
'@img/sharp-libvips-darwin-x64': 1.3.4
'@img/sharp-libvips-linux-arm': 1.3.4
'@img/sharp-libvips-linux-arm64': 1.3.4
'@img/sharp-libvips-linux-ppc64': 1.3.4
'@img/sharp-libvips-linux-riscv64': 1.3.4
'@img/sharp-libvips-linux-s390x': 1.3.4
'@img/sharp-libvips-linux-x64': 1.3.4
'@img/sharp-libvips-linuxmusl-arm64': 1.3.4
'@img/sharp-libvips-linuxmusl-x64': 1.3.4
'@img/sharp-linux-arm': 0.35.5
'@img/sharp-linux-arm64': 0.35.5
'@img/sharp-linux-ppc64': 0.35.5
'@img/sharp-linux-riscv64': 0.35.5
'@img/sharp-linux-s390x': 0.35.5
'@img/sharp-linux-x64': 0.35.5
'@img/sharp-linuxmusl-arm64': 0.35.5
'@img/sharp-linuxmusl-x64': 0.35.5
'@img/sharp-webcontainers-wasm32': 0.35.5
'@img/sharp-win32-arm64': 0.35.5
'@img/sharp-win32-ia32': 0.35.5
'@img/sharp-win32-x64': 0.35.5
'@types/node': 26.2.0
shebang-command@2.0.0:
@@ -8954,7 +9055,7 @@ snapshots:
socks@2.8.9:
dependencies:
ip-address: 10.5.0
ip-address: 10.7.1
smart-buffer: 4.2.0
sonic-boom@4.2.1:
@@ -8964,7 +9065,7 @@ snapshots:
sortablejs@1.14.0:
optional: true
source-map-js@1.2.1: {}
source-map-js@1.2.2: {}
source-map@0.6.1:
optional: true
@@ -8975,7 +9076,7 @@ snapshots:
split2@4.2.0: {}
sqlstring@2.3.3: {}
sql-escaper@1.5.2: {}
stackback@0.0.2: {}
@@ -9200,15 +9301,20 @@ snapshots:
'@types/unist': 3.0.3
vfile-message: 4.0.3
vite@5.4.21(@types/node@26.2.0)(lightningcss@1.33.0):
vite@6.4.3(@types/node@26.2.0)(jiti@2.7.0)(lightningcss@1.33.0)(tsx@4.23.12):
dependencies:
esbuild: 0.21.5
esbuild: 0.25.12
fdir: 6.5.0(picomatch@4.0.7)
picomatch: 4.0.7
postcss: 8.5.26
rollup: 4.62.4
tinyglobby: 0.2.17
optionalDependencies:
'@types/node': 26.2.0
fsevents: 2.3.3
jiti: 2.7.0
lightningcss: 1.33.0
tsx: 4.23.12
vite@8.3.0(@types/node@26.2.0)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.12):
dependencies:
@@ -9224,7 +9330,7 @@ snapshots:
jiti: 2.7.0
tsx: 4.23.12
vitepress@1.6.4(@algolia/client-search@5.56.0)(@types/node@26.2.0)(@types/react@19.2.18)(lightningcss@1.33.0)(postcss@8.5.26)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(search-insights@2.17.3)(sortablejs@1.14.0)(typescript@6.0.3):
vitepress@1.6.4(@algolia/client-search@5.56.0)(@types/node@26.2.0)(@types/react@19.2.18)(jiti@2.7.0)(lightningcss@1.33.0)(postcss@8.5.26)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(search-insights@2.17.3)(sortablejs@1.14.0)(tsx@4.23.12)(typescript@6.0.3):
dependencies:
'@docsearch/css': 3.8.2
'@docsearch/js': 3.8.2(@algolia/client-search@5.56.0)(@types/react@19.2.18)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(search-insights@2.17.3)
@@ -9233,7 +9339,7 @@ snapshots:
'@shikijs/transformers': 2.5.0
'@shikijs/types': 2.5.0
'@types/markdown-it': 14.2.0
'@vitejs/plugin-vue': 5.2.4(vite@5.4.21(@types/node@26.2.0)(lightningcss@1.33.0))(vue@3.5.41(typescript@6.0.3))
'@vitejs/plugin-vue': 5.2.4(vite@6.4.3(@types/node@26.2.0)(jiti@2.7.0)(lightningcss@1.33.0)(tsx@4.23.12))(vue@3.5.43(typescript@6.0.3))
'@vue/devtools-api': 7.7.10
'@vue/shared': 3.5.41
'@vueuse/core': 12.8.2(typescript@6.0.3)
@@ -9242,8 +9348,8 @@ snapshots:
mark.js: 8.11.1
minisearch: 7.2.0
shiki: 2.5.0
vite: 5.4.21(@types/node@26.2.0)(lightningcss@1.33.0)
vue: 3.5.41(typescript@6.0.3)
vite: 6.4.3(@types/node@26.2.0)(jiti@2.7.0)(lightningcss@1.33.0)(tsx@4.23.12)
vue: 3.5.43(typescript@6.0.3)
optionalDependencies:
postcss: 8.5.26
transitivePeerDependencies:
@@ -9256,6 +9362,7 @@ snapshots:
- drauu
- fuse.js
- idb-keyval
- jiti
- jwt-decode
- less
- lightningcss
@@ -9271,8 +9378,10 @@ snapshots:
- stylus
- sugarss
- terser
- tsx
- typescript
- universal-cookie
- yaml
vitest@5.0.1(@types/node@26.2.0)(vite@8.3.0(@types/node@26.2.0)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.12)):
dependencies:
@@ -9313,10 +9422,10 @@ snapshots:
transitivePeerDependencies:
- supports-color
vue-router@4.6.4(vue@3.5.41(typescript@6.0.3)):
vue-router@4.6.4(vue@3.5.43(typescript@6.0.3)):
dependencies:
'@vue/devtools-api': 6.6.4
vue: 3.5.41(typescript@6.0.3)
vue: 3.5.43(typescript@6.0.3)
vue-tsc@3.3.10(typescript@6.0.3):
dependencies:
@@ -9324,13 +9433,13 @@ snapshots:
'@vue/language-core': 3.3.10
typescript: 6.0.3
vue@3.5.41(typescript@6.0.3):
vue@3.5.43(typescript@6.0.3):
dependencies:
'@vue/compiler-dom': 3.5.41
'@vue/compiler-sfc': 3.5.41
'@vue/runtime-dom': 3.5.41
'@vue/server-renderer': 3.5.41
'@vue/shared': 3.5.41
'@vue/compiler-dom': 3.5.43
'@vue/compiler-sfc': 3.5.43
'@vue/runtime-dom': 3.5.43
'@vue/server-renderer': 3.5.43
'@vue/shared': 3.5.43
optionalDependencies:
typescript: 6.0.3
+11 -2
View File
@@ -5,8 +5,17 @@ packages:
overrides:
deepmerge-ts: 8.0.1
fast-uri: 3.1.5
js-yaml: 4.3.1
mysql2: 3.23.1
basic-ftp: 6.2.1
ip-address: 10.7.1
source-map-js: 1.2.2
prosemirror-view: 1.42.3
postcss-selector-parser: 7.1.6
'vitepress>vite': 6.4.3
'vue@>=3.5.0 <3.5.42': 3.5.43
'brace-expansion@>=4.0.0 <6.0.0': 5.0.12
fast-uri: 3.1.8
js-yaml: 4.3.2
lodash: 4.18.1
postcss: 8.5.26
rolldown-plugin-dts: 0.27.13
@@ -31,3 +31,4 @@ TURN_DIFFERENTIAL_DATABASE_URL core
TURN_FULL_LIFECYCLE_PERSISTENCE_DATABASE_URL reference_full_lifecycle
WEB_PUSH_GATEWAY_DATABASE_URL web_push_gateway
TOURNAMENT_RECOVERY_DATABASE_URL external_fixture
NPC_POLICY_DATABASE_URL external_fixture
1 # Environment variable Execution mode
31 TURN_FULL_LIFECYCLE_PERSISTENCE_DATABASE_URL reference_full_lifecycle
32 WEB_PUSH_GATEWAY_DATABASE_URL web_push_gateway
33 TOURNAMENT_RECOVERY_DATABASE_URL external_fixture
34 NPC_POLICY_DATABASE_URL external_fixture
+1 -1
View File
@@ -18,7 +18,7 @@
"@sammo-ts/common": "workspace:*",
"mariadb": "3.5.3",
"pg": "^8.16.3",
"sharp": "^0.35.0"
"sharp": "^0.35.5"
},
"devDependencies": {
"@types/node": "^26.2.0",
+33
View File
@@ -0,0 +1,33 @@
import { spawnSync } from 'node:child_process';
// 개발 전용 .env.ci를 node --env-file로 전달한다. 매번 새 schema를 만들며
// 기존 게임 schema·Redis key를 초기화하거나 volume을 삭제하지 않는다.
for (const key of [
'POSTGRES_HOST',
'POSTGRES_PORT',
'POSTGRES_DB',
'POSTGRES_USER',
'POSTGRES_PASSWORD',
'REDIS_URL',
]) {
if (!process.env[key]) throw new Error(`Missing environment variable: ${key}`);
}
const schema = `ci_${Date.now()}_${process.pid}_npc_policy_fixture`;
const url = new URL('postgresql://localhost');
url.hostname = process.env.POSTGRES_HOST;
url.port = process.env.POSTGRES_PORT;
url.username = process.env.POSTGRES_USER;
url.password = process.env.POSTGRES_PASSWORD;
url.pathname = `/${process.env.POSTGRES_DB}`;
url.searchParams.set('schema', schema);
const env = { ...process.env, DATABASE_URL: url.href, NPC_POLICY_DATABASE_URL: url.href, POSTGRES_SCHEMA: schema };
console.info(`NPC policy verification schema: ${schema}`);
for (const args of [
['--filter', '@sammo-ts/infra', 'prisma:migrate:deploy:game'],
['--filter', '@sammo-ts/infra', 'prisma:migrate:deploy:game'],
['--filter', '@sammo-ts/game-api', 'test', 'npcPolicyLifecycle.integration.test.ts'],
]) {
const result = spawnSync('pnpm', args, { env, stdio: 'inherit' });
if (result.error) throw new Error(`Could not start verification command: ${result.error.code}`);
if (result.status !== 0) process.exit(result.status ?? 1);
}