diff --git a/cli/src/commands/hits.ts b/cli/src/commands/hits.ts index c47375e..f244470 100644 --- a/cli/src/commands/hits.ts +++ b/cli/src/commands/hits.ts @@ -9,6 +9,7 @@ import { truncate, } from "../lib/out.js"; import { parseIntervalMs, parseLimit } from "../lib/parse.js"; +import { primeHitAnchor } from "../lib/hit-anchor.js"; import { resolveKeyRef } from "../lib/resolve.js"; import { withClient, type GlobalOpts } from "../lib/runner.js"; @@ -86,15 +87,14 @@ async function followHits( const seen = new Set(); const seenOrder: string[] = []; - // Anchor at start time. A one-millisecond overlap covers hits sharing a - // timestamp; the ID set prevents reprinting them on the next poll. - let watermarkMs = Date.now(); - const initial = await client.listRecentHits({ key_id: id, limit: 500 }); - for (const h of initial.data) { - if (new Date(h.occurred_at).getTime() < watermarkMs) { - seen.add(h.id); - seenOrder.push(h.id); - } + // A one-millisecond overlap covers hits sharing the anchor timestamp; IDs + // prevent reprinting them on the next poll. + const initial = await client.listRecentHits({ key_id: id, limit: 500, anchor: 1 }); + const anchor = primeHitAnchor(initial); + let watermarkMs = anchor.watermarkMs; + for (const id of anchor.seenIds) { + seen.add(id); + seenOrder.push(id); } process.stderr.write( @@ -111,7 +111,7 @@ async function followHits( await new Promise((r) => setTimeout(r, intervalMs)); if (stop) break; try { - const since = new Date(watermarkMs - 1).toISOString(); + const since = new Date(Math.max(0, watermarkMs - 1)).toISOString(); const arrived: Hit[] = []; let cursor: string | undefined; do { diff --git a/cli/src/commands/new.ts b/cli/src/commands/new.ts index 584fcde..9f23263 100644 --- a/cli/src/commands/new.ts +++ b/cli/src/commands/new.ts @@ -181,6 +181,9 @@ export async function newCmd( `unknown installer type "${effectiveOpts.install}". Available: ${ALL_INSTALL_TYPES.join(", ")}`, ); } + if (effectiveOpts.install && !effectiveOpts.out && (effectiveOpts.idOnly || effectiveOpts.urlOnly)) { + fail("--install needs --out when used with --id-only or --url-only; otherwise the snippet has nowhere to go"); + } await withClient(effectiveOpts, async (client) => { const key = await client.createKey({ @@ -326,7 +329,10 @@ export async function newCmd( ); } catch (err) { const reason = err instanceof Error ? err.message : String(err); - throw new Error(`key ${key.id} was created, but setup did not finish: ${reason}. Resume with \`mantis show ${key.id}\`.`); + const recovery = effectiveOpts.install + ? `Finish the installer with \`mantis install ${key.id} --type ${effectiveOpts.install}\`.` + : `Inspect the key with \`mantis show ${key.id}\`.`; + throw new Error(`key ${key.id} was created, but setup did not finish: ${reason}. ${recovery}`); } }); } diff --git a/cli/src/commands/watch.ts b/cli/src/commands/watch.ts index 9de6fcd..8a0a251 100644 --- a/cli/src/commands/watch.ts +++ b/cli/src/commands/watch.ts @@ -1,6 +1,7 @@ import type { MantisClient, RecentHit } from "../lib/api.js"; import { c, formatTime, isJsonMode } from "../lib/out.js"; import { parseIntervalMs } from "../lib/parse.js"; +import { primeHitAnchor } from "../lib/hit-anchor.js"; import { resolveKeyRef } from "../lib/resolve.js"; import { withClient, type GlobalOpts } from "../lib/runner.js"; @@ -19,14 +20,14 @@ export async function watchCmd(opts: WatchOpts): Promise { ); const seen = new Set(); - let since = oneSecondAgo(); - const prime = await client.listRecentHits({ ...(keyId ? { key_id: keyId } : {}), limit: 500, + anchor: 1, }); - for (const hit of prime.data) seen.add(hit.id); - since = backUpOneMs(newestOccurredAt(prime.data) ?? since); + const anchor = primeHitAnchor(prime); + for (const id of anchor.seenIds) seen.add(id); + let since = new Date(Math.max(0, anchor.watermarkMs - 1)).toISOString(); const tick = async () => { try { @@ -108,7 +109,3 @@ function backUpOneMs(iso: string): string { if (Number.isNaN(t)) return iso; return new Date(t - 1).toISOString(); } - -function oneSecondAgo(): string { - return new Date(Date.now() - 1000).toISOString(); -} diff --git a/cli/src/lib/api.ts b/cli/src/lib/api.ts index eb02b37..86007d2 100644 --- a/cli/src/lib/api.ts +++ b/cli/src/lib/api.ts @@ -137,6 +137,7 @@ export type AuditEvent = { }; export type Page = { data: T[]; next_cursor: string | null }; +export type RecentHitsPage = Page & { server_time?: string }; export type Health = { status: "ok" | "degraded"; @@ -400,9 +401,9 @@ export class MantisClient { } listRecentHits( - query: { limit?: number; since?: string; since_id?: string; cursor?: string; key_id?: string } = {}, - ): Promise> { - return this.req>("/api/hits/recent", { query }); + query: { limit?: number; since?: string; since_id?: string; cursor?: string; key_id?: string; anchor?: number } = {}, + ): Promise { + return this.req("/api/hits/recent", { query }); } async fetchInstaller( diff --git a/cli/src/lib/hit-anchor.ts b/cli/src/lib/hit-anchor.ts new file mode 100644 index 0000000..2f9198f --- /dev/null +++ b/cli/src/lib/hit-anchor.ts @@ -0,0 +1,24 @@ +import type { RecentHitsPage } from "./api.js"; + +/** Seed a live stream from database time, never from the operator's clock. */ +export function primeHitAnchor(page: RecentHitsPage): { + watermarkMs: number; + seenIds: string[]; +} { + const serverMs = Date.parse(page.server_time ?? ""); + if (Number.isFinite(serverMs)) { + return { + watermarkMs: serverMs, + seenIds: page.data + .filter((hit) => Date.parse(hit.occurred_at) < serverMs) + .map((hit) => hit.id), + }; + } + + // Older servers do not return server_time. Anchor at their newest hit, or + // the epoch for an empty feed, so local clock skew still cannot skip hits. + return { + watermarkMs: Math.max(0, ...page.data.map((hit) => Date.parse(hit.occurred_at))), + seenIds: page.data.map((hit) => hit.id), + }; +} diff --git a/cli/tests/hit-anchor.test.ts b/cli/tests/hit-anchor.test.ts new file mode 100644 index 0000000..ca2ee88 --- /dev/null +++ b/cli/tests/hit-anchor.test.ts @@ -0,0 +1,34 @@ +import { describe, expect, it } from "vitest"; +import type { RecentHitsPage } from "../src/lib/api.js"; +import { primeHitAnchor } from "../src/lib/hit-anchor.js"; + +function page(serverTime?: string): RecentHitsPage { + return { + data: [ + { id: "older", occurred_at: "2026-09-23T10:00:00.000Z" }, + { id: "during-prime", occurred_at: "2026-09-23T10:00:01.000Z" }, + ] as RecentHitsPage["data"], + next_cursor: null, + ...(serverTime ? { server_time: serverTime } : {}), + }; +} + +describe("live hit anchor", () => { + it("uses server time and leaves hits arriving during priming unseen", () => { + expect(primeHitAnchor(page("2026-09-23T10:00:00.500Z"))).toEqual({ + watermarkMs: Date.parse("2026-09-23T10:00:00.500Z"), + seenIds: ["older"], + }); + }); + + it("uses existing hits when an older server omits the anchor", () => { + expect(primeHitAnchor(page())).toEqual({ + watermarkMs: Date.parse("2026-09-23T10:00:01.000Z"), + seenIds: ["older", "during-prime"], + }); + expect(primeHitAnchor({ data: [], next_cursor: null })).toEqual({ + watermarkMs: 0, + seenIds: [], + }); + }); +}); diff --git a/cli/tests/new-installer.test.ts b/cli/tests/new-installer.test.ts index 1ee0d0d..41224e7 100644 --- a/cli/tests/new-installer.test.ts +++ b/cli/tests/new-installer.test.ts @@ -9,6 +9,7 @@ vi.mock("../src/commands/install.js", () => ({ })); import { newCmd } from "../src/commands/new.js"; +import { runInstaller } from "../src/commands/install.js"; afterEach(() => { vi.restoreAllMocks(); @@ -38,4 +39,44 @@ describe("create and install", () => { expect(output.join("")).toContain("curl https://mantis.example.com/c/abc123\n"); }); + + it("rejects output modes that would discard a generated snippet before creating a key", async () => { + const fetch = vi.fn(); + vi.stubGlobal("fetch", fetch); + vi.spyOn(process, "exit").mockImplementation(() => { throw new Error("exited"); }); + vi.spyOn(process.stderr, "write").mockImplementation(() => true); + + await expect(newCmd("first key", { + baseUrl: "https://mantis.example.com", + key: "test-key", + install: "shell", + idOnly: true, + })).rejects.toThrow("exited"); + expect(fetch).not.toHaveBeenCalled(); + expect(vi.mocked(process.stderr.write).mock.calls.join(" ")).toContain("--install needs --out"); + }); + + it("gives a runnable recovery command after an installer fails", async () => { + vi.mocked(runInstaller).mockRejectedValueOnce(new Error("write failed")); + const errors: string[] = []; + vi.spyOn(process.stderr, "write").mockImplementation((chunk) => { + errors.push(String(chunk)); + return true; + }); + vi.spyOn(process, "exit").mockImplementation(() => { throw new Error("exited"); }); + vi.stubGlobal("fetch", async () => new Response(JSON.stringify({ + id: "00000000-0000-4000-8000-000000000001", + public_id: "abc123", + url: "https://mantis.example.com/c/abc123", + memo: "first key", + destinations: [], + }), { status: 201, headers: { "content-type": "application/json" } })); + + await expect(newCmd("first key", { + baseUrl: "https://mantis.example.com", + key: "test-key", + install: "shell", + })).rejects.toThrow("exited"); + expect(errors.join(" ")).toContain("mantis install 00000000-0000-4000-8000-000000000001 --type shell"); + }); }); diff --git a/package.json b/package.json index f3c4cc0..b16b869 100644 --- a/package.json +++ b/package.json @@ -40,8 +40,8 @@ "drizzle-orm": "^0.45.2", "jszip": "^3.10.1", "nanoid": "^6.0.0", - "next": "^16.2.12", - "nodemailer": "^9.0.3", + "next": "^16.3.6", + "nodemailer": "^10.0.6", "passkit-generator": "^3.5.7", "pdf-lib": "^1.17.1", "pino": "^10.3.1", @@ -51,7 +51,7 @@ "react-dom": "^19.2.7", "tailwindcss": "^4.3.2", "ua-parser-js": "^2.0.10", - "undici": "^8.9.0", + "undici": "^8.10.2", "zod": "^4.4.3" }, "devDependencies": { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 47aa867..4378278 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -6,11 +6,11 @@ settings: overrides: nanoid@3: ^3.3.18 - undici@7: ^7.29.0 - sharp: ^0.35.3 + undici@7: ^7.29.1 + sharp: ^0.35.4 postcss: ^8.5.10 esbuild: ^0.28.1 - joi: ^17.13.4 + joi: ^17.13.8 ws: ^8.20.1 uuid: ^11.1.1 form-data: ^4.0.6 @@ -36,11 +36,11 @@ importers: specifier: ^6.0.0 version: 6.0.0 next: - specifier: ^16.2.12 - version: 16.2.12(@types/node@26.1.2)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + specifier: ^16.3.6 + version: 16.3.7(@types/node@26.1.2)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) nodemailer: - specifier: ^9.0.3 - version: 9.0.3 + specifier: ^10.0.6 + version: 10.0.12 passkit-generator: specifier: ^3.5.7 version: 3.5.7 @@ -69,8 +69,8 @@ importers: specifier: ^2.0.10 version: 2.0.10 undici: - specifier: ^8.9.0 - version: 8.10.0 + specifier: ^8.10.2 + version: 8.11.2 zod: specifier: ^4.4.3 version: 4.4.3 @@ -443,160 +443,160 @@ packages: resolution: {integrity: sha512-Td76q7j57o/tLVdgS746cYARfSyxk8iEfRxewL9h4OMzYhbW4TAcppl0mT4eyqXddh6L/jwoM75mo7ixa/pCeQ==} engines: {node: '>=18'} - '@img/sharp-darwin-arm64@0.35.3': - resolution: {integrity: sha512-RMnFX7YQsMoh7lWfcM4NEHHymBX/rLuKNPVM84XE9ONPcaSCDgE7CHIHpSgPcO2xcRthgBy1HfNO319mwhIAkg==} + '@img/sharp-darwin-arm64@0.35.4': + resolution: {integrity: sha512-Uhfl4V4lhP2nbUVF9+hyH1+luj86f1gUFeo8ALYxFoULoU+G87D43BfeMP8XHsk9boxAnCY/bf2EHwhA7MuGsA==} engines: {node: '>=20.9.0'} cpu: [arm64] os: [darwin] - '@img/sharp-darwin-x64@0.35.3': - resolution: {integrity: sha512-Xo+5uFBtLN0BKqieTxiFzFPQAUlBbbH5iBKyRX/z1JrbnYsHTfKJnUfL8+p2TPXr1pXqao4eeL4Rl144uDpK9w==} + '@img/sharp-darwin-x64@0.35.4': + resolution: {integrity: sha512-hWniXY3bG5qKpkKrAwPe4y+VTPmf086YQAnkxWh7uA1YrlRouWGa0M0Mxj3ZjnXFkv7/TD1bTy9lGUK26vRvWw==} engines: {node: '>=20.9.0'} cpu: [x64] os: [darwin] - '@img/sharp-freebsd-wasm32@0.35.3': - resolution: {integrity: sha512-lUxcqWIj2wMQ9BrwNjngcr1gWUr5xgaGThBRqPPalIC2n67Cqj1uPh8NnA/ZhAg8hUbKl+kVHKwgUIwe6ZYPrg==} + '@img/sharp-freebsd-wasm32@0.35.4': + resolution: {integrity: sha512-lIsKw/BU+kjB4eZjxrYrZmwOJYi3Ajrv66iAlBmUPyKc3HpnloevB1g3wxGD9P/5BbQ1brBGl65VRRrCvQDEqA==} engines: {node: '>=20.9.0'} os: [freebsd] - '@img/sharp-libvips-darwin-arm64@1.3.2': - resolution: {integrity: sha512-9J6ypZFpQBj4YnePGoq/S38w6nz+vqg5WZLrLGY4YuSemdMq47GMLBPO42MzwdGwpg/agZ7xzZcFHa48xlywfg==} + '@img/sharp-libvips-darwin-arm64@1.3.3': + resolution: {integrity: sha512-suTBPTDGrI9WodccaDdwZItTSaBYASlBk1NSfElSHrUfzu3szG6lvIF58+WiFvnfzuK8ZBFS5zE00PxqxnRiPg==} cpu: [arm64] os: [darwin] - '@img/sharp-libvips-darwin-x64@1.3.2': - resolution: {integrity: sha512-m2pW1n6cns9VaubNwsZ+c3CRYjxNQWgJ5gPlnL1nbBcpkBvFm6SCFN5o0psFHI8w9n11NKhFkeEDns98tiqbEw==} + '@img/sharp-libvips-darwin-x64@1.3.3': + resolution: {integrity: sha512-FVJZ5mITMobmXIz/hPDTw0EintTW5H3WfrxwLqEqjiIihlu+hVRyGrFQ60xl0Lxn7Bt3zdpevPaQi0HEzqz9fw==} cpu: [x64] os: [darwin] - '@img/sharp-libvips-linux-arm64@1.3.2': - resolution: {integrity: sha512-dqVSFynCox4C/J8kT16V7SIFAns0IjgLwkvYT7p8LQVmJ5OS5b6tI9IGflxTeuBS//zXeFIUbwt5dwxyZ17cnA==} + '@img/sharp-libvips-linux-arm64@1.3.3': + resolution: {integrity: sha512-0DaL0A6Xu6sQSQFwe4iVCrKWU2cCTItnRsYsCdxAMm9NF6twAA9BKnoqy4hqz4+azQ0JHuA26qiUKsf1XJ/v5A==} cpu: [arm64] os: [linux] libc: [glibc] - '@img/sharp-libvips-linux-arm@1.3.2': - resolution: {integrity: sha512-1eMLzy92I4J6rmi4mAT8yC3HxOtniyGELlzGbNMLLeqe052ahFQ0h6LFq+lh5DsDIdYViIDst08abvSbcEdLXQ==} + '@img/sharp-libvips-linux-arm@1.3.3': + resolution: {integrity: sha512-3rbU4vqXXc3hY/OiXdl52xZvT0F1yEngWfvqudtPJg/KkyiaQw2DRsFrNzpmLvfavbwOq3qXn36GP8obHRULQA==} cpu: [arm] os: [linux] libc: [glibc] - '@img/sharp-libvips-linux-ppc64@1.3.2': - resolution: {integrity: sha512-3z0NHDxD6n5I9gc05U1eW1AyRm+Gznzq3naMrthPNqE6oYykcogW0l/jfpJdjYnuNl8R7yI9pNbE1XiUeyq0Aw==} + '@img/sharp-libvips-linux-ppc64@1.3.3': + resolution: {integrity: sha512-cdn1OvUBwsXhbC0zSzJnNzf5MZ/mTrobawDvNXBTxe8VtqKAm0sRuEY2Evzovb/w9JMk4TvRxqt1mekSuJz64w==} cpu: [ppc64] os: [linux] libc: [glibc] - '@img/sharp-libvips-linux-riscv64@1.3.2': - resolution: {integrity: sha512-bsb4rI+NldGOsXuej2r8OdSS8+zXDVaCWxyWrcv6kneTOlgAHtZABRzBBCwdsPiD90J4myNJuHpg6kA20ImW/w==} + '@img/sharp-libvips-linux-riscv64@1.3.3': + resolution: {integrity: sha512-HjPVx7yKz+0lqdhDlTw1tt90wamBoxhiXpvl1XZpJLiHH4RCJ5yDTqH+VlYPv2fwFs89JFw4c1IexYOcQUi4IQ==} cpu: [riscv64] os: [linux] libc: [glibc] - '@img/sharp-libvips-linux-s390x@1.3.2': - resolution: {integrity: sha512-/ABshyj8gCpyIrNXnHn4LorDJ0HHm1VhXPBlxZ8zAtfVPAaSafXPGn+sUSIRiwaSBy0mmFjSjiXI5mkcwdChKQ==} + '@img/sharp-libvips-linux-s390x@1.3.3': + resolution: {integrity: sha512-neWLh+3yCNThxnfy3c4BbVBeGgt9aftno+XbT56iK28RgeDs3UOFWviLWlUu0bArYVYJaFDK+RRohbicUNCm8Q==} cpu: [s390x] os: [linux] libc: [glibc] - '@img/sharp-libvips-linux-x64@1.3.2': - resolution: {integrity: sha512-ITPEtgffGJ0S6G9dRyw/366tJQqFRcHWPHhC+Stpg3Z8AEMrDrTr2lhdz4f/Y/HMbRh//7Z5mBzEpVdi62Oc3w==} + '@img/sharp-libvips-linux-x64@1.3.3': + resolution: {integrity: sha512-4vKmvAst9nrowcqquKFAyZJUDolUaIp8uRiN0mWFguJ1IplC9/pitXtlnnlU4aa/eJw3J7i67V+pwUL+wZGdsA==} cpu: [x64] os: [linux] libc: [glibc] - '@img/sharp-libvips-linuxmusl-arm64@1.3.2': - resolution: {integrity: sha512-zE9EdiUzUmg5mDT5a1rk5fYJ6GWPloTwWBYDS14naqHsL+EaMpDj1AWnpLgh3u0YCORv2Tt50wrcrpYqkP97Kw==} + '@img/sharp-libvips-linuxmusl-arm64@1.3.3': + resolution: {integrity: sha512-Y9kQaLMuNoB0bPYOOdcZMaseNrFpPodIWWMrx+CZyydf2xn68j9WYc6sWWRrDwNkzCQjKYfc68L7jKjGlHMibw==} cpu: [arm64] os: [linux] libc: [musl] - '@img/sharp-libvips-linuxmusl-x64@1.3.2': - resolution: {integrity: sha512-m0lrLiUt+lBYnCFr8qV/65yMR4E/c7/wf78I5eKTdkEakFAlZ9QlzEM3QIhhAwVeUhLAHLcCq7a7Vszq/oFNZQ==} + '@img/sharp-libvips-linuxmusl-x64@1.3.3': + resolution: {integrity: sha512-fj8Mv0HHfD1Rr+4I68+3agJynxDWtBFgicTbSOb9Bke6pIwzGcJ+RX/yHjmiEGFMCavY/dxvem7MyNaJF+wDiw==} cpu: [x64] os: [linux] libc: [musl] - '@img/sharp-linux-arm64@0.35.3': - resolution: {integrity: sha512-QgKDspHPnrU+GQ55XPhGwyhC8acLVOOSyAvo1oVfFmrIXLkDNmGWzAfDZ4xK8oSA1qBQrALcHX0G5UZni/SuFQ==} + '@img/sharp-linux-arm64@0.35.4': + resolution: {integrity: sha512-De4jpEnAU8Hd5oT0j1G3uL4ZvTuipVMn7YC6vPaJhy6/7EwEae0SVAoBrUMYQbkLGDm85taVWwuPc1a44LTzCQ==} engines: {node: '>=20.9.0'} cpu: [arm64] os: [linux] libc: [glibc] - '@img/sharp-linux-arm@0.35.3': - resolution: {integrity: sha512-affVWCTLooy8TSxbDx2qkzuDeaWLNVBA+P//FNBirHsXpP2fuBhk5AuboYUnrDnzoXes8GFjpTx0SBFOCRg+FA==} + '@img/sharp-linux-arm@0.35.4': + resolution: {integrity: sha512-7OAS8gI0EReKGVN2HssHlM6umJgxF5VI3xN0p9FA91p/YO+ou5hiNghLdZ5BEHztwaaK5+bLKRf8x/o2L2nk9A==} engines: {node: '>=20.9.0'} cpu: [arm] os: [linux] libc: [glibc] - '@img/sharp-linux-ppc64@0.35.3': - resolution: {integrity: sha512-sMd8rDxmpLOwv/7N44klFjOD5DUO7FLdjiXDI0hoxYaf7Ar262dQIEkosE98bps+5HPLtp/EvNqeqQtOycP/IA==} + '@img/sharp-linux-ppc64@0.35.4': + resolution: {integrity: sha512-2oYZJeIl4kCcMGk4ouZVjnkCtFrpQFlNEtJ6GbxzhHQchwH0NH/qEb9ykmOl29dqwMq+JhFdZn+1ak2FKhI9fQ==} engines: {node: '>=20.9.0'} cpu: [ppc64] os: [linux] libc: [glibc] - '@img/sharp-linux-riscv64@0.35.3': - resolution: {integrity: sha512-0Eob78yjlYPfL5vMNWAW55l3R9Y6BQS/gOfe0ZcP9mEz9ohhKSt4im1hayiknXgf8AWrFqMvJcKIdmLmEe7yeQ==} + '@img/sharp-linux-riscv64@0.35.4': + resolution: {integrity: sha512-cPbNChoRURAWdebDIHSenxRpgEdy7JkPydSnUxRm9VvKD7m0/xVaR/8Fzlu81pk5nHEvHH87UZUA7cTtwnbJSA==} engines: {node: '>=20.9.0'} cpu: [riscv64] os: [linux] libc: [glibc] - '@img/sharp-linux-s390x@0.35.3': - resolution: {integrity: sha512-KgAxQ0DxpNOq1rG2t5cgTgShJFGSuU7XO45cqC+1NVOuZnP6tlgZRuSYOfNupGkHID0o3cJOsw4DVeJpMovcGw==} + '@img/sharp-linux-s390x@0.35.4': + resolution: {integrity: sha512-RY0JFY8Fd6RonCBtHz+DvadaPkXDSI1AUn6yWL9TipqkZ1vY8w8evqdgyDFnkm4/K1ve1TvZiaePP5oSd4+WVQ==} engines: {node: '>=20.9.0'} cpu: [s390x] os: [linux] libc: [glibc] - '@img/sharp-linux-x64@0.35.3': - resolution: {integrity: sha512-8pqvxubL2PGdhlPy6GLqzDYMUjyRmKAwKHYKixpdJYBUK7PJ0C029XdsnpFIdgRZG68fZiGdHVWcKPvtiPB4cA==} + '@img/sharp-linux-x64@0.35.4': + resolution: {integrity: sha512-9qvvEAuk8k89TfWUoX2htWjbAMX8p+NxCppjpcg5k6xMsjhBQPTsoIh36h9Qde4WRuGpJeYnOjdosDn/cnv+OA==} engines: {node: '>=20.9.0'} cpu: [x64] os: [linux] libc: [glibc] - '@img/sharp-linuxmusl-arm64@0.35.3': - resolution: {integrity: sha512-Vz0iQjzzcSX3HCbfwFfCSG/9SCIqyO0mH2sXyiHaAYfBk0cRsCWXRyQYX0ovCK/PAQBbTzQ0dsPQHh5MAFL59w==} + '@img/sharp-linuxmusl-arm64@0.35.4': + resolution: {integrity: sha512-KB5jxpfWQTr0nc3xdHtWChdbifHrBGsd2SM62Eyxrl8afikm+f5qGBU75SJIZBT/S1MC8XyacdlXBMSWq6OURA==} engines: {node: '>=20.9.0'} cpu: [arm64] os: [linux] libc: [musl] - '@img/sharp-linuxmusl-x64@0.35.3': - resolution: {integrity: sha512-6O1NPKcDVj9QEdg7Hx549EX8U0rp6yXQERqru6yRN7fGBn32UvIRJUlWnk+8xDCiG76hXVBbX82NZ/ZKr0euIg==} + '@img/sharp-linuxmusl-x64@0.35.4': + resolution: {integrity: sha512-f+eZJZIQNEEd26RPSW+76chwOf1XtA2Y/O+5ocVyLliHkeih3e+jhLVBdNTd2rS3IbNXK8+ug93Vf5ZXtF5Lxg==} engines: {node: '>=20.9.0'} cpu: [x64] os: [linux] libc: [musl] - '@img/sharp-wasm32@0.35.3': - resolution: {integrity: sha512-cZ0XkcYGpHZkqW6iCkqTcmUC0CD9DhD5d/qeZlZkfRBn6GnHniZXLUo5+9xw8Iv76YE6LQFN9YNBlKREcCG76w==} + '@img/sharp-wasm32@0.35.4': + resolution: {integrity: sha512-zQnl4Kwp7Q6NHsENtU2T/00Zi+w3AQNwz3+UaTyVBy2FpXrzXzGjndpK61onhZjRtRpQXxCTeqw19bVyXOh7jA==} engines: {node: '>=20.9.0'} - '@img/sharp-webcontainers-wasm32@0.35.3': - resolution: {integrity: sha512-2rnq7bX3NzeR2T4YWgz8qiG4h3TSdMe+vN1iQXpJleSJ3SM5zQ8Fy2SyyXAWlbxpEZ2Y+Z4u1BePgJEYbSy80Q==} + '@img/sharp-webcontainers-wasm32@0.35.4': + resolution: {integrity: sha512-ESfNkywmCfPNyaZjxooddJQiQ+l/nTpGEOGthxiLnIHXC/CmcBixnfwUleX9mCz9ovrUUvKMap/pm8RYbzfwaA==} engines: {node: '>=20.9.0'} cpu: [wasm32] - '@img/sharp-win32-arm64@0.35.3': - resolution: {integrity: sha512-4bPwFdMbeC4JQ8L8LOyWp6nsHcboP5fxkp6iPOXz2Vg49R42TuMs2whkJ5OAP4/Ul035qOzy0AecOF9VOscn4w==} + '@img/sharp-win32-arm64@0.35.4': + resolution: {integrity: sha512-iNdlBX9gLVvqe2I3uIJSIKTq6wckP/DYxZtcqxm09x5Gi24DnFBmPAWZmr60ZyYMG0xlzo6goG3670ar+RXvRw==} engines: {node: '>=20.9.0'} cpu: [arm64] os: [win32] - '@img/sharp-win32-ia32@0.35.3': - resolution: {integrity: sha512-r53mXsBN6lFUDiST764SvgwUdHAqM4rPAiDzAmf4fLoB6X/rkfyTrLCg6+g17wJJiCmB3JYgHuUldCWUIRFSXw==} + '@img/sharp-win32-ia32@0.35.4': + resolution: {integrity: sha512-kqRsbaa5CS6KHlpxnN7WhE6vAAugXyZButpRdvDWetlv6Qv4N9WTcrWzF7tXfB9T7MsoadqdI8hmwLq6UlLvtw==} engines: {node: ^20.9.0} cpu: [ia32] os: [win32] - '@img/sharp-win32-x64@0.35.3': - resolution: {integrity: sha512-D4y1vNeZrIIJCN+uHaWVtH86B+aCrdMYYjicy9pXHvbGZeGYLLSd3wdVuC37FxVXlU1ARsk84eKWfWMXGYEqvA==} + '@img/sharp-win32-x64@0.35.4': + resolution: {integrity: sha512-XtmnYhBcrORsJ4XJngyzr/EWP0hRZLAZRFaApdKuviyqF78+ylxh2y06ZmtULAMOnObJ3ucpN0AcwSWnMowTRg==} engines: {node: '>=20.9.0'} cpu: [x64] os: [win32] @@ -704,57 +704,57 @@ packages: resolution: {integrity: sha512-WrOw/bcXm0f9qHkumlT1QlArXSTWqaY9sunsDpOk+yCCorCKMxvWT/a3xko4EYHVdeZoh00yI2TydXn6eyICDA==} engines: {node: '>= 10'} - '@next/env@16.2.12': - resolution: {integrity: sha512-d0Z5Bc13Fa4nR8pFAKx2jay2yhJM16vlfHbTzYnUQAxlNb6B6lmn4hjt69lYNt4kRtyYP6gEM49lPRHNbIyneg==} + '@next/env@16.3.7': + resolution: {integrity: sha512-/HuhBN1CorqNTmewTIh81yXOQri6B8Ye/c9D7+830XqwVaf8qMnJNrgNxDn6dLtihq8YzBbI6NSAuMKvpr+DvQ==} - '@next/swc-darwin-arm64@16.2.12': - resolution: {integrity: sha512-0W1R0teHWJrqKX0FH20IzzIWAOuGtBxPGuObrxy1lE8hQvCFj49KE8a3WUg0D7sq6rn6zkM4c7YGUnhudBS6oA==} + '@next/swc-darwin-arm64@16.3.7': + resolution: {integrity: sha512-MDAd3woxfJOVFtfG2VAuaz5zyyVZTMVUPJAknotCNgeEPrPlRBlcV9YSd8S620P6TiSc2MWpfwO/UAHl9EWRug==} engines: {node: '>= 10'} cpu: [arm64] os: [darwin] - '@next/swc-darwin-x64@16.2.12': - resolution: {integrity: sha512-Hy5Ls099+aFUmOLmIgPfLqNi6iCwhL3uQCssz5rWk+5Nkc6TUKCE83DY5BbNylfm3+mfwcSFnLRfrZDJhVxdtw==} + '@next/swc-darwin-x64@16.3.7': + resolution: {integrity: sha512-Usd86QilBWl2G7JxfWskV9NLAxacVRrDDQ3WQIoPKRB0MXiVuVSdyu3wt5EA8SyjQpaVk/7VOzSkRB4LgJltyA==} engines: {node: '>= 10'} cpu: [x64] os: [darwin] - '@next/swc-linux-arm64-gnu@16.2.12': - resolution: {integrity: sha512-+YqU2h1cQkHsGfvjAsrSmst8UIFBibBGm5x3Xgel8NLMiDQtNOM4sM2GOEMvG5YiOBNeN/Ykk8cQC2S0Xrqljg==} + '@next/swc-linux-arm64-gnu@16.3.7': + resolution: {integrity: sha512-pVauSs1WomgtBgfJj/Q+846nBix4CdohLUIDafyJGdAvNw5kOusLawoX+rpc2P4U3Kd7tLwqFXsnHeSkLZrqyw==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] libc: [glibc] - '@next/swc-linux-arm64-musl@16.2.12': - resolution: {integrity: sha512-0qjhiYBaKAqF63LA1ZWAAnKTzFUguAaZiRa5etMLGGPj/B6uEVjtIZldIzFEp3wHlB0koK6aTzqPtSdplTCjoA==} + '@next/swc-linux-arm64-musl@16.3.7': + resolution: {integrity: sha512-vY+iamd6cOfk29bGTgxeS/OXlrlfOfyFgyd34cibC7e25bXRMxuqNUfs2Z9Fxb/VHpV5dopffMSWnCeR2tc8rA==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] libc: [musl] - '@next/swc-linux-x64-gnu@16.2.12': - resolution: {integrity: sha512-7A3q26W+h7gnA15uqBToNuDqBEFZZcqh0mW2mn4AJh/G5pdg2RVE3n4slzLEliASZFG3NmsbEzng/x2Sh09mBg==} + '@next/swc-linux-x64-gnu@16.3.7': + resolution: {integrity: sha512-NWx0LRZ9IO9rTDXLc+Hi/bNcTKblT86du4OLMRm5Z62C2T5/+mNEhK2XeQxwoSmVFvU2rjMSjJMK4ttZAk88+w==} engines: {node: '>= 10'} cpu: [x64] os: [linux] libc: [glibc] - '@next/swc-linux-x64-musl@16.2.12': - resolution: {integrity: sha512-qSjL/uppm+cbh21s72Ss8gkiOhQ4dExWHNGOWy6eZV7STj5WsKehgxT61beSsOj+YYQuTplL376lOCdMQU5T8w==} + '@next/swc-linux-x64-musl@16.3.7': + resolution: {integrity: sha512-v5Dk/iMB4JyQZwQ+UWOZGGAqA4HjkBewKwaCkXi5DqL1Dy1TjXpRmAh8N7VOuID0hLmouICSXozJrMBBG1Xeyw==} engines: {node: '>= 10'} cpu: [x64] os: [linux] libc: [musl] - '@next/swc-win32-arm64-msvc@16.2.12': - resolution: {integrity: sha512-X6hzsOUJac/e7AWSbn9gQ9nzHld1xWP5iyjHpYWvud8pufB679O1xg4JDyKr8Xd69Jvd+kM2Der6uftiZCmjYA==} + '@next/swc-win32-arm64-msvc@16.3.7': + resolution: {integrity: sha512-G4BkB7AhfKJnaoIpIkoA66l5pXPGJXD3EF1EsuYj/sgM4rHA41dsa3CIIqjR4FQFZEjU+zGQRbIEPYZIOi5EoA==} engines: {node: '>= 10'} cpu: [arm64] os: [win32] - '@next/swc-win32-x64-msvc@16.2.12': - resolution: {integrity: sha512-F6fakeHuFTLOPt0bslQJdf+xtT+WIP9DVn/m4y1w1mRnVPyh3D/cNvzlRkxM444xfm+IvvYNSOrKiA2CDJ0Uxw==} + '@next/swc-win32-x64-msvc@16.3.7': + resolution: {integrity: sha512-DuvRhf50tGU7leT9Ow0cs/9lo81X5lHdNoxSRk4ckSk2Ihn2dsM+BEe5j7ANpEFhD25p//SmV8ajdRtuaBIg3g==} engines: {node: '>= 10'} cpu: [x64] os: [win32] @@ -892,8 +892,8 @@ packages: '@standard-schema/spec@1.1.0': resolution: {integrity: sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==} - '@swc/helpers@0.5.15': - resolution: {integrity: sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g==} + '@swc/helpers@0.5.23': + resolution: {integrity: sha512-5lSsMOTXURePglDfvuAQUqkGek9Hg2kksOYay2m0+XR++b2NWYL/4sWyuvVBIs8oKnJaxkdi9whaL/sqN13afw==} '@tailwindcss/node@4.3.3': resolution: {integrity: sha512-/T8IKEsf9VTU6tLjgC7+sv2mOPtQxzE2jMw7u4Tt40Tx+QSZxpzh95/H6cMKoja9XuW7iMdLJYBB0o9G1CaAgg==} @@ -1458,8 +1458,8 @@ packages: resolution: {integrity: sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ==} hasBin: true - joi@17.13.4: - resolution: {integrity: sha512-1RuuER6kmt8K8I3nIWvPZKi5RQCb568ZPyY4Pwjlua+yo+63ZTmIwxLZH0heBmiKN4uxjvCiarDrjaeH84xicQ==} + joi@17.13.8: + resolution: {integrity: sha512-iPKOGmiRw1jxf/JOPwxmCcUQAOdF359mdzYiP2DJ+TMX0YK2zjK3D+zYOaGjpumWxOFF/l2xVWjRVK5bGSLdEw==} joycon@3.1.1: resolution: {integrity: sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw==} @@ -1681,8 +1681,8 @@ packages: engines: {node: ^22 || ^24 || >=26} hasBin: true - next@16.2.12: - resolution: {integrity: sha512-iD59eYQWmbFcEbX7v/acG5DRym9iw1DdaPoD0WTA920naWsE25wShzJW4+UvAs8MK9EC2kBfIH6vtto1H1PHGw==} + next@16.3.7: + resolution: {integrity: sha512-S4AlB0KMYcvVyEVjfD2Ze/3JsX9PWjpD3hJPcEJTVvkAQsMTYVs9DB1NtaBbs41ZZ18NxYwkmM8ljqtrFrmLsQ==} engines: {node: '>=20.9.0'} hasBin: true peerDependencies: @@ -1706,9 +1706,9 @@ packages: resolution: {integrity: sha512-LarFH0+6VfriEhqMMcLX2F7SwSXeWwnEAJEsYm5QKWchiVYVvJyV9v7UDvUv+w5HO23ZpQTXDv/GxdDdMyOuoQ==} engines: {node: '>= 6.13.0'} - nodemailer@9.0.3: - resolution: {integrity: sha512-n+YP+NKwR5zRWa60k3GiQ6Q3B4KXCoAw40dAKeCtYn020iNN74aWK2liXIC3ZEATeGql7we3tE3t8QwhY0eskw==} - engines: {node: '>=6.0.0'} + nodemailer@10.0.12: + resolution: {integrity: sha512-PQ46oNbNMuH/Sno7B5IWIU9etytwrO0xdTFvWXpIQFRXhBAXtzD48243AS+BCbQQTPD8y3fqUrgLn0tSo77gvg==} + engines: {node: '>=20.0.0'} obug@2.1.4: resolution: {integrity: sha512-4a+OsYv9UktOJKE+l1A4OufDgdRF9PifWj+tJnHURo/P+WOxpG4GzUFL9qCalmWauao6ogiG+QvnCovwPoyAWA==} @@ -1879,8 +1879,8 @@ packages: setimmediate@1.0.5: resolution: {integrity: sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==} - sharp@0.35.3: - resolution: {integrity: sha512-ej0zVHuZGHCiABXcNxeYhpRnPNPAcvbG8RMdBAhDAxLKkCRVSpK3Iyu7qbqw3JMzoj0REeM6f3tJLtVwl0023Q==} + sharp@0.35.4: + resolution: {integrity: sha512-n++8XWcj+jCOr2IOl7h8LbKnGBDY4aPbmprMONBNFdn0ImXqpGVv5zliDs0V9HbmbCQLpbuo2ej9rAoOQTvMDA==} engines: {node: '>=20.9.0'} peerDependencies: '@types/node': '*' @@ -2007,12 +2007,12 @@ packages: undici-types@8.3.0: resolution: {integrity: sha512-j375ScV60dom+YkPFIfTLcOiPxkN/buHz5GobjLhixFuANaNs3C9l4GmrWqejgXWJ7BbJcFYpTEUkS1Ge8bpZQ==} - undici@7.29.0: - resolution: {integrity: sha512-IDxfleLmmbSskfWSUATiN1nfn2rDuvnMOqb5CWR92iIfojA0Ud+ulOAAEQ57LPr9rWmsreUyf5lwyao+7GNNVw==} + undici@7.30.0: + resolution: {integrity: sha512-dkrQXeHSaoamnItlYbmzG0wFYrM0ZwDxCIg0A7aKjTyyhh9svRzCNFEzV+Vm05/yehjCzjDZ31KXfGEjYSztDQ==} engines: {node: '>=20.18.1'} - undici@8.10.0: - resolution: {integrity: sha512-HvltHd7avK13QIw/oLe4qoOLyoVSoafqJ2jYOrtMRBkbYT31eiBQ8O0ehRKZiEZCMEyLFQNIADpgCWC5fALvYQ==} + undici@8.11.2: + resolution: {integrity: sha512-u4UB2/IrKdU6lFxumHmmo1a3fCQO5tzQllRorfoRS63txhrB7xTpSn1PftwC4qEHkOaqP95fCWW4lJzwErwzhQ==} engines: {node: '>=22.19.0'} unenv@2.0.0-rc.24: @@ -2330,108 +2330,108 @@ snapshots: '@img/colour@1.1.0': {} - '@img/sharp-darwin-arm64@0.35.3': + '@img/sharp-darwin-arm64@0.35.4': optionalDependencies: - '@img/sharp-libvips-darwin-arm64': 1.3.2 + '@img/sharp-libvips-darwin-arm64': 1.3.3 optional: true - '@img/sharp-darwin-x64@0.35.3': + '@img/sharp-darwin-x64@0.35.4': optionalDependencies: - '@img/sharp-libvips-darwin-x64': 1.3.2 + '@img/sharp-libvips-darwin-x64': 1.3.3 optional: true - '@img/sharp-freebsd-wasm32@0.35.3': + '@img/sharp-freebsd-wasm32@0.35.4': dependencies: - '@img/sharp-wasm32': 0.35.3 + '@img/sharp-wasm32': 0.35.4 optional: true - '@img/sharp-libvips-darwin-arm64@1.3.2': + '@img/sharp-libvips-darwin-arm64@1.3.3': optional: true - '@img/sharp-libvips-darwin-x64@1.3.2': + '@img/sharp-libvips-darwin-x64@1.3.3': optional: true - '@img/sharp-libvips-linux-arm64@1.3.2': + '@img/sharp-libvips-linux-arm64@1.3.3': optional: true - '@img/sharp-libvips-linux-arm@1.3.2': + '@img/sharp-libvips-linux-arm@1.3.3': optional: true - '@img/sharp-libvips-linux-ppc64@1.3.2': + '@img/sharp-libvips-linux-ppc64@1.3.3': optional: true - '@img/sharp-libvips-linux-riscv64@1.3.2': + '@img/sharp-libvips-linux-riscv64@1.3.3': optional: true - '@img/sharp-libvips-linux-s390x@1.3.2': + '@img/sharp-libvips-linux-s390x@1.3.3': optional: true - '@img/sharp-libvips-linux-x64@1.3.2': + '@img/sharp-libvips-linux-x64@1.3.3': optional: true - '@img/sharp-libvips-linuxmusl-arm64@1.3.2': + '@img/sharp-libvips-linuxmusl-arm64@1.3.3': optional: true - '@img/sharp-libvips-linuxmusl-x64@1.3.2': + '@img/sharp-libvips-linuxmusl-x64@1.3.3': optional: true - '@img/sharp-linux-arm64@0.35.3': + '@img/sharp-linux-arm64@0.35.4': optionalDependencies: - '@img/sharp-libvips-linux-arm64': 1.3.2 + '@img/sharp-libvips-linux-arm64': 1.3.3 optional: true - '@img/sharp-linux-arm@0.35.3': + '@img/sharp-linux-arm@0.35.4': optionalDependencies: - '@img/sharp-libvips-linux-arm': 1.3.2 + '@img/sharp-libvips-linux-arm': 1.3.3 optional: true - '@img/sharp-linux-ppc64@0.35.3': + '@img/sharp-linux-ppc64@0.35.4': optionalDependencies: - '@img/sharp-libvips-linux-ppc64': 1.3.2 + '@img/sharp-libvips-linux-ppc64': 1.3.3 optional: true - '@img/sharp-linux-riscv64@0.35.3': + '@img/sharp-linux-riscv64@0.35.4': optionalDependencies: - '@img/sharp-libvips-linux-riscv64': 1.3.2 + '@img/sharp-libvips-linux-riscv64': 1.3.3 optional: true - '@img/sharp-linux-s390x@0.35.3': + '@img/sharp-linux-s390x@0.35.4': optionalDependencies: - '@img/sharp-libvips-linux-s390x': 1.3.2 + '@img/sharp-libvips-linux-s390x': 1.3.3 optional: true - '@img/sharp-linux-x64@0.35.3': + '@img/sharp-linux-x64@0.35.4': optionalDependencies: - '@img/sharp-libvips-linux-x64': 1.3.2 + '@img/sharp-libvips-linux-x64': 1.3.3 optional: true - '@img/sharp-linuxmusl-arm64@0.35.3': + '@img/sharp-linuxmusl-arm64@0.35.4': optionalDependencies: - '@img/sharp-libvips-linuxmusl-arm64': 1.3.2 + '@img/sharp-libvips-linuxmusl-arm64': 1.3.3 optional: true - '@img/sharp-linuxmusl-x64@0.35.3': + '@img/sharp-linuxmusl-x64@0.35.4': optionalDependencies: - '@img/sharp-libvips-linuxmusl-x64': 1.3.2 + '@img/sharp-libvips-linuxmusl-x64': 1.3.3 optional: true - '@img/sharp-wasm32@0.35.3': + '@img/sharp-wasm32@0.35.4': dependencies: '@emnapi/runtime': 1.11.3 optional: true - '@img/sharp-webcontainers-wasm32@0.35.3': + '@img/sharp-webcontainers-wasm32@0.35.4': dependencies: - '@img/sharp-wasm32': 0.35.3 + '@img/sharp-wasm32': 0.35.4 optional: true - '@img/sharp-win32-arm64@0.35.3': + '@img/sharp-win32-arm64@0.35.4': optional: true - '@img/sharp-win32-ia32@0.35.3': + '@img/sharp-win32-ia32@0.35.4': optional: true - '@img/sharp-win32-x64@0.35.3': + '@img/sharp-win32-x64@0.35.4': optional: true '@jridgewell/gen-mapping@0.3.13': @@ -2513,30 +2513,30 @@ snapshots: '@napi-rs/keyring-win32-ia32-msvc': 1.3.0 '@napi-rs/keyring-win32-x64-msvc': 1.3.0 - '@next/env@16.2.12': {} + '@next/env@16.3.7': {} - '@next/swc-darwin-arm64@16.2.12': + '@next/swc-darwin-arm64@16.3.7': optional: true - '@next/swc-darwin-x64@16.2.12': + '@next/swc-darwin-x64@16.3.7': optional: true - '@next/swc-linux-arm64-gnu@16.2.12': + '@next/swc-linux-arm64-gnu@16.3.7': optional: true - '@next/swc-linux-arm64-musl@16.2.12': + '@next/swc-linux-arm64-musl@16.3.7': optional: true - '@next/swc-linux-x64-gnu@16.2.12': + '@next/swc-linux-x64-gnu@16.3.7': optional: true - '@next/swc-linux-x64-musl@16.2.12': + '@next/swc-linux-x64-musl@16.3.7': optional: true - '@next/swc-win32-arm64-msvc@16.2.12': + '@next/swc-win32-arm64-msvc@16.3.7': optional: true - '@next/swc-win32-x64-msvc@16.2.12': + '@next/swc-win32-x64-msvc@16.3.7': optional: true '@oxc-project/types@0.142.0': {} @@ -2621,7 +2621,7 @@ snapshots: '@standard-schema/spec@1.1.0': {} - '@swc/helpers@0.5.15': + '@swc/helpers@0.5.23': dependencies: tslib: 2.8.1 @@ -3105,7 +3105,7 @@ snapshots: jiti@2.7.0: {} - joi@17.13.4: + joi@17.13.8: dependencies: '@hapi/hoek': 9.3.0 '@hapi/topo': 5.1.0 @@ -3265,8 +3265,8 @@ snapshots: miniflare@5.20260801.0-alpha(@types/node@26.1.2): dependencies: '@cspotcode/source-map-support': 0.8.1 - sharp: 0.35.3(@types/node@26.1.2) - undici: 7.29.0 + sharp: 0.35.4(@types/node@26.1.2) + undici: 7.30.0 workerd: 1.20260801.1 ws: 8.21.1 youch: 4.1.0-beta.10 @@ -3281,10 +3281,10 @@ snapshots: nanoid@6.0.0: {} - next@16.2.12(@types/node@26.1.2)(react-dom@19.2.8(react@19.2.8))(react@19.2.8): + next@16.3.7(@types/node@26.1.2)(react-dom@19.2.8(react@19.2.8))(react@19.2.8): dependencies: - '@next/env': 16.2.12 - '@swc/helpers': 0.5.15 + '@next/env': 16.3.7 + '@swc/helpers': 0.5.23 baseline-browser-mapping: 2.11.8 caniuse-lite: 1.0.30001806 postcss: 8.5.25 @@ -3292,15 +3292,15 @@ snapshots: react-dom: 19.2.8(react@19.2.8) styled-jsx: 5.1.6(react@19.2.8) optionalDependencies: - '@next/swc-darwin-arm64': 16.2.12 - '@next/swc-darwin-x64': 16.2.12 - '@next/swc-linux-arm64-gnu': 16.2.12 - '@next/swc-linux-arm64-musl': 16.2.12 - '@next/swc-linux-x64-gnu': 16.2.12 - '@next/swc-linux-x64-musl': 16.2.12 - '@next/swc-win32-arm64-msvc': 16.2.12 - '@next/swc-win32-x64-msvc': 16.2.12 - sharp: 0.35.3(@types/node@26.1.2) + '@next/swc-darwin-arm64': 16.3.7 + '@next/swc-darwin-x64': 16.3.7 + '@next/swc-linux-arm64-gnu': 16.3.7 + '@next/swc-linux-arm64-musl': 16.3.7 + '@next/swc-linux-x64-gnu': 16.3.7 + '@next/swc-linux-x64-musl': 16.3.7 + '@next/swc-win32-arm64-msvc': 16.3.7 + '@next/swc-win32-x64-msvc': 16.3.7 + sharp: 0.35.4(@types/node@26.1.2) transitivePeerDependencies: - '@babel/core' - '@types/node' @@ -3308,7 +3308,7 @@ snapshots: node-forge@1.4.0: {} - nodemailer@9.0.3: {} + nodemailer@10.0.12: {} obug@2.1.4: {} @@ -3335,7 +3335,7 @@ snapshots: passkit-generator@3.5.7: dependencies: do-not-zip: 1.0.0 - joi: 17.13.4 + joi: 17.13.8 node-forge: 1.4.0 tslib: 2.8.1 @@ -3488,37 +3488,37 @@ snapshots: setimmediate@1.0.5: {} - sharp@0.35.3(@types/node@26.1.2): + sharp@0.35.4(@types/node@26.1.2): dependencies: '@img/colour': 1.1.0 detect-libc: 2.1.2 semver: 7.8.5 optionalDependencies: - '@img/sharp-darwin-arm64': 0.35.3 - '@img/sharp-darwin-x64': 0.35.3 - '@img/sharp-freebsd-wasm32': 0.35.3 - '@img/sharp-libvips-darwin-arm64': 1.3.2 - '@img/sharp-libvips-darwin-x64': 1.3.2 - '@img/sharp-libvips-linux-arm': 1.3.2 - '@img/sharp-libvips-linux-arm64': 1.3.2 - '@img/sharp-libvips-linux-ppc64': 1.3.2 - '@img/sharp-libvips-linux-riscv64': 1.3.2 - '@img/sharp-libvips-linux-s390x': 1.3.2 - '@img/sharp-libvips-linux-x64': 1.3.2 - '@img/sharp-libvips-linuxmusl-arm64': 1.3.2 - '@img/sharp-libvips-linuxmusl-x64': 1.3.2 - '@img/sharp-linux-arm': 0.35.3 - '@img/sharp-linux-arm64': 0.35.3 - '@img/sharp-linux-ppc64': 0.35.3 - '@img/sharp-linux-riscv64': 0.35.3 - '@img/sharp-linux-s390x': 0.35.3 - '@img/sharp-linux-x64': 0.35.3 - '@img/sharp-linuxmusl-arm64': 0.35.3 - '@img/sharp-linuxmusl-x64': 0.35.3 - '@img/sharp-webcontainers-wasm32': 0.35.3 - '@img/sharp-win32-arm64': 0.35.3 - '@img/sharp-win32-ia32': 0.35.3 - '@img/sharp-win32-x64': 0.35.3 + '@img/sharp-darwin-arm64': 0.35.4 + '@img/sharp-darwin-x64': 0.35.4 + '@img/sharp-freebsd-wasm32': 0.35.4 + '@img/sharp-libvips-darwin-arm64': 1.3.3 + '@img/sharp-libvips-darwin-x64': 1.3.3 + '@img/sharp-libvips-linux-arm': 1.3.3 + '@img/sharp-libvips-linux-arm64': 1.3.3 + '@img/sharp-libvips-linux-ppc64': 1.3.3 + '@img/sharp-libvips-linux-riscv64': 1.3.3 + '@img/sharp-libvips-linux-s390x': 1.3.3 + '@img/sharp-libvips-linux-x64': 1.3.3 + '@img/sharp-libvips-linuxmusl-arm64': 1.3.3 + '@img/sharp-libvips-linuxmusl-x64': 1.3.3 + '@img/sharp-linux-arm': 0.35.4 + '@img/sharp-linux-arm64': 0.35.4 + '@img/sharp-linux-ppc64': 0.35.4 + '@img/sharp-linux-riscv64': 0.35.4 + '@img/sharp-linux-s390x': 0.35.4 + '@img/sharp-linux-x64': 0.35.4 + '@img/sharp-linuxmusl-arm64': 0.35.4 + '@img/sharp-linuxmusl-x64': 0.35.4 + '@img/sharp-webcontainers-wasm32': 0.35.4 + '@img/sharp-win32-arm64': 0.35.4 + '@img/sharp-win32-ia32': 0.35.4 + '@img/sharp-win32-x64': 0.35.4 '@types/node': 26.1.2 siginfo@2.0.0: {} @@ -3612,9 +3612,9 @@ snapshots: undici-types@8.3.0: {} - undici@7.29.0: {} + undici@7.30.0: {} - undici@8.10.0: {} + undici@8.11.2: {} unenv@2.0.0-rc.24: dependencies: diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 102304a..878b72c 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -27,9 +27,11 @@ allowBuilds: # esbuild <0.28.1 → Deno binary-integrity RCE (GHSA-gv7w-rqvm-qjhr); also clears the # Windows dev-server path traversal (GHSA-g7r4-m6w7-qqqr) and the older # dev-server CORS bypass (GHSA-67mh-4wv8-2f99). Build-time devDep only. -# joi <17.13.4 → uncaught RangeError on deep recursive link() schemas (GHSA-q7cg-457f-vx79) -# (pulled in transitively via passkit-generator; ^17.x stays off the -# separate 18.0.0–18.2.0 vulnerable branch) +# joi <17.13.8 → isoDate() regular-expression denial of service (GHSA-6h2x-m376-mqjq), +# uncaught RangeError on deep recursive link() schemas (GHSA-q7cg-457f-vx79) +# and prototype pollution in custom messages/rename() (GHSA-6w3j-5fw6-r9vr, +# GHSA-gg4h-3hg2-grpc); pulled in via passkit-generator. ^17.x stays off +# the separate 18.0.0–18.2.0 vulnerable branch. # ws <8.20.1 → uninitialized memory disclosure via close() reason TypedArray # (GHSA-58qx-3vcg-4xpx); pulled in via miniflare's exact 8.18.0 pin # uuid <11.1.1 → v3/v5/v6 missing output-buffer bounds check (GHSA-w5hq-g745-h8pq); @@ -41,17 +43,18 @@ allowBuilds: # both Windows-only dev-server issues. Pulled in transitively via vitest; we # never run the vite dev server (Next.js serves the app), so not exploitable # here — pinned for hygiene / to clear the alerts. Test-time devDep only. -# sharp <0.35.0 → inherited libvips heap overflows / OOB reads (GHSA-f88m-g3jw-g9cj: -# CVE-2026-33327/33328/35590/35591); optional dep of next (image -# optimization) which pins ^0.34.5, so forced here. -# undici@7 <7.29.0 → the 7.x HTTP-cache/cookie/retry cluster: private-cache directive +# sharp <0.35.4 → inherited libvips heap overflows / OOB reads (GHSA-f88m-g3jw-g9cj: +# CVE-2026-33327/33328/35590/35591) and libheif vulnerabilities +# (GHSA-rgj7-g3m4-5g8c); optional dep of next, so forced here. +# undici@7 <7.29.1 → BalancedPool TLS validation bypass (GHSA-w293-vg96-wgc3) and +# the 7.x HTTP-cache/cookie/retry cluster: private-cache directive # crash + cross-user disclosure (GHSA-4cwx-7wf7-3272, high), # Cache-Control whitespace disclosure (GHSA-jr45-8vmc-qm54), retry # interceptor response desync (GHSA-8xcm-r25x-g524), setCookie # attribute injection (GHSA-v3r7-h72x-cjcm) and blob `type` CRLF # injection (GHSA-m8rv-5g2x-5cg5). NOTE the `undici@7` key: the # app's own dependency is undici ^8, which these advisories cover -# separately (fixed in 8.9.0, and the root spec floor is ^8.9.0), so +# separately (the root spec floor is now ^8.10.2), so # a bare `undici:` override would drag the app back onto the 7.x # line. Keying on the 7 range patches every 7.x consumer while # leaving 8.x alone — today that is miniflare's exact 7.28.0 pin @@ -59,11 +62,11 @@ allowBuilds: # ships in the server image. overrides: nanoid@3: ^3.3.18 - undici@7: ^7.29.0 - sharp: ^0.35.3 + undici@7: ^7.29.1 + sharp: ^0.35.4 postcss: ^8.5.10 esbuild: ^0.28.1 - joi: ^17.13.4 + joi: ^17.13.8 ws: ^8.20.1 uuid: ^11.1.1 form-data: ^4.0.6 diff --git a/src/app/(app)/keys/new/actions.ts b/src/app/(app)/keys/new/actions.ts index 6c317fb..c38f570 100644 --- a/src/app/(app)/keys/new/actions.ts +++ b/src/app/(app)/keys/new/actions.ts @@ -9,7 +9,7 @@ import { createKeyWithDestinations, type DestinationInput, } from "@/lib/notify/destinations"; -import { isPresetId } from "@/lib/presets"; +import { getPreset, isPresetId } from "@/lib/presets"; export type CreateState = { error?: string; @@ -39,10 +39,13 @@ export async function createKeyAction( if (!memo) return { error: "memo is required" }; if (memo.length > 500) return { error: "memo too long (max 500)" }; - const responseKindRaw = String(formData.get("response_kind") ?? "gif"); - const responseKind = (VALID_KINDS as string[]).includes(responseKindRaw) - ? (responseKindRaw as ResponseKind) - : "gif"; + const presetRaw = String(formData.get("preset") ?? ""); + const preset = getPreset(isPresetId(presetRaw) ? presetRaw : null); + const responseKindRaw = String(formData.get("response_kind") ?? preset.responseKind); + if (!(VALID_KINDS as string[]).includes(responseKindRaw)) { + return { error: "invalid trigger response" }; + } + const responseKind = responseKindRaw as ResponseKind; let responsePayload: unknown = null; if (responseKind === "redirect") { @@ -97,7 +100,7 @@ export async function createKeyAction( destinations.push({ channel, target }); } - const dedupRaw = String(formData.get("dedupe_window_seconds") ?? "60"); + const dedupRaw = String(formData.get("dedupe_window_seconds") ?? preset.dedupeWindowSeconds); const dedupeWindowSeconds = Number.parseInt(dedupRaw, 10); if (!Number.isFinite(dedupeWindowSeconds) || dedupeWindowSeconds < 0 || dedupeWindowSeconds > 86_400) { return { error: "dedupe window must be 0–86400 seconds" }; @@ -117,7 +120,6 @@ export async function createKeyAction( // Carry the preset through so the key page can surface the matching // download format and deployment hint instead of a generic format list. - const presetRaw = String(formData.get("preset") ?? ""); const presetQuery = isPresetId(presetRaw) ? `?preset=${presetRaw}` : ""; redirect(`/keys/${row.id}${presetQuery}`); } diff --git a/src/app/(app)/keys/new/form.tsx b/src/app/(app)/keys/new/form.tsx index 5cb4a09..ecd6bf8 100644 --- a/src/app/(app)/keys/new/form.tsx +++ b/src/app/(app)/keys/new/form.tsx @@ -40,6 +40,9 @@ export function NewKeyForm({ ); const [touched, setTouched] = useState({ kind: false, dedupe: false }); const [showAdvanced, setShowAdvanced] = useState(false); + const [redirectUrl, setRedirectUrl] = useState(""); + const [htmlBody, setHtmlBody] = useState(""); + const [jsonBody, setJsonBody] = useState(""); const [destinations, setDestinations] = useState([]); const choosePreset = (id: string) => { @@ -60,6 +63,11 @@ export function NewKeyForm({ return (
+ + + + +

A mantis key is a tripwire. Pick what you're planting and mantis fills in sensible defaults — you can change any of them. @@ -106,7 +114,6 @@ export function NewKeyForm({ required maxLength={500} defaultValue={defaultMemo} - key={preset.id} // re-render placeholder when the preset changes placeholder={preset.memoExample || "e.g. honeypot doc in /finance"} className={`${inputBase} w-full`} /> @@ -140,7 +147,6 @@ export function NewKeyForm({ hint="What the mantis URL returns when fetched. The preset picks the least conspicuous option for that medium." > setRedirectUrl(e.target.value)} placeholder="https://example.com" className={`${inputBase} w-full`} /> @@ -170,7 +177,8 @@ export function NewKeyForm({ {kind === "html" && (