Compare commits
5 Commits
v1.5.0-ref
...
v1.5.1-boo
| Author | SHA1 | Date | |
|---|---|---|---|
| 4a9f207fe8 | |||
| 782c2b183d | |||
| 7f0fd1281b | |||
| 2f6be39efd | |||
| 1ecb79476e |
@@ -3,5 +3,6 @@ PORT=3000
|
|||||||
DATABASE_URL=postgres://boocode:CHANGE_ME@boocode_db:5432/boocode
|
DATABASE_URL=postgres://boocode:CHANGE_ME@boocode_db:5432/boocode
|
||||||
LLAMA_SWAP_URL=http://100.101.41.16:8401
|
LLAMA_SWAP_URL=http://100.101.41.16:8401
|
||||||
PROJECT_ROOT_WHITELIST=/opt
|
PROJECT_ROOT_WHITELIST=/opt
|
||||||
|
BOOTSTRAP_ROOT=/opt/projects
|
||||||
DEFAULT_MODEL=qwen3.6-35b-a3b-mxfp4
|
DEFAULT_MODEL=qwen3.6-35b-a3b-mxfp4
|
||||||
POSTGRES_PASSWORD=CHANGE_ME
|
POSTGRES_PASSWORD=CHANGE_ME
|
||||||
|
|||||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -5,3 +5,4 @@ dist
|
|||||||
.DS_Store
|
.DS_Store
|
||||||
.vite
|
.vite
|
||||||
coverage
|
coverage
|
||||||
|
secrets/
|
||||||
|
|||||||
@@ -88,7 +88,7 @@ Position-shift pattern for panes (legacy `session_panes` table): negate-and-rest
|
|||||||
|
|
||||||
## Environment
|
## Environment
|
||||||
|
|
||||||
Required: `DATABASE_URL`, `LLAMA_SWAP_URL`. Optional: `PORT` (3000), `HOST` (0.0.0.0), `PROJECT_ROOT_WHITELIST` (/opt), `DEFAULT_MODEL`, `LOG_LEVEL`.
|
Required: `DATABASE_URL`, `LLAMA_SWAP_URL`. Optional: `PORT` (3000), `HOST` (0.0.0.0), `PROJECT_ROOT_WHITELIST` (/opt, read-only scope for add-existing path resolution), `BOOTSTRAP_ROOT` (/opt/projects, writable scope for create-new-project bootstrap mkdir target — host must `mkdir -p /opt/projects` before container start), `DEFAULT_MODEL`, `LOG_LEVEL`.
|
||||||
|
|
||||||
## Workflow
|
## Workflow
|
||||||
|
|
||||||
|
|||||||
@@ -19,7 +19,8 @@ RUN pnpm deploy --filter=@boocode/server --prod --legacy /out/server
|
|||||||
|
|
||||||
|
|
||||||
FROM node:20-alpine AS runtime
|
FROM node:20-alpine AS runtime
|
||||||
RUN apk add --no-cache ripgrep
|
RUN apk add --no-cache ripgrep git openssh-client
|
||||||
|
RUN mkdir -p /root/.ssh && ssh-keyscan -p 2222 -H 100.114.205.53 git.indifferentketchup.com >> /root/.ssh/known_hosts && chmod 700 /root/.ssh && chmod 600 /root/.ssh/known_hosts
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
COPY --from=builder /out/server ./
|
COPY --from=builder /out/server ./
|
||||||
|
|||||||
@@ -7,7 +7,8 @@
|
|||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "tsx watch src/index.ts",
|
"dev": "tsx watch src/index.ts",
|
||||||
"build": "tsc && node -e \"import('node:fs').then(fs=>fs.copyFileSync('src/schema.sql','dist/schema.sql'))\"",
|
"build": "tsc && node -e \"import('node:fs').then(fs=>fs.copyFileSync('src/schema.sql','dist/schema.sql'))\"",
|
||||||
"typecheck": "tsc --noEmit"
|
"typecheck": "tsc --noEmit",
|
||||||
|
"test": "vitest run"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@fastify/static": "^7.0.4",
|
"@fastify/static": "^7.0.4",
|
||||||
@@ -21,6 +22,7 @@
|
|||||||
"@types/node": "^20.14.10",
|
"@types/node": "^20.14.10",
|
||||||
"@types/ws": "^8.5.10",
|
"@types/ws": "^8.5.10",
|
||||||
"tsx": "^4.16.2",
|
"tsx": "^4.16.2",
|
||||||
"typescript": "^5.5.0"
|
"typescript": "^5.5.0",
|
||||||
|
"vitest": "^3.2.4"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ const ConfigSchema = z.object({
|
|||||||
DATABASE_URL: z.string().url(),
|
DATABASE_URL: z.string().url(),
|
||||||
LLAMA_SWAP_URL: z.string().url(),
|
LLAMA_SWAP_URL: z.string().url(),
|
||||||
PROJECT_ROOT_WHITELIST: z.string().default('/opt'),
|
PROJECT_ROOT_WHITELIST: z.string().default('/opt'),
|
||||||
|
BOOTSTRAP_ROOT: z.string().default('/opt/projects'),
|
||||||
DEFAULT_MODEL: z.string().default('qwen3.6-35b-a3b-mxfp4'),
|
DEFAULT_MODEL: z.string().default('qwen3.6-35b-a3b-mxfp4'),
|
||||||
LOG_LEVEL: z.string().default('info'),
|
LOG_LEVEL: z.string().default('info'),
|
||||||
GITEA_BASE_URL: z.string().url().default('https://git.indifferentketchup.com'),
|
GITEA_BASE_URL: z.string().url().default('https://git.indifferentketchup.com'),
|
||||||
|
|||||||
100
apps/server/src/routes/__tests__/projects.test.ts
Normal file
100
apps/server/src/routes/__tests__/projects.test.ts
Normal file
@@ -0,0 +1,100 @@
|
|||||||
|
import { describe, it, expect, beforeAll, afterAll } from 'vitest';
|
||||||
|
import { mkdtemp, mkdir, rm, realpath, symlink, writeFile } from 'node:fs/promises';
|
||||||
|
import { tmpdir } from 'node:os';
|
||||||
|
import { join } from 'node:path';
|
||||||
|
import { resolveProjectPath } from '../projects.js';
|
||||||
|
|
||||||
|
describe('resolveProjectPath', () => {
|
||||||
|
let scratch: string;
|
||||||
|
let whitelist: string;
|
||||||
|
let outside: string;
|
||||||
|
|
||||||
|
beforeAll(async () => {
|
||||||
|
// mkdtemp returns a real path on most platforms, but symlink-resolve it
|
||||||
|
// anyway to defeat /tmp -> /private/tmp style indirection on macOS, and
|
||||||
|
// keep this stable for the comparisons below.
|
||||||
|
scratch = await realpath(await mkdtemp(join(tmpdir(), 'boocode-projects-test-')));
|
||||||
|
whitelist = join(scratch, 'wl');
|
||||||
|
outside = join(scratch, 'other');
|
||||||
|
await mkdir(whitelist, { recursive: true });
|
||||||
|
await mkdir(outside, { recursive: true });
|
||||||
|
});
|
||||||
|
|
||||||
|
afterAll(async () => {
|
||||||
|
if (scratch) {
|
||||||
|
await rm(scratch, { recursive: true, force: true });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns real path and basename for a valid subdirectory under the whitelist', async () => {
|
||||||
|
const projectDir = join(whitelist, 'my-project');
|
||||||
|
await mkdir(projectDir);
|
||||||
|
const result = await resolveProjectPath(projectDir, whitelist);
|
||||||
|
expect('error' in result).toBe(false);
|
||||||
|
if ('error' in result) return; // narrow
|
||||||
|
expect(result.real).toBe(projectDir);
|
||||||
|
expect(result.name).toBe('my-project');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('rejects a path outside the whitelist', async () => {
|
||||||
|
const projectDir = join(outside, 'foo');
|
||||||
|
await mkdir(projectDir);
|
||||||
|
const result = await resolveProjectPath(projectDir, whitelist);
|
||||||
|
expect('error' in result).toBe(true);
|
||||||
|
if (!('error' in result)) return;
|
||||||
|
expect(result.error.toLowerCase()).toContain('path must be under');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('rejects relative paths', async () => {
|
||||||
|
const result = await resolveProjectPath('relative/path', whitelist);
|
||||||
|
expect('error' in result).toBe(true);
|
||||||
|
if (!('error' in result)) return;
|
||||||
|
expect(result.error.toLowerCase()).toContain('absolute');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('rejects nonexistent paths', async () => {
|
||||||
|
const result = await resolveProjectPath('/nonexistent/foo-boocode-test', whitelist);
|
||||||
|
expect('error' in result).toBe(true);
|
||||||
|
if (!('error' in result)) return;
|
||||||
|
expect(result.error.toLowerCase()).toContain('does not exist');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('rejects symlink escapes (realpath resolution catches traversal)', async () => {
|
||||||
|
// Create a symlink INSIDE the whitelist that points OUTSIDE. realpath
|
||||||
|
// should resolve through it and the resulting real path should fail the
|
||||||
|
// whitelist scope check.
|
||||||
|
const escapeLink = join(whitelist, 'escape-link');
|
||||||
|
await symlink(outside, escapeLink);
|
||||||
|
const result = await resolveProjectPath(escapeLink, whitelist);
|
||||||
|
expect('error' in result).toBe(true);
|
||||||
|
if (!('error' in result)) return;
|
||||||
|
expect(result.error.toLowerCase()).toContain('path must be under');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('BEHAVIOR GAP: currently accepts the whitelist itself as a project root', async () => {
|
||||||
|
// SPEC says: the whitelist directory itself should be rejected — a
|
||||||
|
// project's parent can't be the project. The current implementation does
|
||||||
|
// NOT enforce this: the scope check is
|
||||||
|
// if (real !== whitelistReal && !real.startsWith(whitelistReal + sep))
|
||||||
|
// which evaluates to false when real === whitelistReal, so the whitelist
|
||||||
|
// path falls through and is accepted as a valid project root.
|
||||||
|
//
|
||||||
|
// This test documents the ACTUAL current behavior. Reported as a bug in
|
||||||
|
// the harness report; not silently fixed here. To tighten the check,
|
||||||
|
// change the condition to:
|
||||||
|
// if (!real.startsWith(whitelistReal + sep))
|
||||||
|
const result = await resolveProjectPath(whitelist, whitelist);
|
||||||
|
expect('error' in result).toBe(false);
|
||||||
|
if ('error' in result) return;
|
||||||
|
expect(result.real).toBe(whitelist);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('rejects non-directory targets (file under whitelist)', async () => {
|
||||||
|
const filePath = join(whitelist, 'a-file.txt');
|
||||||
|
await writeFile(filePath, 'content', 'utf8');
|
||||||
|
const result = await resolveProjectPath(filePath, whitelist);
|
||||||
|
expect('error' in result).toBe(true);
|
||||||
|
if (!('error' in result)) return;
|
||||||
|
expect(result.error.toLowerCase()).toContain('not a directory');
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -41,7 +41,7 @@ async function isDir(path: string): Promise<boolean> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function resolveProjectPath(
|
export async function resolveProjectPath(
|
||||||
raw: string,
|
raw: string,
|
||||||
whitelist: string
|
whitelist: string
|
||||||
): Promise<{ real: string; name: string } | { error: string }> {
|
): Promise<{ real: string; name: string } | { error: string }> {
|
||||||
|
|||||||
237
apps/server/src/services/__tests__/inference.test.ts
Normal file
237
apps/server/src/services/__tests__/inference.test.ts
Normal file
@@ -0,0 +1,237 @@
|
|||||||
|
import { describe, it, expect } from 'vitest';
|
||||||
|
import { buildMessagesPayload } from '../inference.js';
|
||||||
|
import type {
|
||||||
|
Message,
|
||||||
|
MessageRole,
|
||||||
|
Project,
|
||||||
|
Session,
|
||||||
|
ToolCall,
|
||||||
|
ToolResult,
|
||||||
|
} from '../../types/api.js';
|
||||||
|
|
||||||
|
// ---- fixtures ---------------------------------------------------------------
|
||||||
|
|
||||||
|
function makeSession(overrides: Partial<Session> = {}): Session {
|
||||||
|
return {
|
||||||
|
id: 'sess',
|
||||||
|
project_id: 'proj',
|
||||||
|
name: 'test session',
|
||||||
|
model: 'test-model',
|
||||||
|
system_prompt: '',
|
||||||
|
status: 'open',
|
||||||
|
created_at: new Date(0).toISOString(),
|
||||||
|
updated_at: new Date(0).toISOString(),
|
||||||
|
...overrides,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function makeProject(overrides: Partial<Project> = {}): Project {
|
||||||
|
return {
|
||||||
|
id: 'proj',
|
||||||
|
name: 'test project',
|
||||||
|
path: '/tmp/proj',
|
||||||
|
added_at: new Date(0).toISOString(),
|
||||||
|
last_session_id: null,
|
||||||
|
status: 'open',
|
||||||
|
gitea_remote: null,
|
||||||
|
...overrides,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
let counter = 0;
|
||||||
|
function makeMessage(
|
||||||
|
role: MessageRole,
|
||||||
|
content: string,
|
||||||
|
overrides: Partial<Message> = {}
|
||||||
|
): Message {
|
||||||
|
counter += 1;
|
||||||
|
return {
|
||||||
|
id: `m${counter}`,
|
||||||
|
session_id: 'sess',
|
||||||
|
chat_id: 'chat',
|
||||||
|
role,
|
||||||
|
content,
|
||||||
|
kind: 'message',
|
||||||
|
tool_calls: null,
|
||||||
|
tool_results: null,
|
||||||
|
status: 'complete',
|
||||||
|
last_seq: 0,
|
||||||
|
tokens_used: null,
|
||||||
|
ctx_used: null,
|
||||||
|
ctx_max: null,
|
||||||
|
started_at: null,
|
||||||
|
finished_at: null,
|
||||||
|
created_at: new Date(counter * 1000).toISOString(),
|
||||||
|
...overrides,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---- tests ------------------------------------------------------------------
|
||||||
|
|
||||||
|
describe('buildMessagesPayload', () => {
|
||||||
|
it('prepends a system prompt containing the project path', () => {
|
||||||
|
const session = makeSession();
|
||||||
|
const project = makeProject({ path: '/tmp/my-proj' });
|
||||||
|
const result = buildMessagesPayload(session, project, []);
|
||||||
|
expect(result).toHaveLength(1);
|
||||||
|
expect(result[0]!.role).toBe('system');
|
||||||
|
expect(result[0]!.content).toContain('/tmp/my-proj');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('appends session.system_prompt to the system message when set', () => {
|
||||||
|
const session = makeSession({ system_prompt: 'Be terse.' });
|
||||||
|
const project = makeProject();
|
||||||
|
const result = buildMessagesPayload(session, project, []);
|
||||||
|
expect(result).toHaveLength(1);
|
||||||
|
expect(result[0]!.role).toBe('system');
|
||||||
|
expect(result[0]!.content).toContain('Be terse.');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns user/assistant messages in order when no compact marker is present', () => {
|
||||||
|
const session = makeSession();
|
||||||
|
const project = makeProject();
|
||||||
|
const history: Message[] = [
|
||||||
|
makeMessage('user', 'hi'),
|
||||||
|
makeMessage('assistant', 'hello'),
|
||||||
|
makeMessage('user', 'how are you'),
|
||||||
|
makeMessage('assistant', 'great'),
|
||||||
|
];
|
||||||
|
const result = buildMessagesPayload(session, project, history);
|
||||||
|
// 1 system + 4 history messages
|
||||||
|
expect(result).toHaveLength(5);
|
||||||
|
expect(result[0]!.role).toBe('system');
|
||||||
|
expect(result[1]).toMatchObject({ role: 'user', content: 'hi' });
|
||||||
|
expect(result[2]).toMatchObject({ role: 'assistant', content: 'hello' });
|
||||||
|
expect(result[3]).toMatchObject({ role: 'user', content: 'how are you' });
|
||||||
|
expect(result[4]).toMatchObject({ role: 'assistant', content: 'great' });
|
||||||
|
});
|
||||||
|
|
||||||
|
it('starts from the latest compact marker, emitting it as a system message', () => {
|
||||||
|
const session = makeSession();
|
||||||
|
const project = makeProject();
|
||||||
|
const history: Message[] = [
|
||||||
|
makeMessage('user', 'old1'),
|
||||||
|
makeMessage('assistant', 'oldreply1'),
|
||||||
|
makeMessage('user', 'old2'),
|
||||||
|
makeMessage('assistant', 'compacted summary text', { kind: 'compact' }),
|
||||||
|
makeMessage('user', 'new1'),
|
||||||
|
makeMessage('assistant', 'newreply1'),
|
||||||
|
];
|
||||||
|
const result = buildMessagesPayload(session, project, history);
|
||||||
|
// Expect: leading base-system prompt, then the compact as system, then
|
||||||
|
// the user/assistant pair following it.
|
||||||
|
expect(result).toHaveLength(4);
|
||||||
|
expect(result[0]!.role).toBe('system');
|
||||||
|
expect(result[1]).toMatchObject({
|
||||||
|
role: 'system',
|
||||||
|
content: 'compacted summary text',
|
||||||
|
});
|
||||||
|
expect(result[2]).toMatchObject({ role: 'user', content: 'new1' });
|
||||||
|
expect(result[3]).toMatchObject({ role: 'assistant', content: 'newreply1' });
|
||||||
|
});
|
||||||
|
|
||||||
|
it('uses only the most recent compact when multiple are present', () => {
|
||||||
|
const session = makeSession();
|
||||||
|
const project = makeProject();
|
||||||
|
const history: Message[] = [
|
||||||
|
makeMessage('user', 'u1'),
|
||||||
|
makeMessage('assistant', 'first compact summary', { kind: 'compact' }),
|
||||||
|
makeMessage('user', 'u2'),
|
||||||
|
makeMessage('assistant', 'second compact summary', { kind: 'compact' }),
|
||||||
|
makeMessage('user', 'u3'),
|
||||||
|
makeMessage('assistant', 'final reply'),
|
||||||
|
];
|
||||||
|
const result = buildMessagesPayload(session, project, history);
|
||||||
|
// Expect: base system + latest compact as system + the two messages
|
||||||
|
// following it. The earlier compact and pre-compact history are dropped.
|
||||||
|
expect(result).toHaveLength(4);
|
||||||
|
expect(result[0]!.role).toBe('system');
|
||||||
|
expect(result[1]).toMatchObject({
|
||||||
|
role: 'system',
|
||||||
|
content: 'second compact summary',
|
||||||
|
});
|
||||||
|
expect(result[2]).toMatchObject({ role: 'user', content: 'u3' });
|
||||||
|
expect(result[3]).toMatchObject({ role: 'assistant', content: 'final reply' });
|
||||||
|
// None of the earlier content should leak through
|
||||||
|
const concatenated = result.map((m) => m.content ?? '').join(' ');
|
||||||
|
expect(concatenated).not.toContain('first compact summary');
|
||||||
|
expect(concatenated).not.toContain('u1');
|
||||||
|
expect(concatenated).not.toContain('u2');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('skips streaming and cancelled assistant rows', () => {
|
||||||
|
const session = makeSession();
|
||||||
|
const project = makeProject();
|
||||||
|
const history: Message[] = [
|
||||||
|
makeMessage('user', 'hi'),
|
||||||
|
makeMessage('assistant', 'partial...', { status: 'streaming' }),
|
||||||
|
makeMessage('assistant', 'cancelled fragment', { status: 'cancelled' }),
|
||||||
|
makeMessage('assistant', 'final answer'),
|
||||||
|
];
|
||||||
|
const result = buildMessagesPayload(session, project, history);
|
||||||
|
// 1 system + 1 user + 1 assistant (only the complete one)
|
||||||
|
expect(result).toHaveLength(3);
|
||||||
|
expect(result[1]).toMatchObject({ role: 'user', content: 'hi' });
|
||||||
|
expect(result[2]).toMatchObject({ role: 'assistant', content: 'final answer' });
|
||||||
|
});
|
||||||
|
|
||||||
|
it('round-trips an assistant-with-tool_calls followed by its tool result', () => {
|
||||||
|
const session = makeSession();
|
||||||
|
const project = makeProject();
|
||||||
|
const toolCall: ToolCall = {
|
||||||
|
id: 'call_abc',
|
||||||
|
name: 'view_file',
|
||||||
|
args: { path: 'src/index.ts' },
|
||||||
|
};
|
||||||
|
const toolResult: ToolResult = {
|
||||||
|
tool_call_id: 'call_abc',
|
||||||
|
output: { contents: 'console.log(1)' },
|
||||||
|
truncated: false,
|
||||||
|
};
|
||||||
|
const history: Message[] = [
|
||||||
|
makeMessage('user', 'show me the file'),
|
||||||
|
makeMessage('assistant', '', { tool_calls: [toolCall] }),
|
||||||
|
makeMessage('tool', '', { tool_results: toolResult }),
|
||||||
|
makeMessage('assistant', 'here it is'),
|
||||||
|
];
|
||||||
|
const result = buildMessagesPayload(session, project, history);
|
||||||
|
// 1 system + 1 user + 1 assistant(tool_calls) + 1 tool + 1 assistant
|
||||||
|
expect(result).toHaveLength(5);
|
||||||
|
expect(result[1]).toMatchObject({ role: 'user', content: 'show me the file' });
|
||||||
|
expect(result[2]!.role).toBe('assistant');
|
||||||
|
expect(result[2]!.tool_calls).toBeDefined();
|
||||||
|
expect(result[2]!.tool_calls).toHaveLength(1);
|
||||||
|
expect(result[2]!.tool_calls![0]).toMatchObject({
|
||||||
|
id: 'call_abc',
|
||||||
|
type: 'function',
|
||||||
|
function: { name: 'view_file' },
|
||||||
|
});
|
||||||
|
// The OpenAI shape stringifies args.
|
||||||
|
expect(result[2]!.tool_calls![0]!.function.arguments).toBe(
|
||||||
|
JSON.stringify({ path: 'src/index.ts' })
|
||||||
|
);
|
||||||
|
// assistant with empty content should be serialized as content: null
|
||||||
|
expect(result[2]!.content).toBeNull();
|
||||||
|
expect(result[3]).toMatchObject({
|
||||||
|
role: 'tool',
|
||||||
|
tool_call_id: 'call_abc',
|
||||||
|
});
|
||||||
|
// Non-string tool output is JSON-stringified.
|
||||||
|
expect(result[3]!.content).toBe(JSON.stringify({ contents: 'console.log(1)' }));
|
||||||
|
expect(result[4]).toMatchObject({ role: 'assistant', content: 'here it is' });
|
||||||
|
});
|
||||||
|
|
||||||
|
it('skips tool rows with no tool_results', () => {
|
||||||
|
const session = makeSession();
|
||||||
|
const project = makeProject();
|
||||||
|
const history: Message[] = [
|
||||||
|
makeMessage('user', 'do it'),
|
||||||
|
makeMessage('tool', '', { tool_results: null }),
|
||||||
|
makeMessage('assistant', 'done'),
|
||||||
|
];
|
||||||
|
const result = buildMessagesPayload(session, project, history);
|
||||||
|
// 1 system + 1 user + 1 assistant; the empty tool row is dropped.
|
||||||
|
expect(result).toHaveLength(3);
|
||||||
|
expect(result.find((m) => m.role === 'tool')).toBeUndefined();
|
||||||
|
});
|
||||||
|
});
|
||||||
47
apps/server/src/services/__tests__/project_bootstrap.test.ts
Normal file
47
apps/server/src/services/__tests__/project_bootstrap.test.ts
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
import { describe, it, expect } from 'vitest';
|
||||||
|
import { sanitizeFolderName } from '../project_bootstrap.js';
|
||||||
|
|
||||||
|
describe('sanitizeFolderName', () => {
|
||||||
|
it('passes through a normal slug-like name', () => {
|
||||||
|
expect(sanitizeFolderName('my-project')).toBe('my-project');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('lowercases and replaces whitespace with hyphens', () => {
|
||||||
|
expect(sanitizeFolderName('Hello World')).toBe('hello-world');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('strips path-traversal characters', () => {
|
||||||
|
// dots and slashes fall outside [a-z0-9-] and are removed entirely.
|
||||||
|
expect(sanitizeFolderName('../etc/passwd')).toBe('etcpasswd');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('strips trailing and leading dots and slashes', () => {
|
||||||
|
expect(sanitizeFolderName('./foo/')).toBe('foo');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('collapses runs of hyphens and strips leading/trailing ones', () => {
|
||||||
|
expect(sanitizeFolderName('---foo---')).toBe('foo');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns empty string when nothing survives sanitization', () => {
|
||||||
|
// NOTE: sanitizeFolderName itself does NOT throw — it returns ''. The
|
||||||
|
// BootstrapNameError is raised by the caller (bootstrapProject) when the
|
||||||
|
// sanitized result fails the SAFE_NAME regex. The spec's "throws" phrasing
|
||||||
|
// refers to that caller-level validation, not this pure function.
|
||||||
|
expect(sanitizeFolderName('...')).toBe('');
|
||||||
|
expect(sanitizeFolderName(' ')).toBe('');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('strips control characters and null bytes', () => {
|
||||||
|
// Null bytes and control characters are not in [a-z0-9-] so they're
|
||||||
|
// filtered out (effectively rejected as folder-name content).
|
||||||
|
expect(sanitizeFolderName('my\x00proj\x01')).toBe('myproj');
|
||||||
|
expect(sanitizeFolderName('foo\x00bar')).toBe('foobar');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('truncates names longer than 64 characters', () => {
|
||||||
|
const long = 'a'.repeat(100);
|
||||||
|
expect(sanitizeFolderName(long)).toBe('a'.repeat(64));
|
||||||
|
expect(sanitizeFolderName(long)).toHaveLength(64);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -33,6 +33,7 @@ async function snapMtimes(root: string): Promise<MtimeSnap> {
|
|||||||
const rootStat = await fs.stat(root);
|
const rootStat = await fs.stat(root);
|
||||||
let gitHead: number | null = null;
|
let gitHead: number | null = null;
|
||||||
let gitIndex: number | null = null;
|
let gitIndex: number | null = null;
|
||||||
|
// best-effort; ignore failure because the project may not be a git repo
|
||||||
try { gitHead = (await fs.stat(path.join(root, '.git', 'HEAD'))).mtimeMs; } catch {}
|
try { gitHead = (await fs.stat(path.join(root, '.git', 'HEAD'))).mtimeMs; } catch {}
|
||||||
try { gitIndex = (await fs.stat(path.join(root, '.git', 'index'))).mtimeMs; } catch {}
|
try { gitIndex = (await fs.stat(path.join(root, '.git', 'index'))).mtimeMs; } catch {}
|
||||||
return { root: rootStat.mtimeMs, gitHead, gitIndex };
|
return { root: rootStat.mtimeMs, gitHead, gitIndex };
|
||||||
|
|||||||
@@ -82,11 +82,13 @@ export async function bootstrapProject(
|
|||||||
throw new BootstrapNameError(`invalid name after sanitization: "${folder}"`);
|
throw new BootstrapNameError(`invalid name after sanitization: "${folder}"`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Whitelist resolution
|
// Bootstrap target resolution. Uses BOOTSTRAP_ROOT (writable), not
|
||||||
const whitelistReal = await realpath(config.PROJECT_ROOT_WHITELIST);
|
// PROJECT_ROOT_WHITELIST (which may be a wider read-only scope for
|
||||||
const fullPath = resolve(whitelistReal, folder);
|
// add-existing flow).
|
||||||
if (!fullPath.startsWith(whitelistReal + sep)) {
|
const bootstrapReal = await realpath(config.BOOTSTRAP_ROOT);
|
||||||
throw new BootstrapPathError('path escapes whitelist');
|
const fullPath = resolve(bootstrapReal, folder);
|
||||||
|
if (!fullPath.startsWith(bootstrapReal + sep)) {
|
||||||
|
throw new BootstrapPathError('path escapes bootstrap root');
|
||||||
}
|
}
|
||||||
if (existsSync(fullPath)) {
|
if (existsSync(fullPath)) {
|
||||||
throw new BootstrapCollisionError(`path already exists: ${fullPath}`);
|
throw new BootstrapCollisionError(`path already exists: ${fullPath}`);
|
||||||
@@ -144,7 +146,8 @@ export async function bootstrapProject(
|
|||||||
|
|
||||||
// Step 6: git remote add + push
|
// Step 6: git remote add + push
|
||||||
try {
|
try {
|
||||||
await execFileAsync('git', ['remote', 'add', 'origin', repo.ssh_url], { cwd: fullPath });
|
const sshUrl = repo.ssh_url.replace('git.indifferentketchup.com', '100.114.205.53');
|
||||||
|
await execFileAsync('git', ['remote', 'add', 'origin', sshUrl], { cwd: fullPath });
|
||||||
await execFileAsync('git', ['push', '-u', 'origin', 'main'], { cwd: fullPath });
|
await execFileAsync('git', ['push', '-u', 'origin', 'main'], { cwd: fullPath });
|
||||||
gitea_pushed = true;
|
gitea_pushed = true;
|
||||||
log.info({ folder }, 'project_bootstrap: pushed to gitea');
|
log.info({ folder }, 'project_bootstrap: pushed to gitea');
|
||||||
|
|||||||
@@ -10,5 +10,6 @@
|
|||||||
"declaration": false,
|
"declaration": false,
|
||||||
"sourceMap": true
|
"sourceMap": true
|
||||||
},
|
},
|
||||||
"include": ["src/**/*"]
|
"include": ["src/**/*"],
|
||||||
|
"exclude": ["src/**/__tests__/**", "**/*.test.ts"]
|
||||||
}
|
}
|
||||||
|
|||||||
9
apps/server/vitest.config.ts
Normal file
9
apps/server/vitest.config.ts
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
import { defineConfig } from 'vitest/config';
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
test: {
|
||||||
|
environment: 'node',
|
||||||
|
globals: false,
|
||||||
|
include: ['src/**/__tests__/**/*.test.ts'],
|
||||||
|
},
|
||||||
|
});
|
||||||
@@ -18,7 +18,10 @@ function SessionRightRail() {
|
|||||||
function RightRailForSession({ sessionId }: { sessionId: string }) {
|
function RightRailForSession({ sessionId }: { sessionId: string }) {
|
||||||
const [projectId, setProjectId] = useState<string | null>(null);
|
const [projectId, setProjectId] = useState<string | null>(null);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
api.sessions.get(sessionId).then((s) => setProjectId(s.project_id)).catch(() => {});
|
api.sessions
|
||||||
|
.get(sessionId)
|
||||||
|
.then((s) => setProjectId(s.project_id))
|
||||||
|
.catch((err) => console.warn('RightRail: failed to fetch session', err));
|
||||||
}, [sessionId]);
|
}, [sessionId]);
|
||||||
if (!projectId) return null;
|
if (!projectId) return null;
|
||||||
return <RightRail projectId={projectId} />;
|
return <RightRail projectId={projectId} />;
|
||||||
|
|||||||
@@ -191,6 +191,5 @@ export type WsFrame =
|
|||||||
finished_at?: string | null;
|
finished_at?: string | null;
|
||||||
}
|
}
|
||||||
| { type: 'messages_deleted'; message_ids: string[]; chat_id?: string }
|
| { type: 'messages_deleted'; message_ids: string[]; chat_id?: string }
|
||||||
| { type: 'session_renamed'; session_id: string; name: string; chat_id?: string }
|
|
||||||
| { type: 'chat_renamed'; chat_id: string; name: string }
|
| { type: 'chat_renamed'; chat_id: string; name: string }
|
||||||
| { type: 'error'; message_id?: string; chat_id?: string; error: string };
|
| { type: 'error'; message_id?: string; chat_id?: string; error: string };
|
||||||
|
|||||||
55
apps/web/src/components/ChatContextPopover.tsx
Normal file
55
apps/web/src/components/ChatContextPopover.tsx
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
import type { ChatContextStats } from '@/hooks/useChatContextStats';
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
stats: ChatContextStats | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Formats a token count into a compact k/m-suffix string.
|
||||||
|
* - < 1_000 → raw integer (e.g. "42")
|
||||||
|
* - 1_000–999_999 → "Nk" or "N.Nk" (e.g. "30k", "12.5k", "100k")
|
||||||
|
* - >= 1_000_000 → "Nm" or "N.Nm" (e.g. "1m", "1.5m", "100m")
|
||||||
|
*
|
||||||
|
* Drops a trailing ".0" so we get "30k" instead of "30.0k".
|
||||||
|
*/
|
||||||
|
function formatTokens(n: number): string {
|
||||||
|
if (n < 1000) return String(n);
|
||||||
|
if (n < 1_000_000) {
|
||||||
|
const k = n / 1000;
|
||||||
|
return k >= 100 ? `${Math.round(k)}k` : `${k.toFixed(1).replace(/\.0$/, '')}k`;
|
||||||
|
}
|
||||||
|
const m = n / 1_000_000;
|
||||||
|
return m >= 100 ? `${Math.round(m)}m` : `${m.toFixed(1).replace(/\.0$/, '')}m`;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Color thresholds:
|
||||||
|
* - > 85% → text-destructive
|
||||||
|
* - >= 60% → text-amber-500
|
||||||
|
* - else → text-muted-foreground
|
||||||
|
* (85% itself falls into the amber band.)
|
||||||
|
*/
|
||||||
|
function percentColorClass(percent: number): string {
|
||||||
|
if (percent > 85) return 'text-destructive';
|
||||||
|
if (percent >= 60) return 'text-amber-500';
|
||||||
|
return 'text-muted-foreground';
|
||||||
|
}
|
||||||
|
|
||||||
|
export function ChatContextPopover({ stats }: Props) {
|
||||||
|
if (!stats) return null;
|
||||||
|
return (
|
||||||
|
<div className="absolute bottom-full right-4 mb-4 z-20 pointer-events-none">
|
||||||
|
<div className="rounded-md border border-border bg-card text-card-foreground shadow-sm px-3 py-2 text-xs min-w-[140px]">
|
||||||
|
<div className="text-muted-foreground/80 text-[10px] uppercase tracking-wide mb-0.5">
|
||||||
|
Context window
|
||||||
|
</div>
|
||||||
|
<div className={`text-base font-medium ${percentColorClass(stats.percent)}`}>
|
||||||
|
{stats.percent}% used
|
||||||
|
</div>
|
||||||
|
<div className="text-muted-foreground text-[10px] font-mono">
|
||||||
|
{formatTokens(stats.used)} / {formatTokens(stats.max)} tokens
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -103,7 +103,7 @@ export function CreateProjectModal({ open, onOpenChange }: Props) {
|
|||||||
/>
|
/>
|
||||||
{name && (
|
{name && (
|
||||||
<div className="text-xs text-muted-foreground font-mono">
|
<div className="text-xs text-muted-foreground font-mono">
|
||||||
Folder: /opt/{folderPreview || <span className="text-destructive">(empty after sanitization)</span>}
|
Folder: /opt/projects/{folderPreview || <span className="text-destructive">(empty after sanitization)</span>}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -19,7 +19,6 @@ import {
|
|||||||
} from '@/components/ui/dialog';
|
} from '@/components/ui/dialog';
|
||||||
import { AddProjectModal } from './AddProjectModal';
|
import { AddProjectModal } from './AddProjectModal';
|
||||||
import { api } from '@/api/client';
|
import { api } from '@/api/client';
|
||||||
import { sessionEvents } from '@/hooks/sessionEvents';
|
|
||||||
import { useSidebar } from '@/hooks/useSidebar';
|
import { useSidebar } from '@/hooks/useSidebar';
|
||||||
import type { SidebarProject } from '@/api/types';
|
import type { SidebarProject } from '@/api/types';
|
||||||
import { giteaUrlFor } from '@/lib/projectUrls';
|
import { giteaUrlFor } from '@/lib/projectUrls';
|
||||||
@@ -186,7 +185,8 @@ export function ProjectSidebar() {
|
|||||||
if (!trimmed) return;
|
if (!trimmed) return;
|
||||||
try {
|
try {
|
||||||
await api.sessions.update(sessionId, { name: trimmed });
|
await api.sessions.update(sessionId, { name: trimmed });
|
||||||
sessionEvents.emit({ type: 'session_renamed', session_id: sessionId, name: trimmed });
|
// Server publishes session_renamed via broker.publishUser; useUserEvents
|
||||||
|
// forwards onto the bus. No local emit needed.
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
toast.error(err instanceof Error ? err.message : 'failed to rename session');
|
toast.error(err instanceof Error ? err.message : 'failed to rename session');
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -35,6 +35,7 @@ export function RightRail({ projectId }: Props) {
|
|||||||
const [viewerFile, setViewerFile] = useState<{ path: string; content: string } | null>(null);
|
const [viewerFile, setViewerFile] = useState<{ path: string; content: string } | null>(null);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
// best-effort; ignore failure because localStorage may be unavailable (quota, private mode)
|
||||||
try { localStorage.setItem(`${STORAGE_KEY}.open`, String(open)); } catch {}
|
try { localStorage.setItem(`${STORAGE_KEY}.open`, String(open)); } catch {}
|
||||||
}, [open]);
|
}, [open]);
|
||||||
|
|
||||||
|
|||||||
@@ -3,8 +3,10 @@ import { ChevronDown, Square, X } from 'lucide-react';
|
|||||||
import { toast } from 'sonner';
|
import { toast } from 'sonner';
|
||||||
import { api } from '@/api/client';
|
import { api } from '@/api/client';
|
||||||
import { useSessionStream } from '@/hooks/useSessionStream';
|
import { useSessionStream } from '@/hooks/useSessionStream';
|
||||||
|
import { useChatContextStats } from '@/hooks/useChatContextStats';
|
||||||
import { MessageList } from '@/components/MessageList';
|
import { MessageList } from '@/components/MessageList';
|
||||||
import { ChatInput } from '@/components/ChatInput';
|
import { ChatInput } from '@/components/ChatInput';
|
||||||
|
import { ChatContextPopover } from '@/components/ChatContextPopover';
|
||||||
import {
|
import {
|
||||||
DropdownMenu,
|
DropdownMenu,
|
||||||
DropdownMenuContent,
|
DropdownMenuContent,
|
||||||
@@ -37,6 +39,7 @@ export function ChatPane({ sessionId, chatId, projectId, sessionChats }: Props)
|
|||||||
|
|
||||||
const chatMessages = stream.messages.filter((m) => m.chat_id === chatId);
|
const chatMessages = stream.messages.filter((m) => m.chat_id === chatId);
|
||||||
const streaming = chatMessages.some((m) => m.status === 'streaming');
|
const streaming = chatMessages.some((m) => m.status === 'streaming');
|
||||||
|
const contextStats = useChatContextStats(chatId, chatMessages);
|
||||||
|
|
||||||
// Auto-send next queued message when streaming completes
|
// Auto-send next queued message when streaming completes
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -162,7 +165,10 @@ export function ChatPane({ sessionId, chatId, projectId, sessionChats }: Props)
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<ChatInput disabled={false} projectId={projectId} onSend={handleSend} onForceSend={streaming ? handleForceSend : undefined} />
|
<div className="relative">
|
||||||
|
<ChatContextPopover stats={contextStats} />
|
||||||
|
<ChatInput disabled={false} projectId={projectId} onSend={handleSend} onForceSend={streaming ? handleForceSend : undefined} />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
37
apps/web/src/hooks/useChatContextStats.ts
Normal file
37
apps/web/src/hooks/useChatContextStats.ts
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
import { useMemo } from 'react';
|
||||||
|
import type { Message } from '@/api/types';
|
||||||
|
|
||||||
|
export interface ChatContextStats {
|
||||||
|
used: number;
|
||||||
|
max: number;
|
||||||
|
percent: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the latest context-window usage for the given chat, derived from the
|
||||||
|
* assistant message (with both ctx_used and ctx_max populated) having the most
|
||||||
|
* recent created_at. Returns null when no such message exists.
|
||||||
|
*
|
||||||
|
* Re-evaluates whenever the `messages` reference or `chatId` changes, which
|
||||||
|
* matches the cadence of streaming updates from `useSessionStream`.
|
||||||
|
*/
|
||||||
|
export function useChatContextStats(
|
||||||
|
chatId: string,
|
||||||
|
messages: Message[],
|
||||||
|
): ChatContextStats | null {
|
||||||
|
return useMemo(() => {
|
||||||
|
let latest: Message | null = null;
|
||||||
|
for (const m of messages) {
|
||||||
|
if (m.chat_id !== chatId) continue;
|
||||||
|
if (m.role !== 'assistant') continue;
|
||||||
|
if (m.ctx_used == null || m.ctx_max == null) continue;
|
||||||
|
if (!latest || m.created_at > latest.created_at) latest = m;
|
||||||
|
}
|
||||||
|
if (!latest || latest.ctx_used == null || latest.ctx_max == null) return null;
|
||||||
|
const used = latest.ctx_used;
|
||||||
|
const max = latest.ctx_max;
|
||||||
|
if (max <= 0) return null;
|
||||||
|
const percent = Math.round((used / max) * 100);
|
||||||
|
return { used, max, percent };
|
||||||
|
}, [chatId, messages]);
|
||||||
|
}
|
||||||
@@ -2,6 +2,10 @@ import { useEffect, useRef, useState } from 'react';
|
|||||||
import type { Message, WsFrame } from '@/api/types';
|
import type { Message, WsFrame } from '@/api/types';
|
||||||
import { sessionEvents } from './sessionEvents';
|
import { sessionEvents } from './sessionEvents';
|
||||||
|
|
||||||
|
// session_renamed frame removed from WsFrame — it was declared but never
|
||||||
|
// published on the per-session WS channel (server publishes via broker.publishUser
|
||||||
|
// since v1.4). chat_renamed remains; auto_name.ts publishes it on session WS.
|
||||||
|
|
||||||
interface State {
|
interface State {
|
||||||
messages: Message[];
|
messages: Message[];
|
||||||
connected: boolean;
|
connected: boolean;
|
||||||
@@ -118,14 +122,6 @@ function applyFrame(state: State, frame: WsFrame): State {
|
|||||||
messages: state.messages.filter((m) => !removeSet.has(m.id)),
|
messages: state.messages.filter((m) => !removeSet.has(m.id)),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
case 'session_renamed': {
|
|
||||||
sessionEvents.emit({
|
|
||||||
type: 'session_renamed',
|
|
||||||
session_id: frame.session_id,
|
|
||||||
name: frame.name,
|
|
||||||
});
|
|
||||||
return state;
|
|
||||||
}
|
|
||||||
case 'chat_renamed': {
|
case 'chat_renamed': {
|
||||||
sessionEvents.emit({
|
sessionEvents.emit({
|
||||||
type: 'chat_updated',
|
type: 'chat_updated',
|
||||||
|
|||||||
@@ -40,7 +40,8 @@ export function useUserEvents(): void {
|
|||||||
};
|
};
|
||||||
|
|
||||||
ws.onerror = () => {
|
ws.onerror = () => {
|
||||||
// close handler will trigger reconnect
|
// close handler will trigger reconnect; best-effort, ignore failure
|
||||||
|
// because the socket may already be closing
|
||||||
try { ws?.close(); } catch {}
|
try { ws?.close(); } catch {}
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
@@ -50,6 +51,7 @@ export function useUserEvents(): void {
|
|||||||
return () => {
|
return () => {
|
||||||
unmounted = true;
|
unmounted = true;
|
||||||
if (reconnectTimer) clearTimeout(reconnectTimer);
|
if (reconnectTimer) clearTimeout(reconnectTimer);
|
||||||
|
// best-effort cleanup; ignore failure because the socket may already be closed
|
||||||
if (ws) try { ws.close(); } catch {}
|
if (ws) try { ws.close(); } catch {}
|
||||||
};
|
};
|
||||||
}, []);
|
}, []);
|
||||||
|
|||||||
@@ -38,9 +38,9 @@ export function Session() {
|
|||||||
if (cancelled) return;
|
if (cancelled) return;
|
||||||
const p = projects.find((x) => x.id === s.project_id);
|
const p = projects.find((x) => x.id === s.project_id);
|
||||||
if (p) setProject(p);
|
if (p) setProject(p);
|
||||||
}).catch(() => {});
|
}).catch((err) => console.warn('Session: failed to load project for breadcrumb', err));
|
||||||
})
|
})
|
||||||
.catch(() => {});
|
.catch((err) => console.warn('Session: failed to fetch session', err));
|
||||||
return () => {
|
return () => {
|
||||||
cancelled = true;
|
cancelled = true;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -9,7 +9,12 @@ services:
|
|||||||
environment:
|
environment:
|
||||||
DATABASE_URL: postgres://boocode:${POSTGRES_PASSWORD}@boocode_db:5432/boocode
|
DATABASE_URL: postgres://boocode:${POSTGRES_PASSWORD}@boocode_db:5432/boocode
|
||||||
volumes:
|
volumes:
|
||||||
- /opt:/opt:rw
|
# Read-only mount for legacy/existing project add-existing flow.
|
||||||
|
- /opt:/opt:ro
|
||||||
|
# Writable mount only for the create-new-project bootstrap target.
|
||||||
|
# Host must `mkdir -p /opt/projects` before container start.
|
||||||
|
- /opt/projects:/opt/projects:rw
|
||||||
|
- ./secrets/boocode_gitea:/root/.ssh/id_ed25519:ro
|
||||||
depends_on:
|
depends_on:
|
||||||
- boocode_db
|
- boocode_db
|
||||||
networks:
|
networks:
|
||||||
|
|||||||
322
pnpm-lock.yaml
generated
322
pnpm-lock.yaml
generated
@@ -45,6 +45,9 @@ importers:
|
|||||||
typescript:
|
typescript:
|
||||||
specifier: ^5.5.0
|
specifier: ^5.5.0
|
||||||
version: 5.9.3
|
version: 5.9.3
|
||||||
|
vitest:
|
||||||
|
specifier: ^3.2.4
|
||||||
|
version: 3.2.4(@types/debug@4.1.13)(@types/node@20.19.41)(lightningcss@1.32.0)(msw@2.14.6(@types/node@20.19.41)(typescript@5.9.3))
|
||||||
|
|
||||||
apps/web:
|
apps/web:
|
||||||
dependencies:
|
dependencies:
|
||||||
@@ -1697,9 +1700,15 @@ packages:
|
|||||||
'@types/babel__traverse@7.28.0':
|
'@types/babel__traverse@7.28.0':
|
||||||
resolution: {integrity: sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==}
|
resolution: {integrity: sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==}
|
||||||
|
|
||||||
|
'@types/chai@5.2.3':
|
||||||
|
resolution: {integrity: sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==}
|
||||||
|
|
||||||
'@types/debug@4.1.13':
|
'@types/debug@4.1.13':
|
||||||
resolution: {integrity: sha512-KSVgmQmzMwPlmtljOomayoR89W4FynCAi3E8PPs7vmDVPe84hT+vGPKkJfThkmXs0x0jAaa9U8uW8bbfyS2fWw==}
|
resolution: {integrity: sha512-KSVgmQmzMwPlmtljOomayoR89W4FynCAi3E8PPs7vmDVPe84hT+vGPKkJfThkmXs0x0jAaa9U8uW8bbfyS2fWw==}
|
||||||
|
|
||||||
|
'@types/deep-eql@4.0.2':
|
||||||
|
resolution: {integrity: sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==}
|
||||||
|
|
||||||
'@types/estree-jsx@1.0.5':
|
'@types/estree-jsx@1.0.5':
|
||||||
resolution: {integrity: sha512-52CcUVNFyfb1A2ALocQw/Dd1BQFNmSdkuC3BkZ6iqhdMfQz7JWOFRuJFloOzjk+6WijU56m9oKXFAXc7o3Towg==}
|
resolution: {integrity: sha512-52CcUVNFyfb1A2ALocQw/Dd1BQFNmSdkuC3BkZ6iqhdMfQz7JWOFRuJFloOzjk+6WijU56m9oKXFAXc7o3Towg==}
|
||||||
|
|
||||||
@@ -1756,6 +1765,35 @@ packages:
|
|||||||
peerDependencies:
|
peerDependencies:
|
||||||
vite: ^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0
|
vite: ^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0
|
||||||
|
|
||||||
|
'@vitest/expect@3.2.4':
|
||||||
|
resolution: {integrity: sha512-Io0yyORnB6sikFlt8QW5K7slY4OjqNX9jmJQ02QDda8lyM6B5oNgVWoSoKPac8/kgnCUzuHQKrSLtu/uOqqrig==}
|
||||||
|
|
||||||
|
'@vitest/mocker@3.2.4':
|
||||||
|
resolution: {integrity: sha512-46ryTE9RZO/rfDd7pEqFl7etuyzekzEhUbTW3BvmeO/BcCMEgq59BKhek3dXDWgAj4oMK6OZi+vRr1wPW6qjEQ==}
|
||||||
|
peerDependencies:
|
||||||
|
msw: ^2.4.9
|
||||||
|
vite: ^5.0.0 || ^6.0.0 || ^7.0.0-0
|
||||||
|
peerDependenciesMeta:
|
||||||
|
msw:
|
||||||
|
optional: true
|
||||||
|
vite:
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@vitest/pretty-format@3.2.4':
|
||||||
|
resolution: {integrity: sha512-IVNZik8IVRJRTr9fxlitMKeJeXFFFN0JaB9PHPGQ8NKQbGpfjlTx9zO4RefN8gp7eqjNy8nyK3NZmBzOPeIxtA==}
|
||||||
|
|
||||||
|
'@vitest/runner@3.2.4':
|
||||||
|
resolution: {integrity: sha512-oukfKT9Mk41LreEW09vt45f8wx7DordoWUZMYdY/cyAk7w5TWkTRCNZYF7sX7n2wB7jyGAl74OxgwhPgKaqDMQ==}
|
||||||
|
|
||||||
|
'@vitest/snapshot@3.2.4':
|
||||||
|
resolution: {integrity: sha512-dEYtS7qQP2CjU27QBC5oUOxLE/v5eLkGqPE0ZKEIDGMs4vKWe7IjgLOeauHsR0D5YuuycGRO5oSRXnwnmA78fQ==}
|
||||||
|
|
||||||
|
'@vitest/spy@3.2.4':
|
||||||
|
resolution: {integrity: sha512-vAfasCOe6AIK70iP5UD11Ac4siNUNJ9i/9PZ3NKx07sG6sUxeag1LWdNrMWeKKYBLlzuK+Gn65Yd5nyL6ds+nw==}
|
||||||
|
|
||||||
|
'@vitest/utils@3.2.4':
|
||||||
|
resolution: {integrity: sha512-fB2V0JFrQSMsCo9HiSq3Ezpdv4iYaXRG1Sx8edX3MwxfyNn83mKiGzOcH+Fkxt4MHxr3y42fQi1oeAInqgX2QA==}
|
||||||
|
|
||||||
abstract-logging@2.0.1:
|
abstract-logging@2.0.1:
|
||||||
resolution: {integrity: sha512-2BjRTZxTPvheOvGbBslFSYOUkr+SjPtOnrLP33f+VIWLzezQpZcqVg7ja3L4dBXmzzgwT+a029jRx5PCi3JuiA==}
|
resolution: {integrity: sha512-2BjRTZxTPvheOvGbBslFSYOUkr+SjPtOnrLP33f+VIWLzezQpZcqVg7ja3L4dBXmzzgwT+a029jRx5PCi3JuiA==}
|
||||||
|
|
||||||
@@ -1809,6 +1847,10 @@ packages:
|
|||||||
resolution: {integrity: sha512-ik3ZgC9dY/lYVVM++OISsaYDeg1tb0VtP5uL3ouh1koGOaUMDPpbFIei4JkFimWUFPn90sbMNMXQAIVOlnYKJA==}
|
resolution: {integrity: sha512-ik3ZgC9dY/lYVVM++OISsaYDeg1tb0VtP5uL3ouh1koGOaUMDPpbFIei4JkFimWUFPn90sbMNMXQAIVOlnYKJA==}
|
||||||
engines: {node: '>=10'}
|
engines: {node: '>=10'}
|
||||||
|
|
||||||
|
assertion-error@2.0.1:
|
||||||
|
resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==}
|
||||||
|
engines: {node: '>=12'}
|
||||||
|
|
||||||
ast-types@0.16.1:
|
ast-types@0.16.1:
|
||||||
resolution: {integrity: sha512-6t10qk83GOG8p0vKmaCr8eiilZwO171AvbROMtvvNiwrTly62t+7XkA8RdIIVbpMhCASAsxgAzdRSwh6nw/5Dg==}
|
resolution: {integrity: sha512-6t10qk83GOG8p0vKmaCr8eiilZwO171AvbROMtvvNiwrTly62t+7XkA8RdIIVbpMhCASAsxgAzdRSwh6nw/5Dg==}
|
||||||
engines: {node: '>=4'}
|
engines: {node: '>=4'}
|
||||||
@@ -1863,6 +1905,10 @@ packages:
|
|||||||
resolution: {integrity: sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==}
|
resolution: {integrity: sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==}
|
||||||
engines: {node: '>= 0.8'}
|
engines: {node: '>= 0.8'}
|
||||||
|
|
||||||
|
cac@6.7.14:
|
||||||
|
resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==}
|
||||||
|
engines: {node: '>=8'}
|
||||||
|
|
||||||
call-bind-apply-helpers@1.0.2:
|
call-bind-apply-helpers@1.0.2:
|
||||||
resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==}
|
resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==}
|
||||||
engines: {node: '>= 0.4'}
|
engines: {node: '>= 0.4'}
|
||||||
@@ -1881,6 +1927,10 @@ packages:
|
|||||||
ccount@2.0.1:
|
ccount@2.0.1:
|
||||||
resolution: {integrity: sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==}
|
resolution: {integrity: sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==}
|
||||||
|
|
||||||
|
chai@5.3.3:
|
||||||
|
resolution: {integrity: sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
|
||||||
chalk@5.6.2:
|
chalk@5.6.2:
|
||||||
resolution: {integrity: sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==}
|
resolution: {integrity: sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==}
|
||||||
engines: {node: ^12.17.0 || ^14.13 || >=16.0.0}
|
engines: {node: ^12.17.0 || ^14.13 || >=16.0.0}
|
||||||
@@ -1897,6 +1947,10 @@ packages:
|
|||||||
character-reference-invalid@2.0.1:
|
character-reference-invalid@2.0.1:
|
||||||
resolution: {integrity: sha512-iBZ4F4wRbyORVsu0jPV7gXkOsGYjGHPmAyv+HiHG8gi5PtC9KI2j1+v8/tlibRvjoWX027ypmG/n0HtO5t7unw==}
|
resolution: {integrity: sha512-iBZ4F4wRbyORVsu0jPV7gXkOsGYjGHPmAyv+HiHG8gi5PtC9KI2j1+v8/tlibRvjoWX027ypmG/n0HtO5t7unw==}
|
||||||
|
|
||||||
|
check-error@2.1.3:
|
||||||
|
resolution: {integrity: sha512-PAJdDJusoxnwm1VwW07VWwUN1sl7smmC3OKggvndJFadxxDRyFJBX/ggnu/KE4kQAB7a3Dp8f/YXC1FlUprWmA==}
|
||||||
|
engines: {node: '>= 16'}
|
||||||
|
|
||||||
class-variance-authority@0.7.1:
|
class-variance-authority@0.7.1:
|
||||||
resolution: {integrity: sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg==}
|
resolution: {integrity: sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg==}
|
||||||
|
|
||||||
@@ -2021,6 +2075,10 @@ packages:
|
|||||||
babel-plugin-macros:
|
babel-plugin-macros:
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
|
deep-eql@5.0.2:
|
||||||
|
resolution: {integrity: sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==}
|
||||||
|
engines: {node: '>=6'}
|
||||||
|
|
||||||
deepmerge@4.3.1:
|
deepmerge@4.3.1:
|
||||||
resolution: {integrity: sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==}
|
resolution: {integrity: sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==}
|
||||||
engines: {node: '>=0.10.0'}
|
engines: {node: '>=0.10.0'}
|
||||||
@@ -2121,6 +2179,9 @@ packages:
|
|||||||
resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==}
|
resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==}
|
||||||
engines: {node: '>= 0.4'}
|
engines: {node: '>= 0.4'}
|
||||||
|
|
||||||
|
es-module-lexer@1.7.0:
|
||||||
|
resolution: {integrity: sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==}
|
||||||
|
|
||||||
es-object-atoms@1.1.1:
|
es-object-atoms@1.1.1:
|
||||||
resolution: {integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==}
|
resolution: {integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==}
|
||||||
engines: {node: '>= 0.4'}
|
engines: {node: '>= 0.4'}
|
||||||
@@ -2154,6 +2215,9 @@ packages:
|
|||||||
estree-util-is-identifier-name@3.0.0:
|
estree-util-is-identifier-name@3.0.0:
|
||||||
resolution: {integrity: sha512-hFtqIDZTIUZ9BXLb8y4pYGyk6+wekIivNVTcmvk8NoOh+VeRn5y6cEHzbURrWbfp1fIqdVipilzj+lfaadNZmg==}
|
resolution: {integrity: sha512-hFtqIDZTIUZ9BXLb8y4pYGyk6+wekIivNVTcmvk8NoOh+VeRn5y6cEHzbURrWbfp1fIqdVipilzj+lfaadNZmg==}
|
||||||
|
|
||||||
|
estree-walker@3.0.3:
|
||||||
|
resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==}
|
||||||
|
|
||||||
etag@1.8.1:
|
etag@1.8.1:
|
||||||
resolution: {integrity: sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==}
|
resolution: {integrity: sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==}
|
||||||
engines: {node: '>= 0.6'}
|
engines: {node: '>= 0.6'}
|
||||||
@@ -2174,6 +2238,10 @@ packages:
|
|||||||
resolution: {integrity: sha512-9Be3ZoN4LmYR90tUoVu2te2BsbzHfhJyfEiAVfz7N5/zv+jduIfLrV2xdQXOHbaD6KgpGdO9PRPM1Y4Q9QkPkA==}
|
resolution: {integrity: sha512-9Be3ZoN4LmYR90tUoVu2te2BsbzHfhJyfEiAVfz7N5/zv+jduIfLrV2xdQXOHbaD6KgpGdO9PRPM1Y4Q9QkPkA==}
|
||||||
engines: {node: ^18.19.0 || >=20.5.0}
|
engines: {node: ^18.19.0 || >=20.5.0}
|
||||||
|
|
||||||
|
expect-type@1.3.0:
|
||||||
|
resolution: {integrity: sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==}
|
||||||
|
engines: {node: '>=12.0.0'}
|
||||||
|
|
||||||
express-rate-limit@8.5.2:
|
express-rate-limit@8.5.2:
|
||||||
resolution: {integrity: sha512-5Kb34ipNX694DH48vN9irak1Qx30nb0PLYHXfJgw4YEjiC3ZEmZJhwOp+VfiCYwFzvFTdB9QkArYS5kXa2cx2A==}
|
resolution: {integrity: sha512-5Kb34ipNX694DH48vN9irak1Qx30nb0PLYHXfJgw4YEjiC3ZEmZJhwOp+VfiCYwFzvFTdB9QkArYS5kXa2cx2A==}
|
||||||
engines: {node: '>= 16'}
|
engines: {node: '>= 16'}
|
||||||
@@ -2529,6 +2597,9 @@ packages:
|
|||||||
js-tokens@4.0.0:
|
js-tokens@4.0.0:
|
||||||
resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==}
|
resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==}
|
||||||
|
|
||||||
|
js-tokens@9.0.1:
|
||||||
|
resolution: {integrity: sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==}
|
||||||
|
|
||||||
js-yaml@4.1.1:
|
js-yaml@4.1.1:
|
||||||
resolution: {integrity: sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==}
|
resolution: {integrity: sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
@@ -2653,6 +2724,9 @@ packages:
|
|||||||
resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==}
|
resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
|
|
||||||
|
loupe@3.2.1:
|
||||||
|
resolution: {integrity: sha512-CdzqowRJCeLU72bHvWqwRBBlLcMEtIvGrlvef74kMnV2AolS9Y8xUv1I0U/MNAWMhBlKIoyuEgoJ0t/bbwHbLQ==}
|
||||||
|
|
||||||
lru-cache@10.4.3:
|
lru-cache@10.4.3:
|
||||||
resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==}
|
resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==}
|
||||||
|
|
||||||
@@ -2998,6 +3072,13 @@ packages:
|
|||||||
path-to-regexp@8.4.2:
|
path-to-regexp@8.4.2:
|
||||||
resolution: {integrity: sha512-qRcuIdP69NPm4qbACK+aDogI5CBDMi1jKe0ry5rSQJz8JVLsC7jV8XpiJjGRLLol3N+R5ihGYcrPLTno6pAdBA==}
|
resolution: {integrity: sha512-qRcuIdP69NPm4qbACK+aDogI5CBDMi1jKe0ry5rSQJz8JVLsC7jV8XpiJjGRLLol3N+R5ihGYcrPLTno6pAdBA==}
|
||||||
|
|
||||||
|
pathe@2.0.3:
|
||||||
|
resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==}
|
||||||
|
|
||||||
|
pathval@2.0.1:
|
||||||
|
resolution: {integrity: sha512-//nshmD55c46FuFw26xV/xFAaB5HF9Xdap7HJBBnrKdAd6/GxDBaNA1870O79+9ueg61cZLSVc+OaFlfmObYVQ==}
|
||||||
|
engines: {node: '>= 14.16'}
|
||||||
|
|
||||||
picocolors@1.1.1:
|
picocolors@1.1.1:
|
||||||
resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==}
|
resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==}
|
||||||
|
|
||||||
@@ -3308,6 +3389,9 @@ packages:
|
|||||||
resolution: {integrity: sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==}
|
resolution: {integrity: sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==}
|
||||||
engines: {node: '>= 0.4'}
|
engines: {node: '>= 0.4'}
|
||||||
|
|
||||||
|
siginfo@2.0.0:
|
||||||
|
resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==}
|
||||||
|
|
||||||
signal-exit@3.0.7:
|
signal-exit@3.0.7:
|
||||||
resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==}
|
resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==}
|
||||||
|
|
||||||
@@ -3342,6 +3426,9 @@ packages:
|
|||||||
resolution: {integrity: sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==}
|
resolution: {integrity: sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==}
|
||||||
engines: {node: '>= 10.x'}
|
engines: {node: '>= 10.x'}
|
||||||
|
|
||||||
|
stackback@0.0.2:
|
||||||
|
resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==}
|
||||||
|
|
||||||
statuses@2.0.1:
|
statuses@2.0.1:
|
||||||
resolution: {integrity: sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==}
|
resolution: {integrity: sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==}
|
||||||
engines: {node: '>= 0.8'}
|
engines: {node: '>= 0.8'}
|
||||||
@@ -3350,6 +3437,9 @@ packages:
|
|||||||
resolution: {integrity: sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==}
|
resolution: {integrity: sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==}
|
||||||
engines: {node: '>= 0.8'}
|
engines: {node: '>= 0.8'}
|
||||||
|
|
||||||
|
std-env@3.10.0:
|
||||||
|
resolution: {integrity: sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==}
|
||||||
|
|
||||||
stdin-discarder@0.2.2:
|
stdin-discarder@0.2.2:
|
||||||
resolution: {integrity: sha512-UhDfHmA92YAlNnCfhmq0VeNL5bDbiZGg7sZ2IvPsXubGkiNa9EC+tUTsjBRsYUAz87btI6/1wf4XoVvQ3uRnmQ==}
|
resolution: {integrity: sha512-UhDfHmA92YAlNnCfhmq0VeNL5bDbiZGg7sZ2IvPsXubGkiNa9EC+tUTsjBRsYUAz87btI6/1wf4XoVvQ3uRnmQ==}
|
||||||
engines: {node: '>=18'}
|
engines: {node: '>=18'}
|
||||||
@@ -3402,6 +3492,9 @@ packages:
|
|||||||
resolution: {integrity: sha512-aulFJcD6YK8V1G7iRB5tigAP4TsHBZZrOV8pjV++zdUwmeV8uzbY7yn6h9MswN62adStNZFuCIx4haBnRuMDaw==}
|
resolution: {integrity: sha512-aulFJcD6YK8V1G7iRB5tigAP4TsHBZZrOV8pjV++zdUwmeV8uzbY7yn6h9MswN62adStNZFuCIx4haBnRuMDaw==}
|
||||||
engines: {node: '>=18'}
|
engines: {node: '>=18'}
|
||||||
|
|
||||||
|
strip-literal@3.1.0:
|
||||||
|
resolution: {integrity: sha512-8r3mkIM/2+PpjHoOtiAW8Rg3jJLHaV7xPwG+YRGrv6FP0wwk/toTpATxWYOW0BKdWwl82VT2tFYi5DlROa0Mxg==}
|
||||||
|
|
||||||
style-to-js@1.1.21:
|
style-to-js@1.1.21:
|
||||||
resolution: {integrity: sha512-RjQetxJrrUJLQPHbLku6U/ocGtzyjbJMP9lCNK7Ag0CNh690nSH8woqWH9u16nMjYBAok+i7JO1NP2pOy8IsPQ==}
|
resolution: {integrity: sha512-RjQetxJrrUJLQPHbLku6U/ocGtzyjbJMP9lCNK7Ag0CNh690nSH8woqWH9u16nMjYBAok+i7JO1NP2pOy8IsPQ==}
|
||||||
|
|
||||||
@@ -3428,6 +3521,28 @@ packages:
|
|||||||
tiny-invariant@1.3.3:
|
tiny-invariant@1.3.3:
|
||||||
resolution: {integrity: sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==}
|
resolution: {integrity: sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==}
|
||||||
|
|
||||||
|
tinybench@2.9.0:
|
||||||
|
resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==}
|
||||||
|
|
||||||
|
tinyexec@0.3.2:
|
||||||
|
resolution: {integrity: sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==}
|
||||||
|
|
||||||
|
tinyglobby@0.2.16:
|
||||||
|
resolution: {integrity: sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg==}
|
||||||
|
engines: {node: '>=12.0.0'}
|
||||||
|
|
||||||
|
tinypool@1.1.1:
|
||||||
|
resolution: {integrity: sha512-Zba82s87IFq9A9XmjiX5uZA/ARWDrB03OHlq+Vw1fSdt0I+4/Kutwy8BP4Y/y/aORMo61FQ0vIb5j44vSo5Pkg==}
|
||||||
|
engines: {node: ^18.0.0 || >=20.0.0}
|
||||||
|
|
||||||
|
tinyrainbow@2.0.0:
|
||||||
|
resolution: {integrity: sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw==}
|
||||||
|
engines: {node: '>=14.0.0'}
|
||||||
|
|
||||||
|
tinyspy@4.0.4:
|
||||||
|
resolution: {integrity: sha512-azl+t0z7pw/z958Gy9svOTuzqIk6xq+NSheJzn5MMWtWTFywIacg2wUlzKFGtt3cthx0r2SxMK0yzJOR0IES7Q==}
|
||||||
|
engines: {node: '>=14.0.0'}
|
||||||
|
|
||||||
tldts-core@7.0.30:
|
tldts-core@7.0.30:
|
||||||
resolution: {integrity: sha512-uiHN8PIB1VmWyS98eZYja4xzlYqeFZVjb4OuYlJQnZAuJhMw4PbKQOKgHKhBdJR3FE/t5mUQ1Kd80++B+qhD1Q==}
|
resolution: {integrity: sha512-uiHN8PIB1VmWyS98eZYja4xzlYqeFZVjb4OuYlJQnZAuJhMw4PbKQOKgHKhBdJR3FE/t5mUQ1Kd80++B+qhD1Q==}
|
||||||
|
|
||||||
@@ -3572,6 +3687,11 @@ packages:
|
|||||||
vfile@6.0.3:
|
vfile@6.0.3:
|
||||||
resolution: {integrity: sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==}
|
resolution: {integrity: sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==}
|
||||||
|
|
||||||
|
vite-node@3.2.4:
|
||||||
|
resolution: {integrity: sha512-EbKSKh+bh1E1IFxeO0pg1n4dvoOTt0UDiXMd/qn++r98+jPO1xtJilvXldeuQ8giIB5IkpjCgMleHMNEsGH6pg==}
|
||||||
|
engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0}
|
||||||
|
hasBin: true
|
||||||
|
|
||||||
vite@5.4.21:
|
vite@5.4.21:
|
||||||
resolution: {integrity: sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==}
|
resolution: {integrity: sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==}
|
||||||
engines: {node: ^18.0.0 || >=20.0.0}
|
engines: {node: ^18.0.0 || >=20.0.0}
|
||||||
@@ -3603,6 +3723,34 @@ packages:
|
|||||||
terser:
|
terser:
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
|
vitest@3.2.4:
|
||||||
|
resolution: {integrity: sha512-LUCP5ev3GURDysTWiP47wRRUpLKMOfPh+yKTx3kVIEiu5KOMeqzpnYNsKyOoVrULivR8tLcks4+lga33Whn90A==}
|
||||||
|
engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0}
|
||||||
|
hasBin: true
|
||||||
|
peerDependencies:
|
||||||
|
'@edge-runtime/vm': '*'
|
||||||
|
'@types/debug': ^4.1.12
|
||||||
|
'@types/node': ^18.0.0 || ^20.0.0 || >=22.0.0
|
||||||
|
'@vitest/browser': 3.2.4
|
||||||
|
'@vitest/ui': 3.2.4
|
||||||
|
happy-dom: '*'
|
||||||
|
jsdom: '*'
|
||||||
|
peerDependenciesMeta:
|
||||||
|
'@edge-runtime/vm':
|
||||||
|
optional: true
|
||||||
|
'@types/debug':
|
||||||
|
optional: true
|
||||||
|
'@types/node':
|
||||||
|
optional: true
|
||||||
|
'@vitest/browser':
|
||||||
|
optional: true
|
||||||
|
'@vitest/ui':
|
||||||
|
optional: true
|
||||||
|
happy-dom:
|
||||||
|
optional: true
|
||||||
|
jsdom:
|
||||||
|
optional: true
|
||||||
|
|
||||||
web-streams-polyfill@3.3.3:
|
web-streams-polyfill@3.3.3:
|
||||||
resolution: {integrity: sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==}
|
resolution: {integrity: sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==}
|
||||||
engines: {node: '>= 8'}
|
engines: {node: '>= 8'}
|
||||||
@@ -3617,6 +3765,11 @@ packages:
|
|||||||
engines: {node: ^16.13.0 || >=18.0.0}
|
engines: {node: ^16.13.0 || >=18.0.0}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
|
|
||||||
|
why-is-node-running@2.3.0:
|
||||||
|
resolution: {integrity: sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==}
|
||||||
|
engines: {node: '>=8'}
|
||||||
|
hasBin: true
|
||||||
|
|
||||||
wrap-ansi@7.0.0:
|
wrap-ansi@7.0.0:
|
||||||
resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==}
|
resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==}
|
||||||
engines: {node: '>=10'}
|
engines: {node: '>=10'}
|
||||||
@@ -5196,10 +5349,17 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
'@babel/types': 7.29.0
|
'@babel/types': 7.29.0
|
||||||
|
|
||||||
|
'@types/chai@5.2.3':
|
||||||
|
dependencies:
|
||||||
|
'@types/deep-eql': 4.0.2
|
||||||
|
assertion-error: 2.0.1
|
||||||
|
|
||||||
'@types/debug@4.1.13':
|
'@types/debug@4.1.13':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@types/ms': 2.1.0
|
'@types/ms': 2.1.0
|
||||||
|
|
||||||
|
'@types/deep-eql@4.0.2': {}
|
||||||
|
|
||||||
'@types/estree-jsx@1.0.5':
|
'@types/estree-jsx@1.0.5':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@types/estree': 1.0.8
|
'@types/estree': 1.0.8
|
||||||
@@ -5261,6 +5421,49 @@ snapshots:
|
|||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
|
|
||||||
|
'@vitest/expect@3.2.4':
|
||||||
|
dependencies:
|
||||||
|
'@types/chai': 5.2.3
|
||||||
|
'@vitest/spy': 3.2.4
|
||||||
|
'@vitest/utils': 3.2.4
|
||||||
|
chai: 5.3.3
|
||||||
|
tinyrainbow: 2.0.0
|
||||||
|
|
||||||
|
'@vitest/mocker@3.2.4(msw@2.14.6(@types/node@20.19.41)(typescript@5.9.3))(vite@5.4.21(@types/node@20.19.41)(lightningcss@1.32.0))':
|
||||||
|
dependencies:
|
||||||
|
'@vitest/spy': 3.2.4
|
||||||
|
estree-walker: 3.0.3
|
||||||
|
magic-string: 0.30.21
|
||||||
|
optionalDependencies:
|
||||||
|
msw: 2.14.6(@types/node@20.19.41)(typescript@5.9.3)
|
||||||
|
vite: 5.4.21(@types/node@20.19.41)(lightningcss@1.32.0)
|
||||||
|
|
||||||
|
'@vitest/pretty-format@3.2.4':
|
||||||
|
dependencies:
|
||||||
|
tinyrainbow: 2.0.0
|
||||||
|
|
||||||
|
'@vitest/runner@3.2.4':
|
||||||
|
dependencies:
|
||||||
|
'@vitest/utils': 3.2.4
|
||||||
|
pathe: 2.0.3
|
||||||
|
strip-literal: 3.1.0
|
||||||
|
|
||||||
|
'@vitest/snapshot@3.2.4':
|
||||||
|
dependencies:
|
||||||
|
'@vitest/pretty-format': 3.2.4
|
||||||
|
magic-string: 0.30.21
|
||||||
|
pathe: 2.0.3
|
||||||
|
|
||||||
|
'@vitest/spy@3.2.4':
|
||||||
|
dependencies:
|
||||||
|
tinyspy: 4.0.4
|
||||||
|
|
||||||
|
'@vitest/utils@3.2.4':
|
||||||
|
dependencies:
|
||||||
|
'@vitest/pretty-format': 3.2.4
|
||||||
|
loupe: 3.2.1
|
||||||
|
tinyrainbow: 2.0.0
|
||||||
|
|
||||||
abstract-logging@2.0.1: {}
|
abstract-logging@2.0.1: {}
|
||||||
|
|
||||||
accepts@2.0.0:
|
accepts@2.0.0:
|
||||||
@@ -5301,6 +5504,8 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
tslib: 2.8.1
|
tslib: 2.8.1
|
||||||
|
|
||||||
|
assertion-error@2.0.1: {}
|
||||||
|
|
||||||
ast-types@0.16.1:
|
ast-types@0.16.1:
|
||||||
dependencies:
|
dependencies:
|
||||||
tslib: 2.8.1
|
tslib: 2.8.1
|
||||||
@@ -5360,6 +5565,8 @@ snapshots:
|
|||||||
|
|
||||||
bytes@3.1.2: {}
|
bytes@3.1.2: {}
|
||||||
|
|
||||||
|
cac@6.7.14: {}
|
||||||
|
|
||||||
call-bind-apply-helpers@1.0.2:
|
call-bind-apply-helpers@1.0.2:
|
||||||
dependencies:
|
dependencies:
|
||||||
es-errors: 1.3.0
|
es-errors: 1.3.0
|
||||||
@@ -5376,6 +5583,14 @@ snapshots:
|
|||||||
|
|
||||||
ccount@2.0.1: {}
|
ccount@2.0.1: {}
|
||||||
|
|
||||||
|
chai@5.3.3:
|
||||||
|
dependencies:
|
||||||
|
assertion-error: 2.0.1
|
||||||
|
check-error: 2.1.3
|
||||||
|
deep-eql: 5.0.2
|
||||||
|
loupe: 3.2.1
|
||||||
|
pathval: 2.0.1
|
||||||
|
|
||||||
chalk@5.6.2: {}
|
chalk@5.6.2: {}
|
||||||
|
|
||||||
character-entities-html4@2.1.0: {}
|
character-entities-html4@2.1.0: {}
|
||||||
@@ -5386,6 +5601,8 @@ snapshots:
|
|||||||
|
|
||||||
character-reference-invalid@2.0.1: {}
|
character-reference-invalid@2.0.1: {}
|
||||||
|
|
||||||
|
check-error@2.1.3: {}
|
||||||
|
|
||||||
class-variance-authority@0.7.1:
|
class-variance-authority@0.7.1:
|
||||||
dependencies:
|
dependencies:
|
||||||
clsx: 2.1.1
|
clsx: 2.1.1
|
||||||
@@ -5474,6 +5691,8 @@ snapshots:
|
|||||||
|
|
||||||
dedent@1.7.2: {}
|
dedent@1.7.2: {}
|
||||||
|
|
||||||
|
deep-eql@5.0.2: {}
|
||||||
|
|
||||||
deepmerge@4.3.1: {}
|
deepmerge@4.3.1: {}
|
||||||
|
|
||||||
default-browser-id@5.0.1: {}
|
default-browser-id@5.0.1: {}
|
||||||
@@ -5556,6 +5775,8 @@ snapshots:
|
|||||||
|
|
||||||
es-errors@1.3.0: {}
|
es-errors@1.3.0: {}
|
||||||
|
|
||||||
|
es-module-lexer@1.7.0: {}
|
||||||
|
|
||||||
es-object-atoms@1.1.1:
|
es-object-atoms@1.1.1:
|
||||||
dependencies:
|
dependencies:
|
||||||
es-errors: 1.3.0
|
es-errors: 1.3.0
|
||||||
@@ -5625,6 +5846,10 @@ snapshots:
|
|||||||
|
|
||||||
estree-util-is-identifier-name@3.0.0: {}
|
estree-util-is-identifier-name@3.0.0: {}
|
||||||
|
|
||||||
|
estree-walker@3.0.3:
|
||||||
|
dependencies:
|
||||||
|
'@types/estree': 1.0.8
|
||||||
|
|
||||||
etag@1.8.1: {}
|
etag@1.8.1: {}
|
||||||
|
|
||||||
eventsource-parser@3.0.8: {}
|
eventsource-parser@3.0.8: {}
|
||||||
@@ -5660,6 +5885,8 @@ snapshots:
|
|||||||
strip-final-newline: 4.0.0
|
strip-final-newline: 4.0.0
|
||||||
yoctocolors: 2.1.2
|
yoctocolors: 2.1.2
|
||||||
|
|
||||||
|
expect-type@1.3.0: {}
|
||||||
|
|
||||||
express-rate-limit@8.5.2(express@5.2.1):
|
express-rate-limit@8.5.2(express@5.2.1):
|
||||||
dependencies:
|
dependencies:
|
||||||
express: 5.2.1
|
express: 5.2.1
|
||||||
@@ -6053,6 +6280,8 @@ snapshots:
|
|||||||
|
|
||||||
js-tokens@4.0.0: {}
|
js-tokens@4.0.0: {}
|
||||||
|
|
||||||
|
js-tokens@9.0.1: {}
|
||||||
|
|
||||||
js-yaml@4.1.1:
|
js-yaml@4.1.1:
|
||||||
dependencies:
|
dependencies:
|
||||||
argparse: 2.0.1
|
argparse: 2.0.1
|
||||||
@@ -6149,6 +6378,8 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
js-tokens: 4.0.0
|
js-tokens: 4.0.0
|
||||||
|
|
||||||
|
loupe@3.2.1: {}
|
||||||
|
|
||||||
lru-cache@10.4.3: {}
|
lru-cache@10.4.3: {}
|
||||||
|
|
||||||
lru-cache@5.1.1:
|
lru-cache@5.1.1:
|
||||||
@@ -6700,6 +6931,10 @@ snapshots:
|
|||||||
|
|
||||||
path-to-regexp@8.4.2: {}
|
path-to-regexp@8.4.2: {}
|
||||||
|
|
||||||
|
pathe@2.0.3: {}
|
||||||
|
|
||||||
|
pathval@2.0.1: {}
|
||||||
|
|
||||||
picocolors@1.1.1: {}
|
picocolors@1.1.1: {}
|
||||||
|
|
||||||
picomatch@2.3.2: {}
|
picomatch@2.3.2: {}
|
||||||
@@ -7178,6 +7413,8 @@ snapshots:
|
|||||||
side-channel-map: 1.0.1
|
side-channel-map: 1.0.1
|
||||||
side-channel-weakmap: 1.0.2
|
side-channel-weakmap: 1.0.2
|
||||||
|
|
||||||
|
siginfo@2.0.0: {}
|
||||||
|
|
||||||
signal-exit@3.0.7: {}
|
signal-exit@3.0.7: {}
|
||||||
|
|
||||||
signal-exit@4.1.0: {}
|
signal-exit@4.1.0: {}
|
||||||
@@ -7201,10 +7438,14 @@ snapshots:
|
|||||||
|
|
||||||
split2@4.2.0: {}
|
split2@4.2.0: {}
|
||||||
|
|
||||||
|
stackback@0.0.2: {}
|
||||||
|
|
||||||
statuses@2.0.1: {}
|
statuses@2.0.1: {}
|
||||||
|
|
||||||
statuses@2.0.2: {}
|
statuses@2.0.2: {}
|
||||||
|
|
||||||
|
std-env@3.10.0: {}
|
||||||
|
|
||||||
stdin-discarder@0.2.2: {}
|
stdin-discarder@0.2.2: {}
|
||||||
|
|
||||||
stream-shift@1.0.3: {}
|
stream-shift@1.0.3: {}
|
||||||
@@ -7258,6 +7499,10 @@ snapshots:
|
|||||||
|
|
||||||
strip-final-newline@4.0.0: {}
|
strip-final-newline@4.0.0: {}
|
||||||
|
|
||||||
|
strip-literal@3.1.0:
|
||||||
|
dependencies:
|
||||||
|
js-tokens: 9.0.1
|
||||||
|
|
||||||
style-to-js@1.1.21:
|
style-to-js@1.1.21:
|
||||||
dependencies:
|
dependencies:
|
||||||
style-to-object: 1.0.14
|
style-to-object: 1.0.14
|
||||||
@@ -7280,6 +7525,21 @@ snapshots:
|
|||||||
|
|
||||||
tiny-invariant@1.3.3: {}
|
tiny-invariant@1.3.3: {}
|
||||||
|
|
||||||
|
tinybench@2.9.0: {}
|
||||||
|
|
||||||
|
tinyexec@0.3.2: {}
|
||||||
|
|
||||||
|
tinyglobby@0.2.16:
|
||||||
|
dependencies:
|
||||||
|
fdir: 6.5.0(picomatch@4.0.4)
|
||||||
|
picomatch: 4.0.4
|
||||||
|
|
||||||
|
tinypool@1.1.1: {}
|
||||||
|
|
||||||
|
tinyrainbow@2.0.0: {}
|
||||||
|
|
||||||
|
tinyspy@4.0.4: {}
|
||||||
|
|
||||||
tldts-core@7.0.30: {}
|
tldts-core@7.0.30: {}
|
||||||
|
|
||||||
tldts@7.0.30:
|
tldts@7.0.30:
|
||||||
@@ -7419,6 +7679,24 @@ snapshots:
|
|||||||
'@types/unist': 3.0.3
|
'@types/unist': 3.0.3
|
||||||
vfile-message: 4.0.3
|
vfile-message: 4.0.3
|
||||||
|
|
||||||
|
vite-node@3.2.4(@types/node@20.19.41)(lightningcss@1.32.0):
|
||||||
|
dependencies:
|
||||||
|
cac: 6.7.14
|
||||||
|
debug: 4.4.3
|
||||||
|
es-module-lexer: 1.7.0
|
||||||
|
pathe: 2.0.3
|
||||||
|
vite: 5.4.21(@types/node@20.19.41)(lightningcss@1.32.0)
|
||||||
|
transitivePeerDependencies:
|
||||||
|
- '@types/node'
|
||||||
|
- less
|
||||||
|
- lightningcss
|
||||||
|
- sass
|
||||||
|
- sass-embedded
|
||||||
|
- stylus
|
||||||
|
- sugarss
|
||||||
|
- supports-color
|
||||||
|
- terser
|
||||||
|
|
||||||
vite@5.4.21(@types/node@20.19.41)(lightningcss@1.32.0):
|
vite@5.4.21(@types/node@20.19.41)(lightningcss@1.32.0):
|
||||||
dependencies:
|
dependencies:
|
||||||
esbuild: 0.21.5
|
esbuild: 0.21.5
|
||||||
@@ -7429,6 +7707,45 @@ snapshots:
|
|||||||
fsevents: 2.3.3
|
fsevents: 2.3.3
|
||||||
lightningcss: 1.32.0
|
lightningcss: 1.32.0
|
||||||
|
|
||||||
|
vitest@3.2.4(@types/debug@4.1.13)(@types/node@20.19.41)(lightningcss@1.32.0)(msw@2.14.6(@types/node@20.19.41)(typescript@5.9.3)):
|
||||||
|
dependencies:
|
||||||
|
'@types/chai': 5.2.3
|
||||||
|
'@vitest/expect': 3.2.4
|
||||||
|
'@vitest/mocker': 3.2.4(msw@2.14.6(@types/node@20.19.41)(typescript@5.9.3))(vite@5.4.21(@types/node@20.19.41)(lightningcss@1.32.0))
|
||||||
|
'@vitest/pretty-format': 3.2.4
|
||||||
|
'@vitest/runner': 3.2.4
|
||||||
|
'@vitest/snapshot': 3.2.4
|
||||||
|
'@vitest/spy': 3.2.4
|
||||||
|
'@vitest/utils': 3.2.4
|
||||||
|
chai: 5.3.3
|
||||||
|
debug: 4.4.3
|
||||||
|
expect-type: 1.3.0
|
||||||
|
magic-string: 0.30.21
|
||||||
|
pathe: 2.0.3
|
||||||
|
picomatch: 4.0.4
|
||||||
|
std-env: 3.10.0
|
||||||
|
tinybench: 2.9.0
|
||||||
|
tinyexec: 0.3.2
|
||||||
|
tinyglobby: 0.2.16
|
||||||
|
tinypool: 1.1.1
|
||||||
|
tinyrainbow: 2.0.0
|
||||||
|
vite: 5.4.21(@types/node@20.19.41)(lightningcss@1.32.0)
|
||||||
|
vite-node: 3.2.4(@types/node@20.19.41)(lightningcss@1.32.0)
|
||||||
|
why-is-node-running: 2.3.0
|
||||||
|
optionalDependencies:
|
||||||
|
'@types/debug': 4.1.13
|
||||||
|
'@types/node': 20.19.41
|
||||||
|
transitivePeerDependencies:
|
||||||
|
- less
|
||||||
|
- lightningcss
|
||||||
|
- msw
|
||||||
|
- sass
|
||||||
|
- sass-embedded
|
||||||
|
- stylus
|
||||||
|
- sugarss
|
||||||
|
- supports-color
|
||||||
|
- terser
|
||||||
|
|
||||||
web-streams-polyfill@3.3.3: {}
|
web-streams-polyfill@3.3.3: {}
|
||||||
|
|
||||||
which@2.0.2:
|
which@2.0.2:
|
||||||
@@ -7439,6 +7756,11 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
isexe: 3.1.5
|
isexe: 3.1.5
|
||||||
|
|
||||||
|
why-is-node-running@2.3.0:
|
||||||
|
dependencies:
|
||||||
|
siginfo: 2.0.0
|
||||||
|
stackback: 0.0.2
|
||||||
|
|
||||||
wrap-ansi@7.0.0:
|
wrap-ansi@7.0.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
ansi-styles: 4.3.0
|
ansi-styles: 4.3.0
|
||||||
|
|||||||
Reference in New Issue
Block a user