Merge remote-tracking branch 'origin/dev' into feature/configurable-agent-ordering
This commit is contained in:
@@ -8,7 +8,7 @@ on:
|
||||
required: false
|
||||
default: ""
|
||||
push:
|
||||
branches: [master]
|
||||
branches: [master, dev]
|
||||
paths:
|
||||
- "web/**"
|
||||
- ".github/workflows/web-deploy.yml"
|
||||
|
||||
@@ -55,6 +55,7 @@ export const HookNameSchema = z.enum([
|
||||
"read-image-resizer",
|
||||
"todo-description-override",
|
||||
"webfetch-redirect-guard",
|
||||
"fsync-skip-warning",
|
||||
"legacy-plugin-toast",
|
||||
])
|
||||
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import { randomUUID } from "node:crypto"
|
||||
import { open, readFile, rename, rm, unlink, writeFile } from "node:fs/promises"
|
||||
|
||||
import { tolerantFsync } from "../../../shared/tolerant-fsync"
|
||||
|
||||
type LockOptions = {
|
||||
staleAfterMs?: number
|
||||
ownerTag?: string
|
||||
@@ -51,7 +53,7 @@ async function acquireLock(lockPath: string, ownerTag: string, staleAfterMs: num
|
||||
const fileHandle = await open(lockPath, "wx")
|
||||
try {
|
||||
await fileHandle.writeFile(buildOwnerContent(ownerTag))
|
||||
await fileHandle.sync()
|
||||
await tolerantFsync(fileHandle, `acquireLock:${lockPath}`)
|
||||
} finally {
|
||||
await fileHandle.close()
|
||||
}
|
||||
@@ -116,7 +118,7 @@ export async function atomicWrite(
|
||||
await writeFile(tmpPath, content)
|
||||
const fileHandle = await open(tmpPath, "r")
|
||||
try {
|
||||
await fileHandle.sync()
|
||||
await tolerantFsync(fileHandle, `atomicWrite:${filePath}`)
|
||||
} finally {
|
||||
await fileHandle.close()
|
||||
}
|
||||
|
||||
@@ -0,0 +1,98 @@
|
||||
import { beforeEach, describe, expect, it } from "bun:test"
|
||||
|
||||
import { classifyPathEnvironment } from "../../shared/classify-path-environment"
|
||||
import { clearAllSkips, recordFsyncSkip } from "../../shared/fsync-skip-tracker"
|
||||
import { createFsyncSkipWarningHook } from "./index"
|
||||
|
||||
describe("createFsyncSkipWarningHook", () => {
|
||||
beforeEach(() => {
|
||||
clearAllSkips()
|
||||
})
|
||||
|
||||
it("records callID start timestamp in tool.execute.before", async () => {
|
||||
const hook = createFsyncSkipWarningHook()
|
||||
const input = { tool: "bash", sessionID: "ses1", callID: "call-1" }
|
||||
const output = { args: {} as Record<string, unknown> }
|
||||
|
||||
await hook["tool.execute.before"](input, output)
|
||||
await Bun.sleep(2)
|
||||
|
||||
recordFsyncSkip({
|
||||
filePath: "/tmp/a",
|
||||
contextLabel: "atomicWrite:/tmp/a",
|
||||
errorCode: "EPERM",
|
||||
message: "operation not permitted",
|
||||
pathClassification: classifyPathEnvironment("/tmp/a"),
|
||||
})
|
||||
|
||||
const afterOutput = { title: "ok", output: "done", metadata: {} as Record<string, unknown> }
|
||||
await hook["tool.execute.after"](input, afterOutput)
|
||||
|
||||
expect(afterOutput.output).toContain("[fsync-skipped]")
|
||||
})
|
||||
|
||||
it("drains skips after start time and appends warning to output text", async () => {
|
||||
const hook = createFsyncSkipWarningHook()
|
||||
const input = { tool: "write", sessionID: "ses1", callID: "call-2" }
|
||||
const beforeOutput = { args: {} as Record<string, unknown> }
|
||||
const afterOutput = { title: "ok", output: "base", metadata: {} as Record<string, unknown> }
|
||||
|
||||
await hook["tool.execute.before"](input, beforeOutput)
|
||||
await Bun.sleep(2)
|
||||
|
||||
recordFsyncSkip({
|
||||
filePath: "/Users/x/OneDrive/a",
|
||||
contextLabel: "atomicWrite:/Users/x/OneDrive/a",
|
||||
errorCode: "EPERM",
|
||||
message: "operation not permitted",
|
||||
pathClassification: classifyPathEnvironment("/Users/x/OneDrive/a"),
|
||||
})
|
||||
|
||||
await hook["tool.execute.after"](input, afterOutput)
|
||||
|
||||
expect(afterOutput.output).toContain("base\n\n---")
|
||||
expect(afterOutput.output).toContain("OneDrive")
|
||||
})
|
||||
|
||||
it("leaves output unchanged when no skips happen during window", async () => {
|
||||
const hook = createFsyncSkipWarningHook()
|
||||
const input = { tool: "write", sessionID: "ses1", callID: "call-3" }
|
||||
const beforeOutput = { args: {} as Record<string, unknown> }
|
||||
const afterOutput = { title: "ok", output: "base", metadata: {} as Record<string, unknown> }
|
||||
|
||||
await hook["tool.execute.before"](input, beforeOutput)
|
||||
await hook["tool.execute.after"](input, afterOutput)
|
||||
|
||||
expect(afterOutput.output).toBe("base")
|
||||
})
|
||||
|
||||
it("isolates multiple parallel calls by callID watermark", async () => {
|
||||
const hook = createFsyncSkipWarningHook()
|
||||
const beforeOutput = { args: {} as Record<string, unknown> }
|
||||
|
||||
const inputA = { tool: "write", sessionID: "ses1", callID: "call-A" }
|
||||
const inputB = { tool: "write", sessionID: "ses1", callID: "call-B" }
|
||||
|
||||
await hook["tool.execute.before"](inputA, beforeOutput)
|
||||
await Bun.sleep(2)
|
||||
await hook["tool.execute.before"](inputB, beforeOutput)
|
||||
await Bun.sleep(2)
|
||||
|
||||
recordFsyncSkip({
|
||||
filePath: "/tmp/a",
|
||||
contextLabel: "atomicWrite:/tmp/a",
|
||||
errorCode: "EPERM",
|
||||
message: "operation not permitted",
|
||||
pathClassification: classifyPathEnvironment("/tmp/a"),
|
||||
})
|
||||
|
||||
const outputA = { title: "ok", output: "A", metadata: {} as Record<string, unknown> }
|
||||
const outputB = { title: "ok", output: "B", metadata: {} as Record<string, unknown> }
|
||||
|
||||
await hook["tool.execute.after"](inputA, outputA)
|
||||
await hook["tool.execute.after"](inputB, outputB)
|
||||
|
||||
expect(outputA.output).toContain("[fsync-skipped]")
|
||||
expect(outputB.output).toBe("B")
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,50 @@
|
||||
import { drainSkipsAfter } from "../../shared/fsync-skip-tracker"
|
||||
import { formatFsyncSkipWarning } from "../../shared/fsync-skip-warning-formatter"
|
||||
|
||||
type ToolExecuteInput = {
|
||||
tool: string
|
||||
sessionID: string
|
||||
callID: string
|
||||
}
|
||||
|
||||
type ToolBeforeOutput = {
|
||||
args: Record<string, unknown>
|
||||
}
|
||||
|
||||
type ToolAfterOutput = {
|
||||
title: string
|
||||
output: string
|
||||
metadata: unknown
|
||||
}
|
||||
|
||||
export function createFsyncSkipWarningHook() {
|
||||
const startTimesByCallId = new Map<string, number>()
|
||||
|
||||
const toolExecuteBefore = async (
|
||||
input: ToolExecuteInput,
|
||||
_output: ToolBeforeOutput,
|
||||
): Promise<void> => {
|
||||
startTimesByCallId.set(input.callID, Date.now())
|
||||
}
|
||||
|
||||
const toolExecuteAfter = async (
|
||||
input: ToolExecuteInput,
|
||||
output: ToolAfterOutput,
|
||||
): Promise<void> => {
|
||||
if (typeof output.output !== "string") return
|
||||
|
||||
const startTimestamp = startTimesByCallId.get(input.callID) ?? 0
|
||||
startTimesByCallId.delete(input.callID)
|
||||
|
||||
const skips = drainSkipsAfter(startTimestamp)
|
||||
const warning = formatFsyncSkipWarning(skips)
|
||||
if (warning.length === 0) return
|
||||
|
||||
output.output = `${output.output}\n\n${warning}`
|
||||
}
|
||||
|
||||
return {
|
||||
"tool.execute.before": toolExecuteBefore,
|
||||
"tool.execute.after": toolExecuteAfter,
|
||||
}
|
||||
}
|
||||
@@ -65,3 +65,4 @@ export { createReadImageResizerHook } from "./read-image-resizer"
|
||||
export { createTodoDescriptionOverrideHook } from "./todo-description-override"
|
||||
export { createWebFetchRedirectGuardHook } from "./webfetch-redirect-guard"
|
||||
export { createLegacyPluginToastHook } from "./legacy-plugin-toast"
|
||||
export { createFsyncSkipWarningHook } from "./fsync-skip-warning"
|
||||
|
||||
@@ -18,6 +18,7 @@ import {
|
||||
createTodoDescriptionOverrideHook,
|
||||
createWebFetchRedirectGuardHook,
|
||||
createTeamToolGating,
|
||||
createFsyncSkipWarningHook,
|
||||
} from "../../hooks"
|
||||
import {
|
||||
getOpenCodeVersion,
|
||||
@@ -42,6 +43,7 @@ export type ToolGuardHooks = {
|
||||
readImageResizer: ReturnType<typeof createReadImageResizerHook> | null
|
||||
todoDescriptionOverride: ReturnType<typeof createTodoDescriptionOverrideHook> | null
|
||||
webfetchRedirectGuard: ReturnType<typeof createWebFetchRedirectGuardHook> | null
|
||||
fsyncSkipWarning: ReturnType<typeof createFsyncSkipWarningHook> | null
|
||||
teamToolGating: ReturnType<typeof createTeamToolGating> | null
|
||||
}
|
||||
|
||||
@@ -139,6 +141,10 @@ export function createToolGuardHooks(args: {
|
||||
? safeHook("team-tool-gating", () => createTeamToolGating(ctx, pluginConfig.team_mode))
|
||||
: null
|
||||
|
||||
const fsyncSkipWarning = isHookEnabled("fsync-skip-warning")
|
||||
? safeHook("fsync-skip-warning", () => createFsyncSkipWarningHook())
|
||||
: null
|
||||
|
||||
return {
|
||||
commentChecker,
|
||||
toolOutputTruncator,
|
||||
@@ -154,6 +160,7 @@ export function createToolGuardHooks(args: {
|
||||
readImageResizer,
|
||||
todoDescriptionOverride,
|
||||
webfetchRedirectGuard,
|
||||
fsyncSkipWarning,
|
||||
teamToolGating,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -153,6 +153,7 @@ export function createToolExecuteAfterHandler(args: {
|
||||
await hooks.readImageResizer?.["tool.execute.after"]?.(hookInput, output)
|
||||
await hooks.hashlineReadEnhancer?.["tool.execute.after"]?.(hookInput, output)
|
||||
await hooks.webfetchRedirectGuard?.["tool.execute.after"]?.(hookInput, output)
|
||||
await hooks.fsyncSkipWarning?.["tool.execute.after"]?.(hookInput, output)
|
||||
await hooks.jsonErrorRecovery?.["tool.execute.after"]?.(hookInput, output)
|
||||
}
|
||||
|
||||
|
||||
@@ -72,8 +72,9 @@ export function createToolExecuteBeforeHandler(args: {
|
||||
await hooks.directoryReadmeInjector?.["tool.execute.before"]?.(input, output)
|
||||
await hooks.rulesInjector?.["tool.execute.before"]?.(input, output)
|
||||
await hooks.tasksTodowriteDisabler?.["tool.execute.before"]?.(input, output)
|
||||
await hooks.webfetchRedirectGuard?.["tool.execute.before"]?.(input, output)
|
||||
await hooks.prometheusMdOnly?.["tool.execute.before"]?.(input, output)
|
||||
await hooks.webfetchRedirectGuard?.["tool.execute.before"]?.(input, output)
|
||||
await hooks.fsyncSkipWarning?.["tool.execute.before"]?.(input, output)
|
||||
await hooks.prometheusMdOnly?.["tool.execute.before"]?.(input, output)
|
||||
await hooks.sisyphusJuniorNotepad?.["tool.execute.before"]?.(input, output)
|
||||
await hooks.atlasHook?.["tool.execute.before"]?.(input, output)
|
||||
await hooks.teamToolGating?.["tool.execute.before"]?.(input, output)
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
import { describe, expect, it } from "bun:test"
|
||||
|
||||
import {
|
||||
classifyPathEnvironment,
|
||||
describePathClassification,
|
||||
} from "./classify-path-environment"
|
||||
|
||||
describe("classifyPathEnvironment", () => {
|
||||
it("classifies macOS iCloud path as icloud", () => {
|
||||
expect(
|
||||
classifyPathEnvironment(
|
||||
"/Users/x/Library/Mobile Documents/com~apple~CloudDocs/project/file.txt",
|
||||
),
|
||||
).toBe("icloud")
|
||||
})
|
||||
|
||||
it("classifies OneDrive path on unix style", () => {
|
||||
expect(classifyPathEnvironment("/Users/x/OneDrive/foo")).toBe("onedrive")
|
||||
})
|
||||
|
||||
it("classifies OneDrive path on windows style", () => {
|
||||
expect(classifyPathEnvironment("C:\\Users\\x\\OneDrive\\foo")).toBe("onedrive")
|
||||
})
|
||||
|
||||
it("classifies macOS Desktop path as desktop-sync", () => {
|
||||
expect(classifyPathEnvironment("/Users/x/Desktop/foo")).toBe("desktop-sync")
|
||||
})
|
||||
|
||||
it("classifies /Volumes path as network-drive", () => {
|
||||
expect(classifyPathEnvironment("/Volumes/NetworkShare/foo")).toBe("network-drive")
|
||||
})
|
||||
|
||||
it("classifies random path as unknown", () => {
|
||||
expect(classifyPathEnvironment("/tmp/foo")).toBe("unknown")
|
||||
})
|
||||
|
||||
it("classifies empty string as unknown", () => {
|
||||
expect(classifyPathEnvironment("")).toBe("unknown")
|
||||
})
|
||||
|
||||
it("matches OneDrive case-insensitively", () => {
|
||||
expect(classifyPathEnvironment("/Users/x/oNeDrIvE/foo")).toBe("onedrive")
|
||||
})
|
||||
})
|
||||
|
||||
describe("describePathClassification", () => {
|
||||
it("returns human-readable descriptions", () => {
|
||||
expect(describePathClassification("icloud")).toBe("iCloud Drive")
|
||||
expect(describePathClassification("onedrive")).toBe("OneDrive")
|
||||
expect(describePathClassification("desktop-sync")).toBe("Desktop sync (macOS)")
|
||||
expect(describePathClassification("network-drive")).toBe("Network drive")
|
||||
expect(describePathClassification("unknown")).toBe(
|
||||
"filesystem that does not support fsync",
|
||||
)
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,68 @@
|
||||
import { homedir } from "node:os"
|
||||
import path from "node:path"
|
||||
|
||||
export type PathClassification =
|
||||
| "icloud"
|
||||
| "onedrive"
|
||||
| "desktop-sync"
|
||||
| "network-drive"
|
||||
| "unknown"
|
||||
|
||||
function normalizeInputPath(absolutePath: string): string {
|
||||
return absolutePath.replaceAll("\\", "/")
|
||||
}
|
||||
|
||||
function isUnderPath(normalizedPath: string, normalizedParentPath: string): boolean {
|
||||
return normalizedPath === normalizedParentPath || normalizedPath.startsWith(`${normalizedParentPath}/`)
|
||||
}
|
||||
|
||||
export function classifyPathEnvironment(absolutePath: string): PathClassification {
|
||||
if (absolutePath.length === 0) return "unknown"
|
||||
|
||||
const normalizedPath = normalizeInputPath(absolutePath)
|
||||
const lowercasePath = normalizedPath.toLowerCase()
|
||||
if (lowercasePath.includes("/onedrive") || lowercasePath.includes("/onedrive/")) {
|
||||
return "onedrive"
|
||||
}
|
||||
|
||||
if (normalizedPath.includes("/Library/Mobile Documents/")) {
|
||||
return "icloud"
|
||||
}
|
||||
|
||||
if (isUnderPath(normalizedPath, "/Volumes")) {
|
||||
return "network-drive"
|
||||
}
|
||||
|
||||
if (
|
||||
normalizedPath.startsWith("/Users/")
|
||||
&& (normalizedPath.includes("/Desktop/") || normalizedPath.endsWith("/Desktop")
|
||||
|| normalizedPath.includes("/Documents/") || normalizedPath.endsWith("/Documents"))
|
||||
) {
|
||||
return "desktop-sync"
|
||||
}
|
||||
|
||||
const normalizedHome = normalizeInputPath(homedir())
|
||||
const desktopPath = normalizeInputPath(path.join(normalizedHome, "Desktop"))
|
||||
const documentsPath = normalizeInputPath(path.join(normalizedHome, "Documents"))
|
||||
|
||||
if (isUnderPath(normalizedPath, desktopPath) || isUnderPath(normalizedPath, documentsPath)) {
|
||||
return "desktop-sync"
|
||||
}
|
||||
|
||||
return "unknown"
|
||||
}
|
||||
|
||||
export function describePathClassification(pathClassification: PathClassification): string {
|
||||
switch (pathClassification) {
|
||||
case "icloud":
|
||||
return "iCloud Drive"
|
||||
case "onedrive":
|
||||
return "OneDrive"
|
||||
case "desktop-sync":
|
||||
return "Desktop sync (macOS)"
|
||||
case "network-drive":
|
||||
return "Network drive"
|
||||
case "unknown":
|
||||
return "filesystem that does not support fsync"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
import { beforeEach, describe, expect, it } from "bun:test"
|
||||
|
||||
import {
|
||||
clearAllSkips,
|
||||
drainSkipsAfter,
|
||||
recordFsyncSkip,
|
||||
} from "./fsync-skip-tracker"
|
||||
|
||||
type PathClassification =
|
||||
| "icloud"
|
||||
| "onedrive"
|
||||
| "desktop-sync"
|
||||
| "network-drive"
|
||||
| "unknown"
|
||||
|
||||
function recordSkip(index: number, pathClassification: PathClassification = "unknown"): void {
|
||||
recordFsyncSkip({
|
||||
filePath: `/tmp/file-${index}.txt`,
|
||||
contextLabel: `atomicWrite:/tmp/file-${index}.txt`,
|
||||
errorCode: "EPERM",
|
||||
message: "operation not permitted",
|
||||
pathClassification,
|
||||
})
|
||||
}
|
||||
|
||||
describe("fsync-skip-tracker", () => {
|
||||
beforeEach(() => {
|
||||
clearAllSkips()
|
||||
})
|
||||
|
||||
it("recordFsyncSkip adds entry with timestamp", () => {
|
||||
const before = Date.now()
|
||||
recordSkip(1)
|
||||
const entries = drainSkipsAfter(0)
|
||||
|
||||
expect(entries).toHaveLength(1)
|
||||
expect(entries[0]?.filePath).toBe("/tmp/file-1.txt")
|
||||
expect(entries[0]?.timestamp).toBeGreaterThanOrEqual(before)
|
||||
})
|
||||
|
||||
it("drainSkipsAfter(timestamp) returns entries strictly after the timestamp", async () => {
|
||||
recordSkip(1)
|
||||
const firstTimestamp = Date.now()
|
||||
|
||||
await Bun.sleep(2)
|
||||
|
||||
recordSkip(2)
|
||||
const drained = drainSkipsAfter(firstTimestamp)
|
||||
expect(drained).toHaveLength(1)
|
||||
expect(drained[0]?.filePath).toBe("/tmp/file-2.txt")
|
||||
})
|
||||
|
||||
it("drainSkipsAfter removes drained entries from buffer", () => {
|
||||
recordSkip(1)
|
||||
recordSkip(2)
|
||||
|
||||
const drained = drainSkipsAfter(0)
|
||||
expect(drained).toHaveLength(2)
|
||||
expect(drainSkipsAfter(0)).toEqual([])
|
||||
})
|
||||
|
||||
it("buffer is bounded to max 200 entries and drops oldest on overflow", () => {
|
||||
for (let index = 1; index <= 205; index += 1) {
|
||||
recordSkip(index)
|
||||
}
|
||||
|
||||
const drained = drainSkipsAfter(0)
|
||||
expect(drained).toHaveLength(200)
|
||||
expect(drained[0]?.filePath).toBe("/tmp/file-6.txt")
|
||||
expect(drained[199]?.filePath).toBe("/tmp/file-205.txt")
|
||||
})
|
||||
|
||||
it("multiple records with same path are kept", () => {
|
||||
recordSkip(1)
|
||||
recordFsyncSkip({
|
||||
filePath: "/tmp/file-1.txt",
|
||||
contextLabel: "acquireLock:/tmp/file-1.txt",
|
||||
errorCode: "EPERM",
|
||||
message: "second",
|
||||
pathClassification: "unknown",
|
||||
})
|
||||
|
||||
const drained = drainSkipsAfter(0)
|
||||
expect(drained).toHaveLength(2)
|
||||
expect(drained[0]?.filePath).toBe("/tmp/file-1.txt")
|
||||
expect(drained[1]?.filePath).toBe("/tmp/file-1.txt")
|
||||
})
|
||||
|
||||
it("drainSkipsAfter(0) returns all entries", () => {
|
||||
recordSkip(1)
|
||||
recordSkip(2)
|
||||
|
||||
const drained = drainSkipsAfter(0)
|
||||
expect(drained).toHaveLength(2)
|
||||
})
|
||||
|
||||
it("empty buffer returns empty array", () => {
|
||||
expect(drainSkipsAfter(0)).toEqual([])
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,42 @@
|
||||
import type { PathClassification } from "./classify-path-environment"
|
||||
|
||||
export type FsyncSkipEntry = {
|
||||
filePath: string
|
||||
contextLabel: string
|
||||
errorCode: string
|
||||
message: string
|
||||
pathClassification: PathClassification
|
||||
timestamp: number
|
||||
}
|
||||
|
||||
const MAX_SKIPS = 200
|
||||
const fsyncSkips: FsyncSkipEntry[] = []
|
||||
|
||||
export function recordFsyncSkip(entry: Omit<FsyncSkipEntry, "timestamp">): void {
|
||||
fsyncSkips.push({ ...entry, timestamp: Date.now() })
|
||||
|
||||
if (fsyncSkips.length > MAX_SKIPS) {
|
||||
fsyncSkips.splice(0, fsyncSkips.length - MAX_SKIPS)
|
||||
}
|
||||
}
|
||||
|
||||
export function drainSkipsAfter(timestampMs: number): FsyncSkipEntry[] {
|
||||
const drainedEntries: FsyncSkipEntry[] = []
|
||||
const retainedEntries: FsyncSkipEntry[] = []
|
||||
|
||||
for (const entry of fsyncSkips) {
|
||||
if (entry.timestamp > timestampMs) {
|
||||
drainedEntries.push(entry)
|
||||
continue
|
||||
}
|
||||
|
||||
retainedEntries.push(entry)
|
||||
}
|
||||
|
||||
fsyncSkips.splice(0, fsyncSkips.length, ...retainedEntries)
|
||||
return drainedEntries
|
||||
}
|
||||
|
||||
export function clearAllSkips(): void {
|
||||
fsyncSkips.length = 0
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
import { describe, expect, it } from "bun:test"
|
||||
|
||||
import type { FsyncSkipEntry } from "./fsync-skip-tracker"
|
||||
import { formatFsyncSkipWarning } from "./fsync-skip-warning-formatter"
|
||||
|
||||
function makeEntry(index: number, classification: FsyncSkipEntry["pathClassification"]): FsyncSkipEntry {
|
||||
return {
|
||||
filePath: `/path/${index}`,
|
||||
contextLabel: `atomicWrite:/path/${index}`,
|
||||
errorCode: "EPERM",
|
||||
message: "operation not permitted",
|
||||
pathClassification: classification,
|
||||
timestamp: 1000 + index,
|
||||
}
|
||||
}
|
||||
|
||||
describe("formatFsyncSkipWarning", () => {
|
||||
it("returns empty string for zero entries", () => {
|
||||
expect(formatFsyncSkipWarning([])).toBe("")
|
||||
})
|
||||
|
||||
it("includes iCloud environment, path, and code for one entry", () => {
|
||||
const warning = formatFsyncSkipWarning([makeEntry(1, "icloud")])
|
||||
expect(warning).toContain("iCloud Drive")
|
||||
expect(warning).toContain("/path/1")
|
||||
expect(warning).toContain("EPERM")
|
||||
})
|
||||
|
||||
it("shows all five paths when exactly five entries exist", () => {
|
||||
const warning = formatFsyncSkipWarning([
|
||||
makeEntry(1, "icloud"),
|
||||
makeEntry(2, "icloud"),
|
||||
makeEntry(3, "icloud"),
|
||||
makeEntry(4, "icloud"),
|
||||
makeEntry(5, "icloud"),
|
||||
])
|
||||
|
||||
expect(warning).toContain("/path/1")
|
||||
expect(warning).toContain("/path/5")
|
||||
expect(warning).not.toContain("and 1 more")
|
||||
})
|
||||
|
||||
it("shows five paths plus overflow summary when six entries exist", () => {
|
||||
const warning = formatFsyncSkipWarning([
|
||||
makeEntry(1, "icloud"),
|
||||
makeEntry(2, "icloud"),
|
||||
makeEntry(3, "icloud"),
|
||||
makeEntry(4, "icloud"),
|
||||
makeEntry(5, "icloud"),
|
||||
makeEntry(6, "icloud"),
|
||||
])
|
||||
|
||||
expect(warning).toContain("/path/5")
|
||||
expect(warning).not.toContain("/path/6")
|
||||
expect(warning).toContain("... and 1 more")
|
||||
})
|
||||
|
||||
it("uses the most common classification when entries are mixed", () => {
|
||||
const warning = formatFsyncSkipWarning([
|
||||
makeEntry(1, "onedrive"),
|
||||
makeEntry(2, "onedrive"),
|
||||
makeEntry(3, "icloud"),
|
||||
])
|
||||
|
||||
expect(warning).toContain("Detected environment: OneDrive")
|
||||
})
|
||||
|
||||
it("matches required section format", () => {
|
||||
const warning = formatFsyncSkipWarning([makeEntry(1, "unknown")])
|
||||
|
||||
expect(warning).toContain("[fsync-skipped] 1 write(s) bypassed fsync")
|
||||
expect(warning).toContain("Affected paths:")
|
||||
expect(warning).toContain("What this means:")
|
||||
expect(warning).toContain("The write+rename succeeded")
|
||||
expect(warning).not.toContain("Detected environment:")
|
||||
expect(warning).toContain("filesystem does not support fsync")
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,61 @@
|
||||
import { describePathClassification } from "./classify-path-environment"
|
||||
import type { FsyncSkipEntry } from "./fsync-skip-tracker"
|
||||
|
||||
const MAX_PATH_LINES = 5
|
||||
|
||||
function selectMostCommonClassification(
|
||||
entries: FsyncSkipEntry[],
|
||||
): FsyncSkipEntry["pathClassification"] {
|
||||
const counts = new Map<FsyncSkipEntry["pathClassification"], number>()
|
||||
|
||||
for (const entry of entries) {
|
||||
const currentCount = counts.get(entry.pathClassification) ?? 0
|
||||
counts.set(entry.pathClassification, currentCount + 1)
|
||||
}
|
||||
|
||||
let selected: FsyncSkipEntry["pathClassification"] = "unknown"
|
||||
let selectedCount = -1
|
||||
for (const [classification, count] of counts.entries()) {
|
||||
if (count > selectedCount) {
|
||||
selected = classification
|
||||
selectedCount = count
|
||||
}
|
||||
}
|
||||
|
||||
return selected
|
||||
}
|
||||
|
||||
export function formatFsyncSkipWarning(entries: FsyncSkipEntry[]): string {
|
||||
if (entries.length === 0) return ""
|
||||
|
||||
const selectedClassification = selectMostCommonClassification(entries)
|
||||
const selectedDescription = describePathClassification(selectedClassification)
|
||||
const shownEntries = entries.slice(0, MAX_PATH_LINES)
|
||||
const hiddenCount = Math.max(entries.length - shownEntries.length, 0)
|
||||
const pathLines = shownEntries.map((entry) => ` - ${entry.filePath} (code: ${entry.errorCode})`)
|
||||
if (hiddenCount > 0) {
|
||||
pathLines.push(` ... and ${hiddenCount} more`)
|
||||
}
|
||||
|
||||
const environmentLines = selectedClassification === "unknown"
|
||||
? []
|
||||
: [`Detected environment: ${selectedDescription}`]
|
||||
|
||||
const durabilityLine = selectedClassification === "unknown"
|
||||
? " - Crash durability is best-effort because this filesystem does not support fsync."
|
||||
: " - Crash durability is best-effort on this filesystem (this is normal for iCloud, OneDrive, network drives, antivirus-locked paths)."
|
||||
|
||||
return [
|
||||
"---",
|
||||
`[fsync-skipped] ${entries.length} write(s) bypassed fsync because the underlying filesystem rejected the syscall.`,
|
||||
"",
|
||||
...environmentLines,
|
||||
"Affected paths:",
|
||||
...pathLines,
|
||||
"",
|
||||
"What this means:",
|
||||
" - The write+rename succeeded — the file is on disk, atomicity is preserved.",
|
||||
durabilityLine,
|
||||
" - No action required. Operation completed successfully.",
|
||||
].join("\n")
|
||||
}
|
||||
@@ -0,0 +1,158 @@
|
||||
import { beforeEach, describe, expect, it } from "bun:test"
|
||||
import { fsyncSync } from "node:fs"
|
||||
import type { FileHandle } from "node:fs/promises"
|
||||
|
||||
import { clearAllSkips, drainSkipsAfter } from "./fsync-skip-tracker"
|
||||
import { isToleratedFsyncError, tolerantFsync, tolerantFsyncSync } from "./tolerant-fsync"
|
||||
|
||||
function makeFsError(code: string, message?: string): NodeJS.ErrnoException {
|
||||
const error = new Error(message ?? `${code}: simulated`) as NodeJS.ErrnoException
|
||||
error.code = code
|
||||
return error
|
||||
}
|
||||
|
||||
function fakeHandleWithSyncError(error: NodeJS.ErrnoException): FileHandle {
|
||||
return {
|
||||
sync: async () => {
|
||||
throw error
|
||||
},
|
||||
} as FileHandle
|
||||
}
|
||||
|
||||
describe("isToleratedFsyncError", () => {
|
||||
it("#given EPERM error #when checked #then returns true", () => {
|
||||
expect(isToleratedFsyncError(makeFsError("EPERM"))).toBe(true)
|
||||
})
|
||||
|
||||
it("#given EACCES error #when checked #then returns true", () => {
|
||||
expect(isToleratedFsyncError(makeFsError("EACCES"))).toBe(true)
|
||||
})
|
||||
|
||||
it("#given ENOTSUP error #when checked #then returns true", () => {
|
||||
expect(isToleratedFsyncError(makeFsError("ENOTSUP"))).toBe(true)
|
||||
})
|
||||
|
||||
it("#given EINVAL error #when checked #then returns true", () => {
|
||||
expect(isToleratedFsyncError(makeFsError("EINVAL"))).toBe(true)
|
||||
})
|
||||
|
||||
it("#given EIO error #when checked #then returns false", () => {
|
||||
expect(isToleratedFsyncError(makeFsError("EIO"))).toBe(false)
|
||||
})
|
||||
|
||||
it("#given ENOSPC error (disk full) #when checked #then returns false", () => {
|
||||
expect(isToleratedFsyncError(makeFsError("ENOSPC"))).toBe(false)
|
||||
})
|
||||
|
||||
it("#given EBADF error (bad fd) #when checked #then returns false", () => {
|
||||
expect(isToleratedFsyncError(makeFsError("EBADF"))).toBe(false)
|
||||
})
|
||||
|
||||
it("#given non-Error value #when checked #then returns false", () => {
|
||||
expect(isToleratedFsyncError("EPERM string")).toBe(false)
|
||||
expect(isToleratedFsyncError(null)).toBe(false)
|
||||
expect(isToleratedFsyncError(undefined)).toBe(false)
|
||||
expect(isToleratedFsyncError({ code: "EPERM" })).toBe(false)
|
||||
})
|
||||
|
||||
it("#given Error without code #when checked #then returns false", () => {
|
||||
expect(isToleratedFsyncError(new Error("no code"))).toBe(false)
|
||||
})
|
||||
})
|
||||
|
||||
describe("tolerantFsync (async)", () => {
|
||||
beforeEach(() => {
|
||||
clearAllSkips()
|
||||
})
|
||||
|
||||
it("#given fsync throws EPERM #when called #then resolves without throwing", async () => {
|
||||
const handle = fakeHandleWithSyncError(makeFsError("EPERM", "operation not permitted, fsync"))
|
||||
await expect(tolerantFsync(handle, "test:async-eperm")).resolves.toBeUndefined()
|
||||
})
|
||||
|
||||
it("#given fsync throws EACCES #when called #then resolves without throwing", async () => {
|
||||
const handle = fakeHandleWithSyncError(makeFsError("EACCES"))
|
||||
await expect(tolerantFsync(handle, "test:async-eacces")).resolves.toBeUndefined()
|
||||
})
|
||||
|
||||
it("#given fsync throws ENOTSUP #when called #then resolves without throwing", async () => {
|
||||
const handle = fakeHandleWithSyncError(makeFsError("ENOTSUP"))
|
||||
await expect(tolerantFsync(handle, "test:async-enotsup")).resolves.toBeUndefined()
|
||||
})
|
||||
|
||||
it("#given fsync throws EINVAL #when called #then resolves without throwing", async () => {
|
||||
const handle = fakeHandleWithSyncError(makeFsError("EINVAL"))
|
||||
await expect(tolerantFsync(handle, "test:async-einval")).resolves.toBeUndefined()
|
||||
})
|
||||
|
||||
it("#given fsync throws EIO #when called #then propagates the error", async () => {
|
||||
const handle = fakeHandleWithSyncError(makeFsError("EIO"))
|
||||
await expect(tolerantFsync(handle, "test:async-eio")).rejects.toThrow("EIO: simulated")
|
||||
})
|
||||
|
||||
it("#given fsync throws ENOSPC #when called #then propagates the error", async () => {
|
||||
const handle = fakeHandleWithSyncError(makeFsError("ENOSPC"))
|
||||
await expect(tolerantFsync(handle, "test:async-enospc")).rejects.toThrow("ENOSPC: simulated")
|
||||
})
|
||||
|
||||
it("#given fsync succeeds #when called #then resolves and sync was invoked", async () => {
|
||||
let syncCalled = false
|
||||
const handle = {
|
||||
sync: async () => {
|
||||
syncCalled = true
|
||||
},
|
||||
} as FileHandle
|
||||
await tolerantFsync(handle, "test:async-success")
|
||||
expect(syncCalled).toBe(true)
|
||||
})
|
||||
|
||||
it("#given fsync throws EPERM #when called #then tracker records one skip", async () => {
|
||||
const handle = fakeHandleWithSyncError(makeFsError("EPERM", "operation not permitted, fsync"))
|
||||
|
||||
await tolerantFsync(handle, "atomicWrite:/Users/x/Library/Mobile Documents/com~apple~CloudDocs/file.txt")
|
||||
|
||||
const entries = drainSkipsAfter(0)
|
||||
expect(entries).toHaveLength(1)
|
||||
expect(entries[0]?.errorCode).toBe("EPERM")
|
||||
})
|
||||
|
||||
it("#given fsync throws EIO #when called #then tracker remains empty", async () => {
|
||||
const handle = fakeHandleWithSyncError(makeFsError("EIO"))
|
||||
|
||||
await expect(tolerantFsync(handle, "atomicWrite:/tmp/file.txt")).rejects.toThrow("EIO: simulated")
|
||||
|
||||
expect(drainSkipsAfter(0)).toHaveLength(0)
|
||||
})
|
||||
})
|
||||
|
||||
describe("tolerantFsyncSync (synchronous)", () => {
|
||||
it("#given fsyncSync throws EPERM #when called #then returns without throwing", () => {
|
||||
const fakeFsync = ((_fileDescriptor: number): void => {
|
||||
throw makeFsError("EPERM", "operation not permitted, fsync")
|
||||
}) as typeof fsyncSync
|
||||
expect(() => tolerantFsyncSync(123, "test:sync-eperm", fakeFsync)).not.toThrow()
|
||||
})
|
||||
|
||||
it("#given fsyncSync throws EACCES #when called #then returns without throwing", () => {
|
||||
const fakeFsync = ((_fileDescriptor: number): void => {
|
||||
throw makeFsError("EACCES")
|
||||
}) as typeof fsyncSync
|
||||
expect(() => tolerantFsyncSync(123, "test:sync-eacces", fakeFsync)).not.toThrow()
|
||||
})
|
||||
|
||||
it("#given fsyncSync throws EIO #when called #then propagates the error", () => {
|
||||
const fakeFsync = ((_fileDescriptor: number): void => {
|
||||
throw makeFsError("EIO")
|
||||
}) as typeof fsyncSync
|
||||
expect(() => tolerantFsyncSync(123, "test:sync-eio", fakeFsync)).toThrow("EIO: simulated")
|
||||
})
|
||||
|
||||
it("#given fsyncSync succeeds #when called #then returns and impl was invoked", () => {
|
||||
let called = false
|
||||
const fakeFsync = ((_fileDescriptor: number): void => {
|
||||
called = true
|
||||
}) as typeof fsyncSync
|
||||
tolerantFsyncSync(123, "test:sync-success", fakeFsync)
|
||||
expect(called).toBe(true)
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,85 @@
|
||||
import { fsyncSync } from "node:fs"
|
||||
import type { FileHandle } from "node:fs/promises"
|
||||
|
||||
import { classifyPathEnvironment } from "./classify-path-environment"
|
||||
import { recordFsyncSkip } from "./fsync-skip-tracker"
|
||||
import { log } from "./logger"
|
||||
|
||||
const TOLERATED_FSYNC_CODES: ReadonlySet<string> = new Set([
|
||||
"EPERM",
|
||||
"EACCES",
|
||||
"ENOTSUP",
|
||||
"EINVAL",
|
||||
])
|
||||
|
||||
export function isToleratedFsyncError(error: unknown): boolean {
|
||||
if (!(error instanceof Error)) return false
|
||||
const code = (error as NodeJS.ErrnoException).code
|
||||
return code !== undefined && TOLERATED_FSYNC_CODES.has(code)
|
||||
}
|
||||
|
||||
function extractPathFromContextLabel(contextLabel: string): string {
|
||||
const separatorIndex = contextLabel.indexOf(":")
|
||||
if (separatorIndex < 0) return contextLabel
|
||||
|
||||
return contextLabel.slice(separatorIndex + 1)
|
||||
}
|
||||
|
||||
export async function tolerantFsync(
|
||||
fileHandle: FileHandle,
|
||||
contextLabel: string,
|
||||
): Promise<void> {
|
||||
try {
|
||||
await fileHandle.sync()
|
||||
} catch (error) {
|
||||
if (!isToleratedFsyncError(error)) throw error
|
||||
const errorCode = (error as NodeJS.ErrnoException).code ?? "UNKNOWN"
|
||||
const message = error instanceof Error ? error.message : String(error)
|
||||
const filePath = extractPathFromContextLabel(contextLabel)
|
||||
|
||||
log("fsync skipped due to filesystem limitation", {
|
||||
event: "fsync-skipped",
|
||||
contextLabel,
|
||||
code: errorCode,
|
||||
message,
|
||||
})
|
||||
|
||||
recordFsyncSkip({
|
||||
filePath,
|
||||
contextLabel,
|
||||
errorCode,
|
||||
message,
|
||||
pathClassification: classifyPathEnvironment(filePath),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
export function tolerantFsyncSync(
|
||||
fileDescriptor: number,
|
||||
contextLabel: string,
|
||||
fsyncImpl: typeof fsyncSync = fsyncSync,
|
||||
): void {
|
||||
try {
|
||||
fsyncImpl(fileDescriptor)
|
||||
} catch (error) {
|
||||
if (!isToleratedFsyncError(error)) throw error
|
||||
const errorCode = (error as NodeJS.ErrnoException).code ?? "UNKNOWN"
|
||||
const message = error instanceof Error ? error.message : String(error)
|
||||
const filePath = extractPathFromContextLabel(contextLabel)
|
||||
|
||||
log("fsync skipped due to filesystem limitation", {
|
||||
event: "fsync-skipped",
|
||||
contextLabel,
|
||||
code: errorCode,
|
||||
message,
|
||||
})
|
||||
|
||||
recordFsyncSkip({
|
||||
filePath,
|
||||
contextLabel,
|
||||
errorCode,
|
||||
message,
|
||||
pathClassification: classifyPathEnvironment(filePath),
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -51,4 +51,39 @@ describe("writeFileAtomically", () => {
|
||||
// when/then
|
||||
expect(() => writeFileAtomically(filePath, "content")).toThrow()
|
||||
})
|
||||
|
||||
it("#given fsync fails with EPERM (synced folder) #when writeFileAtomically called #then write succeeds", () => {
|
||||
// given
|
||||
const filePath = join(testDir, "synced-folder.txt")
|
||||
const content = "content from a synced folder where fsync is rejected"
|
||||
|
||||
// when
|
||||
writeFileAtomically(filePath, content, {
|
||||
fsyncSync: () => {
|
||||
const error = new Error("EPERM: operation not permitted, fsync") as NodeJS.ErrnoException
|
||||
error.code = "EPERM"
|
||||
throw error
|
||||
},
|
||||
})
|
||||
|
||||
// then
|
||||
expect(existsSync(filePath)).toBe(true)
|
||||
expect(readFileSync(filePath, "utf-8")).toBe(content)
|
||||
})
|
||||
|
||||
it("#given fsync fails with EIO (real I/O error) #when writeFileAtomically called #then propagates the error", () => {
|
||||
// given
|
||||
const filePath = join(testDir, "io-error.txt")
|
||||
|
||||
// when/then
|
||||
expect(() =>
|
||||
writeFileAtomically(filePath, "content", {
|
||||
fsyncSync: () => {
|
||||
const error = new Error("EIO: input/output error") as NodeJS.ErrnoException
|
||||
error.code = "EIO"
|
||||
throw error
|
||||
},
|
||||
}),
|
||||
).toThrow("EIO")
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,11 +1,24 @@
|
||||
import { closeSync, fsyncSync, openSync, renameSync, unlinkSync, writeFileSync } from "node:fs"
|
||||
import {
|
||||
closeSync,
|
||||
type fsyncSync as FsyncSync,
|
||||
openSync,
|
||||
renameSync,
|
||||
unlinkSync,
|
||||
writeFileSync,
|
||||
} from "node:fs"
|
||||
|
||||
export function writeFileAtomically(filePath: string, content: string): void {
|
||||
const tempPath = `${filePath}.tmp`
|
||||
writeFileSync(tempPath, content, "utf-8")
|
||||
import { tolerantFsyncSync } from "./tolerant-fsync"
|
||||
|
||||
export function writeFileAtomically(
|
||||
filePath: string,
|
||||
content: string,
|
||||
deps: { fsyncSync?: typeof FsyncSync } = {},
|
||||
): void {
|
||||
const tempPath = `${filePath}.tmp`
|
||||
writeFileSync(tempPath, content, "utf-8")
|
||||
const tempFileDescriptor = openSync(tempPath, "r")
|
||||
try {
|
||||
fsyncSync(tempFileDescriptor)
|
||||
tolerantFsyncSync(tempFileDescriptor, `writeFileAtomically:${filePath}`, deps.fsyncSync)
|
||||
} finally {
|
||||
closeSync(tempFileDescriptor)
|
||||
}
|
||||
|
||||
+18
-82
@@ -13,7 +13,7 @@
|
||||
"geist": "^1.7.0",
|
||||
"lucide-react": "0.577.0",
|
||||
"motion": "^12.38.0",
|
||||
"next": "16.2.6",
|
||||
"next": "15.5.18",
|
||||
"next-intl": "^4.11.0",
|
||||
"react": "^19.2.6",
|
||||
"react-dom": "^19.2.6",
|
||||
@@ -28,7 +28,7 @@
|
||||
"@types/react": "^19.2.14",
|
||||
"@types/react-dom": "^19.2.3",
|
||||
"eslint": "^10.3.0",
|
||||
"eslint-config-next": "16.2.6",
|
||||
"eslint-config-next": "15.5.18",
|
||||
"eslint-config-prettier": "10.1.8",
|
||||
"eslint-plugin-prettier": "5.5.5",
|
||||
"globals": "^17.6.0",
|
||||
@@ -163,38 +163,6 @@
|
||||
|
||||
"@aws/lambda-invoke-store": ["@aws/lambda-invoke-store@0.2.3", "", {}, "sha512-oLvsaPMTBejkkmHhjf09xTgk71mOqyr/409NKhRIL08If7AhVfUsJhVsx386uJaqNd42v9kWamQ9lFbkoC2dYw=="],
|
||||
|
||||
"@babel/code-frame": ["@babel/code-frame@7.29.0", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.28.5", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" } }, "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw=="],
|
||||
|
||||
"@babel/compat-data": ["@babel/compat-data@7.29.3", "", {}, "sha512-LIVqM46zQWZhj17qA8wb4nW/ixr2y1Nw+r1etiAWgRM6U1IqP+LNhL1yg440jYZR72jCWcWbLWzIosH+uP1fqg=="],
|
||||
|
||||
"@babel/core": ["@babel/core@7.29.0", "", { "dependencies": { "@babel/code-frame": "^7.29.0", "@babel/generator": "^7.29.0", "@babel/helper-compilation-targets": "^7.28.6", "@babel/helper-module-transforms": "^7.28.6", "@babel/helpers": "^7.28.6", "@babel/parser": "^7.29.0", "@babel/template": "^7.28.6", "@babel/traverse": "^7.29.0", "@babel/types": "^7.29.0", "@jridgewell/remapping": "^2.3.5", "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", "json5": "^2.2.3", "semver": "^6.3.1" } }, "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA=="],
|
||||
|
||||
"@babel/generator": ["@babel/generator@7.29.1", "", { "dependencies": { "@babel/parser": "^7.29.0", "@babel/types": "^7.29.0", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" } }, "sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw=="],
|
||||
|
||||
"@babel/helper-compilation-targets": ["@babel/helper-compilation-targets@7.28.6", "", { "dependencies": { "@babel/compat-data": "^7.28.6", "@babel/helper-validator-option": "^7.27.1", "browserslist": "^4.24.0", "lru-cache": "^5.1.1", "semver": "^6.3.1" } }, "sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA=="],
|
||||
|
||||
"@babel/helper-globals": ["@babel/helper-globals@7.28.0", "", {}, "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw=="],
|
||||
|
||||
"@babel/helper-module-imports": ["@babel/helper-module-imports@7.28.6", "", { "dependencies": { "@babel/traverse": "^7.28.6", "@babel/types": "^7.28.6" } }, "sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw=="],
|
||||
|
||||
"@babel/helper-module-transforms": ["@babel/helper-module-transforms@7.28.6", "", { "dependencies": { "@babel/helper-module-imports": "^7.28.6", "@babel/helper-validator-identifier": "^7.28.5", "@babel/traverse": "^7.28.6" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA=="],
|
||||
|
||||
"@babel/helper-string-parser": ["@babel/helper-string-parser@7.27.1", "", {}, "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA=="],
|
||||
|
||||
"@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.28.5", "", {}, "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q=="],
|
||||
|
||||
"@babel/helper-validator-option": ["@babel/helper-validator-option@7.27.1", "", {}, "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg=="],
|
||||
|
||||
"@babel/helpers": ["@babel/helpers@7.29.2", "", { "dependencies": { "@babel/template": "^7.28.6", "@babel/types": "^7.29.0" } }, "sha512-HoGuUs4sCZNezVEKdVcwqmZN8GoHirLUcLaYVNBK2J0DadGtdcqgr3BCbvH8+XUo4NGjNl3VOtSjEKNzqfFgKw=="],
|
||||
|
||||
"@babel/parser": ["@babel/parser@7.29.3", "", { "dependencies": { "@babel/types": "^7.29.0" }, "bin": "./bin/babel-parser.js" }, "sha512-b3ctpQwp+PROvU/cttc4OYl4MzfJUWy6FZg+PMXfzmt/+39iHVF0sDfqay8TQM3JA2EUOyKcFZt75jWriQijsA=="],
|
||||
|
||||
"@babel/template": ["@babel/template@7.28.6", "", { "dependencies": { "@babel/code-frame": "^7.28.6", "@babel/parser": "^7.28.6", "@babel/types": "^7.28.6" } }, "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ=="],
|
||||
|
||||
"@babel/traverse": ["@babel/traverse@7.29.0", "", { "dependencies": { "@babel/code-frame": "^7.29.0", "@babel/generator": "^7.29.0", "@babel/helper-globals": "^7.28.0", "@babel/parser": "^7.29.0", "@babel/template": "^7.28.6", "@babel/types": "^7.29.0", "debug": "^4.3.1" } }, "sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA=="],
|
||||
|
||||
"@babel/types": ["@babel/types@7.29.0", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.28.5" } }, "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A=="],
|
||||
|
||||
"@cloudflare/kv-asset-handler": ["@cloudflare/kv-asset-handler@0.5.0", "", {}, "sha512-jxQYkj8dSIzc0cD6cMMNdOc1UVjqSqu8BZdor5s8cGjW2I8BjODt/kWPVdY+u9zj3ms75Q5qaZgnxUad83+eAg=="],
|
||||
|
||||
"@cloudflare/unenv-preset": ["@cloudflare/unenv-preset@2.16.1", "", { "peerDependencies": { "unenv": "2.0.0-rc.24", "workerd": ">1.20260305.0 <2.0.0-0" }, "optionalPeers": ["workerd"] }, "sha512-ECxObrMfyTl5bhQf/lZCXwo5G6xX9IAUo+nDMKK4SZ8m4Jvvxp52vilxyySSWh2YTZz8+HQ07qGH/2rEom1vDw=="],
|
||||
@@ -375,25 +343,25 @@
|
||||
|
||||
"@napi-rs/wasm-runtime": ["@napi-rs/wasm-runtime@0.2.12", "", { "dependencies": { "@emnapi/core": "^1.4.3", "@emnapi/runtime": "^1.4.3", "@tybys/wasm-util": "^0.10.0" } }, "sha512-ZVWUcfwY4E/yPitQJl481FjFo3K22D6qF0DuFH6Y/nbnE11GY5uguDxZMGXPQ8WQ0128MXQD7TnfHyK4oWoIJQ=="],
|
||||
|
||||
"@next/env": ["@next/env@16.2.6", "", {}, "sha512-gd8HoHN4ufj73WmR3JmVolrpJR47ILK6LouP5xElPglaVxir6e1a7VzvTvDWkOoPXT9rkkTzyCxBu4yeZfZwcw=="],
|
||||
"@next/env": ["@next/env@15.5.18", "", {}, "sha512-hAV85Ckd9QR6RvH04MEKwsfLTksvFpO47j9xwtoIuvuPnlwecpSi+uZTtm8HirVbtlI2Fnz//xpcSTjFdyJk+g=="],
|
||||
|
||||
"@next/eslint-plugin-next": ["@next/eslint-plugin-next@16.2.6", "", { "dependencies": { "fast-glob": "3.3.1" } }, "sha512-Z8l6o4JWKUl755x4R+wogD86KPeU+Ckw4K+SYG4kHeOJtRenDeK+OSbGcqZpDtbwn9DsJVdir2UxmwXuinUbUw=="],
|
||||
"@next/eslint-plugin-next": ["@next/eslint-plugin-next@15.5.18", "", { "dependencies": { "fast-glob": "3.3.1" } }, "sha512-w4MYq8M26a8PNrfto0JosLf5/3ssln1rsyP96g2DkC8uFVymStM5DLSz5ElxxrPRg2XnTMnFo3kREFlhYvxhWw=="],
|
||||
|
||||
"@next/swc-darwin-arm64": ["@next/swc-darwin-arm64@16.2.6", "", { "os": "darwin", "cpu": "arm64" }, "sha512-ZJGkkcNfYgrrMkqOdZ7zoLa1TOy0qpcMfk/z4Mh/FKUz40gVO+HNQWqmLxf67Z5WB64DRp0dhEbyHfel+6sJUg=="],
|
||||
"@next/swc-darwin-arm64": ["@next/swc-darwin-arm64@15.5.18", "", { "os": "darwin", "cpu": "arm64" }, "sha512-w0WvQf1n+txiwns/9pwIQteCJpZTbxzO2SE0FLcwuD4v0WEh1JPOjdyxWL21XwJsdpx8cFRjyzxzCS/siP7HcQ=="],
|
||||
|
||||
"@next/swc-darwin-x64": ["@next/swc-darwin-x64@16.2.6", "", { "os": "darwin", "cpu": "x64" }, "sha512-v/YLBHIY132Ced3puBJ7YJKw1lqsCrgcNo2aRJlCEyQrrCeRJlvGlnmxhPxNQI3KE3N1DN5r9TPNPvka3nq5RQ=="],
|
||||
"@next/swc-darwin-x64": ["@next/swc-darwin-x64@15.5.18", "", { "os": "darwin", "cpu": "x64" }, "sha512-znn71QmDuxm+BOaglihMZfvyySMnNljkVIY5Z2TCssBmm+WqL6c19VhtH5ktFkHa8EZ2bnTUpcNcmNSQsg67og=="],
|
||||
|
||||
"@next/swc-linux-arm64-gnu": ["@next/swc-linux-arm64-gnu@16.2.6", "", { "os": "linux", "cpu": "arm64" }, "sha512-RPOvqlYBbcQjkz9VQQDZ2T2bARIjXZV1KFlt+V2Mr6SW/e4I9fcKsaA0hdyf2FHoTlsV2xnBd5Y912rP/1Ce6w=="],
|
||||
"@next/swc-linux-arm64-gnu": ["@next/swc-linux-arm64-gnu@15.5.18", "", { "os": "linux", "cpu": "arm64" }, "sha512-yPPe5MNL+igZUa+OsqQJisqSfh6oarIuA1Q0BDxljGJhRQyZeP+WRHh7rs/jZUGMh5aY0YdIjXZG0VohkKkUdw=="],
|
||||
|
||||
"@next/swc-linux-arm64-musl": ["@next/swc-linux-arm64-musl@16.2.6", "", { "os": "linux", "cpu": "arm64" }, "sha512-URUTu1+dMkxJsPFgm+OeEvq9wf5sujw0EvgYy80TDGHTSLTnIHeqb0Eu8A3sC95IRgjejQL+kC4mw+4yPxiAXA=="],
|
||||
"@next/swc-linux-arm64-musl": ["@next/swc-linux-arm64-musl@15.5.18", "", { "os": "linux", "cpu": "arm64" }, "sha512-glaCczEWIrHsokFZ3pP08U4BpKxwIdnT+txdOM32OBgpL9Yw4aqx8NejmgtZQZOdstQ5f0L3CasIZudzCuD+nw=="],
|
||||
|
||||
"@next/swc-linux-x64-gnu": ["@next/swc-linux-x64-gnu@16.2.6", "", { "os": "linux", "cpu": "x64" }, "sha512-DOj182mPV8G3UkrayLoREM5YEYI+Dk5wv7Ox9xl1fFibAELEsFD0lDPfHIeILlutMMfdyhlzYPELG3peuKaurw=="],
|
||||
"@next/swc-linux-x64-gnu": ["@next/swc-linux-x64-gnu@15.5.18", "", { "os": "linux", "cpu": "x64" }, "sha512-oUfg2EgJmU3R0OCOWiokGFUTvZiPfXtriXiuF3YNxRoROCdgvTedHIzYoeKH34gsZxS/V7mHbfq2hpAHwhH1/A=="],
|
||||
|
||||
"@next/swc-linux-x64-musl": ["@next/swc-linux-x64-musl@16.2.6", "", { "os": "linux", "cpu": "x64" }, "sha512-HKQ5SP/V/ub73UvF7n/zeJlxk2kLmtL7Wzrg4WfmkjmNos5onJ2tKu7yZOPdL18A6Svfn3max29ym+ry7NkK4g=="],
|
||||
"@next/swc-linux-x64-musl": ["@next/swc-linux-x64-musl@15.5.18", "", { "os": "linux", "cpu": "x64" }, "sha512-JLxSP3KTd9iu/bvUMQxH7RJo9xKSHf55/6RPE4a6FTSZygGn7uvZbCej0AHXydwkggQGSD9UddSjwv6Xz5ESfA=="],
|
||||
|
||||
"@next/swc-win32-arm64-msvc": ["@next/swc-win32-arm64-msvc@16.2.6", "", { "os": "win32", "cpu": "arm64" }, "sha512-LZXpTlPyS5v7HhSmnvsLGP3iIYgYOBnc8r8ArlT55sGHV89bR2HlDdBjWQ+PY6SJMmk8TuVGFuxalnP3k/0Dwg=="],
|
||||
"@next/swc-win32-arm64-msvc": ["@next/swc-win32-arm64-msvc@15.5.18", "", { "os": "win32", "cpu": "arm64" }, "sha512-ir1v7enP52K2HNz3tQQvwF+x7VNxBk1ciiZ18WBPvxf4C59IqdfmHPJYK3vH7rSxpuCVw/8C712wTXNAtEp+NA=="],
|
||||
|
||||
"@next/swc-win32-x64-msvc": ["@next/swc-win32-x64-msvc@16.2.6", "", { "os": "win32", "cpu": "x64" }, "sha512-F0+4i0h9J6C4eE3EAPWsoCk7UW/dbzOjyzxY0qnDUOYFu6FFmdZ6l97/XdV3/Nz3VYyO7UWjyEJUXkGqcoXfMA=="],
|
||||
"@next/swc-win32-x64-msvc": ["@next/swc-win32-x64-msvc@15.5.18", "", { "os": "win32", "cpu": "x64" }, "sha512-LIu5me6QTANCd25E7I5uIEfvgQ06RK7tvHAbYo3zCb3VpxQEPvMcSpd87NwUABDT6MbGPdEGR5VRiK4PPTJhQg=="],
|
||||
|
||||
"@noble/ciphers": ["@noble/ciphers@1.3.0", "", {}, "sha512-2I0gnIVPtfnMw9ee9h1dJG7tp81+8Ob3OJb3Mv37rx5L40/b0i7djjCVvGOVqc9AEIQyvyu1i6ypKdFw8R8gQw=="],
|
||||
|
||||
@@ -489,6 +457,8 @@
|
||||
|
||||
"@rtsao/scc": ["@rtsao/scc@1.1.0", "", {}, "sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g=="],
|
||||
|
||||
"@rushstack/eslint-patch": ["@rushstack/eslint-patch@1.16.1", "", {}, "sha512-TvZbIpeKqGQQ7X0zSCvPH9riMSFQFSggnfBjFZ1mEoILW+UuXCKwOoPcgjMwiUtRqFZ8jWhPJc4um14vC6I4ag=="],
|
||||
|
||||
"@schummar/icu-type-parser": ["@schummar/icu-type-parser@1.21.5", "", {}, "sha512-bXHSaW5jRTmke9Vd0h5P7BtWZG9Znqb8gSDxZnxaGSJnGwPLDPfS+3g0BKzeWqzgZPsIVZkM7m2tbo18cm5HBw=="],
|
||||
|
||||
"@sindresorhus/is": ["@sindresorhus/is@7.2.0", "", {}, "sha512-P1Cz1dWaFfR4IR+U13mqqiGsLFf1KbayybWwdd2vfctdV6hDpUkgCY0nKOLLTMSoRd/jJNjtbqzf13K8DCCXQw=="],
|
||||
@@ -787,8 +757,6 @@
|
||||
|
||||
"balanced-match": ["balanced-match@4.0.4", "", {}, "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA=="],
|
||||
|
||||
"baseline-browser-mapping": ["baseline-browser-mapping@2.10.27", "", { "bin": { "baseline-browser-mapping": "dist/cli.cjs" } }, "sha512-zEs/ufmZoUd7WftKpKyXaT6RFxpQ5Qm9xytKRHvJfxFV9DFJkZph9RvJ1LcOUi0Z1ZVijMte65JbILeV+8QQEA=="],
|
||||
|
||||
"blake3-wasm": ["blake3-wasm@2.1.5", "", {}, "sha512-F1+K8EbfOZE49dtoPtmxUQrpXaBIl3ICvasLh+nJta0xkz+9kF/7uet9fLnwKqhDrmj6g+6K3Tw9yQPUg2ka5g=="],
|
||||
|
||||
"body-parser": ["body-parser@2.2.2", "", { "dependencies": { "bytes": "^3.1.2", "content-type": "^1.0.5", "debug": "^4.4.3", "http-errors": "^2.0.0", "iconv-lite": "^0.7.0", "on-finished": "^2.4.1", "qs": "^6.14.1", "raw-body": "^3.0.1", "type-is": "^2.0.1" } }, "sha512-oP5VkATKlNwcgvxi0vM0p/D3n2C3EReYVX+DNYs5TjZFn/oQt2j+4sVJtSMr18pdRr8wjTcBl6LoV+FUwzPmNA=="],
|
||||
@@ -799,8 +767,6 @@
|
||||
|
||||
"braces": ["braces@3.0.3", "", { "dependencies": { "fill-range": "^7.1.1" } }, "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA=="],
|
||||
|
||||
"browserslist": ["browserslist@4.28.2", "", { "dependencies": { "baseline-browser-mapping": "^2.10.12", "caniuse-lite": "^1.0.30001782", "electron-to-chromium": "^1.5.328", "node-releases": "^2.0.36", "update-browserslist-db": "^1.2.3" }, "bin": { "browserslist": "cli.js" } }, "sha512-48xSriZYYg+8qXna9kwqjIVzuQxi+KYWp2+5nCYnYKPTr0LvD89Jqk2Or5ogxz0NUMfIjhh2lIUX/LyX9B4oIg=="],
|
||||
|
||||
"buffer-from": ["buffer-from@1.1.2", "", {}, "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ=="],
|
||||
|
||||
"bytes": ["bytes@3.1.2", "", {}, "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg=="],
|
||||
@@ -811,7 +777,7 @@
|
||||
|
||||
"call-bound": ["call-bound@1.0.4", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.2", "get-intrinsic": "^1.3.0" } }, "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg=="],
|
||||
|
||||
"caniuse-lite": ["caniuse-lite@1.0.30001769", "", {}, "sha512-BCfFL1sHijQlBGWBMuJyhZUhzo7wer5sVj9hqekB/7xn0Ypy+pER/edCYQm4exbXj4WiySGp40P8UuTh6w1srg=="],
|
||||
"caniuse-lite": ["caniuse-lite@1.0.30001792", "", {}, "sha512-hVLMUZFgR4JJ6ACt1uEESvQN1/dBVqPAKY0hgrV70eN3391K6juAfTjKZLKvOMsx8PxA7gsY1/tLMMTcfFLLpw=="],
|
||||
|
||||
"chalk": ["chalk@5.6.2", "", {}, "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA=="],
|
||||
|
||||
@@ -839,8 +805,6 @@
|
||||
|
||||
"content-type": ["content-type@1.0.5", "", {}, "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA=="],
|
||||
|
||||
"convert-source-map": ["convert-source-map@2.0.0", "", {}, "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg=="],
|
||||
|
||||
"cookie": ["cookie@1.1.1", "", {}, "sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ=="],
|
||||
|
||||
"cookie-signature": ["cookie-signature@1.2.2", "", {}, "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg=="],
|
||||
@@ -885,8 +849,6 @@
|
||||
|
||||
"ee-first": ["ee-first@1.1.1", "", {}, "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow=="],
|
||||
|
||||
"electron-to-chromium": ["electron-to-chromium@1.5.352", "", {}, "sha512-9wHk8x6dyuimoe18EdiDPWKExNdxYqo4fn4FwOVVper6RxT3cmpBwBkWWfSOCYJjQdIco/nPhJhNLmn4Ufg1Yg=="],
|
||||
|
||||
"emoji-regex": ["emoji-regex@9.2.2", "", {}, "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg=="],
|
||||
|
||||
"encodeurl": ["encodeurl@2.0.0", "", {}, "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg=="],
|
||||
@@ -923,7 +885,7 @@
|
||||
|
||||
"eslint": ["eslint@10.3.0", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.2", "@eslint/config-array": "^0.23.5", "@eslint/config-helpers": "^0.5.5", "@eslint/core": "^1.2.1", "@eslint/plugin-kit": "^0.7.1", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/retry": "^0.4.2", "@types/estree": "^1.0.6", "ajv": "^6.14.0", "cross-spawn": "^7.0.6", "debug": "^4.3.2", "escape-string-regexp": "^4.0.0", "eslint-scope": "^9.1.2", "eslint-visitor-keys": "^5.0.1", "espree": "^11.2.0", "esquery": "^1.7.0", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", "file-entry-cache": "^8.0.0", "find-up": "^5.0.0", "glob-parent": "^6.0.2", "ignore": "^5.2.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", "json-stable-stringify-without-jsonify": "^1.0.1", "minimatch": "^10.2.4", "natural-compare": "^1.4.0", "optionator": "^0.9.3" }, "peerDependencies": { "jiti": "*" }, "optionalPeers": ["jiti"], "bin": { "eslint": "bin/eslint.js" } }, "sha512-XbEXaRva5cF0ZQB8w6MluHA0kZZfV2DuCMJ3ozyEOHLwDpZX2Lmm/7Pp0xdJmI0GL1W05VH5VwIFHEm1Vcw2gw=="],
|
||||
|
||||
"eslint-config-next": ["eslint-config-next@16.2.6", "", { "dependencies": { "@next/eslint-plugin-next": "16.2.6", "eslint-import-resolver-node": "^0.3.6", "eslint-import-resolver-typescript": "^3.5.2", "eslint-plugin-import": "^2.32.0", "eslint-plugin-jsx-a11y": "^6.10.0", "eslint-plugin-react": "^7.37.0", "eslint-plugin-react-hooks": "^7.0.0", "globals": "16.4.0", "typescript-eslint": "^8.46.0" }, "peerDependencies": { "eslint": ">=9.0.0", "typescript": ">=3.3.1" }, "optionalPeers": ["typescript"] }, "sha512-z2ELYSkyrrJ6cuunTU8vhsT/RpouPkjaSah06nVW6Rg2Hpg0Vs8s497/e5s8G8qtdp4ccsiovz5P1rv+5VSW2Q=="],
|
||||
"eslint-config-next": ["eslint-config-next@15.5.18", "", { "dependencies": { "@next/eslint-plugin-next": "15.5.18", "@rushstack/eslint-patch": "^1.10.3", "@typescript-eslint/eslint-plugin": "^5.4.2 || ^6.0.0 || ^7.0.0 || ^8.0.0", "@typescript-eslint/parser": "^5.4.2 || ^6.0.0 || ^7.0.0 || ^8.0.0", "eslint-import-resolver-node": "^0.3.6", "eslint-import-resolver-typescript": "^3.5.2", "eslint-plugin-import": "^2.31.0", "eslint-plugin-jsx-a11y": "^6.10.0", "eslint-plugin-react": "^7.37.0", "eslint-plugin-react-hooks": "^5.0.0" }, "peerDependencies": { "eslint": "^7.23.0 || ^8.0.0 || ^9.0.0", "typescript": ">=3.3.1" }, "optionalPeers": ["typescript"] }, "sha512-HuoJU6uUPD00eyiud78IBnT4HLhztFj2V+ild2Uon5ZUrYZKe0Olu2QRD99e9IgL4/H1eg5Onka3BsfRW2U0Xw=="],
|
||||
|
||||
"eslint-config-prettier": ["eslint-config-prettier@10.1.8", "", { "peerDependencies": { "eslint": ">=7.0.0" }, "bin": { "eslint-config-prettier": "bin/cli.js" } }, "sha512-82GZUjRS0p/jganf6q1rEO25VSoHH0hKPCTrgillPjdI/3bgBhAE1QzHrHTizjpRvy6pGAvKjDJtk2pF9NDq8w=="],
|
||||
|
||||
@@ -941,7 +903,7 @@
|
||||
|
||||
"eslint-plugin-react": ["eslint-plugin-react@7.37.5", "", { "dependencies": { "array-includes": "^3.1.8", "array.prototype.findlast": "^1.2.5", "array.prototype.flatmap": "^1.3.3", "array.prototype.tosorted": "^1.1.4", "doctrine": "^2.1.0", "es-iterator-helpers": "^1.2.1", "estraverse": "^5.3.0", "hasown": "^2.0.2", "jsx-ast-utils": "^2.4.1 || ^3.0.0", "minimatch": "^3.1.2", "object.entries": "^1.1.9", "object.fromentries": "^2.0.8", "object.values": "^1.2.1", "prop-types": "^15.8.1", "resolve": "^2.0.0-next.5", "semver": "^6.3.1", "string.prototype.matchall": "^4.0.12", "string.prototype.repeat": "^1.0.0" }, "peerDependencies": { "eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9.7" } }, "sha512-Qteup0SqU15kdocexFNAJMvCJEfa2xUKNV4CC1xsVMrIIqEy3SQ/rqyxCWNzfrd3/ldy6HMlD2e0JDVpDg2qIA=="],
|
||||
|
||||
"eslint-plugin-react-hooks": ["eslint-plugin-react-hooks@7.1.1", "", { "dependencies": { "@babel/core": "^7.24.4", "@babel/parser": "^7.24.4", "hermes-parser": "^0.25.1", "zod": "^3.25.0 || ^4.0.0", "zod-validation-error": "^3.5.0 || ^4.0.0" }, "peerDependencies": { "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0 || ^10.0.0" } }, "sha512-f2I7Gw6JbvCexzIInuSbZpfdQ44D7iqdWX01FKLvrPgqxoE7oMj8clOfto8U6vYiz4yd5oKu39rRSVOe1zRu0g=="],
|
||||
"eslint-plugin-react-hooks": ["eslint-plugin-react-hooks@5.2.0", "", { "peerDependencies": { "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0" } }, "sha512-+f15FfK64YQwZdJNELETdn5ibXEUQmW1DZL6KXhNnc2heoy/sg9VJJeT7n8TlMWouzWqSWavFkIhHyIbIAEapg=="],
|
||||
|
||||
"eslint-scope": ["eslint-scope@9.1.2", "", { "dependencies": { "@types/esrecurse": "^4.3.1", "@types/estree": "^1.0.8", "esrecurse": "^4.3.0", "estraverse": "^5.2.0" } }, "sha512-xS90H51cKw0jltxmvmHy2Iai1LIqrfbw57b79w/J7MfvDfkIkFZ+kj6zC3BjtUwh150HsSSdxXZcsuv72miDFQ=="],
|
||||
|
||||
@@ -1025,8 +987,6 @@
|
||||
|
||||
"generator-function": ["generator-function@2.0.1", "", {}, "sha512-SFdFmIJi+ybC0vjlHN0ZGVGHc3lgE0DxPAT0djjVg+kjOnSqclqmj0KQ7ykTOLP6YxoqOvuAODGdcHJn+43q3g=="],
|
||||
|
||||
"gensync": ["gensync@1.0.0-beta.2", "", {}, "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg=="],
|
||||
|
||||
"get-caller-file": ["get-caller-file@2.0.5", "", {}, "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg=="],
|
||||
|
||||
"get-east-asian-width": ["get-east-asian-width@1.4.0", "", {}, "sha512-QZjmEOC+IT1uk6Rx0sX22V6uHWVwbdbxf1faPqJ1QhLdGgsRGCZoyaQBm/piRdJy/D2um6hM1UP7ZEeQ4EkP+Q=="],
|
||||
@@ -1067,10 +1027,6 @@
|
||||
|
||||
"hasown": ["hasown@2.0.2", "", { "dependencies": { "function-bind": "^1.1.2" } }, "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ=="],
|
||||
|
||||
"hermes-estree": ["hermes-estree@0.25.1", "", {}, "sha512-0wUoCcLp+5Ev5pDW2OriHC2MJCbwLwuRx+gAqMTOkGKJJiBCLjtrvy4PWUGn6MIVefecRpzoOZ/UV6iGdOr+Cw=="],
|
||||
|
||||
"hermes-parser": ["hermes-parser@0.25.1", "", { "dependencies": { "hermes-estree": "0.25.1" } }, "sha512-6pEjquH3rqaI6cYAXYPcz9MS4rY6R4ngRgrgfDshRptUZIc3lw0MCIJIGDj9++mfySOuPTHB4nrSW99BCvOPIA=="],
|
||||
|
||||
"http-errors": ["http-errors@2.0.1", "", { "dependencies": { "depd": "~2.0.0", "inherits": "~2.0.4", "setprototypeof": "~1.2.0", "statuses": "~2.0.2", "toidentifier": "~1.0.1" } }, "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ=="],
|
||||
|
||||
"human-signals": ["human-signals@2.1.0", "", {}, "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw=="],
|
||||
@@ -1161,8 +1117,6 @@
|
||||
|
||||
"js-tokens": ["js-tokens@4.0.0", "", {}, "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="],
|
||||
|
||||
"jsesc": ["jsesc@3.1.0", "", { "bin": { "jsesc": "bin/jsesc" } }, "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA=="],
|
||||
|
||||
"json-buffer": ["json-buffer@3.0.1", "", {}, "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ=="],
|
||||
|
||||
"json-schema-traverse": ["json-schema-traverse@0.4.1", "", {}, "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg=="],
|
||||
@@ -1263,7 +1217,7 @@
|
||||
|
||||
"negotiator": ["negotiator@1.0.0", "", {}, "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg=="],
|
||||
|
||||
"next": ["next@16.2.6", "", { "dependencies": { "@next/env": "16.2.6", "@swc/helpers": "0.5.15", "baseline-browser-mapping": "^2.9.19", "caniuse-lite": "^1.0.30001579", "postcss": "8.4.31", "styled-jsx": "5.1.6" }, "optionalDependencies": { "@next/swc-darwin-arm64": "16.2.6", "@next/swc-darwin-x64": "16.2.6", "@next/swc-linux-arm64-gnu": "16.2.6", "@next/swc-linux-arm64-musl": "16.2.6", "@next/swc-linux-x64-gnu": "16.2.6", "@next/swc-linux-x64-musl": "16.2.6", "@next/swc-win32-arm64-msvc": "16.2.6", "@next/swc-win32-x64-msvc": "16.2.6", "sharp": "^0.34.5" }, "peerDependencies": { "@opentelemetry/api": "^1.1.0", "@playwright/test": "^1.51.1", "babel-plugin-react-compiler": "*", "react": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", "react-dom": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", "sass": "^1.3.0" }, "optionalPeers": ["@opentelemetry/api", "@playwright/test", "babel-plugin-react-compiler", "sass"], "bin": { "next": "dist/bin/next" } }, "sha512-qOVgKJg1+At15NpeUP+eJgCHvTCgXsogweq87Ri/Ix7PkqQHg4sdaXmSFqKlgaIXE4kW0g25LE68W87UANlHtw=="],
|
||||
"next": ["next@15.5.18", "", { "dependencies": { "@next/env": "15.5.18", "@swc/helpers": "0.5.15", "caniuse-lite": "^1.0.30001579", "postcss": "8.4.31", "styled-jsx": "5.1.6" }, "optionalDependencies": { "@next/swc-darwin-arm64": "15.5.18", "@next/swc-darwin-x64": "15.5.18", "@next/swc-linux-arm64-gnu": "15.5.18", "@next/swc-linux-arm64-musl": "15.5.18", "@next/swc-linux-x64-gnu": "15.5.18", "@next/swc-linux-x64-musl": "15.5.18", "@next/swc-win32-arm64-msvc": "15.5.18", "@next/swc-win32-x64-msvc": "15.5.18", "sharp": "^0.34.3" }, "peerDependencies": { "@opentelemetry/api": "^1.1.0", "@playwright/test": "^1.51.1", "babel-plugin-react-compiler": "*", "react": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", "react-dom": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", "sass": "^1.3.0" }, "optionalPeers": ["@opentelemetry/api", "@playwright/test", "babel-plugin-react-compiler", "sass"], "bin": { "next": "dist/bin/next" } }, "sha512-eKL8zUJkX9Y5lE+RX/2YJoItVdGlIscyVyboeD9wSpp0PaGqjoA4tTpT2qPqz9ax+5IzGESyLSeZ/RCwbSZ2uQ=="],
|
||||
|
||||
"next-intl": ["next-intl@4.11.0", "", { "dependencies": { "@formatjs/intl-localematcher": "^0.8.1", "@parcel/watcher": "^2.4.1", "@swc/core": "^1.15.2", "icu-minify": "^4.11.0", "negotiator": "^1.0.0", "next-intl-swc-plugin-extractor": "^4.11.0", "po-parser": "^2.1.1", "use-intl": "^4.11.0" }, "peerDependencies": { "next": "^12.0.0 || ^13.0.0 || ^14.0.0 || ^15.0.0 || ^16.0.0", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || >=19.0.0-rc <19.0.0 || ^19.0.0" } }, "sha512-Chp8rgEVUYOX/bCtYy+PXH6lDX3X+GPT9sR9HScHroL283em/4urP9btfdHEMEHJJXdq2W/5wDaDDtWONPdNSA=="],
|
||||
|
||||
@@ -1275,8 +1229,6 @@
|
||||
|
||||
"node-fetch": ["node-fetch@2.7.0", "", { "dependencies": { "whatwg-url": "^5.0.0" }, "peerDependencies": { "encoding": "^0.1.0" }, "optionalPeers": ["encoding"] }, "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A=="],
|
||||
|
||||
"node-releases": ["node-releases@2.0.38", "", {}, "sha512-3qT/88Y3FbH/Kx4szpQQ4HzUbVrHPKTLVpVocKiLfoYvw9XSGOX2FmD2d6DrXbVYyAQTF2HeF6My8jmzx7/CRw=="],
|
||||
|
||||
"npm-run-path": ["npm-run-path@4.0.1", "", { "dependencies": { "path-key": "^3.0.0" } }, "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw=="],
|
||||
|
||||
"object-assign": ["object-assign@4.1.1", "", {}, "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg=="],
|
||||
@@ -1521,8 +1473,6 @@
|
||||
|
||||
"unrs-resolver": ["unrs-resolver@1.11.1", "", { "dependencies": { "napi-postinstall": "^0.3.0" }, "optionalDependencies": { "@unrs/resolver-binding-android-arm-eabi": "1.11.1", "@unrs/resolver-binding-android-arm64": "1.11.1", "@unrs/resolver-binding-darwin-arm64": "1.11.1", "@unrs/resolver-binding-darwin-x64": "1.11.1", "@unrs/resolver-binding-freebsd-x64": "1.11.1", "@unrs/resolver-binding-linux-arm-gnueabihf": "1.11.1", "@unrs/resolver-binding-linux-arm-musleabihf": "1.11.1", "@unrs/resolver-binding-linux-arm64-gnu": "1.11.1", "@unrs/resolver-binding-linux-arm64-musl": "1.11.1", "@unrs/resolver-binding-linux-ppc64-gnu": "1.11.1", "@unrs/resolver-binding-linux-riscv64-gnu": "1.11.1", "@unrs/resolver-binding-linux-riscv64-musl": "1.11.1", "@unrs/resolver-binding-linux-s390x-gnu": "1.11.1", "@unrs/resolver-binding-linux-x64-gnu": "1.11.1", "@unrs/resolver-binding-linux-x64-musl": "1.11.1", "@unrs/resolver-binding-wasm32-wasi": "1.11.1", "@unrs/resolver-binding-win32-arm64-msvc": "1.11.1", "@unrs/resolver-binding-win32-ia32-msvc": "1.11.1", "@unrs/resolver-binding-win32-x64-msvc": "1.11.1" } }, "sha512-bSjt9pjaEBnNiGgc9rUiHGKv5l4/TGzDmYw3RhnkJGtLhbnnA/5qJj7x3dNDCRx/PJxu774LlH8lCOlB4hEfKg=="],
|
||||
|
||||
"update-browserslist-db": ["update-browserslist-db@1.2.3", "", { "dependencies": { "escalade": "^3.2.0", "picocolors": "^1.1.1" }, "peerDependencies": { "browserslist": ">= 4.21.0" }, "bin": { "update-browserslist-db": "cli.js" } }, "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w=="],
|
||||
|
||||
"uri-js": ["uri-js@4.4.1", "", { "dependencies": { "punycode": "^2.1.0" } }, "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg=="],
|
||||
|
||||
"urlpattern-polyfill": ["urlpattern-polyfill@10.1.0", "", {}, "sha512-IGjKp/o0NL3Bso1PymYURCJxMPNAf/ILOpendP9f5B6e1rTJgdgiOvgfoT8VxCAdY+Wisb9uhGaJJf3yZ2V9nw=="],
|
||||
@@ -1561,8 +1511,6 @@
|
||||
|
||||
"y18n": ["y18n@5.0.8", "", {}, "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA=="],
|
||||
|
||||
"yallist": ["yallist@3.1.1", "", {}, "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g=="],
|
||||
|
||||
"yaml": ["yaml@2.8.2", "", { "bin": { "yaml": "bin.mjs" } }, "sha512-mplynKqc1C2hTVYxd0PU2xQAc22TI1vShAYGksCCfxbn/dFwnHTNi1bvYsBTkhdUNtGIf5xNOg938rrSSYvS9A=="],
|
||||
|
||||
"yargs": ["yargs@18.0.0", "", { "dependencies": { "cliui": "^9.0.1", "escalade": "^3.1.1", "get-caller-file": "^2.0.5", "string-width": "^7.2.0", "y18n": "^5.0.5", "yargs-parser": "^22.0.0" } }, "sha512-4UEqdc2RYGHZc7Doyqkrqiln3p9X2DZVxaGbwhn2pi7MrRagKaOcIKe8L3OxYcbhXLgLFUS3zAYuQjKBQgmuNg=="],
|
||||
@@ -1575,10 +1523,6 @@
|
||||
|
||||
"youch-core": ["youch-core@0.3.3", "", { "dependencies": { "@poppinss/exception": "^1.2.2", "error-stack-parser-es": "^1.0.5" } }, "sha512-ho7XuGjLaJ2hWHoK8yFnsUGy2Y5uDpqSTq1FkHLK4/oqKtyUU1AFbOOxY4IpC9f0fTLjwYbslUz0Po5BpD1wrA=="],
|
||||
|
||||
"zod": ["zod@4.4.3", "", {}, "sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ=="],
|
||||
|
||||
"zod-validation-error": ["zod-validation-error@4.0.2", "", { "peerDependencies": { "zod": "^3.25.0 || ^4.0.0" } }, "sha512-Q6/nZLe6jxuU80qb/4uJ4t5v2VEZ44lzQjPDhYJNztRQ4wyWc6VF3D3Kb/fAuPetZQnhS3hnajCf9CsWesghLQ=="],
|
||||
|
||||
"@aws-crypto/sha1-browser/@smithy/util-utf8": ["@smithy/util-utf8@2.3.0", "", { "dependencies": { "@smithy/util-buffer-from": "^2.2.0", "tslib": "^2.6.2" } }, "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A=="],
|
||||
|
||||
"@aws-crypto/sha256-browser/@smithy/util-utf8": ["@smithy/util-utf8@2.3.0", "", { "dependencies": { "@smithy/util-buffer-from": "^2.2.0", "tslib": "^2.6.2" } }, "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A=="],
|
||||
@@ -1591,10 +1535,6 @@
|
||||
|
||||
"@aws-sdk/nested-clients/@aws-sdk/util-endpoints": ["@aws-sdk/util-endpoints@3.982.0", "", { "dependencies": { "@aws-sdk/types": "^3.973.1", "@smithy/types": "^4.12.0", "@smithy/url-parser": "^4.2.8", "@smithy/util-endpoints": "^3.2.8", "tslib": "^2.6.2" } }, "sha512-M27u8FJP7O0Of9hMWX5dipp//8iglmV9jr7R8SR8RveU+Z50/8TqH68Tu6wUWBGMfXjzbVwn1INIAO5lZrlxXQ=="],
|
||||
|
||||
"@babel/core/json5": ["json5@2.2.3", "", { "bin": { "json5": "lib/cli.js" } }, "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg=="],
|
||||
|
||||
"@babel/helper-compilation-targets/lru-cache": ["lru-cache@5.1.1", "", { "dependencies": { "yallist": "^3.0.2" } }, "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w=="],
|
||||
|
||||
"@cspotcode/source-map-support/@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.9", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.0.3", "@jridgewell/sourcemap-codec": "^1.4.10" } }, "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ=="],
|
||||
|
||||
"@eslint-community/eslint-utils/eslint-visitor-keys": ["eslint-visitor-keys@3.4.3", "", {}, "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag=="],
|
||||
@@ -1627,16 +1567,12 @@
|
||||
|
||||
"@typescript-eslint/typescript-estree/semver": ["semver@7.7.4", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA=="],
|
||||
|
||||
"browserslist/caniuse-lite": ["caniuse-lite@1.0.30001792", "", {}, "sha512-hVLMUZFgR4JJ6ACt1uEESvQN1/dBVqPAKY0hgrV70eN3391K6juAfTjKZLKvOMsx8PxA7gsY1/tLMMTcfFLLpw=="],
|
||||
|
||||
"cliui/strip-ansi": ["strip-ansi@7.1.2", "", { "dependencies": { "ansi-regex": "^6.0.1" } }, "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA=="],
|
||||
|
||||
"cloudflare/@types/node": ["@types/node@18.19.130", "", { "dependencies": { "undici-types": "~5.26.4" } }, "sha512-GRaXQx6jGfL8sKfaIDD6OupbIHBr9jv7Jnaml9tB7l4v068PAOXqfcujMMo5PhbIs6ggR1XODELqahT2R8v0fg=="],
|
||||
|
||||
"cross-spawn/which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "./bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="],
|
||||
|
||||
"eslint-config-next/globals": ["globals@16.4.0", "", {}, "sha512-ob/2LcVVaVGCYN+r14cnwnoDPUufjiYgSqRhiFD0Q1iI4Odora5RE8Iv1D24hAz5oMophRGkGz+yuvQmmUMnMw=="],
|
||||
|
||||
"eslint-import-resolver-node/debug": ["debug@3.2.7", "", { "dependencies": { "ms": "^2.1.1" } }, "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ=="],
|
||||
|
||||
"eslint-module-utils/debug": ["debug@3.2.7", "", { "dependencies": { "ms": "^2.1.1" } }, "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ=="],
|
||||
|
||||
@@ -18,7 +18,7 @@ export default [
|
||||
},
|
||||
},
|
||||
},
|
||||
nextPlugin.configs["core-web-vitals"],
|
||||
nextPlugin.flatConfig.coreWebVitals,
|
||||
...tseslint.configs.recommended,
|
||||
prettier,
|
||||
{
|
||||
|
||||
+2
-2
@@ -28,7 +28,7 @@
|
||||
"geist": "^1.7.0",
|
||||
"lucide-react": "0.577.0",
|
||||
"motion": "^12.38.0",
|
||||
"next": "16.2.6",
|
||||
"next": "15.5.18",
|
||||
"next-intl": "^4.11.0",
|
||||
"react": "^19.2.6",
|
||||
"react-dom": "^19.2.6",
|
||||
@@ -43,7 +43,7 @@
|
||||
"@types/react": "^19.2.14",
|
||||
"@types/react-dom": "^19.2.3",
|
||||
"eslint": "^10.3.0",
|
||||
"eslint-config-next": "16.2.6",
|
||||
"eslint-config-next": "15.5.18",
|
||||
"eslint-config-prettier": "10.1.8",
|
||||
"eslint-plugin-prettier": "5.5.5",
|
||||
"globals": "^17.6.0",
|
||||
|
||||
+2
-1
@@ -11,13 +11,14 @@
|
||||
"moduleResolution": "bundler",
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"jsx": "react-jsx",
|
||||
"jsx": "preserve",
|
||||
"incremental": true,
|
||||
"noUncheckedIndexedAccess": true,
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedParameters": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"noImplicitReturns": true,
|
||||
"noUncheckedSideEffectImports": false,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"plugins": [
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user