Merge pull request #458 from trails-cool/federation/fedify-spike

feat(journal): Fedify foundation — WebFinger + actor objects behind FEDERATION_ENABLED
This commit is contained in:
Ullrich Schäfer 2026-06-06 14:21:00 +02:00 committed by GitHub
commit ae0e95fcd5
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 518 additions and 13 deletions

View file

@ -0,0 +1,129 @@
import { describe, it, expect, vi, beforeEach } from "vitest";
// Spike validation (social-federation task 1.1): exercise Fedify's URL
// dispatcher the way a remote AP client would — WebFinger lookup, then
// actor fetch with an ActivityPub Accept header — without a running
// server or database.
const dbUsers: Array<Record<string, unknown>> = [];
vi.mock("./db.ts", () => ({
getDb: () => ({
select: () => ({
from: () => ({
where: () => ({
limit: async () => dbUsers,
}),
}),
}),
}),
}));
const { handleFederationRequest, wantsActivityJson } = await import(
"./federation.server.ts"
);
const PUBLIC_USER = {
id: "u1",
username: "bruno",
displayName: "Bruno",
bio: "Riding bikes",
domain: "localhost",
profileVisibility: "public",
};
function webfingerRequest(handle: string): Request {
return new Request(
`http://localhost:3000/.well-known/webfinger?resource=${encodeURIComponent(handle)}`,
{ headers: { accept: "application/jrd+json" } },
);
}
function actorRequest(username: string): Request {
return new Request(`http://localhost:3000/users/${username}`, {
headers: { accept: "application/activity+json" },
});
}
beforeEach(() => {
process.env.FEDERATION_ENABLED = "true";
dbUsers.length = 0;
});
describe("federation flag", () => {
it("404s every federation request when FEDERATION_ENABLED is off", async () => {
process.env.FEDERATION_ENABLED = "false";
dbUsers.push(PUBLIC_USER);
const res = await handleFederationRequest(webfingerRequest("acct:bruno@localhost:3000"));
expect(res.status).toBe(404);
});
});
describe("WebFinger", () => {
it("resolves a public user's handle to their actor IRI", async () => {
dbUsers.push(PUBLIC_USER);
const res = await handleFederationRequest(webfingerRequest("acct:bruno@localhost:3000"));
expect(res.status).toBe(200);
const jrd = await res.json();
expect(jrd.subject).toBe("acct:bruno@localhost:3000");
const self = jrd.links.find((l: { rel: string }) => l.rel === "self");
expect(self.href).toBe("http://localhost:3000/users/bruno");
expect(self.type).toContain("application/activity+json");
});
it("404s for a private user without leaking existence", async () => {
dbUsers.push({ ...PUBLIC_USER, profileVisibility: "private" });
const res = await handleFederationRequest(webfingerRequest("acct:bruno@localhost:3000"));
expect(res.status).toBe(404);
});
it("404s for an unknown user", async () => {
const res = await handleFederationRequest(webfingerRequest("acct:ghost@localhost:3000"));
expect(res.status).toBe(404);
});
});
describe("actor object", () => {
it("serves a Person actor for a public user", async () => {
dbUsers.push(PUBLIC_USER);
const res = await handleFederationRequest(actorRequest("bruno"));
expect(res.status).toBe(200);
expect(res.headers.get("content-type")).toContain("application/activity+json");
const actor = await res.json();
expect(actor.type).toBe("Person");
expect(actor.id).toBe("http://localhost:3000/users/bruno");
expect(actor.preferredUsername).toBe("bruno");
expect(actor.name).toBe("Bruno");
expect(actor.summary).toBe("Riding bikes");
});
it("404s the actor for a private user", async () => {
dbUsers.push({ ...PUBLIC_USER, profileVisibility: "private" });
const res = await handleFederationRequest(actorRequest("bruno"));
expect(res.status).toBe(404);
});
});
describe("wantsActivityJson", () => {
it("matches AP client Accept headers, not browsers", () => {
expect(wantsActivityJson(actorRequest("bruno"))).toBe(true);
expect(
wantsActivityJson(
new Request("http://localhost:3000/users/bruno", {
// Mastodon's exact Accept header
headers: {
accept:
'application/activity+json, application/ld+json; profile="https://www.w3.org/ns/activitystreams"',
},
}),
),
).toBe(true);
expect(
wantsActivityJson(
new Request("http://localhost:3000/users/bruno", {
headers: { accept: "text/html,application/xhtml+xml" },
}),
),
).toBe(false);
});
});

View file

@ -0,0 +1,99 @@
// ActivityPub federation via Fedify. See openspec/changes/social-federation.
//
// Spike scope (task 1.1): a minimal Federation instance serving WebFinger
// discovery + Person actor objects for public users. No keypairs, no inbox
// processing, no delivery yet — those land in later tasks.
//
// Version pinning (task 1.2): @fedify/fedify is pinned to exactly 2.1.16.
// The whole 2.2.x line is uninstallable from npm — each release depends on
// @fedify/webfinger@2.2.x, which was never published (latest is 2.1.16).
// Revisit the pin once `pnpm view @fedify/fedify dependencies` resolves.
//
// Mounting model: Fedify owns URL dispatch for its endpoints via
// `federation.fetch(request)`. We delegate to it from React Router routes:
// - /.well-known/webfinger → resource route (no component, raw Response)
// - /users/:username → route middleware short-circuits to Fedify
// when the request asks for an ActivityPub representation; browsers
// fall through to the HTML profile loader (content negotiation per
// the design decision "actor IRI is the profile URL").
import { createFederation, MemoryKvStore, type Federation } from "@fedify/fedify";
import { Person } from "@fedify/fedify/vocab";
import { eq } from "drizzle-orm";
import { users } from "@trails-cool/db/schema/journal";
import { getDb } from "./db.ts";
import { getOrigin } from "./config.server.ts";
/**
* Feature flag (task 1.3). All federation surfaces WebFinger, actor
* objects, inbox, outbox are disabled (404) unless FEDERATION_ENABLED
* is exactly "true". Default off so schema/code can deploy before any
* federation traffic is possible.
*/
export function federationEnabled(): boolean {
return process.env.FEDERATION_ENABLED === "true";
}
/** Does the request ask for an ActivityPub representation of a resource? */
export function wantsActivityJson(request: Request): boolean {
const accept = request.headers.get("accept") ?? "";
return (
accept.includes("application/activity+json") ||
accept.includes("application/ld+json")
);
}
let _federation: Federation<void> | null = null;
function buildFederation(): Federation<void> {
const federation = createFederation<void>({
// MemoryKvStore is fine for the spike (it only caches remote documents
// and nonces). Replace with a Postgres-backed store before real
// federation traffic (revisit in task 4.x).
kv: new MemoryKvStore(),
// Canonical origin so generated IRIs are correct behind the Caddy
// proxy (the Node server itself only sees plain HTTP).
origin: getOrigin(),
});
federation.setActorDispatcher("/users/{identifier}", async (ctx, identifier) => {
const db = getDb();
const [user] = await db
.select()
.from(users)
.where(eq(users.username, identifier))
.limit(1);
// Private profiles do not federate at all: returning null makes Fedify
// respond 404, and WebFinger lookups for this user 404 with it — no
// leak of user existence (spec: "Private user is invisible to
// federation").
if (!user || user.profileVisibility !== "public") return null;
return new Person({
id: ctx.getActorUri(identifier),
preferredUsername: identifier,
name: user.displayName ?? user.username,
summary: user.bio || undefined,
// Human-facing profile URL — same as the actor IRI by design, but
// declared explicitly so AP clients link browsers to the HTML view.
url: new URL(`/users/${identifier}`, getOrigin()),
});
});
return federation;
}
export function getFederation(): Federation<void> {
_federation ??= buildFederation();
return _federation;
}
/**
* Delegate a request to Fedify's URL dispatcher. 404s when the feature
* flag is off so disabled instances are indistinguishable from instances
* without federation.
*/
export function handleFederationRequest(request: Request): Promise<Response> {
if (!federationEnabled()) {
return Promise.resolve(new Response("Not Found", { status: 404 }));
}
return getFederation().fetch(request, { contextData: undefined });
}

View file

@ -3,6 +3,7 @@ import { type RouteConfig, index, route } from "@react-router/dev/routes";
export default [
index("routes/home.tsx"),
route(".well-known/trails-cool", "routes/api.well-known.trails-cool.ts"),
route(".well-known/webfinger", "routes/api.well-known.webfinger.ts"),
route("oauth/authorize", "routes/oauth.authorize.tsx"),
route("oauth/token", "routes/oauth.token.ts"),
route("auth/register", "routes/auth.register.tsx"),

View file

@ -0,0 +1,13 @@
import type { Route } from "./+types/api.well-known.webfinger";
import { handleFederationRequest } from "~/lib/federation.server";
/**
* GET /.well-known/webfinger?resource=acct:user@domain
*
* ActivityPub discovery: resolves an acct: handle to the user's actor IRI.
* Fully handled by Fedify; 404s while FEDERATION_ENABLED is off, and for
* users whose profile_visibility is not 'public'.
*/
export function loader({ request }: Route.LoaderArgs) {
return handleFederationRequest(request);
}

View file

@ -4,6 +4,24 @@ import { useTranslation } from "react-i18next";
import { ClientDate } from "~/components/ClientDate";
import { FollowButton } from "~/components/FollowButton";
import { loadUserProfile } from "./users.$username.server";
import {
federationEnabled,
wantsActivityJson,
handleFederationRequest,
} from "~/lib/federation.server";
// Content negotiation: this URL is both the human profile (HTML) and the
// ActivityPub actor IRI (application/activity+json). AP clients are
// short-circuited to Fedify before the HTML loader runs; browsers fall
// through untouched.
export const middleware: Route.MiddlewareFunction[] = [
async ({ request }, next) => {
if (federationEnabled() && wantsActivityJson(request)) {
return handleFederationRequest(request);
}
return next();
},
];
export async function loader({ params, request }: Route.LoaderArgs) {
return data(await loadUserProfile(request, params.username));

View file

@ -12,18 +12,19 @@
"test": "vitest run"
},
"dependencies": {
"@fedify/fedify": "2.1.16",
"@react-router/node": "catalog:",
"@react-router/serve": "catalog:",
"@sentry/node": "catalog:",
"@sentry/react": "catalog:",
"@simplewebauthn/browser": "^13.3.0",
"@simplewebauthn/server": "^13.3.1",
"@simplewebauthn/server": "^13.3.0",
"@trails-cool/api": "workspace:*",
"@trails-cool/db": "workspace:*",
"@trails-cool/jobs": "workspace:*",
"@trails-cool/fit": "workspace:*",
"@trails-cool/gpx": "workspace:*",
"@trails-cool/i18n": "workspace:*",
"@trails-cool/jobs": "workspace:*",
"@trails-cool/map": "workspace:*",
"@trails-cool/sentry-config": "workspace:*",
"@trails-cool/types": "workspace:*",
@ -31,7 +32,7 @@
"drizzle-orm": "catalog:",
"isbot": "^5.1.40",
"jose": "^6.2.3",
"nodemailer": "^8.0.10",
"nodemailer": "^8.0.8",
"pino": "^10.3.1",
"prom-client": "^15.1.3",
"react": "catalog:",

View file

@ -2,4 +2,11 @@ import type { Config } from "@react-router/dev/config";
export default {
ssr: true,
future: {
// Route middleware — used for ActivityPub content negotiation on
// /users/:username (see app/lib/federation.server.ts). No loader or
// action reads the `context` arg, so the flag's type change to
// RouterContextProvider is a no-op for existing code.
v8_middleware: true,
},
} satisfies Config;

View file

@ -1,8 +1,8 @@
## 1. Library + dependencies
- [ ] 1.1 Spike Fedify on a branch: import, mount minimal actor object endpoint for one user, validate WebFinger lookup from a remote AP client. Confirm fit. If unfit, document why and pivot to alternative
- [ ] 1.2 Add Fedify (or chosen lib) to `apps/journal/package.json`; document version pinning rationale
- [ ] 1.3 Add `FEDERATION_ENABLED` env var (default `false`) and a thin runtime feature flag the rest of the change reads from
- [x] 1.1 Spike Fedify on a branch: import, mount minimal actor object endpoint for one user, validate WebFinger lookup from a remote AP client. Confirm fit. If unfit, document why and pivot to alternative
- [x] 1.2 Add Fedify (or chosen lib) to `apps/journal/package.json`; document version pinning rationale
- [x] 1.3 Add `FEDERATION_ENABLED` env var (default `false`) and a thin runtime feature flag the rest of the change reads from
## 2. Schema

251
pnpm-lock.yaml generated
View file

@ -203,6 +203,9 @@ importers:
apps/journal:
dependencies:
'@fedify/fedify':
specifier: 2.1.16
version: 2.1.16
'@react-router/node':
specifier: 'catalog:'
version: 7.16.0(react-router@7.16.0(react-dom@19.2.6(react@19.2.6))(react@19.2.6))(typescript@6.0.3)
@ -219,7 +222,7 @@ importers:
specifier: ^13.3.0
version: 13.3.0
'@simplewebauthn/server':
specifier: ^13.3.1
specifier: ^13.3.0
version: 13.3.1
'@trails-cool/api':
specifier: workspace:*
@ -253,7 +256,7 @@ importers:
version: link:../../packages/ui
drizzle-orm:
specifier: 'catalog:'
version: 0.45.2(@opentelemetry/api@1.9.1)(@types/pg@8.15.6)(expo-sqlite@56.0.4(expo@55.0.26)(react-native@0.83.4(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.15)(react@19.2.6))(react@19.2.6))(pg@8.20.0)(postgres@3.4.9)
version: 0.45.2(@opentelemetry/api@1.9.1)(@types/pg@8.15.6)(expo-sqlite@56.0.4(expo@56.0.4)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/jest-preset@0.85.3(@babel/core@7.29.7)(react@19.2.6))(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.15)(react@19.2.6))(react@19.2.6))(pg@8.20.0)(postgres@3.4.9)
isbot:
specifier: ^5.1.40
version: 5.1.40
@ -261,7 +264,7 @@ importers:
specifier: ^6.2.3
version: 6.2.3
nodemailer:
specifier: ^8.0.10
specifier: ^8.0.8
version: 8.0.10
pino:
specifier: ^10.3.1
@ -513,7 +516,7 @@ importers:
version: 6.0.2
drizzle-orm:
specifier: 'catalog:'
version: 0.45.2(@opentelemetry/api@1.9.1)(@types/pg@8.15.6)(expo-sqlite@56.0.4(expo@55.0.26)(react-native@0.83.4(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.15)(react@19.2.6))(react@19.2.6))(pg@8.20.0)(postgres@3.4.9)
version: 0.45.2(@opentelemetry/api@1.9.1)(@types/pg@8.15.6)(expo-sqlite@56.0.4(expo@56.0.4)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/jest-preset@0.85.3(@babel/core@7.29.7)(react@19.2.6))(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.15)(react@19.2.6))(react@19.2.6))(pg@8.20.0)(postgres@3.4.9)
isbot:
specifier: ^5.1.40
version: 5.1.40
@ -601,7 +604,7 @@ importers:
dependencies:
drizzle-orm:
specifier: 'catalog:'
version: 0.45.2(@opentelemetry/api@1.9.1)(@types/pg@8.15.6)(expo-sqlite@56.0.4(expo@55.0.26)(react-native@0.83.4(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.15)(react@19.2.6))(react@19.2.6))(pg@8.20.0)(postgres@3.4.9)
version: 0.45.2(@opentelemetry/api@1.9.1)(@types/pg@8.15.6)(expo-sqlite@56.0.4(expo@56.0.4)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/jest-preset@0.85.3(@babel/core@7.29.7)(react@19.2.6))(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.15)(react@19.2.6))(react@19.2.6))(pg@8.20.0)(postgres@3.4.9)
postgres:
specifier: 'catalog:'
version: 3.4.9
@ -1223,6 +1226,9 @@ packages:
resolution: {integrity: sha512-ctxtJ/eA+t+6q2++vj5j7FYX3nRu311q1wfYH3xjlLOsczhlhxAg2FWNUXhpGvAw3BWo1xBcvOV6/YLc2r5FJw==}
hasBin: true
'@cfworker/json-schema@4.1.1':
resolution: {integrity: sha512-gAmrUZSGtKc3AiBL71iNWxDsyUC5uMaKKGdvzYsBoTW/xi42JQHl7eKV2OYzCUqvc+D2RCcf7EXY2iCyFIk6og==}
'@codemirror/autocomplete@6.20.1':
resolution: {integrity: sha512-1cvg3Vz1dSSToCNlJfRA2WSI4ht3K+WplO0UMOgmUYPivCyy2oueZY6Lx7M9wThm7SDUBViRmuT+OG/i8+ON9A==}
@ -1280,6 +1286,10 @@ packages:
resolution: {integrity: sha512-QxULHAm7cNu72w97JUNCBFODFaXpbDg+dP8b/oWFAZ2MTRppA3U00Y2L1HqaS4J6yBqxwa/Y3nMBaxVKbB/NsA==}
engines: {node: '>=20.19.0'}
'@digitalbazaar/http-client@4.3.0':
resolution: {integrity: sha512-6lMpxpt9BOmqHKGs9Xm6DP4LlZTBFer/ZjHvP3FcW3IaUWYIWC7dw5RFZnvw4fP57kAVcm1dp3IF+Y50qhBvAw==}
engines: {node: '>=18.0'}
'@drizzle-team/brocli@0.10.2':
resolution: {integrity: sha512-z33Il7l5dKjUgGULTqBsQBQwckHh5AbIuxhdsIxDDiZAzBOrZO6q9ogcWC65kU382AfynTfgNumVcNIjuIua6w==}
@ -2113,6 +2123,26 @@ packages:
resolution: {integrity: sha512-4aQzz9vgxcNXFfo/iyNgDDYfsU5XGKKxWxZopw0cVotHiW+U8IJbIxMaxsINs6bHhtkG3StKNPcOrn3eBuxKPw==}
hasBin: true
'@fedify/fedify@2.1.16':
resolution: {integrity: sha512-zBuNLjx7jqg/Dig19IUkUI91UL1JC9M3GWYJOG1cq0RFUkfh/soqiY0UBNhFJGEXyEqI+QIGS976NDZzb9t/ZA==}
engines: {bun: '>=1.1.0', deno: '>=2.0.0', node: '>=22.0.0'}
'@fedify/vocab-runtime@2.1.16':
resolution: {integrity: sha512-1I+x1I+AZmQo956SQXPlGWiMjQNdOGgN0o8bBMCQO4pOgudKCiX9JQjVvfSDyr+3MwAJnC3ay6yuY74gRhhGxw==}
engines: {bun: '>=1.1.0', deno: '>=2.0.0', node: '>=22.0.0'}
'@fedify/vocab-tools@2.1.16':
resolution: {integrity: sha512-MZSJHPOr6hzTtYJUm1r1PBjFBqhLEuNLoJYrC7yj7JiZQwmWYcjbwOYCxeljnQ2SNt52WOqVXV7AFCbofuRNXA==}
engines: {bun: '>=1.1.0', deno: '>=2.0.0', node: '>=22.0.0'}
'@fedify/vocab@2.1.16':
resolution: {integrity: sha512-bVe07C6AFtyMqiqJzB9TO7IR9L6l2gA2ZeOqw5f0/d28jJSahsr0ZnmWi+ckf1/YpPq1Yl6uMx37m/udW8wwUA==}
engines: {bun: '>=1.1.0', deno: '>=2.0.0', node: '>=22.0.0'}
'@fedify/webfinger@2.1.16':
resolution: {integrity: sha512-QVq8sFd0xVg4r2IySeoX7H2HnUwdiGkHyTrOT8G+5qu8/5nqVEP/ufcoM2HSU8zNElCjR+M4yjlOMUiW7hTkWg==}
engines: {bun: '>=1.1.0', deno: '>=2.0.0', node: '>=22.0.0'}
'@fission-ai/openspec@1.3.1':
resolution: {integrity: sha512-QnbJfq/lUNCRY+TTXo87fuIpGCCaOYt280tmbuI112B/1vF0feIneK0/qhoTZNslRDhwwg1YcYDX0suxq2h6tw==}
engines: {node: '>=20.19.0'}
@ -2418,6 +2448,10 @@ packages:
'@jridgewell/trace-mapping@0.3.31':
resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==}
'@js-temporal/polyfill@0.5.1':
resolution: {integrity: sha512-hloP58zRVCRSpgDxmqCWJNlizAlUgJFqG2ypq79DCvyv9tHjRYMDOcPFjzfl/A1/YxDvRCZz8wvZvmapQnKwFQ==}
engines: {node: '>=12'}
'@levischuck/tiny-cbor@0.2.11':
resolution: {integrity: sha512-llBRm4dT4Z89aRsm6u2oEZ8tfwL/2l6BwpZ7JcyieouniDECM5AqNgr/y08zalEIvW3RSK4upYyybDcmjXqAow==}
@ -2430,6 +2464,9 @@ packages:
'@lezer/lr@1.4.8':
resolution: {integrity: sha512-bPWa0Pgx69ylNlMlPvBPryqeLYQjyJjqPx+Aupm5zydLIF3NE+6MMLT8Yi23Bd9cif9VS00aUebn+6fDIGBcDA==}
'@logtape/logtape@2.1.1':
resolution: {integrity: sha512-aULbCqUQGerfOsZ3CMvcKtueKzmdchluXYUd3bIHKmOIS93fx1ko0+hyRQ4flloGZ8EiyRPydZXiy8n1J/eAQA==}
'@mapbox/jsonlint-lines-primitives@2.0.2':
resolution: {integrity: sha512-rY0o9A5ECsTQRVhv7tL/OyDpGAoUB4tTvLiW1DSzQGq4bvTPhNw1VpSNjDJc5GFZ2XuyOtSWSVN05qOtcD71qQ==}
engines: {node: '>= 0.6'}
@ -2493,12 +2530,19 @@ packages:
cpu: [x64]
os: [win32]
'@multiformats/base-x@4.0.1':
resolution: {integrity: sha512-eMk0b9ReBbV23xXU693TAIrLyeO5iTgBZGSJfpqriG8UkYvr/hC9u9pyMlAakDNHWmbhMZCDs6KQO0jzKD8OTw==}
'@napi-rs/wasm-runtime@1.1.4':
resolution: {integrity: sha512-3NQNNgA1YSlJb/kMH1ildASP9HW7/7kYnRI2szWJaofaS1hWmbGI4H+d3+22aGzXXN9IJ+n+GiFVcGipJP18ow==}
peerDependencies:
'@emnapi/core': ^1.7.1
'@emnapi/runtime': ^1.7.1
'@noble/hashes@1.4.0':
resolution: {integrity: sha512-V1JJ1WTRUqHHrOSh597hURcMqVKVGL/ea3kv0gSnEdsEZ0/+VyPghM1lMNGc00z7CIQorSvbKpuJkxvuHbvdbg==}
engines: {node: '>= 16'}
'@nodelib/fs.scandir@2.1.5':
resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==}
engines: {node: '>= 8'}
@ -4466,10 +4510,17 @@ packages:
buffer@6.0.3:
resolution: {integrity: sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==}
byte-encodings@1.0.11:
resolution: {integrity: sha512-+/xR2+ySc2yKGtud3DGkGSH1DNwHfRVK0KTnMhoeH36/KwG+tHQ4d9B3jxJFq7dW27YcfudkywaYJRPA2dmxzg==}
bytes@3.1.2:
resolution: {integrity: sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==}
engines: {node: '>= 0.8'}
bytestreamjs@2.0.1:
resolution: {integrity: sha512-U1Z/ob71V/bXfVABvNr/Kumf5VyeQRBEm6Txb0PQ6S7V5GpBM3w4Cbqz/xPDicR5tN0uvDifng8C+5qECeGwyQ==}
engines: {node: '>=6.0.0'}
cac@6.7.14:
resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==}
engines: {node: '>=8'}
@ -4497,6 +4548,10 @@ packages:
caniuse-lite@1.0.30001793:
resolution: {integrity: sha512-iwSsYWaCOoh26cV8NwNRViHlrfUvYsHDfRVcbtmw0Kg6PJIZZXwMkj1442FYLBGkeUf1juAsU3DTfxW579mrPA==}
canonicalize@2.1.0:
resolution: {integrity: sha512-F705O3xrsUtgt98j7leetNhTWPe+5S72rlL5O4jA1pKqBVQ/dT1O1D6PFxmSXvc0SUOinWS57DKx0I3CHrXJHQ==}
hasBin: true
canvas@3.2.3:
resolution: {integrity: sha512-PzE5nJZPz72YUAfo8oTp0u3fqqY7IzlTubneAihqDYAUcBk7ryeCmBbdJBEdaH0bptSOe2VT2Zwcb3UaFyaSWw==}
engines: {node: ^18.12.0 || >= 20.9.0}
@ -5045,6 +5100,9 @@ packages:
resolution: {integrity: sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==}
engines: {node: '>= 0.4'}
es-toolkit@1.43.0:
resolution: {integrity: sha512-SKCT8AsWvYzBBuUqMk4NPwFlSdqLpJwmy6AP322ERn8W2YLIB6JBXnwMI2Qsh2gfphT3q7EKAxKb23cvFHFwKA==}
esbuild@0.18.20:
resolution: {integrity: sha512-ceqxoedUrcayh7Y7ZX6NdbbDzGROiyVBgC4PriJThBKSVPWnnFHZAkfI1lJT8QFkOwH4qOS2SJkS4wvpGl8BpA==}
engines: {node: '>=12'}
@ -6162,6 +6220,9 @@ packages:
resolution: {integrity: sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==}
hasBin: true
jsbi@4.3.2:
resolution: {integrity: sha512-9fqMSQbhJykSeii05nxKl4m6Eqn2P6rOlYiS+C5Dr/HPIU/7yZxu5qzbs40tgaFORiw2Amd0mirjxatXYMkIew==}
jsc-safe-url@0.2.4:
resolution: {integrity: sha512-0wM3YBWtYePOjfyXQH5MWQ8H7sdk5EXSwZvmSLKk2RboVQ2Bu239jycHDz5J/8Blf3K0Qnoy2b6xD+z10MFB+Q==}
@ -6196,6 +6257,9 @@ packages:
json-buffer@3.0.1:
resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==}
json-canon@1.0.1:
resolution: {integrity: sha512-PQcj4PFOTAQxE8PgoQ4KrM0DcKWZd7S3ELOON8rmysl9I8JuFMgxu1H9v+oZsTPjjkpeS3IHPwLjr7d+gKygnw==}
json-parse-even-better-errors@2.3.1:
resolution: {integrity: sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==}
@ -6213,6 +6277,10 @@ packages:
engines: {node: '>=6'}
hasBin: true
jsonld@9.0.0:
resolution: {integrity: sha512-pjMIdkXfC1T2wrX9B9i2uXhGdyCmgec3qgMht+TDj+S0qX3bjWMQUfL7NeqEhuRTi8G5ESzmL9uGlST7nzSEWg==}
engines: {node: '>=18'}
keyv@4.5.4:
resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==}
@ -6220,6 +6288,10 @@ packages:
resolution: {integrity: sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==}
engines: {node: '>=6'}
ky@1.14.3:
resolution: {integrity: sha512-9zy9lkjac+TR1c2tG+mkNSVlyOpInnWdSMiue4F+kq8TwJSgv6o8jhLRg8Ho6SnZ9wOYUq/yozts9qQCfk7bIw==}
engines: {node: '>=18'}
lan-network@0.2.1:
resolution: {integrity: sha512-ONPnazC96VKDntab9j9JKwIWhZ4ZUceB4A9Epu4Ssg0hYFmtHZSeQ+n15nIwTFmcBUKtExOer8WTJ4GF9MO64A==}
hasBin: true
@ -6373,6 +6445,10 @@ packages:
lru-cache@5.1.1:
resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==}
lru-cache@6.0.0:
resolution: {integrity: sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==}
engines: {node: '>=10'}
luxon@3.7.2:
resolution: {integrity: sha512-vtEhXh/gNjI9Yg1u4jX/0YVPMvxzHuGgCm6tC5kZyb08yjGWGnqAjGJvcXbqQR2P3MyMEFnRbpcdFS6PBcLqew==}
engines: {node: '>=12'}
@ -6935,6 +7011,10 @@ packages:
pkg-types@2.3.1:
resolution: {integrity: sha512-y+ichcgc2LrADuhLNAx8DFjVfgz91pRxfZdI3UDhxHvcVEZsenLO+7XaU5vOp0u/7V/wZ+plyuQxtrDlZJ+yeg==}
pkijs@3.4.0:
resolution: {integrity: sha512-emEcLuomt2j03vxD54giVB4SxTjnsqkU692xZOZXHDVoYyypEm+b3jpiTcc+Cf+myooc+/Ly0z01jqeNHVgJGw==}
engines: {node: '>=16.0.0'}
playwright-core@1.60.0:
resolution: {integrity: sha512-9bW6zvX/m0lEbgTKJ6YppOKx8H3VOPBMOCFh2irXFOT4BbHgrx5hPjwJYLT40Lu+4qtD36qKc/Hn56StUW57IA==}
engines: {node: '>=18'}
@ -7113,6 +7193,10 @@ packages:
resolution: {integrity: sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==}
hasBin: true
rdf-canonize@5.0.0:
resolution: {integrity: sha512-g8OUrgMXAR9ys/ZuJVfBr05sPPoMA7nHIVs8VEvg9QwM5W4GR2qSFEEHjsyHF1eWlBaf8Ev40WNjQFQ+nJTO3w==}
engines: {node: '>=18'}
react-devtools-core@6.1.5:
resolution: {integrity: sha512-ePrwPfxAnB+7hgnEr8vpKxL9cmnp7F322t8oqcPshbIQQhDKgFDW4tjhF2wjVbdXF9O/nyuy3sQWd9JGpiLPvA==}
@ -7475,6 +7559,9 @@ packages:
set-cookie-parser@2.7.2:
resolution: {integrity: sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw==}
setimmediate@1.0.5:
resolution: {integrity: sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==}
setprototypeof@1.2.0:
resolution: {integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==}
@ -7698,6 +7785,9 @@ packages:
resolution: {integrity: sha512-1tB5mhVo7U+ETBKNf92xT4hrQa3pm0MZ0PQvuDnWgAAGHDsfp4lPSpiS6psrSiet87wyGPh9ft6wmhOMQ0hDiw==}
engines: {node: '>=14.16'}
structured-field-values@2.0.4:
resolution: {integrity: sha512-5zpJXYLPwW3WYUD/D58tQjIBs10l3Yx64jZfcKGs/RH79E2t9Xm/b9+ydwdMNVSksnsIY+HR/2IlQmgo0AcTAg==}
structured-headers@0.4.1:
resolution: {integrity: sha512-0MP/Cxx5SzeeZ10p/bZI0S6MpgD+yxAhi1BOQ34jgnMXsCq3j1t6tQnZu+KdlL7dvJTLT3g9xN8tl10TqgFMcg==}
@ -7957,9 +8047,19 @@ packages:
uri-js@4.4.1:
resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==}
uri-template-router@1.0.0:
resolution: {integrity: sha512-WKcL9ZSIEhHE3f5P4Z47Tf0nWbcgV1ISb/OBuF8YKEYi0SQOyTLCzM6B/gAKFWZhRhqA+C/Ks8UXe2qU5W0FVg==}
url-parse@1.5.10:
resolution: {integrity: sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==}
url-template@3.1.1:
resolution: {integrity: sha512-4oszoaEKE/mQOtAmdMWqIRHmkxWkUZMnXFnjQ5i01CuRSK3uluxcH1MRVVVWmhlnzT1SCDfKxxficm2G37qzCA==}
engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
urlpattern-polyfill@10.1.0:
resolution: {integrity: sha512-IGjKp/o0NL3Bso1PymYURCJxMPNAf/ILOpendP9f5B6e1rTJgdgiOvgfoT8VxCAdY+Wisb9uhGaJJf3yZ2V9nw==}
use-callback-ref@1.3.3:
resolution: {integrity: sha512-jQL3lRnocaFtu3V00JToYz/4QkNWswxijDaCVNZRiRTO3HQDLsdu1ZtmIUvV4yPp+rvWm5j0y0TG/S61cuijTg==}
engines: {node: '>=10'}
@ -8343,6 +8443,9 @@ packages:
yallist@3.1.1:
resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==}
yallist@4.0.0:
resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==}
yaml@2.9.0:
resolution: {integrity: sha512-2AvhNX3mb8zd6Zy7INTtSpl1F15HW6Wnqj0srWlkKLcpYl/gMIMJiyuGq2KeI2YFxUPjdlB+3Lc10seMLtL4cA==}
engines: {node: '>= 14.6'}
@ -9005,6 +9108,8 @@ snapshots:
dependencies:
css-tree: 3.2.1
'@cfworker/json-schema@4.1.1': {}
'@codemirror/autocomplete@6.20.1':
dependencies:
'@codemirror/language': 6.12.3
@ -9075,6 +9180,11 @@ snapshots:
'@csstools/css-tokenizer@4.0.0': {}
'@digitalbazaar/http-client@4.3.0':
dependencies:
ky: 1.14.3
undici: 6.25.0
'@drizzle-team/brocli@0.10.2': {}
'@egjs/hammerjs@2.0.17':
@ -10064,7 +10174,7 @@ snapshots:
- '@types/react-dom'
optional: true
'@expo/vector-icons@15.1.1(expo-font@55.0.8(expo@55.0.26)(react-native@0.83.4(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.15)(react@19.2.6))(react@19.2.6))(react-native@0.83.4(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.15)(react@19.2.6))(react@19.2.6)':
'@expo/vector-icons@15.1.1(expo-font@55.0.8)(react-native@0.83.4(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.15)(react@19.2.6))(react@19.2.6)':
dependencies:
expo-font: 55.0.8(expo@55.0.26)(react-native@0.83.4(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.15)(react@19.2.6))(react@19.2.6)
react: 19.2.6
@ -10078,6 +10188,65 @@ snapshots:
chalk: 4.1.2
js-yaml: 4.1.1
'@fedify/fedify@2.1.16':
dependencies:
'@fedify/vocab': 2.1.16
'@fedify/vocab-runtime': 2.1.16
'@fedify/webfinger': 2.1.16
'@js-temporal/polyfill': 0.5.1
'@logtape/logtape': 2.1.1
'@opentelemetry/api': 1.9.1
'@opentelemetry/core': 2.6.1(@opentelemetry/api@1.9.1)
'@opentelemetry/sdk-trace-base': 2.6.1(@opentelemetry/api@1.9.1)
'@opentelemetry/semantic-conventions': 1.40.0
byte-encodings: 1.0.11
es-toolkit: 1.43.0
json-canon: 1.0.1
jsonld: 9.0.0
structured-field-values: 2.0.4
uri-template-router: 1.0.0
url-template: 3.1.1
urlpattern-polyfill: 10.1.0
'@fedify/vocab-runtime@2.1.16':
dependencies:
'@js-temporal/polyfill': 0.5.1
'@logtape/logtape': 2.1.1
'@multiformats/base-x': 4.0.1
'@opentelemetry/api': 1.9.1
asn1js: 3.0.10
byte-encodings: 1.0.11
jsonld: 9.0.0
pkijs: 3.4.0
'@fedify/vocab-tools@2.1.16':
dependencies:
'@cfworker/json-schema': 4.1.1
byte-encodings: 1.0.11
es-toolkit: 1.43.0
yaml: 2.9.0
'@fedify/vocab@2.1.16':
dependencies:
'@fedify/vocab-runtime': 2.1.16
'@fedify/vocab-tools': 2.1.16
'@fedify/webfinger': 2.1.16
'@js-temporal/polyfill': 0.5.1
'@logtape/logtape': 2.1.1
'@multiformats/base-x': 4.0.1
'@opentelemetry/api': 1.9.1
asn1js: 3.0.10
es-toolkit: 1.43.0
jsonld: 9.0.0
pkijs: 3.4.0
'@fedify/webfinger@2.1.16':
dependencies:
'@fedify/vocab-runtime': 2.1.16
'@logtape/logtape': 2.1.1
'@opentelemetry/api': 1.9.1
es-toolkit: 1.43.0
'@fission-ai/openspec@1.3.1(@types/node@25.9.1)':
dependencies:
'@inquirer/core': 10.3.2(@types/node@25.9.1)
@ -10475,6 +10644,10 @@ snapshots:
'@jridgewell/resolve-uri': 3.1.2
'@jridgewell/sourcemap-codec': 1.5.5
'@js-temporal/polyfill@0.5.1':
dependencies:
jsbi: 4.3.2
'@levischuck/tiny-cbor@0.2.11': {}
'@lezer/common@1.5.2': {}
@ -10487,6 +10660,8 @@ snapshots:
dependencies:
'@lezer/common': 1.5.2
'@logtape/logtape@2.1.1': {}
'@mapbox/jsonlint-lines-primitives@2.0.2': {}
'@mapbox/unitbezier@0.0.1': {}
@ -10536,6 +10711,8 @@ snapshots:
'@msgpackr-extract/msgpackr-extract-win32-x64@3.0.3':
optional: true
'@multiformats/base-x@4.0.1': {}
'@napi-rs/wasm-runtime@1.1.4(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)':
dependencies:
'@emnapi/core': 1.10.0
@ -10543,6 +10720,8 @@ snapshots:
'@tybys/wasm-util': 0.10.2
optional: true
'@noble/hashes@1.4.0': {}
'@nodelib/fs.scandir@2.1.5':
dependencies:
'@nodelib/fs.stat': 2.0.5
@ -12811,8 +12990,12 @@ snapshots:
base64-js: 1.5.1
ieee754: 1.2.1
byte-encodings@1.0.11: {}
bytes@3.1.2: {}
bytestreamjs@2.0.1: {}
cac@6.7.14: {}
call-bind-apply-helpers@1.0.2:
@ -12833,6 +13016,8 @@ snapshots:
caniuse-lite@1.0.30001793: {}
canonicalize@2.1.0: {}
canvas@3.2.3:
dependencies:
node-addon-api: 7.1.1
@ -13197,6 +13382,14 @@ snapshots:
pg: 8.20.0
postgres: 3.4.9
drizzle-orm@0.45.2(@opentelemetry/api@1.9.1)(@types/pg@8.15.6)(expo-sqlite@56.0.4(expo@56.0.4)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/jest-preset@0.85.3(@babel/core@7.29.7)(react@19.2.6))(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.15)(react@19.2.6))(react@19.2.6))(pg@8.20.0)(postgres@3.4.9):
optionalDependencies:
'@opentelemetry/api': 1.9.1
'@types/pg': 8.15.6
expo-sqlite: 56.0.4(expo@56.0.4)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/jest-preset@0.85.3(@babel/core@7.29.7)(react@19.2.6))(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.15)(react@19.2.6))(react@19.2.6)
pg: 8.20.0
postgres: 3.4.9
drizzle-postgis@1.1.1:
dependencies:
wkx: 0.5.0
@ -13265,6 +13458,8 @@ snapshots:
has-tostringtag: 1.0.2
hasown: 2.0.4
es-toolkit@1.43.0: {}
esbuild@0.18.20:
optionalDependencies:
'@esbuild/android-arm': 0.18.20
@ -13932,7 +14127,7 @@ snapshots:
'@expo/log-box': 55.0.12(@expo/dom-webview@55.0.5)(expo@55.0.26)(react-native@0.83.4(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.15)(react@19.2.6))(react@19.2.6)
'@expo/metro': 55.1.1
'@expo/metro-config': 55.0.23(expo@55.0.26)(typescript@6.0.3)
'@expo/vector-icons': 15.1.1(expo-font@55.0.8(expo@55.0.26)(react-native@0.83.4(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.15)(react@19.2.6))(react@19.2.6))(react-native@0.83.4(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.15)(react@19.2.6))(react@19.2.6)
'@expo/vector-icons': 15.1.1(expo-font@55.0.8)(react-native@0.83.4(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.15)(react@19.2.6))(react@19.2.6)
'@ungap/structured-clone': 1.3.1
babel-preset-expo: 55.0.22(@babel/core@7.29.7)(@babel/runtime@7.29.7)(expo@55.0.26)(react-refresh@0.14.2)
expo-asset: 55.0.17(expo@55.0.26)(react-native@0.83.4(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.15)(react@19.2.6))(react@19.2.6)(typescript@6.0.3)
@ -14885,6 +15080,8 @@ snapshots:
dependencies:
argparse: 2.0.1
jsbi@4.3.2: {}
jsc-safe-url@0.2.4: {}
jsdom@20.0.3(canvas@3.2.3):
@ -14956,6 +15153,8 @@ snapshots:
json-buffer@3.0.1: {}
json-canon@1.0.1: {}
json-parse-even-better-errors@2.3.1: {}
json-schema-traverse@0.4.1: {}
@ -14966,12 +15165,21 @@ snapshots:
json5@2.2.3: {}
jsonld@9.0.0:
dependencies:
'@digitalbazaar/http-client': 4.3.0
canonicalize: 2.1.0
lru-cache: 6.0.0
rdf-canonize: 5.0.0
keyv@4.5.4:
dependencies:
json-buffer: 3.0.1
kleur@3.0.3: {}
ky@1.14.3: {}
lan-network@0.2.1: {}
leaflet.markercluster@1.5.3(leaflet@1.9.4):
@ -15094,6 +15302,10 @@ snapshots:
dependencies:
yallist: 3.1.1
lru-cache@6.0.0:
dependencies:
yallist: 4.0.0
luxon@3.7.2: {}
lz-string@1.5.0: {}
@ -15862,6 +16074,15 @@ snapshots:
exsolve: 1.0.8
pathe: 2.0.3
pkijs@3.4.0:
dependencies:
'@noble/hashes': 1.4.0
asn1js: 3.0.10
bytestreamjs: 2.0.1
pvtsutils: 1.3.6
pvutils: 1.1.5
tslib: 2.8.1
playwright-core@1.60.0: {}
playwright@1.60.0:
@ -16041,6 +16262,10 @@ snapshots:
strip-json-comments: 2.0.1
optional: true
rdf-canonize@5.0.0:
dependencies:
setimmediate: 1.0.5
react-devtools-core@6.1.5:
dependencies:
shell-quote: 1.8.3
@ -16574,6 +16799,8 @@ snapshots:
set-cookie-parser@2.7.2: {}
setimmediate@1.0.5: {}
setprototypeof@1.2.0: {}
sf-symbols-typescript@2.2.0: {}
@ -16781,6 +17008,8 @@ snapshots:
strip-json-comments@5.0.3: {}
structured-field-values@2.0.4: {}
structured-headers@0.4.1: {}
style-mod@4.1.3: {}
@ -17044,11 +17273,17 @@ snapshots:
dependencies:
punycode: 2.3.1
uri-template-router@1.0.0: {}
url-parse@1.5.10:
dependencies:
querystringify: 2.2.0
requires-port: 1.0.0
url-template@3.1.1: {}
urlpattern-polyfill@10.1.0: {}
use-callback-ref@1.3.3(@types/react@19.2.15)(react@19.2.6):
dependencies:
react: 19.2.6
@ -17332,6 +17567,8 @@ snapshots:
yallist@3.1.1: {}
yallist@4.0.0: {}
yaml@2.9.0: {}
yargs-parser@21.1.1: {}