diff --git a/.changeset/tired-lemons-authorize.md b/.changeset/tired-lemons-authorize.md new file mode 100644 index 0000000..bfbc3e7 --- /dev/null +++ b/.changeset/tired-lemons-authorize.md @@ -0,0 +1,17 @@ +--- +"@fujocoded/authproto": minor +--- + +Add scoped login controls, an Authorize component for requesting extra +permissions after login, and helpers for building ATProto permission scopes. + +## Breaking changes + +- Login and logout now default to returning users to the referring page instead + of `/`. Set `redirects.afterLogin` and `redirects.afterLogout` to `/` to keep + the old behavior. +- `` no longer defaults the input placeholder to `handle.bsky.social`; + pass `placeholder="handle.bsky.social"` if you want that exact prompt. +- `Astro.locals.loggedInUser` now includes a required `scopes` array. Update + tests, mocks, and custom local assignments to include `scopes: []` when no + grants are needed. diff --git a/.github/workflows/scripts/first-release-publish.mjs b/.github/workflows/scripts/first-release-publish.mjs index cc722bd..0204e29 100644 --- a/.github/workflows/scripts/first-release-publish.mjs +++ b/.github/workflows/scripts/first-release-publish.mjs @@ -5,7 +5,9 @@ import { spawnSync } from "node:child_process"; const npmToken = process.env.NODE_AUTH_TOKEN ?? process.env.NPM_TOKEN; if (!npmToken) { - console.error("NODE_AUTH_TOKEN or NPM_TOKEN is required for first package releases."); + console.error( + "NODE_AUTH_TOKEN or NPM_TOKEN is required for first package releases.", + ); process.exit(1); } @@ -109,12 +111,16 @@ for (const pkg of missingPackages) { process.exit(whoami.status ?? 1); } - const result = spawnSync("npm", ["publish", "--provenance", "--access", "public"], { - cwd: pkg.dir, - encoding: "utf8", - env: { ...process.env, NODE_AUTH_TOKEN: npmToken, NPM_TOKEN: npmToken }, - stdio: ["inherit", "pipe", "pipe"], - }); + const result = spawnSync( + "npm", + ["publish", "--provenance", "--access", "public"], + { + cwd: pkg.dir, + encoding: "utf8", + env: { ...process.env, NODE_AUTH_TOKEN: npmToken, NPM_TOKEN: npmToken }, + stdio: ["inherit", "pipe", "pipe"], + }, + ); process.stdout.write(result.stdout); process.stderr.write(result.stderr); @@ -133,7 +139,9 @@ for (const pkg of missingPackages) { process.stderr.write(tag.stderr); if (tag.status !== 0) { - console.error(`${pkg.name} was published, but git tag ${tagName} could not be created.`); + console.error( + `${pkg.name} was published, but git tag ${tagName} could not be created.`, + ); process.exit(tag.status ?? 1); } diff --git a/astro-authproto/README.md b/astro-authproto/README.md index 8b11d1b..15343ef 100644 --- a/astro-authproto/README.md +++ b/astro-authproto/README.md @@ -30,6 +30,7 @@ In this package, you'll find: - Gives you easy access the DID and handle of a logged in user, if any - `@fujocoded/authproto/components`, which includes: - A basic login/logout component to help you get started quickly + - An authorization component for asking logged-in users for more permissions - `@fujocoded/authproto/helpers`, which lets you access: - `getLoggedInAgent` returns an authenticated agent for the logged in user, routing requests through their PDS. @@ -112,7 +113,7 @@ npm add @fujocoded/authproto 2. Add the integration to your `astro.config.mjs` file, like this: -````js +```js import { defineConfig } from "astro/config"; import node from "@astrojs/node"; import authproto from "@fujocoded/authproto"; @@ -134,8 +135,9 @@ export default defineConfig({ }), ], }); +``` -This confinguration is enough to develop your authenticated +This configuration is enough to develop your authenticated website on your machine. Before putting your site online, see [Shipping it](#shipping-it-going-to-production-that-is) for things to pay attention to. @@ -157,13 +159,13 @@ import { Login } from "@fujocoded/authproto/components"; --- -```` +``` It'll look like a plain form: -See [Customizing the login form](#customizing-the-login-form) for ways to change how it looks and where it sends people after they log in. +See [Customizing login and authorization forms](#customizing-login-and-authorization-forms) for ways to change how it looks and where it sends people after they log in. ## Make a page only visible to logged in users @@ -199,6 +201,9 @@ if (!loggedInUser) { # Okay how do I _actually_ do stuff with this? Check out the example sites included under the [examples folder](./__examples__/). +When `` is _Not Enough™_ (a custom checkbox list, a reauthorization +screen, full control over the markup), start from +[`06-custom-scopes`](./__examples__/06-custom-scopes/). # Configuring authproto @@ -211,43 +216,180 @@ These settings go inside the `authproto({ ... })` call in your available in production, including the scheme. For example: `"https://example.com"`. - `defaultDevUser`, optional. A handle that gets pre-filled into the - [login form](#customizing-the-login-form) while you're developing your site - locally (never in production). Saves you from re-typing your handle every - time you restart the dev server. + [login form](#customizing-login-and-authorization-forms) while you're + developing your site locally (never in production). Saves you from re-typing + your handle every time you restart the dev server. - `driver`, optional. The driver used to store data about OAuth sessions. This takes Astro's [session driver options](https://docs.astro.build/en/reference/configuration-reference/#sessiondriver). You can also set this with `name: "astro:db"` to utilize [Astro's DB integration](https://docs.astro.build/en/guides/integrations-guide/db/) for OAuth sessions. This will set up tables for sessions in your database. - - NOTE: The default driver is `memory`. This is fine for development, but it's - recommended that you switch to a more reliable solution for production. -- `scopes`, optional. By default, only the `"atproto"` scope is added. This - scope is included with any other scope that's enabled. See [ATproto's +- `scopes`, optional. The most any login may ever request. Defaults to just + `"atproto"`. See [Choosing which scopes to + request](#choosing-which-scopes-to-request). +- `defaultScopes`, optional. What a login asks for when nothing overrides it. + Defaults to all of your configured `scopes`. See [Choosing which scopes to + request](#choosing-which-scopes-to-request). +- `resolveScopesEntrypoint`, optional. Path to a module that adjusts the final + scope list per account. See [Per-account scopes with + `resolveScopesEntrypoint`](#per-account-scopes-with-resolvescopesentrypoint). +- `redirects`, optional. Where to send users after login and logout, as + `{ afterLogin, afterLogout }`. Since 0.4.0, both default to sending users back to + the page they came from. The URLs support template variables like `{referer}` + and `{loggedInUser.did}`. You can override these per form using a `redirect` + prop on `` / ``. See + [`05-redirects`](./__examples__/05-redirects/) for a full example. +- `externalDomain` (advanced), optional. The public URL Authproto advertises + to a user's PDS in its OAuth client metadata. Defaults to `applicationDomain` + in production (and a loopback address during development). You only need to + set it when the URL a PDS must reach differs from `applicationDomain`, usually + when using a dev tunnel, a reverse proxy, or a separate auth subdomain. Can + also be set with the `AUTHPROTO_EXTERNAL_DOMAIN` environment variable, which + takes precedence. + +> [!NOTE] +> +> The default `driver` is `memory`. That's fine for development, but switch to a +> more reliable store for production. See [Storing authentication +> data](#storing-authentication-data). + +## Choosing which scopes to request + +Scopes control the permissions your app asks a user's PDS to grant. They're +controlled by two options, both optional: + +- `scopes`, optional. The most any login may ever request. No form, built-in or + custom, can ask for a scope outside this set. Defaults to just `"atproto"`, + which is always included alongside any other scope. See [ATproto's documentation for OAuth scopes](https://atproto.com/specs/oauth#authorization-scopes). - - `email`: boolean, optional. Only used to identify you by email. Does nothing - to a PDS. - - `genericData`: boolean, optional. Allows you to read/write data to a user's - PDS, but does not access BlueSky direct messages. - - `directMessages`: boolean, optional. Allows you to access BlueSky direct - messages for a user's account. Requires `genericData` to be enabled. - - `additionalScopes`: array, optional. This is used in case you need to expand - permissions to include other services. This should be an array of strings, - like this: `["scope1", "scope2"]` - -# Customizing the login form +- `defaultScopes`, optional. What a login asks for when nothing overrides it: no + `scopes` or `extendDefaultScopes` on ``, no `scope` fields on a + custom form. Defaults to all of your configured `scopes`, so set it to a + narrower list when default forms should ask for less than the maximum. + +The `scopes` option takes these flags: + +- `email`: boolean, optional. Only used to identify users by email. +- `genericData`: boolean, optional. Allows you to read/write ANY data to a user's + PDS, but does not access BlueSky direct messages. Prefer granular permissions + whenever possible: it protects users (and you) from any accidental oopsie. +- `directMessages`: boolean, optional. Allows you to access BlueSky direct + messages for a user's account. Requires `genericData` to be enabled. +- `additionalScopes`: array, optional. This is used in case you need to expand + permissions to include other services. This should be an array of strings, + like this: `["scope1", "scope2"]` + +### Granular permissions with the scope helpers + +For finer-grained ATproto permissions, use Authproto's scope helpers instead of +hand-writing the strings: + +```ts +import authproto, { + blob, + permissionScopes, + repo, + rpc, +} from "@fujocoded/authproto"; + +export default defineConfig({ + integrations: [ + authproto({ + applicationName: "My Super Cool ATfujo App", + applicationDomain: "https://fujocoded.test", + scopes: { + additionalScopes: permissionScopes([ + // Create, update, and delete the user's post and like records + repo(["test.fujocoded.post", "test.fujocoded.like"]), + // Only delete test.fujocoded.like records + repo("test.fujocoded.like", { action: "delete" }), + // Call the getFeed method on your appview service + rpc("test.fujocoded.getFeed", { + aud: "did:web:api.fujocoded.test#fun_appview", + }), + // Upload image and video files to the user's PDS + blob(["image/*", "video/*"]), + ]), + }, + }), + ], +}); +``` + +### Per-account scopes with `resolveScopesEntrypoint` + +`resolveScopesEntrypoint` (optional) lets you change the final scope list based +on your own custom logic, for example based on which account is requesting log +in. Point it at a `.js/ts` file in your project that exports a +`resolveScopes(input)` function, and Authproto will call it before your final +auth request is sent to the PDS. + +`input` is a read-only object with the account's id and three scope lists: + +- `input.atprotoId`: the account logging in, as typed into the form. Exactly + one of `input.atprotoId.handle` / `input.atprotoId.did` is set (whichever the + user entered); the other is `undefined`, so you can tell which you got. + Because the same account can log in as either, call + `await input.atprotoId.resolve()` to get its canonical `{ did, handle }` when + you need to gate reliably. Just remember: this requires a network roundtrip! +- `input.proposedScopes`: the scopes about to be requested, already narrowed to + ones you configured. `"atproto"` is always present. +- `input.allowedScopes`: every scope your `scopes` config allows. +- `input.defaultScopes`: your configured `defaultScopes`. + +Your function should then return one of: + +- A scope array, to replace what gets requested. +- Nothing (or `null`), to accept `input.proposedScopes` unchanged. + +Either way, Authproto will only keep scopes you configured and always keeps +`"atproto"`, so your function can never grant access beyond your `scopes` +config. + +```ts +import type { ResolveScopesHook } from "@fujocoded/authproto"; + +export const resolveScopes: ResolveScopesHook = async ({ + atprotoId, + proposedScopes, +}) => { + // Resolve to a canonical DID if needed so the check holds whether + // the user typed their handle or their DID. + const { did } = atprotoId.did ? atprotoId : await atprotoId.resolve(); + if (did === "did:plc:b0b474n") { + return [...proposedScopes, "transition:email"]; + } +}; +``` + +# Customizing login and authorization forms + +## The `` form You can change how `` looks and behaves by passing it these options: - `redirect`, optional. Where to send the user after they successfully log in or log out. - `placeholder`, optional. The hint text shown inside the input when it's - empty. Defaults to `"handle.bsky.social"`. + empty. +- `scopes`, optional. Exact scopes to request from this form. Authproto keeps + only scopes from your integration's `scopes` config and always keeps + `"atproto"`. Defaults to your configured `defaultScopes` when omitted. +- `extendDefaultScopes`, optional. Extra scopes to request on top of the + configured `defaultScopes`, NOT on top of the current session's grant. + Authproto keeps only configured scopes here too. To extend the current + session's grant instead, see [How `scopes` and `extendDefaultScopes` + work](#how-scopes-and-extenddefaultscopes-work) for a full example. - Any standard HTML `
` attribute: `class`, `class:list`, `id`, `aria-*`, `data-*`, `style`, and so on. These get applied directly to the - form, so you can style it, label it for screen readers, or use any field other libraries may require. - - NOTE: `action` and `method` are set by the component — they're what makes - login and logout work, so they can't be changed. + form, so you can style it, label it for screen readers, or use any field + other libraries may require. + +> [!NOTE] +> +> `action` and `method` are set by the component. They're what makes login and +> logout work, so they can't be changed. > [!TIP] > @@ -263,6 +405,105 @@ You can change how `` looks and behaves by passing it these options: /> ``` +## Asking for more permissions with `` + +Use `` when a user may already be logged in and you want to ask for +more permissions. + +- When the user is logged out `` behaves the same as ``. +- When the user is authenticated, `` will wrap its slot in a button + and start a new authorization flow when clicked. +- When the user is authenticated and already has every requested scope, + `` renders its `authorized` slot instead. + +```astro +--- +import { Authorize, Login } from "@fujocoded/authproto/components"; +import { blob, repo } from "@fujocoded/authproto"; +--- + + + + + Click to enable posting + Posting is already enabled + +``` + +`` supports the same `redirect`, `placeholder`, `scopes`, +`extendDefaultScopes`, and form attribute props as ``. + +> [!TIP] +> +> After any OAuth returns (including from ``), Authproto stores the +> scopes from that latest flow as the local session grant; it won't merge +> them with the ones previously granted. + +## How `scopes` and `extendDefaultScopes` work + +`` and `` take the same scope props. What a form +requests depends on which you set: + +- No `scopes` or `extendDefaultScopes`: request `defaultScopes`. +- `scopes`: request this exact set, with `"atproto"` added automatically. + Can be used with `Astro.locals.loggedInUser?.scopes` to extend current + scopes. +- `extendDefaultScopes`: request `defaultScopes` plus these extras. Extends your + configured `defaultScopes`, not the user's current scopes. + +```astro +--- +import { repo } from "@fujocoded/authproto"; + +const currentScopes = Astro.locals.loggedInUser?.scopes ?? []; +const currentScopesPlusPost = [ + ...new Set([ + ...currentScopes, + repo("test.fujocoded.post", { action: ["create", "update"] }), + ]), +]; +--- + + + Click here to enable posting + Posting is enabled + +``` + +## Building your own form + +When `` or `` are _Not Enough™_, you can roll your own +HTML form that—just like them!—posts to `/oauth/login`. + +The `/oauth/login` route supports these form inputs: + +- `atproto-id`, required. The handle or DID to log in. +- `redirect`, optional. The URL or path to send the user to after login. +- `scope`, optional and repeatable. Unsupported scopes are ignored: each value + is kept only when it is in your configured Authproto `scopes`. If the form + does not send any `scope` fields, Authproto uses `defaultScopes`. If the form + sends scope fields, you'll need to send every scope you want to keep in the + resulting local grant. + +To build the form's choices from the scopes you configured, you can import them +from `fujocoded:authproto/config` module instead of typing scope strings into +the page by hand. Your form will automatically update whenever you change +`authproto({ scopes })`. + +The most useful exports for custom forms are: + +- `defaultDevUser` +- `scopes` +- `defaultScopes` + +See [`06-custom-scopes`](./__examples__/06-custom-scopes/) for a full example. + # Shipping it (going to production, that is) Before putting your site online, there are a few things to make sure of: @@ -282,12 +523,11 @@ time you ship. ## Storing authentication data -By default, Astro's `session.driver` and AuthProto's `driver` store their data -in memory—that is, on your computer or server's RAM. This means that restarting -or redeploying your site wipes it and logs everyone out. +By default, Astro's `session.driver` and Authproto's `driver` store their data +in memory, which is wiped whenever your site is restarted, logging everyone out. -This is ok for development...and for production, if you don't mind your users -being forced to log back in every time you ship a new versions. +This is ok for development...and for production if you don't mind your users +being forced to log back in every time you ship a new version. ### With Astro DB @@ -354,7 +594,7 @@ you commit anything. ## If something may have gone wrong -If you believe something that shouldn't bee has been expose, or you just want to +If you believe something that shouldn't be exposed has been exposed, or you just want to invalidate every active session, you should clear out Authproto's store: - **Astro DB:** drop all rows in the table Authproto created. diff --git a/astro-authproto/__examples__/06-custom-scopes/README.md b/astro-authproto/__examples__/06-custom-scopes/README.md new file mode 100644 index 0000000..a771bf4 --- /dev/null +++ b/astro-authproto/__examples__/06-custom-scopes/README.md @@ -0,0 +1,149 @@ +# Custom login forms with `@fujocoded/authproto` + +`` covers the common path. This example covers what to do when that +path is _Not Enough™_: pick exactly the scopes you want, render your own +checkbox list, or send a logged-in user back through login to change their +grant. + +## Tweaking scopes without leaving `` + +For small per-form changes, keep using ``: + +- Pass nothing to request your configured `defaultScopes` +- Pass `scopes` to request exactly those (`atproto` is always added) +- Pass `extendDefaultScopes` to add extras on top of your defaults, not on top + of the user's current grant + +`` uses the same scope prop meanings as ``; it just sends +an already logged-in user's DID for you and skips rendering when the current +session already has the requested grant. + +```astro +--- +import { account } from "@fujocoded/authproto"; +--- + + + + + + +``` + +## Not Enough™? Use the advanced custom form path + +For full control over the markup, build an HTML form that posts directly to +`/oauth/login`. This is the supported path for custom Authproto forms. +Don't worry: it is still a normal HTML form. + +The route supports these fields: + +- `atproto-id`, required. The handle or DID to log in. +- `redirect`, optional. The URL or path to send the user to after login. +- `scope`, optional and repeatable. Each value is kept only when it is in your + configured Authproto `scopes`. Unsupported scopes are ignored. If the form + does not send any `scope` fields, Authproto uses `defaultScopes`. If the form + sends scope fields, send every scope you want to keep in the resulting local + grant. + +Each checkbox sends one `scope` value, which has to match a scope from your +config exactly: + +```html + + + + + + +``` + +Rather than copy those strings by hand, the example page imports +`defaultDevUser` and `scopes` from the `fujocoded:authproto/config` virtual +module. The checkbox list then always matches the scopes you configured. + +## Granting more (or fewer) scopes after first login + +A user is already logged in but wants different permissions? Post any custom +form to `/oauth/login` with their DID and the new scope checkboxes. Authproto +will authenticate the user again and replace the stored scopes when they come +back. That means the form should send the complete grant you want after the +flow, not only the scopes you want to add. + +This example pre-checks boxes with `Astro.locals.loggedInUser.scopes` for that +reason: keeping a checked box means "keep this scope in the next grant." + +## Checking what scopes a user actually has + +To know what scopes a user has, read `Astro.locals.loggedInUser.scopes`. This +is the list the PDS actually granted. + +Be careful: it may be narrower than what you asked for! + +## Configuration + +Custom forms are still limited to the maximum scopes you declared in your +config. Authproto keeps custom `scope` fields only when they are in configured +Authproto scopes. + +This example builds those scopes with the granular permission helpers Authproto +exports. Each one returns a single scope string, so you ask for exactly the +access you need instead of a broad bucket: + +- `account("email")` reads the user's email address and confirmation status +- `repo("com.fujocoded.guestbook", { action: [...] })` creates, updates, or + deletes records in one collection +- `rpc("chat.bsky.convo.sendMessage", { aud: "..." })` calls one method on one + service + +The other helpers are `blob`, `identity`, and `include`. `permissionScopes` +joins a list together and drops any `false` entries, which is handy when some +scopes are conditional. + +This example lists every scope any form might ask for under `scopes`. Then it +uses `defaultScopes` to keep the built-in `` narrower: + +```js +import authProto, { account, repo, rpc } from "@fujocoded/authproto"; + +authProto({ + // Every scope any form (built-in or custom) is allowed to request + scopes: [ + account("email"), + repo("com.fujocoded.guestbook", { action: ["create", "update", "delete"] }), + rpc("chat.bsky.convo.sendMessage", { + aud: "did:web:api.bsky.chat#bsky_chat", + }), + ], + // Scopes requested when no form overrides them (`atproto` is always added) + defaultScopes: [ + repo("com.fujocoded.guestbook", { action: ["create", "update", "delete"] }), + ], +}); +``` + +If the final request should depend on the account, set `resolveScopesEntrypoint` +to a server module. Authproto calls the hook after it has read the form and +dropped scopes you did not configure. The hook receives frozen readonly scope +arrays and can return nothing to accept the proposed request: + +```ts +import { account, type ResolveScopesHook } from "@fujocoded/authproto"; + +const resolveScopes: ResolveScopesHook = ({ atprotoId, proposedScopes }) => { + if (atprotoId.handle?.endsWith(".fujocoded.com")) { + return [...proposedScopes, account("email")]; + } +}; + +export default resolveScopes; +``` diff --git a/astro-authproto/__examples__/06-custom-scopes/astro.config.mjs b/astro-authproto/__examples__/06-custom-scopes/astro.config.mjs new file mode 100644 index 0000000..5f274d1 --- /dev/null +++ b/astro-authproto/__examples__/06-custom-scopes/astro.config.mjs @@ -0,0 +1,53 @@ +// @ts-check +import { defineConfig } from "astro/config"; +import node from "@astrojs/node"; +import authProto, { account, repo, rpc } from "@fujocoded/authproto"; + +// https://astro.build/config +export default defineConfig({ + output: "server", + adapter: node({ + mode: "standalone", + }), + session: { + driver: "fs", + }, + security: { + allowedDomains: [{ hostname: "fujocoded.com", protocol: "https" }], + }, + integrations: [ + authProto({ + applicationName: "Custom Scopes Demo", + applicationDomain: "https://fujocoded.com", + defaultDevUser: "bobatan.fujocoded.com", + driver: { + name: "fs", + options: { + base: "./.astro/authproto", + }, + }, + // Every scope this site may ever request, built with the granular + // permission helpers. The login route ignores anything not listed here, + // even if a form asks for it. + scopes: [ + // Read the user's email address and confirmation status + account("email"), + // Create, update, and delete this site's guestbook entries + repo("com.fujocoded.guestbook", { + action: ["create", "update", "delete"], + }), + // Send Bluesky direct messages on the user's behalf + rpc("chat.bsky.convo.sendMessage", { + aud: "did:web:api.bsky.chat#bsky_chat", + }), + ], + // Scopes the built-in asks for when a form does not override + // them. Keep this as a subset of `scopes` above. + defaultScopes: [ + repo("com.fujocoded.guestbook", { + action: ["create", "update", "delete"], + }), + ], + }), + ], +}); diff --git a/astro-authproto/__examples__/06-custom-scopes/package-lock.json b/astro-authproto/__examples__/06-custom-scopes/package-lock.json new file mode 100644 index 0000000..17826a2 --- /dev/null +++ b/astro-authproto/__examples__/06-custom-scopes/package-lock.json @@ -0,0 +1,6344 @@ +{ + "name": "example", + "version": "0.0.1", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "example", + "version": "0.0.1", + "dependencies": { + "@astrojs/db": "^0.17.2", + "@astrojs/node": "^9.4.0", + "@atproto/api": "^0.17.2", + "@fujocoded/authproto": "file:..", + "astro": "^5.12.9" + } + }, + "..": {}, + "node_modules/@astrojs/compiler": { + "version": "2.13.1", + "resolved": "https://registry.npmjs.org/@astrojs/compiler/-/compiler-2.13.1.tgz", + "integrity": "sha512-f3FN83d2G/v32ipNClRKgYv30onQlMZX1vCeZMjPsMMPl1mDpmbl0+N5BYo4S/ofzqJyS5hvwacEo0CCVDn/Qg==", + "license": "MIT" + }, + "node_modules/@astrojs/db": { + "version": "0.17.2", + "resolved": "https://registry.npmjs.org/@astrojs/db/-/db-0.17.2.tgz", + "integrity": "sha512-rFkw8Cj/kLwr63n1bS/sUw3hNywyvTkPZbKCdwAqd9FfbH3LdN+dH29XwmBC0NhXOxK3wA1jZBRsOOxpcVkV5w==", + "license": "MIT", + "dependencies": { + "@libsql/client": "^0.15.14", + "deep-diff": "^1.0.2", + "drizzle-orm": "^0.42.0", + "kleur": "^4.1.5", + "nanoid": "^5.1.5", + "prompts": "^2.4.2", + "yargs-parser": "^21.1.1", + "zod": "^3.25.76" + } + }, + "node_modules/@astrojs/internal-helpers": { + "version": "0.7.6", + "resolved": "https://registry.npmjs.org/@astrojs/internal-helpers/-/internal-helpers-0.7.6.tgz", + "integrity": "sha512-GOle7smBWKfMSP8osUIGOlB5kaHdQLV3foCsf+5Q9Wsuu+C6Fs3Ez/ttXmhjZ1HkSgsogcM1RXSjjOVieHq16Q==", + "license": "MIT" + }, + "node_modules/@astrojs/markdown-remark": { + "version": "6.3.11", + "resolved": "https://registry.npmjs.org/@astrojs/markdown-remark/-/markdown-remark-6.3.11.tgz", + "integrity": "sha512-hcaxX/5aC6lQgHeGh1i+aauvSwIT6cfyFjKWvExYSxUhZZBBdvCliOtu06gbQyhbe0pGJNoNmqNlQZ5zYUuIyQ==", + "license": "MIT", + "dependencies": { + "@astrojs/internal-helpers": "0.7.6", + "@astrojs/prism": "3.3.0", + "github-slugger": "^2.0.0", + "hast-util-from-html": "^2.0.3", + "hast-util-to-text": "^4.0.2", + "import-meta-resolve": "^4.2.0", + "js-yaml": "^4.1.1", + "mdast-util-definitions": "^6.0.0", + "rehype-raw": "^7.0.0", + "rehype-stringify": "^10.0.1", + "remark-gfm": "^4.0.1", + "remark-parse": "^11.0.0", + "remark-rehype": "^11.1.2", + "remark-smartypants": "^3.0.2", + "shiki": "^3.21.0", + "smol-toml": "^1.6.0", + "unified": "^11.0.5", + "unist-util-remove-position": "^5.0.0", + "unist-util-visit": "^5.0.0", + "unist-util-visit-parents": "^6.0.2", + "vfile": "^6.0.3" + } + }, + "node_modules/@astrojs/node": { + "version": "9.5.5", + "resolved": "https://registry.npmjs.org/@astrojs/node/-/node-9.5.5.tgz", + "integrity": "sha512-rtU2BGU5u3SfGURpANfMxVzCIoR86MkaN05ncza9rbtuMKJ/XnRJt/BbyVknDbOJ71hoci0SIsJwKcJR8vvi/A==", + "license": "MIT", + "dependencies": { + "@astrojs/internal-helpers": "0.7.6", + "send": "^1.2.1", + "server-destroy": "^1.0.1" + }, + "peerDependencies": { + "astro": "^5.17.3" + } + }, + "node_modules/@astrojs/prism": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/@astrojs/prism/-/prism-3.3.0.tgz", + "integrity": "sha512-q8VwfU/fDZNoDOf+r7jUnMC2//H2l0TuQ6FkGJL8vD8nw/q5KiL3DS1KKBI3QhI9UQhpJ5dc7AtqfbXWuOgLCQ==", + "license": "MIT", + "dependencies": { + "prismjs": "^1.30.0" + }, + "engines": { + "node": "18.20.8 || ^20.3.0 || >=22.0.0" + } + }, + "node_modules/@astrojs/telemetry": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/@astrojs/telemetry/-/telemetry-3.3.0.tgz", + "integrity": "sha512-UFBgfeldP06qu6khs/yY+q1cDAaArM2/7AEIqQ9Cuvf7B1hNLq0xDrZkct+QoIGyjq56y8IaE2I3CTvG99mlhQ==", + "license": "MIT", + "dependencies": { + "ci-info": "^4.2.0", + "debug": "^4.4.0", + "dlv": "^1.1.3", + "dset": "^3.1.4", + "is-docker": "^3.0.0", + "is-wsl": "^3.1.0", + "which-pm-runs": "^1.1.0" + }, + "engines": { + "node": "18.20.8 || ^20.3.0 || >=22.0.0" + } + }, + "node_modules/@atproto/api": { + "version": "0.17.7", + "resolved": "https://registry.npmjs.org/@atproto/api/-/api-0.17.7.tgz", + "integrity": "sha512-V+OJBZq9chcrD21xk1bUa6oc5DSKfQj5DmUPf5rmZncqL1w9ZEbS38H5cMyqqdhfgo2LWeDRdZHD0rvNyJsIaw==", + "license": "MIT", + "dependencies": { + "@atproto/common-web": "^0.4.3", + "@atproto/lexicon": "^0.5.1", + "@atproto/syntax": "^0.4.1", + "@atproto/xrpc": "^0.7.5", + "await-lock": "^2.2.2", + "multiformats": "^9.9.0", + "tlds": "^1.234.0", + "zod": "^3.23.8" + } + }, + "node_modules/@atproto/common-web": { + "version": "0.4.18", + "resolved": "https://registry.npmjs.org/@atproto/common-web/-/common-web-0.4.18.tgz", + "integrity": "sha512-ilImzP+9N/mtse440kN60pGrEzG7wi4xsV13nGeLrS+Zocybc/ISOpKlbZM13o+twPJ+Q7veGLw9CtGg0GAFoQ==", + "license": "MIT", + "dependencies": { + "@atproto/lex-data": "^0.0.13", + "@atproto/lex-json": "^0.0.13", + "@atproto/syntax": "^0.5.0", + "zod": "^3.23.8" + } + }, + "node_modules/@atproto/common-web/node_modules/@atproto/syntax": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/@atproto/syntax/-/syntax-0.5.0.tgz", + "integrity": "sha512-UA2DSpGdOQzUQ4gi5SH+NEJz/YR3a3Fg3y2oh+xETDSiTRmA4VhHRCojhXAVsBxUT6EnItw190C/KN+DWW90kw==", + "license": "MIT", + "dependencies": { + "tslib": "^2.8.1" + } + }, + "node_modules/@atproto/lex-data": { + "version": "0.0.13", + "resolved": "https://registry.npmjs.org/@atproto/lex-data/-/lex-data-0.0.13.tgz", + "integrity": "sha512-7Z7RwZ1Y/JzBF/Tcn/I4UJ/vIGfh5zn1zjv0KX+flke2JtgFkSE8uh2hOtqgBQMNqE3zdJFM+dcSWln86hR3MQ==", + "license": "MIT", + "dependencies": { + "multiformats": "^9.9.0", + "tslib": "^2.8.1", + "uint8arrays": "3.0.0", + "unicode-segmenter": "^0.14.0" + } + }, + "node_modules/@atproto/lex-json": { + "version": "0.0.13", + "resolved": "https://registry.npmjs.org/@atproto/lex-json/-/lex-json-0.0.13.tgz", + "integrity": "sha512-hwLhkKaIHulGJpt0EfXAEWdrxqM2L1tV/tvilzhMp3QxPqYgXchFnrfVmLsyFDx6P6qkH1GsX/XC2V36U0UlPQ==", + "license": "MIT", + "dependencies": { + "@atproto/lex-data": "^0.0.13", + "tslib": "^2.8.1" + } + }, + "node_modules/@atproto/lexicon": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/@atproto/lexicon/-/lexicon-0.5.2.tgz", + "integrity": "sha512-lRmJgMA8f5j7VB5Iu5cp188ald5FuI4FlmZ7nn6EBrk1dgOstWVrI5Ft6K3z2vjyLZRG6nzknlsw+tDP63p7bQ==", + "license": "MIT", + "dependencies": { + "@atproto/common-web": "^0.4.4", + "@atproto/syntax": "^0.4.1", + "iso-datestring-validator": "^2.2.2", + "multiformats": "^9.9.0", + "zod": "^3.23.8" + } + }, + "node_modules/@atproto/syntax": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/@atproto/syntax/-/syntax-0.4.3.tgz", + "integrity": "sha512-YoZUz40YAJr5nPwvCDWgodEOlt5IftZqPJvA0JDWjuZKD8yXddTwSzXSaKQAzGOpuM+/A3uXRtPzJJqlScc+iA==", + "license": "MIT", + "dependencies": { + "tslib": "^2.8.1" + } + }, + "node_modules/@atproto/xrpc": { + "version": "0.7.7", + "resolved": "https://registry.npmjs.org/@atproto/xrpc/-/xrpc-0.7.7.tgz", + "integrity": "sha512-K1ZyO/BU8JNtXX5dmPp7b5UrkLMMqpsIa/Lrj5D3Su+j1Xwq1m6QJ2XJ1AgjEjkI1v4Muzm7klianLE6XGxtmA==", + "license": "MIT", + "dependencies": { + "@atproto/lexicon": "^0.6.0", + "zod": "^3.23.8" + } + }, + "node_modules/@atproto/xrpc/node_modules/@atproto/lexicon": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/@atproto/lexicon/-/lexicon-0.6.2.tgz", + "integrity": "sha512-p3Ly6hinVZW0ETuAXZMeUGwuMm3g8HvQMQ41yyEE6AL0hAkfeKFaZKos6BdBrr6CjkpbrDZqE8M+5+QOceysMw==", + "license": "MIT", + "dependencies": { + "@atproto/common-web": "^0.4.18", + "@atproto/syntax": "^0.5.0", + "iso-datestring-validator": "^2.2.2", + "multiformats": "^9.9.0", + "zod": "^3.23.8" + } + }, + "node_modules/@atproto/xrpc/node_modules/@atproto/syntax": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/@atproto/syntax/-/syntax-0.5.0.tgz", + "integrity": "sha512-UA2DSpGdOQzUQ4gi5SH+NEJz/YR3a3Fg3y2oh+xETDSiTRmA4VhHRCojhXAVsBxUT6EnItw190C/KN+DWW90kw==", + "license": "MIT", + "dependencies": { + "tslib": "^2.8.1" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", + "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.0.tgz", + "integrity": "sha512-IyDgFV5GeDUVX4YdF/3CPULtVGSXXMLh1xVIgdCgxApktqnQV0r7/8Nqthg+8YLGaAtdyIlo2qIdZrbCv4+7ww==", + "license": "MIT", + "dependencies": { + "@babel/types": "^7.29.0" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/types": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz", + "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==", + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.28.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@capsizecss/unpack": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@capsizecss/unpack/-/unpack-4.0.0.tgz", + "integrity": "sha512-VERIM64vtTP1C4mxQ5thVT9fK0apjPFobqybMtA1UdUujWka24ERHbRHFGmpbbhp73MhV+KSsHQH9C6uOTdEQA==", + "license": "MIT", + "dependencies": { + "fontkitten": "^1.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@emnapi/runtime": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.9.0.tgz", + "integrity": "sha512-QN75eB0IH2ywSpRpNddCRfQIhmJYBCJ1x5Lb3IscKAL8bMnVAKnRg8dCoXbHzVLLH7P38N2Z3mtulB7W0J0FKw==", + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.4.tgz", + "integrity": "sha512-cQPwL2mp2nSmHHJlCyoXgHGhbEPMrEEU5xhkcy3Hs/O7nGZqEpZ2sUtLaL9MORLtDfRvVl2/3PAuEkYZH0Ty8Q==", + "cpu": [ + "ppc64" + ], + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.4.tgz", + "integrity": "sha512-X9bUgvxiC8CHAGKYufLIHGXPJWnr0OCdR0anD2e21vdvgCI8lIfqFbnoeOz7lBjdrAGUhqLZLcQo6MLhTO2DKQ==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.4.tgz", + "integrity": "sha512-gdLscB7v75wRfu7QSm/zg6Rx29VLdy9eTr2t44sfTW7CxwAtQghZ4ZnqHk3/ogz7xao0QAgrkradbBzcqFPasw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.4.tgz", + "integrity": "sha512-PzPFnBNVF292sfpfhiyiXCGSn9HZg5BcAz+ivBuSsl6Rk4ga1oEXAamhOXRFyMcjwr2DVtm40G65N3GLeH1Lvw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.4.tgz", + "integrity": "sha512-b7xaGIwdJlht8ZFCvMkpDN6uiSmnxxK56N2GDTMYPr2/gzvfdQN8rTfBsvVKmIVY/X7EM+/hJKEIbbHs9oA4tQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.4.tgz", + "integrity": "sha512-sR+OiKLwd15nmCdqpXMnuJ9W2kpy0KigzqScqHI3Hqwr7IXxBp3Yva+yJwoqh7rE8V77tdoheRYataNKL4QrPw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.4.tgz", + "integrity": "sha512-jnfpKe+p79tCnm4GVav68A7tUFeKQwQyLgESwEAUzyxk/TJr4QdGog9sqWNcUbr/bZt/O/HXouspuQDd9JxFSw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.4.tgz", + "integrity": "sha512-2kb4ceA/CpfUrIcTUl1wrP/9ad9Atrp5J94Lq69w7UwOMolPIGrfLSvAKJp0RTvkPPyn6CIWrNy13kyLikZRZQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.4.tgz", + "integrity": "sha512-aBYgcIxX/wd5n2ys0yESGeYMGF+pv6g0DhZr3G1ZG4jMfruU9Tl1i2Z+Wnj9/KjGz1lTLCcorqE2viePZqj4Eg==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.4.tgz", + "integrity": "sha512-7nQOttdzVGth1iz57kxg9uCz57dxQLHWxopL6mYuYthohPKEK0vU0C3O21CcBK6KDlkYVcnDXY099HcCDXd9dA==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.4.tgz", + "integrity": "sha512-oPtixtAIzgvzYcKBQM/qZ3R+9TEUd1aNJQu0HhGyqtx6oS7qTpvjheIWBbes4+qu1bNlo2V4cbkISr8q6gRBFA==", + "cpu": [ + "ia32" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.4.tgz", + "integrity": "sha512-8mL/vh8qeCoRcFH2nM8wm5uJP+ZcVYGGayMavi8GmRJjuI3g1v6Z7Ni0JJKAJW+m0EtUuARb6Lmp4hMjzCBWzA==", + "cpu": [ + "loong64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.4.tgz", + "integrity": "sha512-1RdrWFFiiLIW7LQq9Q2NES+HiD4NyT8Itj9AUeCl0IVCA459WnPhREKgwrpaIfTOe+/2rdntisegiPWn/r/aAw==", + "cpu": [ + "mips64el" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.4.tgz", + "integrity": "sha512-tLCwNG47l3sd9lpfyx9LAGEGItCUeRCWeAx6x2Jmbav65nAwoPXfewtAdtbtit/pJFLUWOhpv0FpS6GQAmPrHA==", + "cpu": [ + "ppc64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.4.tgz", + "integrity": "sha512-BnASypppbUWyqjd1KIpU4AUBiIhVr6YlHx/cnPgqEkNoVOhHg+YiSVxM1RLfiy4t9cAulbRGTNCKOcqHrEQLIw==", + "cpu": [ + "riscv64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.4.tgz", + "integrity": "sha512-+eUqgb/Z7vxVLezG8bVB9SfBie89gMueS+I0xYh2tJdw3vqA/0ImZJ2ROeWwVJN59ihBeZ7Tu92dF/5dy5FttA==", + "cpu": [ + "s390x" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.4.tgz", + "integrity": "sha512-S5qOXrKV8BQEzJPVxAwnryi2+Iq5pB40gTEIT69BQONqR7JH1EPIcQ/Uiv9mCnn05jff9umq/5nqzxlqTOg9NA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.4.tgz", + "integrity": "sha512-xHT8X4sb0GS8qTqiwzHqpY00C95DPAq7nAwX35Ie/s+LO9830hrMd3oX0ZMKLvy7vsonee73x0lmcdOVXFzd6Q==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.4.tgz", + "integrity": "sha512-RugOvOdXfdyi5Tyv40kgQnI0byv66BFgAqjdgtAKqHoZTbTF2QqfQrFwa7cHEORJf6X2ht+l9ABLMP0dnKYsgg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.4.tgz", + "integrity": "sha512-2MyL3IAaTX+1/qP0O1SwskwcwCoOI4kV2IBX1xYnDDqthmq5ArrW94qSIKCAuRraMgPOmG0RDTA74mzYNQA9ow==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.4.tgz", + "integrity": "sha512-u8fg/jQ5aQDfsnIV6+KwLOf1CmJnfu1ShpwqdwC0uA7ZPwFws55Ngc12vBdeUdnuWoQYx/SOQLGDcdlfXhYmXQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.4.tgz", + "integrity": "sha512-JkTZrl6VbyO8lDQO3yv26nNr2RM2yZzNrNHEsj9bm6dOwwu9OYN28CjzZkH57bh4w0I2F7IodpQvUAEd1mbWXg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.4.tgz", + "integrity": "sha512-/gOzgaewZJfeJTlsWhvUEmUG4tWEY2Spp5M20INYRg2ZKl9QPO3QEEgPeRtLjEWSW8FilRNacPOg8R1uaYkA6g==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.4.tgz", + "integrity": "sha512-Z9SExBg2y32smoDQdf1HRwHRt6vAHLXcxD2uGgO/v2jK7Y718Ix4ndsbNMU/+1Qiem9OiOdaqitioZwxivhXYg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.4.tgz", + "integrity": "sha512-DAyGLS0Jz5G5iixEbMHi5KdiApqHBWMGzTtMiJ72ZOLhbu/bzxgAe8Ue8CTS3n3HbIUHQz/L51yMdGMeoxXNJw==", + "cpu": [ + "ia32" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.4.tgz", + "integrity": "sha512-+knoa0BDoeXgkNvvV1vvbZX4+hizelrkwmGJBdT17t8FNPwG2lKemmuMZlmaNQ3ws3DKKCxpb4zRZEIp3UxFCg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@fujocoded/authproto": { + "resolved": "..", + "link": true + }, + "node_modules/@img/colour": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@img/colour/-/colour-1.1.0.tgz", + "integrity": "sha512-Td76q7j57o/tLVdgS746cYARfSyxk8iEfRxewL9h4OMzYhbW4TAcppl0mT4eyqXddh6L/jwoM75mo7ixa/pCeQ==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@img/sharp-darwin-arm64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.34.5.tgz", + "integrity": "sha512-imtQ3WMJXbMY4fxb/Ndp6HBTNVtWCUI0WdobyheGf5+ad6xX8VIDO8u2xE4qc/fr08CKG/7dDseFtn6M6g/r3w==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-darwin-arm64": "1.2.4" + } + }, + "node_modules/@img/sharp-darwin-x64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.34.5.tgz", + "integrity": "sha512-YNEFAF/4KQ/PeW0N+r+aVVsoIY0/qxxikF2SWdp+NRkmMB7y9LBZAVqQ4yhGCm/H3H270OSykqmQMKLBhBJDEw==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-darwin-x64": "1.2.4" + } + }, + "node_modules/@img/sharp-libvips-darwin-arm64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.2.4.tgz", + "integrity": "sha512-zqjjo7RatFfFoP0MkQ51jfuFZBnVE2pRiaydKJ1G/rHZvnsrHAOcQALIi9sA5co5xenQdTugCvtb1cuf78Vf4g==", + "cpu": [ + "arm64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "darwin" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-darwin-x64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.2.4.tgz", + "integrity": "sha512-1IOd5xfVhlGwX+zXv2N93k0yMONvUlANylbJw1eTah8K/Jtpi15KC+WSiaX/nBmbm2HxRM1gZ0nSdjSsrZbGKg==", + "cpu": [ + "x64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "darwin" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-arm": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.2.4.tgz", + "integrity": "sha512-bFI7xcKFELdiNCVov8e44Ia4u2byA+l3XtsAj+Q8tfCwO6BQ8iDojYdvoPMqsKDkuoOo+X6HZA0s0q11ANMQ8A==", + "cpu": [ + "arm" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-arm64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.2.4.tgz", + "integrity": "sha512-excjX8DfsIcJ10x1Kzr4RcWe1edC9PquDRRPx3YVCvQv+U5p7Yin2s32ftzikXojb1PIFc/9Mt28/y+iRklkrw==", + "cpu": [ + "arm64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-ppc64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-ppc64/-/sharp-libvips-linux-ppc64-1.2.4.tgz", + "integrity": "sha512-FMuvGijLDYG6lW+b/UvyilUWu5Ayu+3r2d1S8notiGCIyYU/76eig1UfMmkZ7vwgOrzKzlQbFSuQfgm7GYUPpA==", + "cpu": [ + "ppc64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-riscv64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-riscv64/-/sharp-libvips-linux-riscv64-1.2.4.tgz", + "integrity": "sha512-oVDbcR4zUC0ce82teubSm+x6ETixtKZBh/qbREIOcI3cULzDyb18Sr/Wcyx7NRQeQzOiHTNbZFF1UwPS2scyGA==", + "cpu": [ + "riscv64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-s390x": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.2.4.tgz", + "integrity": "sha512-qmp9VrzgPgMoGZyPvrQHqk02uyjA0/QrTO26Tqk6l4ZV0MPWIW6LTkqOIov+J1yEu7MbFQaDpwdwJKhbJvuRxQ==", + "cpu": [ + "s390x" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-x64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.2.4.tgz", + "integrity": "sha512-tJxiiLsmHc9Ax1bz3oaOYBURTXGIRDODBqhveVHonrHJ9/+k89qbLl0bcJns+e4t4rvaNBxaEZsFtSfAdquPrw==", + "cpu": [ + "x64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linuxmusl-arm64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.2.4.tgz", + "integrity": "sha512-FVQHuwx1IIuNow9QAbYUzJ+En8KcVm9Lk5+uGUQJHaZmMECZmOlix9HnH7n1TRkXMS0pGxIJokIVB9SuqZGGXw==", + "cpu": [ + "arm64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linuxmusl-x64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.2.4.tgz", + "integrity": "sha512-+LpyBk7L44ZIXwz/VYfglaX/okxezESc6UxDSoyo2Ks6Jxc4Y7sGjpgU9s4PMgqgjj1gZCylTieNamqA1MF7Dg==", + "cpu": [ + "x64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-linux-arm": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.34.5.tgz", + "integrity": "sha512-9dLqsvwtg1uuXBGZKsxem9595+ujv0sJ6Vi8wcTANSFpwV/GONat5eCkzQo/1O6zRIkh0m/8+5BjrRr7jDUSZw==", + "cpu": [ + "arm" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-arm": "1.2.4" + } + }, + "node_modules/@img/sharp-linux-arm64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.34.5.tgz", + "integrity": "sha512-bKQzaJRY/bkPOXyKx5EVup7qkaojECG6NLYswgktOZjaXecSAeCWiZwwiFf3/Y+O1HrauiE3FVsGxFg8c24rZg==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-arm64": "1.2.4" + } + }, + "node_modules/@img/sharp-linux-ppc64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-ppc64/-/sharp-linux-ppc64-0.34.5.tgz", + "integrity": "sha512-7zznwNaqW6YtsfrGGDA6BRkISKAAE1Jo0QdpNYXNMHu2+0dTrPflTLNkpc8l7MUP5M16ZJcUvysVWWrMefZquA==", + "cpu": [ + "ppc64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-ppc64": "1.2.4" + } + }, + "node_modules/@img/sharp-linux-riscv64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-riscv64/-/sharp-linux-riscv64-0.34.5.tgz", + "integrity": "sha512-51gJuLPTKa7piYPaVs8GmByo7/U7/7TZOq+cnXJIHZKavIRHAP77e3N2HEl3dgiqdD/w0yUfiJnII77PuDDFdw==", + "cpu": [ + "riscv64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-riscv64": "1.2.4" + } + }, + "node_modules/@img/sharp-linux-s390x": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.34.5.tgz", + "integrity": "sha512-nQtCk0PdKfho3eC5MrbQoigJ2gd1CgddUMkabUj+rBevs8tZ2cULOx46E7oyX+04WGfABgIwmMC0VqieTiR4jg==", + "cpu": [ + "s390x" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-s390x": "1.2.4" + } + }, + "node_modules/@img/sharp-linux-x64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.34.5.tgz", + "integrity": "sha512-MEzd8HPKxVxVenwAa+JRPwEC7QFjoPWuS5NZnBt6B3pu7EG2Ge0id1oLHZpPJdn3OQK+BQDiw9zStiHBTJQQQQ==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-x64": "1.2.4" + } + }, + "node_modules/@img/sharp-linuxmusl-arm64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.34.5.tgz", + "integrity": "sha512-fprJR6GtRsMt6Kyfq44IsChVZeGN97gTD331weR1ex1c1rypDEABN6Tm2xa1wE6lYb5DdEnk03NZPqA7Id21yg==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linuxmusl-arm64": "1.2.4" + } + }, + "node_modules/@img/sharp-linuxmusl-x64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.34.5.tgz", + "integrity": "sha512-Jg8wNT1MUzIvhBFxViqrEhWDGzqymo3sV7z7ZsaWbZNDLXRJZoRGrjulp60YYtV4wfY8VIKcWidjojlLcWrd8Q==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linuxmusl-x64": "1.2.4" + } + }, + "node_modules/@img/sharp-wasm32": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.34.5.tgz", + "integrity": "sha512-OdWTEiVkY2PHwqkbBI8frFxQQFekHaSSkUIJkwzclWZe64O1X4UlUjqqqLaPbUpMOQk6FBu/HtlGXNblIs0huw==", + "cpu": [ + "wasm32" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT", + "optional": true, + "dependencies": { + "@emnapi/runtime": "^1.7.0" + }, + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-arm64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-arm64/-/sharp-win32-arm64-0.34.5.tgz", + "integrity": "sha512-WQ3AgWCWYSb2yt+IG8mnC6Jdk9Whs7O0gxphblsLvdhSpSTtmu69ZG1Gkb6NuvxsNACwiPV6cNSZNzt0KPsw7g==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-ia32": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.34.5.tgz", + "integrity": "sha512-FV9m/7NmeCmSHDD5j4+4pNI8Cp3aW+JvLoXcTUo0IqyjSfAZJ8dIUmijx1qaJsIiU+Hosw6xM5KijAWRJCSgNg==", + "cpu": [ + "ia32" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-x64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.34.5.tgz", + "integrity": "sha512-+29YMsqY2/9eFEiW93eqWnuLcWcufowXewwSNIT6UwZdUUCrM3oFjMWH/Z6/TMmb4hlFenmfAVbpWeup2jryCw==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "license": "MIT" + }, + "node_modules/@libsql/client": { + "version": "0.15.15", + "resolved": "https://registry.npmjs.org/@libsql/client/-/client-0.15.15.tgz", + "integrity": "sha512-twC0hQxPNHPKfeOv3sNT6u2pturQjLcI+CnpTM0SjRpocEGgfiZ7DWKXLNnsothjyJmDqEsBQJ5ztq9Wlu470w==", + "license": "MIT", + "dependencies": { + "@libsql/core": "^0.15.14", + "@libsql/hrana-client": "^0.7.0", + "js-base64": "^3.7.5", + "libsql": "^0.5.22", + "promise-limit": "^2.7.0" + } + }, + "node_modules/@libsql/core": { + "version": "0.15.15", + "resolved": "https://registry.npmjs.org/@libsql/core/-/core-0.15.15.tgz", + "integrity": "sha512-C88Z6UKl+OyuKKPwz224riz02ih/zHYI3Ho/LAcVOgjsunIRZoBw7fjRfaH9oPMmSNeQfhGklSG2il1URoOIsA==", + "license": "MIT", + "dependencies": { + "js-base64": "^3.7.5" + } + }, + "node_modules/@libsql/darwin-arm64": { + "version": "0.5.22", + "resolved": "https://registry.npmjs.org/@libsql/darwin-arm64/-/darwin-arm64-0.5.22.tgz", + "integrity": "sha512-4B8ZlX3nIDPndfct7GNe0nI3Yw6ibocEicWdC4fvQbSs/jdq/RC2oCsoJxJ4NzXkvktX70C1J4FcmmoBy069UA==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@libsql/darwin-x64": { + "version": "0.5.22", + "resolved": "https://registry.npmjs.org/@libsql/darwin-x64/-/darwin-x64-0.5.22.tgz", + "integrity": "sha512-ny2HYWt6lFSIdNFzUFIJ04uiW6finXfMNJ7wypkAD8Pqdm6nAByO+Fdqu8t7sD0sqJGeUCiOg480icjyQ2/8VA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@libsql/hrana-client": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/@libsql/hrana-client/-/hrana-client-0.7.0.tgz", + "integrity": "sha512-OF8fFQSkbL7vJY9rfuegK1R7sPgQ6kFMkDamiEccNUvieQ+3urzfDFI616oPl8V7T9zRmnTkSjMOImYCAVRVuw==", + "license": "MIT", + "dependencies": { + "@libsql/isomorphic-fetch": "^0.3.1", + "@libsql/isomorphic-ws": "^0.1.5", + "js-base64": "^3.7.5", + "node-fetch": "^3.3.2" + } + }, + "node_modules/@libsql/isomorphic-fetch": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/@libsql/isomorphic-fetch/-/isomorphic-fetch-0.3.1.tgz", + "integrity": "sha512-6kK3SUK5Uu56zPq/Las620n5aS9xJq+jMBcNSOmjhNf/MUvdyji4vrMTqD7ptY7/4/CAVEAYDeotUz60LNQHtw==", + "license": "MIT", + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@libsql/isomorphic-ws": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/@libsql/isomorphic-ws/-/isomorphic-ws-0.1.5.tgz", + "integrity": "sha512-DtLWIH29onUYR00i0GlQ3UdcTRC6EP4u9w/h9LxpUZJWRMARk6dQwZ6Jkd+QdwVpuAOrdxt18v0K2uIYR3fwFg==", + "license": "MIT", + "dependencies": { + "@types/ws": "^8.5.4", + "ws": "^8.13.0" + } + }, + "node_modules/@libsql/linux-arm-gnueabihf": { + "version": "0.5.22", + "resolved": "https://registry.npmjs.org/@libsql/linux-arm-gnueabihf/-/linux-arm-gnueabihf-0.5.22.tgz", + "integrity": "sha512-3Uo3SoDPJe/zBnyZKosziRGtszXaEtv57raWrZIahtQDsjxBVjuzYQinCm9LRCJCUT5t2r5Z5nLDPJi2CwZVoA==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@libsql/linux-arm-musleabihf": { + "version": "0.5.22", + "resolved": "https://registry.npmjs.org/@libsql/linux-arm-musleabihf/-/linux-arm-musleabihf-0.5.22.tgz", + "integrity": "sha512-LCsXh07jvSojTNJptT9CowOzwITznD+YFGGW+1XxUr7fS+7/ydUrpDfsMX7UqTqjm7xG17eq86VkWJgHJfvpNg==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@libsql/linux-arm64-gnu": { + "version": "0.5.22", + "resolved": "https://registry.npmjs.org/@libsql/linux-arm64-gnu/-/linux-arm64-gnu-0.5.22.tgz", + "integrity": "sha512-KSdnOMy88c9mpOFKUEzPskSaF3VLflfSUCBwas/pn1/sV3pEhtMF6H8VUCd2rsedwoukeeCSEONqX7LLnQwRMA==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@libsql/linux-arm64-musl": { + "version": "0.5.22", + "resolved": "https://registry.npmjs.org/@libsql/linux-arm64-musl/-/linux-arm64-musl-0.5.22.tgz", + "integrity": "sha512-mCHSMAsDTLK5YH//lcV3eFEgiR23Ym0U9oEvgZA0667gqRZg/2px+7LshDvErEKv2XZ8ixzw3p1IrBzLQHGSsw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@libsql/linux-x64-gnu": { + "version": "0.5.22", + "resolved": "https://registry.npmjs.org/@libsql/linux-x64-gnu/-/linux-x64-gnu-0.5.22.tgz", + "integrity": "sha512-kNBHaIkSg78Y4BqAdgjcR2mBilZXs4HYkAmi58J+4GRwDQZh5fIUWbnQvB9f95DkWUIGVeenqLRFY2pcTmlsew==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@libsql/linux-x64-musl": { + "version": "0.5.22", + "resolved": "https://registry.npmjs.org/@libsql/linux-x64-musl/-/linux-x64-musl-0.5.22.tgz", + "integrity": "sha512-UZ4Xdxm4pu3pQXjvfJiyCzZop/9j/eA2JjmhMaAhe3EVLH2g11Fy4fwyUp9sT1QJYR1kpc2JLuybPM0kuXv/Tg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@libsql/win32-x64-msvc": { + "version": "0.5.22", + "resolved": "https://registry.npmjs.org/@libsql/win32-x64-msvc/-/win32-x64-msvc-0.5.22.tgz", + "integrity": "sha512-Fj0j8RnBpo43tVZUVoNK6BV/9AtDUM5S7DF3LB4qTYg1LMSZqi3yeCneUTLJD6XomQJlZzbI4mst89yspVSAnA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@neon-rs/load": { + "version": "0.0.4", + "resolved": "https://registry.npmjs.org/@neon-rs/load/-/load-0.0.4.tgz", + "integrity": "sha512-kTPhdZyTQxB+2wpiRcFWrDcejc4JI6tkPuS7UZCG4l6Zvc5kU/gGQ/ozvHTh1XR5tS+UlfAfGuPajjzQjCiHCw==", + "license": "MIT" + }, + "node_modules/@oslojs/encoding": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@oslojs/encoding/-/encoding-1.1.0.tgz", + "integrity": "sha512-70wQhgYmndg4GCPxPPxPGevRKqTIJ2Nh4OkiMWmDAVYsTQ+Ta7Sq+rPevXyXGdzr30/qZBnyOalCszoMxlyldQ==", + "license": "MIT" + }, + "node_modules/@rollup/pluginutils": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.3.0.tgz", + "integrity": "sha512-5EdhGZtnu3V88ces7s53hhfK5KSASnJZv8Lulpc04cWO3REESroJXg73DFsOmgbU2BhwV0E20bu2IDZb3VKW4Q==", + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "estree-walker": "^2.0.2", + "picomatch": "^4.0.2" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } + } + }, + "node_modules/@rollup/pluginutils/node_modules/estree-walker": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", + "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", + "license": "MIT" + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.59.0.tgz", + "integrity": "sha512-upnNBkA6ZH2VKGcBj9Fyl9IGNPULcjXRlg0LLeaioQWueH30p6IXtJEbKAgvyv+mJaMxSm1l6xwDXYjpEMiLMg==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.59.0.tgz", + "integrity": "sha512-hZ+Zxj3SySm4A/DylsDKZAeVg0mvi++0PYVceVyX7hemkw7OreKdCvW2oQ3T1FMZvCaQXqOTHb8qmBShoqk69Q==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.59.0.tgz", + "integrity": "sha512-W2Psnbh1J8ZJw0xKAd8zdNgF9HRLkdWwwdWqubSVk0pUuQkoHnv7rx4GiF9rT4t5DIZGAsConRE3AxCdJ4m8rg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.59.0.tgz", + "integrity": "sha512-ZW2KkwlS4lwTv7ZVsYDiARfFCnSGhzYPdiOU4IM2fDbL+QGlyAbjgSFuqNRbSthybLbIJ915UtZBtmuLrQAT/w==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.59.0.tgz", + "integrity": "sha512-EsKaJ5ytAu9jI3lonzn3BgG8iRBjV4LxZexygcQbpiU0wU0ATxhNVEpXKfUa0pS05gTcSDMKpn3Sx+QB9RlTTA==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.59.0.tgz", + "integrity": "sha512-d3DuZi2KzTMjImrxoHIAODUZYoUUMsuUiY4SRRcJy6NJoZ6iIqWnJu9IScV9jXysyGMVuW+KNzZvBLOcpdl3Vg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.59.0.tgz", + "integrity": "sha512-t4ONHboXi/3E0rT6OZl1pKbl2Vgxf9vJfWgmUoCEVQVxhW6Cw/c8I6hbbu7DAvgp82RKiH7TpLwxnJeKv2pbsw==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.59.0.tgz", + "integrity": "sha512-CikFT7aYPA2ufMD086cVORBYGHffBo4K8MQ4uPS/ZnY54GKj36i196u8U+aDVT2LX4eSMbyHtyOh7D7Zvk2VvA==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.59.0.tgz", + "integrity": "sha512-jYgUGk5aLd1nUb1CtQ8E+t5JhLc9x5WdBKew9ZgAXg7DBk0ZHErLHdXM24rfX+bKrFe+Xp5YuJo54I5HFjGDAA==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.59.0.tgz", + "integrity": "sha512-peZRVEdnFWZ5Bh2KeumKG9ty7aCXzzEsHShOZEFiCQlDEepP1dpUl/SrUNXNg13UmZl+gzVDPsiCwnV1uI0RUA==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.59.0.tgz", + "integrity": "sha512-gbUSW/97f7+r4gHy3Jlup8zDG190AuodsWnNiXErp9mT90iCy9NKKU0Xwx5k8VlRAIV2uU9CsMnEFg/xXaOfXg==", + "cpu": [ + "loong64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-musl": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.59.0.tgz", + "integrity": "sha512-yTRONe79E+o0FWFijasoTjtzG9EBedFXJMl888NBEDCDV9I2wGbFFfJQQe63OijbFCUZqxpHz1GzpbtSFikJ4Q==", + "cpu": [ + "loong64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.59.0.tgz", + "integrity": "sha512-sw1o3tfyk12k3OEpRddF68a1unZ5VCN7zoTNtSn2KndUE+ea3m3ROOKRCZxEpmT9nsGnogpFP9x6mnLTCaoLkA==", + "cpu": [ + "ppc64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-musl": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.59.0.tgz", + "integrity": "sha512-+2kLtQ4xT3AiIxkzFVFXfsmlZiG5FXYW7ZyIIvGA7Bdeuh9Z0aN4hVyXS/G1E9bTP/vqszNIN/pUKCk/BTHsKA==", + "cpu": [ + "ppc64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.59.0.tgz", + "integrity": "sha512-NDYMpsXYJJaj+I7UdwIuHHNxXZ/b/N2hR15NyH3m2qAtb/hHPA4g4SuuvrdxetTdndfj9b1WOmy73kcPRoERUg==", + "cpu": [ + "riscv64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.59.0.tgz", + "integrity": "sha512-nLckB8WOqHIf1bhymk+oHxvM9D3tyPndZH8i8+35p/1YiVoVswPid2yLzgX7ZJP0KQvnkhM4H6QZ5m0LzbyIAg==", + "cpu": [ + "riscv64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.59.0.tgz", + "integrity": "sha512-oF87Ie3uAIvORFBpwnCvUzdeYUqi2wY6jRFWJAy1qus/udHFYIkplYRW+wo+GRUP4sKzYdmE1Y3+rY5Gc4ZO+w==", + "cpu": [ + "s390x" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.59.0.tgz", + "integrity": "sha512-3AHmtQq/ppNuUspKAlvA8HtLybkDflkMuLK4DPo77DfthRb71V84/c4MlWJXixZz4uruIH4uaa07IqoAkG64fg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.59.0.tgz", + "integrity": "sha512-2UdiwS/9cTAx7qIUZB/fWtToJwvt0Vbo0zmnYt7ED35KPg13Q0ym1g442THLC7VyI6JfYTP4PiSOWyoMdV2/xg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openbsd-x64": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.59.0.tgz", + "integrity": "sha512-M3bLRAVk6GOwFlPTIxVBSYKUaqfLrn8l0psKinkCFxl4lQvOSz8ZrKDz2gxcBwHFpci0B6rttydI4IpS4IS/jQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.59.0.tgz", + "integrity": "sha512-tt9KBJqaqp5i5HUZzoafHZX8b5Q2Fe7UjYERADll83O4fGqJ49O1FsL6LpdzVFQcpwvnyd0i+K/VSwu/o/nWlA==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.59.0.tgz", + "integrity": "sha512-V5B6mG7OrGTwnxaNUzZTDTjDS7F75PO1ae6MJYdiMu60sq0CqN5CVeVsbhPxalupvTX8gXVSU9gq+Rx1/hvu6A==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.59.0.tgz", + "integrity": "sha512-UKFMHPuM9R0iBegwzKF4y0C4J9u8C6MEJgFuXTBerMk7EJ92GFVFYBfOZaSGLu6COf7FxpQNqhNS4c4icUPqxA==", + "cpu": [ + "ia32" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.59.0.tgz", + "integrity": "sha512-laBkYlSS1n2L8fSo1thDNGrCTQMmxjYY5G0WFWjFFYZkKPjsMBsgJfGf4TLxXrF6RyhI60L8TMOjBMvXiTcxeA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.59.0.tgz", + "integrity": "sha512-2HRCml6OztYXyJXAvdDXPKcawukWY2GpR5/nxKp4iBgiO3wcoEGkAaqctIbZcNB6KlUQBIqt8VYkNSj2397EfA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@shikijs/core": { + "version": "3.23.0", + "resolved": "https://registry.npmjs.org/@shikijs/core/-/core-3.23.0.tgz", + "integrity": "sha512-NSWQz0riNb67xthdm5br6lAkvpDJRTgB36fxlo37ZzM2yq0PQFFzbd8psqC2XMPgCzo1fW6cVi18+ArJ44wqgA==", + "license": "MIT", + "dependencies": { + "@shikijs/types": "3.23.0", + "@shikijs/vscode-textmate": "^10.0.2", + "@types/hast": "^3.0.4", + "hast-util-to-html": "^9.0.5" + } + }, + "node_modules/@shikijs/engine-javascript": { + "version": "3.23.0", + "resolved": "https://registry.npmjs.org/@shikijs/engine-javascript/-/engine-javascript-3.23.0.tgz", + "integrity": "sha512-aHt9eiGFobmWR5uqJUViySI1bHMqrAgamWE1TYSUoftkAeCCAiGawPMwM+VCadylQtF4V3VNOZ5LmfItH5f3yA==", + "license": "MIT", + "dependencies": { + "@shikijs/types": "3.23.0", + "@shikijs/vscode-textmate": "^10.0.2", + "oniguruma-to-es": "^4.3.4" + } + }, + "node_modules/@shikijs/engine-oniguruma": { + "version": "3.23.0", + "resolved": "https://registry.npmjs.org/@shikijs/engine-oniguruma/-/engine-oniguruma-3.23.0.tgz", + "integrity": "sha512-1nWINwKXxKKLqPibT5f4pAFLej9oZzQTsby8942OTlsJzOBZ0MWKiwzMsd+jhzu8YPCHAswGnnN1YtQfirL35g==", + "license": "MIT", + "dependencies": { + "@shikijs/types": "3.23.0", + "@shikijs/vscode-textmate": "^10.0.2" + } + }, + "node_modules/@shikijs/langs": { + "version": "3.23.0", + "resolved": "https://registry.npmjs.org/@shikijs/langs/-/langs-3.23.0.tgz", + "integrity": "sha512-2Ep4W3Re5aB1/62RSYQInK9mM3HsLeB91cHqznAJMuylqjzNVAVCMnNWRHFtcNHXsoNRayP9z1qj4Sq3nMqYXg==", + "license": "MIT", + "dependencies": { + "@shikijs/types": "3.23.0" + } + }, + "node_modules/@shikijs/themes": { + "version": "3.23.0", + "resolved": "https://registry.npmjs.org/@shikijs/themes/-/themes-3.23.0.tgz", + "integrity": "sha512-5qySYa1ZgAT18HR/ypENL9cUSGOeI2x+4IvYJu4JgVJdizn6kG4ia5Q1jDEOi7gTbN4RbuYtmHh0W3eccOrjMA==", + "license": "MIT", + "dependencies": { + "@shikijs/types": "3.23.0" + } + }, + "node_modules/@shikijs/types": { + "version": "3.23.0", + "resolved": "https://registry.npmjs.org/@shikijs/types/-/types-3.23.0.tgz", + "integrity": "sha512-3JZ5HXOZfYjsYSk0yPwBrkupyYSLpAE26Qc0HLghhZNGTZg/SKxXIIgoxOpmmeQP0RRSDJTk1/vPfw9tbw+jSQ==", + "license": "MIT", + "dependencies": { + "@shikijs/vscode-textmate": "^10.0.2", + "@types/hast": "^3.0.4" + } + }, + "node_modules/@shikijs/vscode-textmate": { + "version": "10.0.2", + "resolved": "https://registry.npmjs.org/@shikijs/vscode-textmate/-/vscode-textmate-10.0.2.tgz", + "integrity": "sha512-83yeghZ2xxin3Nj8z1NMd/NCuca+gsYXswywDy5bHvwlWL8tpTQmzGeUuHd9FC3E/SBEMvzJRwWEOz5gGes9Qg==", + "license": "MIT" + }, + "node_modules/@types/debug": { + "version": "4.1.12", + "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.12.tgz", + "integrity": "sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==", + "license": "MIT", + "dependencies": { + "@types/ms": "*" + } + }, + "node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "license": "MIT" + }, + "node_modules/@types/hast": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.4.tgz", + "integrity": "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==", + "license": "MIT", + "dependencies": { + "@types/unist": "*" + } + }, + "node_modules/@types/mdast": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-4.0.4.tgz", + "integrity": "sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==", + "license": "MIT", + "dependencies": { + "@types/unist": "*" + } + }, + "node_modules/@types/ms": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@types/ms/-/ms-2.1.0.tgz", + "integrity": "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==", + "license": "MIT" + }, + "node_modules/@types/nlcst": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@types/nlcst/-/nlcst-2.0.3.tgz", + "integrity": "sha512-vSYNSDe6Ix3q+6Z7ri9lyWqgGhJTmzRjZRqyq15N0Z/1/UnVsno9G/N40NBijoYx2seFDIl0+B2mgAb9mezUCA==", + "license": "MIT", + "dependencies": { + "@types/unist": "*" + } + }, + "node_modules/@types/node": { + "version": "25.5.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-25.5.0.tgz", + "integrity": "sha512-jp2P3tQMSxWugkCUKLRPVUpGaL5MVFwF8RDuSRztfwgN1wmqJeMSbKlnEtQqU8UrhTmzEmZdu2I6v2dpp7XIxw==", + "license": "MIT", + "dependencies": { + "undici-types": "~7.18.0" + } + }, + "node_modules/@types/unist": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz", + "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==", + "license": "MIT" + }, + "node_modules/@types/ws": { + "version": "8.18.1", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.1.tgz", + "integrity": "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==", + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@ungap/structured-clone": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.0.tgz", + "integrity": "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==", + "license": "ISC" + }, + "node_modules/acorn": { + "version": "8.16.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz", + "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/ansi-align": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/ansi-align/-/ansi-align-3.0.1.tgz", + "integrity": "sha512-IOfwwBF5iczOjp/WeY4YxyjqAFMQoZufdQWDd19SEExbVLNXqvpzSJ/M7Za4/sCPmQ0+GRquoA7bGcINcxew6w==", + "license": "ISC", + "dependencies": { + "string-width": "^4.1.0" + } + }, + "node_modules/ansi-align/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-align/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "license": "MIT" + }, + "node_modules/ansi-align/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-align/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-regex": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", + "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/ansi-styles": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "license": "ISC", + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/anymatch/node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "license": "Python-2.0" + }, + "node_modules/aria-query": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.2.tgz", + "integrity": "sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==", + "license": "Apache-2.0", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/array-iterate": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/array-iterate/-/array-iterate-2.0.1.tgz", + "integrity": "sha512-I1jXZMjAgCMmxT4qxXfPXa6SthSoE8h6gkSI9BGGNv8mP8G/v0blc+qFnZu6K42vTOiuME596QaLO0TP3Lk0xg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/astro": { + "version": "5.18.1", + "resolved": "https://registry.npmjs.org/astro/-/astro-5.18.1.tgz", + "integrity": "sha512-m4VWilWZ+Xt6NPoYzC4CgGZim/zQUO7WFL0RHCH0AiEavF1153iC3+me2atDvXpf/yX4PyGUeD8wZLq1cirT3g==", + "license": "MIT", + "dependencies": { + "@astrojs/compiler": "^2.13.0", + "@astrojs/internal-helpers": "0.7.6", + "@astrojs/markdown-remark": "6.3.11", + "@astrojs/telemetry": "3.3.0", + "@capsizecss/unpack": "^4.0.0", + "@oslojs/encoding": "^1.1.0", + "@rollup/pluginutils": "^5.3.0", + "acorn": "^8.15.0", + "aria-query": "^5.3.2", + "axobject-query": "^4.1.0", + "boxen": "8.0.1", + "ci-info": "^4.3.1", + "clsx": "^2.1.1", + "common-ancestor-path": "^1.0.1", + "cookie": "^1.1.1", + "cssesc": "^3.0.0", + "debug": "^4.4.3", + "deterministic-object-hash": "^2.0.2", + "devalue": "^5.6.2", + "diff": "^8.0.3", + "dlv": "^1.1.3", + "dset": "^3.1.4", + "es-module-lexer": "^1.7.0", + "esbuild": "^0.27.3", + "estree-walker": "^3.0.3", + "flattie": "^1.1.1", + "fontace": "~0.4.0", + "github-slugger": "^2.0.0", + "html-escaper": "3.0.3", + "http-cache-semantics": "^4.2.0", + "import-meta-resolve": "^4.2.0", + "js-yaml": "^4.1.1", + "magic-string": "^0.30.21", + "magicast": "^0.5.1", + "mrmime": "^2.0.1", + "neotraverse": "^0.6.18", + "p-limit": "^6.2.0", + "p-queue": "^8.1.1", + "package-manager-detector": "^1.6.0", + "piccolore": "^0.1.3", + "picomatch": "^4.0.3", + "prompts": "^2.4.2", + "rehype": "^13.0.2", + "semver": "^7.7.3", + "shiki": "^3.21.0", + "smol-toml": "^1.6.0", + "svgo": "^4.0.0", + "tinyexec": "^1.0.2", + "tinyglobby": "^0.2.15", + "tsconfck": "^3.1.6", + "ultrahtml": "^1.6.0", + "unifont": "~0.7.3", + "unist-util-visit": "^5.0.0", + "unstorage": "^1.17.4", + "vfile": "^6.0.3", + "vite": "^6.4.1", + "vitefu": "^1.1.1", + "xxhash-wasm": "^1.1.0", + "yargs-parser": "^21.1.1", + "yocto-spinner": "^0.2.3", + "zod": "^3.25.76", + "zod-to-json-schema": "^3.25.1", + "zod-to-ts": "^1.2.0" + }, + "bin": { + "astro": "astro.js" + }, + "engines": { + "node": "18.20.8 || ^20.3.0 || >=22.0.0", + "npm": ">=9.6.5", + "pnpm": ">=7.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/astrodotbuild" + }, + "optionalDependencies": { + "sharp": "^0.34.0" + } + }, + "node_modules/await-lock": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/await-lock/-/await-lock-2.2.2.tgz", + "integrity": "sha512-aDczADvlvTGajTDjcjpJMqRkOF6Qdz3YbPZm/PyW6tKPkx2hlYBzxMhEywM/tU72HrVZjgl5VCdRuMlA7pZ8Gw==", + "license": "MIT" + }, + "node_modules/axobject-query": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-4.1.0.tgz", + "integrity": "sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==", + "license": "Apache-2.0", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/bail": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/bail/-/bail-2.0.2.tgz", + "integrity": "sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/base-64": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/base-64/-/base-64-1.0.0.tgz", + "integrity": "sha512-kwDPIFCGx0NZHog36dj+tHiwP4QMzsZ3AgMViUBKI0+V5n4U0ufTCUMhnQ04diaRI8EX/QcPfql7zlhZ7j4zgg==", + "license": "MIT" + }, + "node_modules/boolbase": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", + "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==", + "license": "ISC" + }, + "node_modules/boxen": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/boxen/-/boxen-8.0.1.tgz", + "integrity": "sha512-F3PH5k5juxom4xktynS7MoFY+NUWH5LC4CnH11YB8NPew+HLpmBLCybSAEyb2F+4pRXhuhWqFesoQd6DAyc2hw==", + "license": "MIT", + "dependencies": { + "ansi-align": "^3.0.1", + "camelcase": "^8.0.0", + "chalk": "^5.3.0", + "cli-boxes": "^3.0.0", + "string-width": "^7.2.0", + "type-fest": "^4.21.0", + "widest-line": "^5.0.0", + "wrap-ansi": "^9.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/camelcase": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-8.0.0.tgz", + "integrity": "sha512-8WB3Jcas3swSvjIeA2yvCJ+Miyz5l1ZmB6HFb9R1317dt9LCQoswg/BGrmAmkWVEszSrrg4RwmO46qIm2OEnSA==", + "license": "MIT", + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ccount": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/ccount/-/ccount-2.0.1.tgz", + "integrity": "sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/chalk": { + "version": "5.6.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz", + "integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==", + "license": "MIT", + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/character-entities": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/character-entities/-/character-entities-2.0.2.tgz", + "integrity": "sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-entities-html4": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/character-entities-html4/-/character-entities-html4-2.1.0.tgz", + "integrity": "sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-entities-legacy": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/character-entities-legacy/-/character-entities-legacy-3.0.0.tgz", + "integrity": "sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/chokidar": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-5.0.0.tgz", + "integrity": "sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw==", + "license": "MIT", + "dependencies": { + "readdirp": "^5.0.0" + }, + "engines": { + "node": ">= 20.19.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/ci-info": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-4.4.0.tgz", + "integrity": "sha512-77PSwercCZU2Fc4sX94eF8k8Pxte6JAwL4/ICZLFjJLqegs7kCuAsqqj/70NQF6TvDpgFjkubQB2FW2ZZddvQg==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/sibiraj-s" + } + ], + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/cli-boxes": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cli-boxes/-/cli-boxes-3.0.0.tgz", + "integrity": "sha512-/lzGpEWL/8PfI0BmBOPRwp0c/wFNX1RdUML3jK/RcSBA9T8mZDdQpqYBKtCFTOfQbwPqWEOpjqW+Fnayc0969g==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/clsx": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", + "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/comma-separated-tokens": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-2.0.3.tgz", + "integrity": "sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/commander": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-11.1.0.tgz", + "integrity": "sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ==", + "license": "MIT", + "engines": { + "node": ">=16" + } + }, + "node_modules/common-ancestor-path": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/common-ancestor-path/-/common-ancestor-path-1.0.1.tgz", + "integrity": "sha512-L3sHRo1pXXEqX8VU28kfgUY+YGsk09hPqZiZmLacNib6XNTCM8ubYeT7ryXQw8asB1sKgcU5lkB7ONug08aB8w==", + "license": "ISC" + }, + "node_modules/cookie": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.1.1.tgz", + "integrity": "sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/cookie-es": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/cookie-es/-/cookie-es-1.2.2.tgz", + "integrity": "sha512-+W7VmiVINB+ywl1HGXJXmrqkOhpKrIiVZV6tQuV54ZyQC7MMuBt81Vc336GMLoHBq5hV/F9eXgt5Mnx0Rha5Fg==", + "license": "MIT" + }, + "node_modules/crossws": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/crossws/-/crossws-0.3.5.tgz", + "integrity": "sha512-ojKiDvcmByhwa8YYqbQI/hg7MEU0NC03+pSdEq4ZUnZR9xXpwk7E43SMNGkn+JxJGPFtNvQ48+vV2p+P1ml5PA==", + "license": "MIT", + "dependencies": { + "uncrypto": "^0.1.3" + } + }, + "node_modules/css-select": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-5.2.2.tgz", + "integrity": "sha512-TizTzUddG/xYLA3NXodFM0fSbNizXjOKhqiQQwvhlspadZokn1KDy0NZFS0wuEubIYAV5/c1/lAr0TaaFXEXzw==", + "license": "BSD-2-Clause", + "dependencies": { + "boolbase": "^1.0.0", + "css-what": "^6.1.0", + "domhandler": "^5.0.2", + "domutils": "^3.0.1", + "nth-check": "^2.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/css-tree": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-3.2.1.tgz", + "integrity": "sha512-X7sjQzceUhu1u7Y/ylrRZFU2FS6LRiFVp6rKLPg23y3x3c3DOKAwuXGDp+PAGjh6CSnCjYeAul8pcT8bAl+lSA==", + "license": "MIT", + "dependencies": { + "mdn-data": "2.27.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0" + } + }, + "node_modules/css-what": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.2.2.tgz", + "integrity": "sha512-u/O3vwbptzhMs3L1fQE82ZSLHQQfto5gyZzwteVIEyeaY5Fc7R4dapF/BvRoSYFeqfBk4m0V1Vafq5Pjv25wvA==", + "license": "BSD-2-Clause", + "engines": { + "node": ">= 6" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/cssesc": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", + "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", + "license": "MIT", + "bin": { + "cssesc": "bin/cssesc" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/csso": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/csso/-/csso-5.0.5.tgz", + "integrity": "sha512-0LrrStPOdJj+SPCCrGhzryycLjwcgUSHBtxNA8aIDxf0GLsRh1cKYhB00Gd1lDOS4yGH69+SNn13+TWbVHETFQ==", + "license": "MIT", + "dependencies": { + "css-tree": "~2.2.0" + }, + "engines": { + "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0", + "npm": ">=7.0.0" + } + }, + "node_modules/csso/node_modules/css-tree": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-2.2.1.tgz", + "integrity": "sha512-OA0mILzGc1kCOCSJerOeqDxDQ4HOh+G8NbOJFOTgOCzpw7fCBubk0fEyxp8AgOL/jvLgYA/uV0cMbe43ElF1JA==", + "license": "MIT", + "dependencies": { + "mdn-data": "2.0.28", + "source-map-js": "^1.0.1" + }, + "engines": { + "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0", + "npm": ">=7.0.0" + } + }, + "node_modules/csso/node_modules/mdn-data": { + "version": "2.0.28", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.28.tgz", + "integrity": "sha512-aylIc7Z9y4yzHYAJNuESG3hfhC+0Ibp/MAMiaOZgNv4pmEdFyfZhhhny4MNiAfWdBQ1RQ2mfDWmM1x8SvGyp8g==", + "license": "CC0-1.0" + }, + "node_modules/data-uri-to-buffer": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz", + "integrity": "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==", + "license": "MIT", + "engines": { + "node": ">= 12" + } + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/decode-named-character-reference": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/decode-named-character-reference/-/decode-named-character-reference-1.3.0.tgz", + "integrity": "sha512-GtpQYB283KrPp6nRw50q3U9/VfOutZOe103qlN7BPP6Ad27xYnOIWv4lPzo8HCAL+mMZofJ9KEy30fq6MfaK6Q==", + "license": "MIT", + "dependencies": { + "character-entities": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/deep-diff": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/deep-diff/-/deep-diff-1.0.2.tgz", + "integrity": "sha512-aWS3UIVH+NPGCD1kki+DCU9Dua032iSsO43LqQpcs4R3+dVv7tX0qBGjiVHJHjplsoUM2XRO/KB92glqc68awg==", + "deprecated": "Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.", + "license": "MIT" + }, + "node_modules/defu": { + "version": "6.1.4", + "resolved": "https://registry.npmjs.org/defu/-/defu-6.1.4.tgz", + "integrity": "sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==", + "license": "MIT" + }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/dequal": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", + "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/destr": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/destr/-/destr-2.0.5.tgz", + "integrity": "sha512-ugFTXCtDZunbzasqBxrK93Ik/DRYsO6S/fedkWEMKqt04xZ4csmnmwGDBAb07QWNaGMAmnTIemsYZCksjATwsA==", + "license": "MIT" + }, + "node_modules/detect-libc": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.2.tgz", + "integrity": "sha512-UX6sGumvvqSaXgdKGUsgZWqcUyIXZ/vZTrlRT/iobiKhGL0zL4d3osHj3uqllWJK+i+sixDS/3COVEOFbupFyw==", + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } + }, + "node_modules/deterministic-object-hash": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/deterministic-object-hash/-/deterministic-object-hash-2.0.2.tgz", + "integrity": "sha512-KxektNH63SrbfUyDiwXqRb1rLwKt33AmMv+5Nhsw1kqZ13SJBRTgZHtGbE+hH3a1mVW1cz+4pqSWVPAtLVXTzQ==", + "license": "MIT", + "dependencies": { + "base-64": "^1.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/devalue": { + "version": "5.6.4", + "resolved": "https://registry.npmjs.org/devalue/-/devalue-5.6.4.tgz", + "integrity": "sha512-Gp6rDldRsFh/7XuouDbxMH3Mx8GMCcgzIb1pDTvNyn8pZGQ22u+Wa+lGV9dQCltFQ7uVw0MhRyb8XDskNFOReA==", + "license": "MIT" + }, + "node_modules/devlop": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/devlop/-/devlop-1.1.0.tgz", + "integrity": "sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==", + "license": "MIT", + "dependencies": { + "dequal": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/diff": { + "version": "8.0.3", + "resolved": "https://registry.npmjs.org/diff/-/diff-8.0.3.tgz", + "integrity": "sha512-qejHi7bcSD4hQAZE0tNAawRK1ZtafHDmMTMkrrIGgSLl7hTnQHmKCeB45xAcbfTqK2zowkM3j3bHt/4b/ARbYQ==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/dlv": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz", + "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==", + "license": "MIT" + }, + "node_modules/dom-serializer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz", + "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==", + "license": "MIT", + "dependencies": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.2", + "entities": "^4.2.0" + }, + "funding": { + "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" + } + }, + "node_modules/dom-serializer/node_modules/entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/domelementtype": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", + "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ], + "license": "BSD-2-Clause" + }, + "node_modules/domhandler": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz", + "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==", + "license": "BSD-2-Clause", + "dependencies": { + "domelementtype": "^2.3.0" + }, + "engines": { + "node": ">= 4" + }, + "funding": { + "url": "https://github.com/fb55/domhandler?sponsor=1" + } + }, + "node_modules/domutils": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.2.2.tgz", + "integrity": "sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==", + "license": "BSD-2-Clause", + "dependencies": { + "dom-serializer": "^2.0.0", + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3" + }, + "funding": { + "url": "https://github.com/fb55/domutils?sponsor=1" + } + }, + "node_modules/drizzle-orm": { + "version": "0.42.0", + "resolved": "https://registry.npmjs.org/drizzle-orm/-/drizzle-orm-0.42.0.tgz", + "integrity": "sha512-pS8nNJm2kBNZwrOjTHJfdKkaU+KuUQmV/vk5D57NojDq4FG+0uAYGMulXtYT///HfgsMF0hnFFvu1ezI3OwOkg==", + "license": "Apache-2.0", + "peerDependencies": { + "@aws-sdk/client-rds-data": ">=3", + "@cloudflare/workers-types": ">=4", + "@electric-sql/pglite": ">=0.2.0", + "@libsql/client": ">=0.10.0", + "@libsql/client-wasm": ">=0.10.0", + "@neondatabase/serverless": ">=0.10.0", + "@op-engineering/op-sqlite": ">=2", + "@opentelemetry/api": "^1.4.1", + "@planetscale/database": ">=1.13", + "@prisma/client": "*", + "@tidbcloud/serverless": "*", + "@types/better-sqlite3": "*", + "@types/pg": "*", + "@types/sql.js": "*", + "@vercel/postgres": ">=0.8.0", + "@xata.io/client": "*", + "better-sqlite3": ">=7", + "bun-types": "*", + "expo-sqlite": ">=14.0.0", + "gel": ">=2", + "knex": "*", + "kysely": "*", + "mysql2": ">=2", + "pg": ">=8", + "postgres": ">=3", + "sql.js": ">=1", + "sqlite3": ">=5" + }, + "peerDependenciesMeta": { + "@aws-sdk/client-rds-data": { + "optional": true + }, + "@cloudflare/workers-types": { + "optional": true + }, + "@electric-sql/pglite": { + "optional": true + }, + "@libsql/client": { + "optional": true + }, + "@libsql/client-wasm": { + "optional": true + }, + "@neondatabase/serverless": { + "optional": true + }, + "@op-engineering/op-sqlite": { + "optional": true + }, + "@opentelemetry/api": { + "optional": true + }, + "@planetscale/database": { + "optional": true + }, + "@prisma/client": { + "optional": true + }, + "@tidbcloud/serverless": { + "optional": true + }, + "@types/better-sqlite3": { + "optional": true + }, + "@types/pg": { + "optional": true + }, + "@types/sql.js": { + "optional": true + }, + "@vercel/postgres": { + "optional": true + }, + "@xata.io/client": { + "optional": true + }, + "better-sqlite3": { + "optional": true + }, + "bun-types": { + "optional": true + }, + "expo-sqlite": { + "optional": true + }, + "gel": { + "optional": true + }, + "knex": { + "optional": true + }, + "kysely": { + "optional": true + }, + "mysql2": { + "optional": true + }, + "pg": { + "optional": true + }, + "postgres": { + "optional": true + }, + "prisma": { + "optional": true + }, + "sql.js": { + "optional": true + }, + "sqlite3": { + "optional": true + } + } + }, + "node_modules/dset": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/dset/-/dset-3.1.4.tgz", + "integrity": "sha512-2QF/g9/zTaPDc3BjNcVTGoBbXBgYfMTTceLaYcFJ/W9kggFUkhxD/hMEeuLKbugyef9SqAx8cpgwlIP/jinUTA==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", + "license": "MIT" + }, + "node_modules/emoji-regex": { + "version": "10.6.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.6.0.tgz", + "integrity": "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==", + "license": "MIT" + }, + "node_modules/encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/entities": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz", + "integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/es-module-lexer": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz", + "integrity": "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==", + "license": "MIT" + }, + "node_modules/esbuild": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.4.tgz", + "integrity": "sha512-Rq4vbHnYkK5fws5NF7MYTU68FPRE1ajX7heQ/8QXXWqNgqqJ/GkmmyxIzUnf2Sr/bakf8l54716CcMGHYhMrrQ==", + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.27.4", + "@esbuild/android-arm": "0.27.4", + "@esbuild/android-arm64": "0.27.4", + "@esbuild/android-x64": "0.27.4", + "@esbuild/darwin-arm64": "0.27.4", + "@esbuild/darwin-x64": "0.27.4", + "@esbuild/freebsd-arm64": "0.27.4", + "@esbuild/freebsd-x64": "0.27.4", + "@esbuild/linux-arm": "0.27.4", + "@esbuild/linux-arm64": "0.27.4", + "@esbuild/linux-ia32": "0.27.4", + "@esbuild/linux-loong64": "0.27.4", + "@esbuild/linux-mips64el": "0.27.4", + "@esbuild/linux-ppc64": "0.27.4", + "@esbuild/linux-riscv64": "0.27.4", + "@esbuild/linux-s390x": "0.27.4", + "@esbuild/linux-x64": "0.27.4", + "@esbuild/netbsd-arm64": "0.27.4", + "@esbuild/netbsd-x64": "0.27.4", + "@esbuild/openbsd-arm64": "0.27.4", + "@esbuild/openbsd-x64": "0.27.4", + "@esbuild/openharmony-arm64": "0.27.4", + "@esbuild/sunos-x64": "0.27.4", + "@esbuild/win32-arm64": "0.27.4", + "@esbuild/win32-ia32": "0.27.4", + "@esbuild/win32-x64": "0.27.4" + } + }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", + "license": "MIT" + }, + "node_modules/escape-string-regexp": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz", + "integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/estree-walker": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0" + } + }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/eventemitter3": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.4.tgz", + "integrity": "sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw==", + "license": "MIT" + }, + "node_modules/extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", + "license": "MIT" + }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/fetch-blob": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.2.0.tgz", + "integrity": "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/jimmywarting" + }, + { + "type": "paypal", + "url": "https://paypal.me/jimmywarting" + } + ], + "license": "MIT", + "dependencies": { + "node-domexception": "^1.0.0", + "web-streams-polyfill": "^3.0.3" + }, + "engines": { + "node": "^12.20 || >= 14.13" + } + }, + "node_modules/flattie": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/flattie/-/flattie-1.1.1.tgz", + "integrity": "sha512-9UbaD6XdAL97+k/n+N7JwX46K/M6Zc6KcFYskrYL8wbBV/Uyk0CTAMY0VT+qiK5PM7AIc9aTWYtq65U7T+aCNQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/fontace": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/fontace/-/fontace-0.4.1.tgz", + "integrity": "sha512-lDMvbAzSnHmbYMTEld5qdtvNH2/pWpICOqpean9IgC7vUbUJc3k+k5Dokp85CegamqQpFbXf0rAVkbzpyTA8aw==", + "license": "MIT", + "dependencies": { + "fontkitten": "^1.0.2" + } + }, + "node_modules/fontkitten": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/fontkitten/-/fontkitten-1.0.3.tgz", + "integrity": "sha512-Wp1zXWPVUPBmfoa3Cqc9ctaKuzKAV6uLstRqlR56kSjplf5uAce+qeyYym7F+PHbGTk+tCEdkCW6RD7DX/gBZw==", + "license": "MIT", + "dependencies": { + "tiny-inflate": "^1.0.3" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/formdata-polyfill": { + "version": "4.0.10", + "resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz", + "integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==", + "license": "MIT", + "dependencies": { + "fetch-blob": "^3.1.2" + }, + "engines": { + "node": ">=12.20.0" + } + }, + "node_modules/fresh": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz", + "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/get-east-asian-width": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.5.0.tgz", + "integrity": "sha512-CQ+bEO+Tva/qlmw24dCejulK5pMzVnUOFOijVogd3KQs07HnRIgp8TGipvCCRT06xeYEbpbgwaCxglFyiuIcmA==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/github-slugger": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/github-slugger/-/github-slugger-2.0.0.tgz", + "integrity": "sha512-IaOQ9puYtjrkq7Y0Ygl9KDZnrf/aiUJYUpVf89y8kyaxbRG7Y1SrX/jaumrv81vc61+kiMempujsM3Yw7w5qcw==", + "license": "ISC" + }, + "node_modules/h3": { + "version": "1.15.6", + "resolved": "https://registry.npmjs.org/h3/-/h3-1.15.6.tgz", + "integrity": "sha512-oi15ESLW5LRthZ+qPCi5GNasY/gvynSKUQxgiovrY63bPAtG59wtM+LSrlcwvOHAXzGrXVLnI97brbkdPF9WoQ==", + "license": "MIT", + "dependencies": { + "cookie-es": "^1.2.2", + "crossws": "^0.3.5", + "defu": "^6.1.4", + "destr": "^2.0.5", + "iron-webcrypto": "^1.2.1", + "node-mock-http": "^1.0.4", + "radix3": "^1.1.2", + "ufo": "^1.6.3", + "uncrypto": "^0.1.3" + } + }, + "node_modules/hast-util-from-html": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/hast-util-from-html/-/hast-util-from-html-2.0.3.tgz", + "integrity": "sha512-CUSRHXyKjzHov8yKsQjGOElXy/3EKpyX56ELnkHH34vDVw1N1XSQ1ZcAvTyAPtGqLTuKP/uxM+aLkSPqF/EtMw==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "devlop": "^1.1.0", + "hast-util-from-parse5": "^8.0.0", + "parse5": "^7.0.0", + "vfile": "^6.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-from-parse5": { + "version": "8.0.3", + "resolved": "https://registry.npmjs.org/hast-util-from-parse5/-/hast-util-from-parse5-8.0.3.tgz", + "integrity": "sha512-3kxEVkEKt0zvcZ3hCRYI8rqrgwtlIOFMWkbclACvjlDw8Li9S2hk/d51OI0nr/gIpdMHNepwgOKqZ/sy0Clpyg==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/unist": "^3.0.0", + "devlop": "^1.0.0", + "hastscript": "^9.0.0", + "property-information": "^7.0.0", + "vfile": "^6.0.0", + "vfile-location": "^5.0.0", + "web-namespaces": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-is-element": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/hast-util-is-element/-/hast-util-is-element-3.0.0.tgz", + "integrity": "sha512-Val9mnv2IWpLbNPqc/pUem+a7Ipj2aHacCwgNfTiK0vJKl0LF+4Ba4+v1oPHFpf3bLYmreq0/l3Gud9S5OH42g==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-parse-selector": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/hast-util-parse-selector/-/hast-util-parse-selector-4.0.0.tgz", + "integrity": "sha512-wkQCkSYoOGCRKERFWcxMVMOcYE2K1AaNLU8DXS9arxnLOUEWbOXKXiJUNzEpqZ3JOKpnha3jkFrumEjVliDe7A==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-raw": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/hast-util-raw/-/hast-util-raw-9.1.0.tgz", + "integrity": "sha512-Y8/SBAHkZGoNkpzqqfCldijcuUKh7/su31kEBp67cFY09Wy0mTRgtsLYsiIxMJxlu0f6AA5SUTbDR8K0rxnbUw==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/unist": "^3.0.0", + "@ungap/structured-clone": "^1.0.0", + "hast-util-from-parse5": "^8.0.0", + "hast-util-to-parse5": "^8.0.0", + "html-void-elements": "^3.0.0", + "mdast-util-to-hast": "^13.0.0", + "parse5": "^7.0.0", + "unist-util-position": "^5.0.0", + "unist-util-visit": "^5.0.0", + "vfile": "^6.0.0", + "web-namespaces": "^2.0.0", + "zwitch": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-to-html": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/hast-util-to-html/-/hast-util-to-html-9.0.5.tgz", + "integrity": "sha512-OguPdidb+fbHQSU4Q4ZiLKnzWo8Wwsf5bZfbvu7//a9oTYoqD/fWpe96NuHkoS9h0ccGOTe0C4NGXdtS0iObOw==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/unist": "^3.0.0", + "ccount": "^2.0.0", + "comma-separated-tokens": "^2.0.0", + "hast-util-whitespace": "^3.0.0", + "html-void-elements": "^3.0.0", + "mdast-util-to-hast": "^13.0.0", + "property-information": "^7.0.0", + "space-separated-tokens": "^2.0.0", + "stringify-entities": "^4.0.0", + "zwitch": "^2.0.4" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-to-parse5": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/hast-util-to-parse5/-/hast-util-to-parse5-8.0.1.tgz", + "integrity": "sha512-MlWT6Pjt4CG9lFCjiz4BH7l9wmrMkfkJYCxFwKQic8+RTZgWPuWxwAfjJElsXkex7DJjfSJsQIt931ilUgmwdA==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "comma-separated-tokens": "^2.0.0", + "devlop": "^1.0.0", + "property-information": "^7.0.0", + "space-separated-tokens": "^2.0.0", + "web-namespaces": "^2.0.0", + "zwitch": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-to-text": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/hast-util-to-text/-/hast-util-to-text-4.0.2.tgz", + "integrity": "sha512-KK6y/BN8lbaq654j7JgBydev7wuNMcID54lkRav1P0CaE1e47P72AWWPiGKXTJU271ooYzcvTAn/Zt0REnvc7A==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/unist": "^3.0.0", + "hast-util-is-element": "^3.0.0", + "unist-util-find-after": "^5.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-whitespace": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/hast-util-whitespace/-/hast-util-whitespace-3.0.0.tgz", + "integrity": "sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hastscript": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/hastscript/-/hastscript-9.0.1.tgz", + "integrity": "sha512-g7df9rMFX/SPi34tyGCyUBREQoKkapwdY/T04Qn9TDWfHhAYt4/I0gMVirzK5wEzeUqIjEB+LXC/ypb7Aqno5w==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "comma-separated-tokens": "^2.0.0", + "hast-util-parse-selector": "^4.0.0", + "property-information": "^7.0.0", + "space-separated-tokens": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/html-escaper": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-3.0.3.tgz", + "integrity": "sha512-RuMffC89BOWQoY0WKGpIhn5gX3iI54O6nRA0yC124NYVtzjmFWBIiFd8M0x+ZdX0P9R4lADg1mgP8C7PxGOWuQ==", + "license": "MIT" + }, + "node_modules/html-void-elements": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/html-void-elements/-/html-void-elements-3.0.0.tgz", + "integrity": "sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/http-cache-semantics": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.2.0.tgz", + "integrity": "sha512-dTxcvPXqPvXBQpq5dUr6mEMJX4oIEFv6bwom3FDwKRDsuIjjJGANqhBuoAn9c1RQJIdAKav33ED65E2ys+87QQ==", + "license": "BSD-2-Clause" + }, + "node_modules/http-errors": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", + "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", + "license": "MIT", + "dependencies": { + "depd": "~2.0.0", + "inherits": "~2.0.4", + "setprototypeof": "~1.2.0", + "statuses": "~2.0.2", + "toidentifier": "~1.0.1" + }, + "engines": { + "node": ">= 0.8" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/import-meta-resolve": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/import-meta-resolve/-/import-meta-resolve-4.2.0.tgz", + "integrity": "sha512-Iqv2fzaTQN28s/FwZAoFq0ZSs/7hMAHJVX+w8PZl3cY19Pxk6jFFalxQoIfW2826i/fDLXv8IiEZRIT0lDuWcg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "license": "ISC" + }, + "node_modules/iron-webcrypto": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/iron-webcrypto/-/iron-webcrypto-1.2.1.tgz", + "integrity": "sha512-feOM6FaSr6rEABp/eDfVseKyTMDt+KGpeB35SkVn9Tyn0CqvVsY3EwI0v5i8nMHyJnzCIQf7nsy3p41TPkJZhg==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/brc-dd" + } + }, + "node_modules/is-docker": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-3.0.0.tgz", + "integrity": "sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==", + "license": "MIT", + "bin": { + "is-docker": "cli.js" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-inside-container": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-inside-container/-/is-inside-container-1.0.0.tgz", + "integrity": "sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==", + "license": "MIT", + "dependencies": { + "is-docker": "^3.0.0" + }, + "bin": { + "is-inside-container": "cli.js" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-plain-obj": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz", + "integrity": "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-wsl": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-3.1.1.tgz", + "integrity": "sha512-e6rvdUCiQCAuumZslxRJWR/Doq4VpPR82kqclvcS0efgt430SlGIk05vdCN58+VrzgtIcfNODjozVielycD4Sw==", + "license": "MIT", + "dependencies": { + "is-inside-container": "^1.0.0" + }, + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/iso-datestring-validator": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/iso-datestring-validator/-/iso-datestring-validator-2.2.2.tgz", + "integrity": "sha512-yLEMkBbLZTlVQqOnQ4FiMujR6T4DEcCb1xizmvXS+OxuhwcbtynoosRzdMA69zZCShCNAbi+gJ71FxZBBXx1SA==", + "license": "MIT" + }, + "node_modules/js-base64": { + "version": "3.7.8", + "resolved": "https://registry.npmjs.org/js-base64/-/js-base64-3.7.8.tgz", + "integrity": "sha512-hNngCeKxIUQiEUN3GPJOkz4wF/YvdUdbNL9hsBcMQTkKzboD7T/q3OYOuuPZLUE6dBxSGpwhk5mwuDud7JVAow==", + "license": "BSD-3-Clause" + }, + "node_modules/js-yaml": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", + "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/kleur": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/kleur/-/kleur-4.1.5.tgz", + "integrity": "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/libsql": { + "version": "0.5.22", + "resolved": "https://registry.npmjs.org/libsql/-/libsql-0.5.22.tgz", + "integrity": "sha512-NscWthMQt7fpU8lqd7LXMvT9pi+KhhmTHAJWUB/Lj6MWa0MKFv0F2V4C6WKKpjCVZl0VwcDz4nOI3CyaT1DDiA==", + "cpu": [ + "x64", + "arm64", + "wasm32", + "arm" + ], + "license": "MIT", + "os": [ + "darwin", + "linux", + "win32" + ], + "dependencies": { + "@neon-rs/load": "^0.0.4", + "detect-libc": "2.0.2" + }, + "optionalDependencies": { + "@libsql/darwin-arm64": "0.5.22", + "@libsql/darwin-x64": "0.5.22", + "@libsql/linux-arm-gnueabihf": "0.5.22", + "@libsql/linux-arm-musleabihf": "0.5.22", + "@libsql/linux-arm64-gnu": "0.5.22", + "@libsql/linux-arm64-musl": "0.5.22", + "@libsql/linux-x64-gnu": "0.5.22", + "@libsql/linux-x64-musl": "0.5.22", + "@libsql/win32-x64-msvc": "0.5.22" + } + }, + "node_modules/longest-streak": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/longest-streak/-/longest-streak-3.1.0.tgz", + "integrity": "sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/lru-cache": { + "version": "11.2.7", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.7.tgz", + "integrity": "sha512-aY/R+aEsRelme17KGQa/1ZSIpLpNYYrhcrepKTZgE+W3WM16YMCaPwOHLHsmopZHELU0Ojin1lPVxKR0MihncA==", + "license": "BlueOak-1.0.0", + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/magic-string": { + "version": "0.30.21", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, + "node_modules/magicast": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/magicast/-/magicast-0.5.2.tgz", + "integrity": "sha512-E3ZJh4J3S9KfwdjZhe2afj6R9lGIN5Pher1pF39UGrXRqq/VDaGVIGN13BjHd2u8B61hArAGOnso7nBOouW3TQ==", + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.0", + "@babel/types": "^7.29.0", + "source-map-js": "^1.2.1" + } + }, + "node_modules/markdown-table": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/markdown-table/-/markdown-table-3.0.4.tgz", + "integrity": "sha512-wiYz4+JrLyb/DqW2hkFJxP7Vd7JuTDm77fvbM8VfEQdmSMqcImWeeRbHwZjBjIFki/VaMK2BhFi7oUUZeM5bqw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/mdast-util-definitions": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-definitions/-/mdast-util-definitions-6.0.0.tgz", + "integrity": "sha512-scTllyX6pnYNZH/AIp/0ePz6s4cZtARxImwoPJ7kS42n+MnVsI4XbnG6d4ibehRIldYMWM2LD7ImQblVhUejVQ==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "@types/unist": "^3.0.0", + "unist-util-visit": "^5.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-find-and-replace": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/mdast-util-find-and-replace/-/mdast-util-find-and-replace-3.0.2.tgz", + "integrity": "sha512-Tmd1Vg/m3Xz43afeNxDIhWRtFZgM2VLyaf4vSTYwudTyeuTneoL3qtWMA5jeLyz/O1vDJmmV4QuScFCA2tBPwg==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "escape-string-regexp": "^5.0.0", + "unist-util-is": "^6.0.0", + "unist-util-visit-parents": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-from-markdown": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/mdast-util-from-markdown/-/mdast-util-from-markdown-2.0.3.tgz", + "integrity": "sha512-W4mAWTvSlKvf8L6J+VN9yLSqQ9AOAAvHuoDAmPkz4dHf553m5gVj2ejadHJhoJmcmxEnOv6Pa8XJhpxE93kb8Q==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "@types/unist": "^3.0.0", + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "mdast-util-to-string": "^4.0.0", + "micromark": "^4.0.0", + "micromark-util-decode-numeric-character-reference": "^2.0.0", + "micromark-util-decode-string": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0", + "unist-util-stringify-position": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/mdast-util-gfm/-/mdast-util-gfm-3.1.0.tgz", + "integrity": "sha512-0ulfdQOM3ysHhCJ1p06l0b0VKlhU0wuQs3thxZQagjcjPrlFRqY215uZGHHJan9GEAXd9MbfPjFJz+qMkVR6zQ==", + "license": "MIT", + "dependencies": { + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-gfm-autolink-literal": "^2.0.0", + "mdast-util-gfm-footnote": "^2.0.0", + "mdast-util-gfm-strikethrough": "^2.0.0", + "mdast-util-gfm-table": "^2.0.0", + "mdast-util-gfm-task-list-item": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm-autolink-literal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-autolink-literal/-/mdast-util-gfm-autolink-literal-2.0.1.tgz", + "integrity": "sha512-5HVP2MKaP6L+G6YaxPNjuL0BPrq9orG3TsrZ9YXbA3vDw/ACI4MEsnoDpn6ZNm7GnZgtAcONJyPhOP8tNJQavQ==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "ccount": "^2.0.0", + "devlop": "^1.0.0", + "mdast-util-find-and-replace": "^3.0.0", + "micromark-util-character": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm-footnote": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-footnote/-/mdast-util-gfm-footnote-2.1.0.tgz", + "integrity": "sha512-sqpDWlsHn7Ac9GNZQMeUzPQSMzR6Wv0WKRNvQRg0KqHh02fpTz69Qc1QSseNX29bhz1ROIyNyxExfawVKTm1GQ==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "devlop": "^1.1.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm-strikethrough": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-strikethrough/-/mdast-util-gfm-strikethrough-2.0.0.tgz", + "integrity": "sha512-mKKb915TF+OC5ptj5bJ7WFRPdYtuHv0yTRxK2tJvi+BDqbkiG7h7u/9SI89nRAYcmap2xHQL9D+QG/6wSrTtXg==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm-table": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-table/-/mdast-util-gfm-table-2.0.0.tgz", + "integrity": "sha512-78UEvebzz/rJIxLvE7ZtDd/vIQ0RHv+3Mh5DR96p7cS7HsBhYIICDBCu8csTNWNO6tBWfqXPWekRuj2FNOGOZg==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "markdown-table": "^3.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm-task-list-item": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-task-list-item/-/mdast-util-gfm-task-list-item-2.0.0.tgz", + "integrity": "sha512-IrtvNvjxC1o06taBAVJznEnkiHxLFTzgonUdy8hzFVeDun0uTjxxrRGVaNFqkU1wJR3RBPEfsxmU6jDWPofrTQ==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-phrasing": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/mdast-util-phrasing/-/mdast-util-phrasing-4.1.0.tgz", + "integrity": "sha512-TqICwyvJJpBwvGAMZjj4J2n0X8QWp21b9l0o7eXyVJ25YNWYbJDVIyD1bZXE6WtV6RmKJVYmQAKWa0zWOABz2w==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "unist-util-is": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-to-hast": { + "version": "13.2.1", + "resolved": "https://registry.npmjs.org/mdast-util-to-hast/-/mdast-util-to-hast-13.2.1.tgz", + "integrity": "sha512-cctsq2wp5vTsLIcaymblUriiTcZd0CwWtCbLvrOzYCDZoWyMNV8sZ7krj09FSnsiJi3WVsHLM4k6Dq/yaPyCXA==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "@ungap/structured-clone": "^1.0.0", + "devlop": "^1.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "trim-lines": "^3.0.0", + "unist-util-position": "^5.0.0", + "unist-util-visit": "^5.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-to-markdown": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/mdast-util-to-markdown/-/mdast-util-to-markdown-2.1.2.tgz", + "integrity": "sha512-xj68wMTvGXVOKonmog6LwyJKrYXZPvlwabaryTjLh9LuvovB/KAH+kvi8Gjj+7rJjsFi23nkUxRQv1KqSroMqA==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "@types/unist": "^3.0.0", + "longest-streak": "^3.0.0", + "mdast-util-phrasing": "^4.0.0", + "mdast-util-to-string": "^4.0.0", + "micromark-util-classify-character": "^2.0.0", + "micromark-util-decode-string": "^2.0.0", + "unist-util-visit": "^5.0.0", + "zwitch": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-to-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-to-string/-/mdast-util-to-string-4.0.0.tgz", + "integrity": "sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdn-data": { + "version": "2.27.1", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.27.1.tgz", + "integrity": "sha512-9Yubnt3e8A0OKwxYSXyhLymGW4sCufcLG6VdiDdUGVkPhpqLxlvP5vl1983gQjJl3tqbrM731mjaZaP68AgosQ==", + "license": "CC0-1.0" + }, + "node_modules/micromark": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/micromark/-/micromark-4.0.2.tgz", + "integrity": "sha512-zpe98Q6kvavpCr1NPVSCMebCKfD7CA2NqZ+rykeNhONIJBpc1tFKt9hucLGwha3jNTNI8lHpctWJWoimVF4PfA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "@types/debug": "^4.0.0", + "debug": "^4.0.0", + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "micromark-core-commonmark": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-combine-extensions": "^2.0.0", + "micromark-util-decode-numeric-character-reference": "^2.0.0", + "micromark-util-encode": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-resolve-all": "^2.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "micromark-util-subtokenize": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-core-commonmark": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/micromark-core-commonmark/-/micromark-core-commonmark-2.0.3.tgz", + "integrity": "sha512-RDBrHEMSxVFLg6xvnXmb1Ayr2WzLAWjeSATAoxwKYJV94TeNavgoIdA0a9ytzDSVzBy2YKFK+emCPOEibLeCrg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "micromark-factory-destination": "^2.0.0", + "micromark-factory-label": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-factory-title": "^2.0.0", + "micromark-factory-whitespace": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-classify-character": "^2.0.0", + "micromark-util-html-tag-name": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-resolve-all": "^2.0.0", + "micromark-util-subtokenize": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-extension-gfm": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm/-/micromark-extension-gfm-3.0.0.tgz", + "integrity": "sha512-vsKArQsicm7t0z2GugkCKtZehqUm31oeGBV/KVSorWSy8ZlNAv7ytjFhvaryUiCUJYqs+NoE6AFhpQvBTM6Q4w==", + "license": "MIT", + "dependencies": { + "micromark-extension-gfm-autolink-literal": "^2.0.0", + "micromark-extension-gfm-footnote": "^2.0.0", + "micromark-extension-gfm-strikethrough": "^2.0.0", + "micromark-extension-gfm-table": "^2.0.0", + "micromark-extension-gfm-tagfilter": "^2.0.0", + "micromark-extension-gfm-task-list-item": "^2.0.0", + "micromark-util-combine-extensions": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-autolink-literal": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-autolink-literal/-/micromark-extension-gfm-autolink-literal-2.1.0.tgz", + "integrity": "sha512-oOg7knzhicgQ3t4QCjCWgTmfNhvQbDDnJeVu9v81r7NltNCVmhPy1fJRX27pISafdjL+SVc4d3l48Gb6pbRypw==", + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-footnote": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-footnote/-/micromark-extension-gfm-footnote-2.1.0.tgz", + "integrity": "sha512-/yPhxI1ntnDNsiHtzLKYnE3vf9JZ6cAisqVDauhp4CEHxlb4uoOTxOCJ+9s51bIB8U1N1FJ1RXOKTIlD5B/gqw==", + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-core-commonmark": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-strikethrough": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-strikethrough/-/micromark-extension-gfm-strikethrough-2.1.0.tgz", + "integrity": "sha512-ADVjpOOkjz1hhkZLlBiYA9cR2Anf8F4HqZUO6e5eDcPQd0Txw5fxLzzxnEkSkfnD0wziSGiv7sYhk/ktvbf1uw==", + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-classify-character": "^2.0.0", + "micromark-util-resolve-all": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-table": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-table/-/micromark-extension-gfm-table-2.1.1.tgz", + "integrity": "sha512-t2OU/dXXioARrC6yWfJ4hqB7rct14e8f7m0cbI5hUmDyyIlwv5vEtooptH8INkbLzOatzKuVbQmAYcbWoyz6Dg==", + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-tagfilter": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-tagfilter/-/micromark-extension-gfm-tagfilter-2.0.0.tgz", + "integrity": "sha512-xHlTOmuCSotIA8TW1mDIM6X2O1SiX5P9IuDtqGonFhEK0qgRI4yeC6vMxEV2dgyr2TiD+2PQ10o+cOhdVAcwfg==", + "license": "MIT", + "dependencies": { + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-task-list-item": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-task-list-item/-/micromark-extension-gfm-task-list-item-2.1.0.tgz", + "integrity": "sha512-qIBZhqxqI6fjLDYFTBIa4eivDMnP+OZqsNwmQ3xNLE4Cxwc+zfQEfbs6tzAo2Hjq+bh6q5F+Z8/cksrLFYWQQw==", + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-factory-destination": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-destination/-/micromark-factory-destination-2.0.1.tgz", + "integrity": "sha512-Xe6rDdJlkmbFRExpTOmRj9N3MaWmbAgdpSrBQvCFqhezUn4AHqJHbaEnfbVYYiexVSs//tqOdY/DxhjdCiJnIA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-label": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-label/-/micromark-factory-label-2.0.1.tgz", + "integrity": "sha512-VFMekyQExqIW7xIChcXn4ok29YE3rnuyveW3wZQWWqF4Nv9Wk5rgJ99KzPvHjkmPXF93FXIbBp6YdW3t71/7Vg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-space": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.1.tgz", + "integrity": "sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-title": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-title/-/micromark-factory-title-2.0.1.tgz", + "integrity": "sha512-5bZ+3CjhAd9eChYTHsjy6TGxpOFSKgKKJPJxr293jTbfry2KDoWkhBb6TcPVB4NmzaPhMs1Frm9AZH7OD4Cjzw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-whitespace": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-whitespace/-/micromark-factory-whitespace-2.0.1.tgz", + "integrity": "sha512-Ob0nuZ3PKt/n0hORHyvoD9uZhr+Za8sFoP+OnMcnWK5lngSzALgQYKMr9RJVOWLqQYuyn6ulqGWSXdwf6F80lQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-character": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", + "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-chunked": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-chunked/-/micromark-util-chunked-2.0.1.tgz", + "integrity": "sha512-QUNFEOPELfmvv+4xiNg2sRYeS/P84pTW0TCgP5zc9FpXetHY0ab7SxKyAQCNCc1eK0459uoLI1y5oO5Vc1dbhA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-classify-character": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-classify-character/-/micromark-util-classify-character-2.0.1.tgz", + "integrity": "sha512-K0kHzM6afW/MbeWYWLjoHQv1sgg2Q9EccHEDzSkxiP/EaagNzCm7T/WMKZ3rjMbvIpvBiZgwR3dKMygtA4mG1Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-combine-extensions": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-combine-extensions/-/micromark-util-combine-extensions-2.0.1.tgz", + "integrity": "sha512-OnAnH8Ujmy59JcyZw8JSbK9cGpdVY44NKgSM7E9Eh7DiLS2E9RNQf0dONaGDzEG9yjEl5hcqeIsj4hfRkLH/Bg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-chunked": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-decode-numeric-character-reference": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/micromark-util-decode-numeric-character-reference/-/micromark-util-decode-numeric-character-reference-2.0.2.tgz", + "integrity": "sha512-ccUbYk6CwVdkmCQMyr64dXz42EfHGkPQlBj5p7YVGzq8I7CtjXZJrubAYezf7Rp+bjPseiROqe7G6foFd+lEuw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-decode-string": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-decode-string/-/micromark-util-decode-string-2.0.1.tgz", + "integrity": "sha512-nDV/77Fj6eH1ynwscYTOsbK7rR//Uj0bZXBwJZRfaLEJ1iGBR6kIfNmlNqaqJf649EP0F3NWNdeJi03elllNUQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "decode-named-character-reference": "^1.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-decode-numeric-character-reference": "^2.0.0", + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-encode": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-encode/-/micromark-util-encode-2.0.1.tgz", + "integrity": "sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-util-html-tag-name": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-html-tag-name/-/micromark-util-html-tag-name-2.0.1.tgz", + "integrity": "sha512-2cNEiYDhCWKI+Gs9T0Tiysk136SnR13hhO8yW6BGNyhOC4qYFnwF1nKfD3HFAIXA5c45RrIG1ub11GiXeYd1xA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-util-normalize-identifier": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-normalize-identifier/-/micromark-util-normalize-identifier-2.0.1.tgz", + "integrity": "sha512-sxPqmo70LyARJs0w2UclACPUUEqltCkJ6PhKdMIDuJ3gSf/Q+/GIe3WKl0Ijb/GyH9lOpUkRAO2wp0GVkLvS9Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-resolve-all": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-resolve-all/-/micromark-util-resolve-all-2.0.1.tgz", + "integrity": "sha512-VdQyxFWFT2/FGJgwQnJYbe1jjQoNTS4RjglmSjTUlpUMa95Htx9NHeYW4rGDJzbjvCsl9eLjMQwGeElsqmzcHg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-sanitize-uri": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-2.0.1.tgz", + "integrity": "sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-encode": "^2.0.0", + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-subtokenize": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-subtokenize/-/micromark-util-subtokenize-2.1.0.tgz", + "integrity": "sha512-XQLu552iSctvnEcgXw6+Sx75GflAPNED1qx7eBJ+wydBb2KCbRZe+NwvIEEMM83uml1+2WSXpBAcp9IUCgCYWA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-symbol": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-util-types": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-2.0.2.tgz", + "integrity": "sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/mime-db": { + "version": "1.54.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz", + "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==", + "license": "MIT", + "dependencies": { + "mime-db": "^1.54.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/mrmime": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mrmime/-/mrmime-2.0.1.tgz", + "integrity": "sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ==", + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/multiformats": { + "version": "9.9.0", + "resolved": "https://registry.npmjs.org/multiformats/-/multiformats-9.9.0.tgz", + "integrity": "sha512-HoMUjhH9T8DDBNT+6xzkrd9ga/XiBI4xLr58LJACwK6G3HTOPeMz4nB4KJs33L2BelrIJa7P0VuNaVF3hMYfjg==", + "license": "(Apache-2.0 AND MIT)" + }, + "node_modules/nanoid": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-5.1.6.tgz", + "integrity": "sha512-c7+7RQ+dMB5dPwwCp4ee1/iV/q2P6aK1mTZcfr1BTuVlyW9hJYiMPybJCcnBlQtuSmTIWNeazm/zqNoZSSElBg==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.js" + }, + "engines": { + "node": "^18 || >=20" + } + }, + "node_modules/neotraverse": { + "version": "0.6.18", + "resolved": "https://registry.npmjs.org/neotraverse/-/neotraverse-0.6.18.tgz", + "integrity": "sha512-Z4SmBUweYa09+o6pG+eASabEpP6QkQ70yHj351pQoEXIs8uHbaU2DWVmzBANKgflPa47A50PtB2+NgRpQvr7vA==", + "license": "MIT", + "engines": { + "node": ">= 10" + } + }, + "node_modules/nlcst-to-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/nlcst-to-string/-/nlcst-to-string-4.0.0.tgz", + "integrity": "sha512-YKLBCcUYKAg0FNlOBT6aI91qFmSiFKiluk655WzPF+DDMA02qIyy8uiRqI8QXtcFpEvll12LpL5MXqEmAZ+dcA==", + "license": "MIT", + "dependencies": { + "@types/nlcst": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/node-domexception": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz", + "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==", + "deprecated": "Use your platform's native DOMException instead", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/jimmywarting" + }, + { + "type": "github", + "url": "https://paypal.me/jimmywarting" + } + ], + "license": "MIT", + "engines": { + "node": ">=10.5.0" + } + }, + "node_modules/node-fetch": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.2.tgz", + "integrity": "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==", + "license": "MIT", + "dependencies": { + "data-uri-to-buffer": "^4.0.0", + "fetch-blob": "^3.1.4", + "formdata-polyfill": "^4.0.10" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/node-fetch" + } + }, + "node_modules/node-fetch-native": { + "version": "1.6.7", + "resolved": "https://registry.npmjs.org/node-fetch-native/-/node-fetch-native-1.6.7.tgz", + "integrity": "sha512-g9yhqoedzIUm0nTnTqAQvueMPVOuIY16bqgAJJC8XOOubYFNwz6IER9qs0Gq2Xd0+CecCKFjtdDTMA4u4xG06Q==", + "license": "MIT" + }, + "node_modules/node-mock-http": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/node-mock-http/-/node-mock-http-1.0.4.tgz", + "integrity": "sha512-8DY+kFsDkNXy1sJglUfuODx1/opAGJGyrTuFqEoN90oRc2Vk0ZbD4K2qmKXBBEhZQzdKHIVfEJpDU8Ak2NJEvQ==", + "license": "MIT" + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/nth-check": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", + "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==", + "license": "BSD-2-Clause", + "dependencies": { + "boolbase": "^1.0.0" + }, + "funding": { + "url": "https://github.com/fb55/nth-check?sponsor=1" + } + }, + "node_modules/ofetch": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/ofetch/-/ofetch-1.5.1.tgz", + "integrity": "sha512-2W4oUZlVaqAPAil6FUg/difl6YhqhUR7x2eZY4bQCko22UXg3hptq9KLQdqFClV+Wu85UX7hNtdGTngi/1BxcA==", + "license": "MIT", + "dependencies": { + "destr": "^2.0.5", + "node-fetch-native": "^1.6.7", + "ufo": "^1.6.1" + } + }, + "node_modules/ohash": { + "version": "2.0.11", + "resolved": "https://registry.npmjs.org/ohash/-/ohash-2.0.11.tgz", + "integrity": "sha512-RdR9FQrFwNBNXAr4GixM8YaRZRJ5PUWbKYbE5eOsrwAjJW0q2REGcf79oYPsLyskQCZG1PLN+S/K1V00joZAoQ==", + "license": "MIT" + }, + "node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "license": "MIT", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/oniguruma-parser": { + "version": "0.12.1", + "resolved": "https://registry.npmjs.org/oniguruma-parser/-/oniguruma-parser-0.12.1.tgz", + "integrity": "sha512-8Unqkvk1RYc6yq2WBYRj4hdnsAxVze8i7iPfQr8e4uSP3tRv0rpZcbGUDvxfQQcdwHt/e9PrMvGCsa8OqG9X3w==", + "license": "MIT" + }, + "node_modules/oniguruma-to-es": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/oniguruma-to-es/-/oniguruma-to-es-4.3.4.tgz", + "integrity": "sha512-3VhUGN3w2eYxnTzHn+ikMI+fp/96KoRSVK9/kMTcFqj1NRDh2IhQCKvYxDnWePKRXY/AqH+Fuiyb7VHSzBjHfA==", + "license": "MIT", + "dependencies": { + "oniguruma-parser": "^0.12.1", + "regex": "^6.0.1", + "regex-recursion": "^6.0.2" + } + }, + "node_modules/p-limit": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-6.2.0.tgz", + "integrity": "sha512-kuUqqHNUqoIWp/c467RI4X6mmyuojY5jGutNU0wVTmEOOfcuwLqyMVoAi9MKi2Ak+5i9+nhmrK4ufZE8069kHA==", + "license": "MIT", + "dependencies": { + "yocto-queue": "^1.1.1" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-queue": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/p-queue/-/p-queue-8.1.1.tgz", + "integrity": "sha512-aNZ+VfjobsWryoiPnEApGGmf5WmNsCo9xu8dfaYamG5qaLP7ClhLN6NgsFe6SwJ2UbLEBK5dv9x8Mn5+RVhMWQ==", + "license": "MIT", + "dependencies": { + "eventemitter3": "^5.0.1", + "p-timeout": "^6.1.2" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-timeout": { + "version": "6.1.4", + "resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-6.1.4.tgz", + "integrity": "sha512-MyIV3ZA/PmyBN/ud8vV9XzwTrNtR4jFrObymZYnZqMmW0zA8Z17vnT0rBgFE/TlohB+YCHqXMgZzb3Csp49vqg==", + "license": "MIT", + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/package-manager-detector": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/package-manager-detector/-/package-manager-detector-1.6.0.tgz", + "integrity": "sha512-61A5ThoTiDG/C8s8UMZwSorAGwMJ0ERVGj2OjoW5pAalsNOg15+iQiPzrLJ4jhZ1HJzmC2PIHT2oEiH3R5fzNA==", + "license": "MIT" + }, + "node_modules/parse-latin": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/parse-latin/-/parse-latin-7.0.0.tgz", + "integrity": "sha512-mhHgobPPua5kZ98EF4HWiH167JWBfl4pvAIXXdbaVohtK7a6YBOy56kvhCqduqyo/f3yrHFWmqmiMg/BkBkYYQ==", + "license": "MIT", + "dependencies": { + "@types/nlcst": "^2.0.0", + "@types/unist": "^3.0.0", + "nlcst-to-string": "^4.0.0", + "unist-util-modify-children": "^4.0.0", + "unist-util-visit-children": "^3.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/parse5": { + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.3.0.tgz", + "integrity": "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==", + "license": "MIT", + "dependencies": { + "entities": "^6.0.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/piccolore": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/piccolore/-/piccolore-0.1.3.tgz", + "integrity": "sha512-o8bTeDWjE086iwKrROaDf31K0qC/BENdm15/uH9usSC/uZjJOKb2YGiVHfLY4GhwsERiPI1jmwI2XrA7ACOxVw==", + "license": "ISC" + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/postcss": { + "version": "8.5.8", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.8.tgz", + "integrity": "sha512-OW/rX8O/jXnm82Ey1k44pObPtdblfiuWnrd8X7GJ7emImCOstunGbXUpp7HdBrFQX6rJzn3sPT397Wp5aCwCHg==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/postcss/node_modules/nanoid": { + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/prismjs": { + "version": "1.30.0", + "resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.30.0.tgz", + "integrity": "sha512-DEvV2ZF2r2/63V+tK8hQvrR2ZGn10srHbXviTlcv7Kpzw8jWiNTqbVgjO3IY8RxrrOUF8VPMQQFysYYYv0YZxw==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/promise-limit": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/promise-limit/-/promise-limit-2.7.0.tgz", + "integrity": "sha512-7nJ6v5lnJsXwGprnGXga4wx6d1POjvi5Qmf1ivTRxTjH4Z/9Czja/UCMLVmB9N93GeWOU93XaFaEt6jbuoagNw==", + "license": "ISC" + }, + "node_modules/prompts": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", + "integrity": "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==", + "license": "MIT", + "dependencies": { + "kleur": "^3.0.3", + "sisteransi": "^1.0.5" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/prompts/node_modules/kleur": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", + "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/property-information": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/property-information/-/property-information-7.1.0.tgz", + "integrity": "sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/radix3": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/radix3/-/radix3-1.1.2.tgz", + "integrity": "sha512-b484I/7b8rDEdSDKckSSBA8knMpcdsXudlE/LNL639wFoHKwLbEkQFZHWEYwDC0wa0FKUcCY+GAF73Z7wxNVFA==", + "license": "MIT" + }, + "node_modules/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/readdirp": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-5.0.0.tgz", + "integrity": "sha512-9u/XQ1pvrQtYyMpZe7DXKv2p5CNvyVwzUB6uhLAnQwHMSgKMBR62lc7AHljaeteeHXn11XTAaLLUVZYVZyuRBQ==", + "license": "MIT", + "engines": { + "node": ">= 20.19.0" + }, + "funding": { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/regex": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/regex/-/regex-6.1.0.tgz", + "integrity": "sha512-6VwtthbV4o/7+OaAF9I5L5V3llLEsoPyq9P1JVXkedTP33c7MfCG0/5NOPcSJn0TzXcG9YUrR0gQSWioew3LDg==", + "license": "MIT", + "dependencies": { + "regex-utilities": "^2.3.0" + } + }, + "node_modules/regex-recursion": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/regex-recursion/-/regex-recursion-6.0.2.tgz", + "integrity": "sha512-0YCaSCq2VRIebiaUviZNs0cBz1kg5kVS2UKUfNIx8YVs1cN3AV7NTctO5FOKBA+UT2BPJIWZauYHPqJODG50cg==", + "license": "MIT", + "dependencies": { + "regex-utilities": "^2.3.0" + } + }, + "node_modules/regex-utilities": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/regex-utilities/-/regex-utilities-2.3.0.tgz", + "integrity": "sha512-8VhliFJAWRaUiVvREIiW2NXXTmHs4vMNnSzuJVhscgmGav3g9VDxLrQndI3dZZVVdp0ZO/5v0xmX516/7M9cng==", + "license": "MIT" + }, + "node_modules/rehype": { + "version": "13.0.2", + "resolved": "https://registry.npmjs.org/rehype/-/rehype-13.0.2.tgz", + "integrity": "sha512-j31mdaRFrwFRUIlxGeuPXXKWQxet52RBQRvCmzl5eCefn/KGbomK5GMHNMsOJf55fgo3qw5tST5neDuarDYR2A==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "rehype-parse": "^9.0.0", + "rehype-stringify": "^10.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/rehype-parse": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/rehype-parse/-/rehype-parse-9.0.1.tgz", + "integrity": "sha512-ksCzCD0Fgfh7trPDxr2rSylbwq9iYDkSn8TCDmEJ49ljEUBxDVCzCHv7QNzZOfODanX4+bWQ4WZqLCRWYLfhag==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "hast-util-from-html": "^2.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/rehype-raw": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/rehype-raw/-/rehype-raw-7.0.0.tgz", + "integrity": "sha512-/aE8hCfKlQeA8LmyeyQvQF3eBiLRGNlfBJEvWH7ivp9sBqs7TNqBL5X3v157rM4IFETqDnIOO+z5M/biZbo9Ww==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "hast-util-raw": "^9.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/rehype-stringify": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/rehype-stringify/-/rehype-stringify-10.0.1.tgz", + "integrity": "sha512-k9ecfXHmIPuFVI61B9DeLPN0qFHfawM6RsuX48hoqlaKSF61RskNjSm1lI8PhBEM0MRdLxVVm4WmTqJQccH9mA==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "hast-util-to-html": "^9.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-gfm": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/remark-gfm/-/remark-gfm-4.0.1.tgz", + "integrity": "sha512-1quofZ2RQ9EWdeN34S79+KExV1764+wCUGop5CPL1WGdD0ocPpu91lzPGbwWMECpEpd42kJGQwzRfyov9j4yNg==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "mdast-util-gfm": "^3.0.0", + "micromark-extension-gfm": "^3.0.0", + "remark-parse": "^11.0.0", + "remark-stringify": "^11.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-parse": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/remark-parse/-/remark-parse-11.0.0.tgz", + "integrity": "sha512-FCxlKLNGknS5ba/1lmpYijMUzX2esxW5xQqjWxw2eHFfS2MSdaHVINFmhjo+qN1WhZhNimq0dZATN9pH0IDrpA==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "mdast-util-from-markdown": "^2.0.0", + "micromark-util-types": "^2.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-rehype": { + "version": "11.1.2", + "resolved": "https://registry.npmjs.org/remark-rehype/-/remark-rehype-11.1.2.tgz", + "integrity": "sha512-Dh7l57ianaEoIpzbp0PC9UKAdCSVklD8E5Rpw7ETfbTl3FqcOOgq5q2LVDhgGCkaBv7p24JXikPdvhhmHvKMsw==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "mdast-util-to-hast": "^13.0.0", + "unified": "^11.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-smartypants": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/remark-smartypants/-/remark-smartypants-3.0.2.tgz", + "integrity": "sha512-ILTWeOriIluwEvPjv67v7Blgrcx+LZOkAUVtKI3putuhlZm84FnqDORNXPPm+HY3NdZOMhyDwZ1E+eZB/Df5dA==", + "license": "MIT", + "dependencies": { + "retext": "^9.0.0", + "retext-smartypants": "^6.0.0", + "unified": "^11.0.4", + "unist-util-visit": "^5.0.0" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/remark-stringify": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/remark-stringify/-/remark-stringify-11.0.0.tgz", + "integrity": "sha512-1OSmLd3awB/t8qdoEOMazZkNsfVTeY4fTsgzcQFdXNq8ToTN4ZGwrMnlda4K6smTFKD+GRV6O48i6Z4iKgPPpw==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "mdast-util-to-markdown": "^2.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/retext": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/retext/-/retext-9.0.0.tgz", + "integrity": "sha512-sbMDcpHCNjvlheSgMfEcVrZko3cDzdbe1x/e7G66dFp0Ff7Mldvi2uv6JkJQzdRcvLYE8CA8Oe8siQx8ZOgTcA==", + "license": "MIT", + "dependencies": { + "@types/nlcst": "^2.0.0", + "retext-latin": "^4.0.0", + "retext-stringify": "^4.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/retext-latin": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/retext-latin/-/retext-latin-4.0.0.tgz", + "integrity": "sha512-hv9woG7Fy0M9IlRQloq/N6atV82NxLGveq+3H2WOi79dtIYWN8OaxogDm77f8YnVXJL2VD3bbqowu5E3EMhBYA==", + "license": "MIT", + "dependencies": { + "@types/nlcst": "^2.0.0", + "parse-latin": "^7.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/retext-smartypants": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/retext-smartypants/-/retext-smartypants-6.2.0.tgz", + "integrity": "sha512-kk0jOU7+zGv//kfjXEBjdIryL1Acl4i9XNkHxtM7Tm5lFiCog576fjNC9hjoR7LTKQ0DsPWy09JummSsH1uqfQ==", + "license": "MIT", + "dependencies": { + "@types/nlcst": "^2.0.0", + "nlcst-to-string": "^4.0.0", + "unist-util-visit": "^5.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/retext-stringify": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/retext-stringify/-/retext-stringify-4.0.0.tgz", + "integrity": "sha512-rtfN/0o8kL1e+78+uxPTqu1Klt0yPzKuQ2BfWwwfgIUSayyzxpM1PJzkKt4V8803uB9qSy32MvI7Xep9khTpiA==", + "license": "MIT", + "dependencies": { + "@types/nlcst": "^2.0.0", + "nlcst-to-string": "^4.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/rollup": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.59.0.tgz", + "integrity": "sha512-2oMpl67a3zCH9H79LeMcbDhXW/UmWG/y2zuqnF2jQq5uq9TbM9TVyXvA4+t+ne2IIkBdrLpAaRQAvo7YI/Yyeg==", + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.8" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.59.0", + "@rollup/rollup-android-arm64": "4.59.0", + "@rollup/rollup-darwin-arm64": "4.59.0", + "@rollup/rollup-darwin-x64": "4.59.0", + "@rollup/rollup-freebsd-arm64": "4.59.0", + "@rollup/rollup-freebsd-x64": "4.59.0", + "@rollup/rollup-linux-arm-gnueabihf": "4.59.0", + "@rollup/rollup-linux-arm-musleabihf": "4.59.0", + "@rollup/rollup-linux-arm64-gnu": "4.59.0", + "@rollup/rollup-linux-arm64-musl": "4.59.0", + "@rollup/rollup-linux-loong64-gnu": "4.59.0", + "@rollup/rollup-linux-loong64-musl": "4.59.0", + "@rollup/rollup-linux-ppc64-gnu": "4.59.0", + "@rollup/rollup-linux-ppc64-musl": "4.59.0", + "@rollup/rollup-linux-riscv64-gnu": "4.59.0", + "@rollup/rollup-linux-riscv64-musl": "4.59.0", + "@rollup/rollup-linux-s390x-gnu": "4.59.0", + "@rollup/rollup-linux-x64-gnu": "4.59.0", + "@rollup/rollup-linux-x64-musl": "4.59.0", + "@rollup/rollup-openbsd-x64": "4.59.0", + "@rollup/rollup-openharmony-arm64": "4.59.0", + "@rollup/rollup-win32-arm64-msvc": "4.59.0", + "@rollup/rollup-win32-ia32-msvc": "4.59.0", + "@rollup/rollup-win32-x64-gnu": "4.59.0", + "@rollup/rollup-win32-x64-msvc": "4.59.0", + "fsevents": "~2.3.2" + } + }, + "node_modules/sax": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.5.0.tgz", + "integrity": "sha512-21IYA3Q5cQf089Z6tgaUTr7lDAyzoTPx5HRtbhsME8Udispad8dC/+sziTNugOEx54ilvatQ9YCzl4KQLPcRHA==", + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=11.0.0" + } + }, + "node_modules/semver": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/send": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/send/-/send-1.2.1.tgz", + "integrity": "sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ==", + "license": "MIT", + "dependencies": { + "debug": "^4.4.3", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "fresh": "^2.0.0", + "http-errors": "^2.0.1", + "mime-types": "^3.0.2", + "ms": "^2.1.3", + "on-finished": "^2.4.1", + "range-parser": "^1.2.1", + "statuses": "^2.0.2" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/server-destroy": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/server-destroy/-/server-destroy-1.0.1.tgz", + "integrity": "sha512-rb+9B5YBIEzYcD6x2VKidaa+cqYBJQKnU4oe4E3ANwRRN56yk/ua1YCJT1n21NTS8w6CcOclAKNP3PhdCXKYtQ==", + "license": "ISC" + }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "license": "ISC" + }, + "node_modules/sharp": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.34.5.tgz", + "integrity": "sha512-Ou9I5Ft9WNcCbXrU9cMgPBcCK8LiwLqcbywW3t4oDV37n1pzpuNLsYiAV8eODnjbtQlSDwZ2cUEeQz4E54Hltg==", + "hasInstallScript": true, + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "@img/colour": "^1.0.0", + "detect-libc": "^2.1.2", + "semver": "^7.7.3" + }, + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-darwin-arm64": "0.34.5", + "@img/sharp-darwin-x64": "0.34.5", + "@img/sharp-libvips-darwin-arm64": "1.2.4", + "@img/sharp-libvips-darwin-x64": "1.2.4", + "@img/sharp-libvips-linux-arm": "1.2.4", + "@img/sharp-libvips-linux-arm64": "1.2.4", + "@img/sharp-libvips-linux-ppc64": "1.2.4", + "@img/sharp-libvips-linux-riscv64": "1.2.4", + "@img/sharp-libvips-linux-s390x": "1.2.4", + "@img/sharp-libvips-linux-x64": "1.2.4", + "@img/sharp-libvips-linuxmusl-arm64": "1.2.4", + "@img/sharp-libvips-linuxmusl-x64": "1.2.4", + "@img/sharp-linux-arm": "0.34.5", + "@img/sharp-linux-arm64": "0.34.5", + "@img/sharp-linux-ppc64": "0.34.5", + "@img/sharp-linux-riscv64": "0.34.5", + "@img/sharp-linux-s390x": "0.34.5", + "@img/sharp-linux-x64": "0.34.5", + "@img/sharp-linuxmusl-arm64": "0.34.5", + "@img/sharp-linuxmusl-x64": "0.34.5", + "@img/sharp-wasm32": "0.34.5", + "@img/sharp-win32-arm64": "0.34.5", + "@img/sharp-win32-ia32": "0.34.5", + "@img/sharp-win32-x64": "0.34.5" + } + }, + "node_modules/sharp/node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "license": "Apache-2.0", + "optional": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/shiki": { + "version": "3.23.0", + "resolved": "https://registry.npmjs.org/shiki/-/shiki-3.23.0.tgz", + "integrity": "sha512-55Dj73uq9ZXL5zyeRPzHQsK7Nbyt6Y10k5s7OjuFZGMhpp4r/rsLBH0o/0fstIzX1Lep9VxefWljK/SKCzygIA==", + "license": "MIT", + "dependencies": { + "@shikijs/core": "3.23.0", + "@shikijs/engine-javascript": "3.23.0", + "@shikijs/engine-oniguruma": "3.23.0", + "@shikijs/langs": "3.23.0", + "@shikijs/themes": "3.23.0", + "@shikijs/types": "3.23.0", + "@shikijs/vscode-textmate": "^10.0.2", + "@types/hast": "^3.0.4" + } + }, + "node_modules/sisteransi": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", + "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", + "license": "MIT" + }, + "node_modules/smol-toml": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/smol-toml/-/smol-toml-1.6.0.tgz", + "integrity": "sha512-4zemZi0HvTnYwLfrpk/CF9LOd9Lt87kAt50GnqhMpyF9U3poDAP2+iukq2bZsO/ufegbYehBkqINbsWxj4l4cw==", + "license": "BSD-3-Clause", + "engines": { + "node": ">= 18" + }, + "funding": { + "url": "https://github.com/sponsors/cyyynthia" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/space-separated-tokens": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/space-separated-tokens/-/space-separated-tokens-2.0.2.tgz", + "integrity": "sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/statuses": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", + "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/string-width": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", + "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", + "license": "MIT", + "dependencies": { + "emoji-regex": "^10.3.0", + "get-east-asian-width": "^1.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/stringify-entities": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/stringify-entities/-/stringify-entities-4.0.4.tgz", + "integrity": "sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==", + "license": "MIT", + "dependencies": { + "character-entities-html4": "^2.0.0", + "character-entities-legacy": "^3.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/strip-ansi": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", + "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.2.2" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/svgo": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/svgo/-/svgo-4.0.1.tgz", + "integrity": "sha512-XDpWUOPC6FEibaLzjfe0ucaV0YrOjYotGJO1WpF0Zd+n6ZGEQUsSugaoLq9QkEZtAfQIxT42UChcssDVPP3+/w==", + "license": "MIT", + "dependencies": { + "commander": "^11.1.0", + "css-select": "^5.1.0", + "css-tree": "^3.0.1", + "css-what": "^6.1.0", + "csso": "^5.0.5", + "picocolors": "^1.1.1", + "sax": "^1.5.0" + }, + "bin": { + "svgo": "bin/svgo.js" + }, + "engines": { + "node": ">=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/svgo" + } + }, + "node_modules/tiny-inflate": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/tiny-inflate/-/tiny-inflate-1.0.3.tgz", + "integrity": "sha512-pkY1fj1cKHb2seWDy0B16HeWyczlJA9/WW3u3c4z/NiWDsO3DOU5D7nhTLE9CF0yXv/QZFY7sEJmj24dK+Rrqw==", + "license": "MIT" + }, + "node_modules/tinyexec": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.0.4.tgz", + "integrity": "sha512-u9r3uZC0bdpGOXtlxUIdwf9pkmvhqJdrVCH9fapQtgy/OeTTMZ1nqH7agtvEfmGui6e1XxjcdrlxvxJvc3sMqw==", + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.15", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", + "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.3" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tlds": { + "version": "1.261.0", + "resolved": "https://registry.npmjs.org/tlds/-/tlds-1.261.0.tgz", + "integrity": "sha512-QXqwfEl9ddlGBaRFXIvNKK6OhipSiLXuRuLJX5DErz0o0Q0rYxulWLdFryTkV5PkdZct5iMInwYEGe/eR++1AA==", + "license": "MIT", + "bin": { + "tlds": "bin.js" + } + }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "license": "MIT", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/trim-lines": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/trim-lines/-/trim-lines-3.0.1.tgz", + "integrity": "sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/trough": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/trough/-/trough-2.2.0.tgz", + "integrity": "sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/tsconfck": { + "version": "3.1.6", + "resolved": "https://registry.npmjs.org/tsconfck/-/tsconfck-3.1.6.tgz", + "integrity": "sha512-ks6Vjr/jEw0P1gmOVwutM3B7fWxoWBL2KRDb1JfqGVawBmO5UsvmWOQFGHBPl5yxYz4eERr19E6L7NMv+Fej4w==", + "license": "MIT", + "bin": { + "tsconfck": "bin/tsconfck.js" + }, + "engines": { + "node": "^18 || >=20" + }, + "peerDependencies": { + "typescript": "^5.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/type-fest": { + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.41.0.tgz", + "integrity": "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==", + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "license": "Apache-2.0", + "peer": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/ufo": { + "version": "1.6.3", + "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.6.3.tgz", + "integrity": "sha512-yDJTmhydvl5lJzBmy/hyOAA0d+aqCBuwl818haVdYCRrWV84o7YyeVm4QlVHStqNrrJSTb6jKuFAVqAFsr+K3Q==", + "license": "MIT" + }, + "node_modules/uint8arrays": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/uint8arrays/-/uint8arrays-3.0.0.tgz", + "integrity": "sha512-HRCx0q6O9Bfbp+HHSfQQKD7wU70+lydKVt4EghkdOvlK/NlrF90z+eXV34mUd48rNvVJXwkrMSPpCATkct8fJA==", + "license": "MIT", + "dependencies": { + "multiformats": "^9.4.2" + } + }, + "node_modules/ultrahtml": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/ultrahtml/-/ultrahtml-1.6.0.tgz", + "integrity": "sha512-R9fBn90VTJrqqLDwyMph+HGne8eqY1iPfYhPzZrvKpIfwkWZbcYlfpsb8B9dTvBfpy1/hqAD7Wi8EKfP9e8zdw==", + "license": "MIT" + }, + "node_modules/uncrypto": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/uncrypto/-/uncrypto-0.1.3.tgz", + "integrity": "sha512-Ql87qFHB3s/De2ClA9e0gsnS6zXG27SkTiSJwjCc9MebbfapQfuPzumMIUMi38ezPZVNFcHI9sUIepeQfw8J8Q==", + "license": "MIT" + }, + "node_modules/undici-types": { + "version": "7.18.2", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.18.2.tgz", + "integrity": "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==", + "license": "MIT" + }, + "node_modules/unicode-segmenter": { + "version": "0.14.5", + "resolved": "https://registry.npmjs.org/unicode-segmenter/-/unicode-segmenter-0.14.5.tgz", + "integrity": "sha512-jHGmj2LUuqDcX3hqY12Ql+uhUTn8huuxNZGq7GvtF6bSybzH3aFgedYu/KTzQStEgt1Ra2F3HxadNXsNjb3m3g==", + "license": "MIT" + }, + "node_modules/unified": { + "version": "11.0.5", + "resolved": "https://registry.npmjs.org/unified/-/unified-11.0.5.tgz", + "integrity": "sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "bail": "^2.0.0", + "devlop": "^1.0.0", + "extend": "^3.0.0", + "is-plain-obj": "^4.0.0", + "trough": "^2.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unifont": { + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/unifont/-/unifont-0.7.4.tgz", + "integrity": "sha512-oHeis4/xl42HUIeHuNZRGEvxj5AaIKR+bHPNegRq5LV1gdc3jundpONbjglKpihmJf+dswygdMJn3eftGIMemg==", + "license": "MIT", + "dependencies": { + "css-tree": "^3.1.0", + "ofetch": "^1.5.1", + "ohash": "^2.0.11" + } + }, + "node_modules/unist-util-find-after": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/unist-util-find-after/-/unist-util-find-after-5.0.0.tgz", + "integrity": "sha512-amQa0Ep2m6hE2g72AugUItjbuM8X8cGQnFoHk0pGfrFeT9GZhzN5SW8nRsiGKK7Aif4CrACPENkA6P/Lw6fHGQ==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-is": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-is": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-6.0.1.tgz", + "integrity": "sha512-LsiILbtBETkDz8I9p1dQ0uyRUWuaQzd/cuEeS1hoRSyW5E5XGmTzlwY1OrNzzakGowI9Dr/I8HVaw4hTtnxy8g==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-modify-children": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/unist-util-modify-children/-/unist-util-modify-children-4.0.0.tgz", + "integrity": "sha512-+tdN5fGNddvsQdIzUF3Xx82CU9sMM+fA0dLgR9vOmT0oPT2jH+P1nd5lSqfCfXAw+93NhcXNY2qqvTUtE4cQkw==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "array-iterate": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-position": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/unist-util-position/-/unist-util-position-5.0.0.tgz", + "integrity": "sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-remove-position": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/unist-util-remove-position/-/unist-util-remove-position-5.0.0.tgz", + "integrity": "sha512-Hp5Kh3wLxv0PHj9m2yZhhLt58KzPtEYKQQ4yxfYFEO7EvHwzyDYnduhHnY1mDxoqr7VUwVuHXk9RXKIiYS1N8Q==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-visit": "^5.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-stringify-position": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-4.0.0.tgz", + "integrity": "sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-visit": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-5.1.0.tgz", + "integrity": "sha512-m+vIdyeCOpdr/QeQCu2EzxX/ohgS8KbnPDgFni4dQsfSCtpz8UqDyY5GjRru8PDKuYn7Fq19j1CQ+nJSsGKOzg==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-is": "^6.0.0", + "unist-util-visit-parents": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-visit-children": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/unist-util-visit-children/-/unist-util-visit-children-3.0.0.tgz", + "integrity": "sha512-RgmdTfSBOg04sdPcpTSD1jzoNBjt9a80/ZCzp5cI9n1qPzLZWF9YdvWGN2zmTumP1HWhXKdUWexjy/Wy/lJ7tA==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-visit-parents": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-6.0.2.tgz", + "integrity": "sha512-goh1s1TBrqSqukSc8wrjwWhL0hiJxgA8m4kFxGlQ+8FYQ3C/m11FcTs4YYem7V664AhHVvgoQLk890Ssdsr2IQ==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-is": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unstorage": { + "version": "1.17.4", + "resolved": "https://registry.npmjs.org/unstorage/-/unstorage-1.17.4.tgz", + "integrity": "sha512-fHK0yNg38tBiJKp/Vgsq4j0JEsCmgqH58HAn707S7zGkArbZsVr/CwINoi+nh3h98BRCwKvx1K3Xg9u3VV83sw==", + "license": "MIT", + "dependencies": { + "anymatch": "^3.1.3", + "chokidar": "^5.0.0", + "destr": "^2.0.5", + "h3": "^1.15.5", + "lru-cache": "^11.2.0", + "node-fetch-native": "^1.6.7", + "ofetch": "^1.5.1", + "ufo": "^1.6.3" + }, + "peerDependencies": { + "@azure/app-configuration": "^1.8.0", + "@azure/cosmos": "^4.2.0", + "@azure/data-tables": "^13.3.0", + "@azure/identity": "^4.6.0", + "@azure/keyvault-secrets": "^4.9.0", + "@azure/storage-blob": "^12.26.0", + "@capacitor/preferences": "^6 || ^7 || ^8", + "@deno/kv": ">=0.9.0", + "@netlify/blobs": "^6.5.0 || ^7.0.0 || ^8.1.0 || ^9.0.0 || ^10.0.0", + "@planetscale/database": "^1.19.0", + "@upstash/redis": "^1.34.3", + "@vercel/blob": ">=0.27.1", + "@vercel/functions": "^2.2.12 || ^3.0.0", + "@vercel/kv": "^1 || ^2 || ^3", + "aws4fetch": "^1.0.20", + "db0": ">=0.2.1", + "idb-keyval": "^6.2.1", + "ioredis": "^5.4.2", + "uploadthing": "^7.4.4" + }, + "peerDependenciesMeta": { + "@azure/app-configuration": { + "optional": true + }, + "@azure/cosmos": { + "optional": true + }, + "@azure/data-tables": { + "optional": true + }, + "@azure/identity": { + "optional": true + }, + "@azure/keyvault-secrets": { + "optional": true + }, + "@azure/storage-blob": { + "optional": true + }, + "@capacitor/preferences": { + "optional": true + }, + "@deno/kv": { + "optional": true + }, + "@netlify/blobs": { + "optional": true + }, + "@planetscale/database": { + "optional": true + }, + "@upstash/redis": { + "optional": true + }, + "@vercel/blob": { + "optional": true + }, + "@vercel/functions": { + "optional": true + }, + "@vercel/kv": { + "optional": true + }, + "aws4fetch": { + "optional": true + }, + "db0": { + "optional": true + }, + "idb-keyval": { + "optional": true + }, + "ioredis": { + "optional": true + }, + "uploadthing": { + "optional": true + } + } + }, + "node_modules/vfile": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/vfile/-/vfile-6.0.3.tgz", + "integrity": "sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/vfile-location": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/vfile-location/-/vfile-location-5.0.3.tgz", + "integrity": "sha512-5yXvWDEgqeiYiBe1lbxYF7UMAIm/IcopxMHrMQDq3nvKcjPKIhZklUKL+AE7J7uApI4kwe2snsK+eI6UTj9EHg==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/vfile-message": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-4.0.3.tgz", + "integrity": "sha512-QTHzsGd1EhbZs4AsQ20JX1rC3cOlt/IWJruk893DfLRr57lcnOeMaWG4K0JrRta4mIJZKth2Au3mM3u03/JWKw==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-stringify-position": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/vite": { + "version": "6.4.1", + "resolved": "https://registry.npmjs.org/vite/-/vite-6.4.1.tgz", + "integrity": "sha512-+Oxm7q9hDoLMyJOYfUYBuHQo+dkAloi33apOPP56pzj+vsdJDzr+j1NISE5pyaAuKL4A3UD34qd0lx5+kfKp2g==", + "license": "MIT", + "dependencies": { + "esbuild": "^0.25.0", + "fdir": "^6.4.4", + "picomatch": "^4.0.2", + "postcss": "^8.5.3", + "rollup": "^4.34.9", + "tinyglobby": "^0.2.13" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", + "jiti": ">=1.21.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/vite/node_modules/@esbuild/aix-ppc64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz", + "integrity": "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==", + "cpu": [ + "ppc64" + ], + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/android-arm": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.12.tgz", + "integrity": "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/android-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.12.tgz", + "integrity": "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/android-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.12.tgz", + "integrity": "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/darwin-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.12.tgz", + "integrity": "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/darwin-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.12.tgz", + "integrity": "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/freebsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.12.tgz", + "integrity": "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/freebsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.12.tgz", + "integrity": "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-arm": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.12.tgz", + "integrity": "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.12.tgz", + "integrity": "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-ia32": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.12.tgz", + "integrity": "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==", + "cpu": [ + "ia32" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-loong64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.12.tgz", + "integrity": "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==", + "cpu": [ + "loong64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-mips64el": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.12.tgz", + "integrity": "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==", + "cpu": [ + "mips64el" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-ppc64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.12.tgz", + "integrity": "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==", + "cpu": [ + "ppc64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-riscv64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.12.tgz", + "integrity": "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==", + "cpu": [ + "riscv64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-s390x": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.12.tgz", + "integrity": "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==", + "cpu": [ + "s390x" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.12.tgz", + "integrity": "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/netbsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.12.tgz", + "integrity": "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/netbsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.12.tgz", + "integrity": "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/openbsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.12.tgz", + "integrity": "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/openbsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.12.tgz", + "integrity": "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/openharmony-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.12.tgz", + "integrity": "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/sunos-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.12.tgz", + "integrity": "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/win32-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.12.tgz", + "integrity": "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/win32-ia32": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.12.tgz", + "integrity": "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==", + "cpu": [ + "ia32" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/win32-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.12.tgz", + "integrity": "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/esbuild": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.12.tgz", + "integrity": "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==", + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.25.12", + "@esbuild/android-arm": "0.25.12", + "@esbuild/android-arm64": "0.25.12", + "@esbuild/android-x64": "0.25.12", + "@esbuild/darwin-arm64": "0.25.12", + "@esbuild/darwin-x64": "0.25.12", + "@esbuild/freebsd-arm64": "0.25.12", + "@esbuild/freebsd-x64": "0.25.12", + "@esbuild/linux-arm": "0.25.12", + "@esbuild/linux-arm64": "0.25.12", + "@esbuild/linux-ia32": "0.25.12", + "@esbuild/linux-loong64": "0.25.12", + "@esbuild/linux-mips64el": "0.25.12", + "@esbuild/linux-ppc64": "0.25.12", + "@esbuild/linux-riscv64": "0.25.12", + "@esbuild/linux-s390x": "0.25.12", + "@esbuild/linux-x64": "0.25.12", + "@esbuild/netbsd-arm64": "0.25.12", + "@esbuild/netbsd-x64": "0.25.12", + "@esbuild/openbsd-arm64": "0.25.12", + "@esbuild/openbsd-x64": "0.25.12", + "@esbuild/openharmony-arm64": "0.25.12", + "@esbuild/sunos-x64": "0.25.12", + "@esbuild/win32-arm64": "0.25.12", + "@esbuild/win32-ia32": "0.25.12", + "@esbuild/win32-x64": "0.25.12" + } + }, + "node_modules/vitefu": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vitefu/-/vitefu-1.1.2.tgz", + "integrity": "sha512-zpKATdUbzbsycPFBN71nS2uzBUQiVnFoOrr2rvqv34S1lcAgMKKkjWleLGeiJlZ8lwCXvtWaRn7R3ZC16SYRuw==", + "license": "MIT", + "workspaces": [ + "tests/deps/*", + "tests/projects/*", + "tests/projects/workspace/packages/*" + ], + "peerDependencies": { + "vite": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-beta.0" + }, + "peerDependenciesMeta": { + "vite": { + "optional": true + } + } + }, + "node_modules/web-namespaces": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/web-namespaces/-/web-namespaces-2.0.1.tgz", + "integrity": "sha512-bKr1DkiNa2krS7qxNtdrtHAmzuYGFQLiQ13TsorsdT6ULTkPLKuu5+GsFpDlg6JFjUTwX2DyhMPG2be8uPrqsQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/web-streams-polyfill": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz", + "integrity": "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==", + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/which-pm-runs": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/which-pm-runs/-/which-pm-runs-1.1.0.tgz", + "integrity": "sha512-n1brCuqClxfFfq/Rb0ICg9giSZqCS+pLtccdag6C2HyufBrh3fBOiy9nb6ggRMvWOVH5GrdJskj5iGTZNxd7SA==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/widest-line": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/widest-line/-/widest-line-5.0.0.tgz", + "integrity": "sha512-c9bZp7b5YtRj2wOe6dlj32MK+Bx/M/d+9VB2SHM1OtsUHR0aV0tdP6DWh/iMt0kWi1t5g1Iudu6hQRNd1A4PVA==", + "license": "MIT", + "dependencies": { + "string-width": "^7.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/wrap-ansi": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.2.tgz", + "integrity": "sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.2.1", + "string-width": "^7.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/ws": { + "version": "8.19.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.19.0.tgz", + "integrity": "sha512-blAT2mjOEIi0ZzruJfIhb3nps74PRWTCz1IjglWEEpQl5XS/UNama6u2/rjFkDDouqr4L67ry+1aGIALViWjDg==", + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/xxhash-wasm": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/xxhash-wasm/-/xxhash-wasm-1.1.0.tgz", + "integrity": "sha512-147y/6YNh+tlp6nd/2pWq38i9h6mz/EuQ6njIrmW8D1BS5nCqs0P6DG+m6zTGnNz5I+uhZ0SHxBs9BsPrwcKDA==", + "license": "MIT" + }, + "node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/yocto-queue": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-1.2.2.tgz", + "integrity": "sha512-4LCcse/U2MHZ63HAJVE+v71o7yOdIe4cZ70Wpf8D/IyjDKYQLV5GD46B+hSTjJsvV5PztjvHoU580EftxjDZFQ==", + "license": "MIT", + "engines": { + "node": ">=12.20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/yocto-spinner": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/yocto-spinner/-/yocto-spinner-0.2.3.tgz", + "integrity": "sha512-sqBChb33loEnkoXte1bLg45bEBsOP9N1kzQh5JZNKj/0rik4zAPTNSAVPj3uQAdc6slYJ0Ksc403G2XgxsJQFQ==", + "license": "MIT", + "dependencies": { + "yoctocolors": "^2.1.1" + }, + "engines": { + "node": ">=18.19" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/yoctocolors": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/yoctocolors/-/yoctocolors-2.1.2.tgz", + "integrity": "sha512-CzhO+pFNo8ajLM2d2IW/R93ipy99LWjtwblvC1RsoSUMZgyLbYFr221TnSNT7GjGdYui6P459mw9JH/g/zW2ug==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/zod": { + "version": "3.25.76", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", + "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, + "node_modules/zod-to-json-schema": { + "version": "3.25.1", + "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.25.1.tgz", + "integrity": "sha512-pM/SU9d3YAggzi6MtR4h7ruuQlqKtad8e9S0fmxcMi+ueAK5Korys/aWcV9LIIHTVbj01NdzxcnXSN+O74ZIVA==", + "license": "ISC", + "peerDependencies": { + "zod": "^3.25 || ^4" + } + }, + "node_modules/zod-to-ts": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/zod-to-ts/-/zod-to-ts-1.2.0.tgz", + "integrity": "sha512-x30XE43V+InwGpvTySRNz9kB7qFU8DlyEy7BsSTCHPH1R0QasMmHWZDCzYm6bVXtj/9NNJAZF3jW8rzFvH5OFA==", + "peerDependencies": { + "typescript": "^4.9.4 || ^5.0.2", + "zod": "^3" + } + }, + "node_modules/zwitch": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/zwitch/-/zwitch-2.0.4.tgz", + "integrity": "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + } + } +} diff --git a/astro-authproto/__examples__/06-custom-scopes/package.json b/astro-authproto/__examples__/06-custom-scopes/package.json new file mode 100644 index 0000000..19f8eea --- /dev/null +++ b/astro-authproto/__examples__/06-custom-scopes/package.json @@ -0,0 +1,19 @@ +{ + "name": "example", + "version": "0.0.1", + "private": true, + "type": "module", + "scripts": { + "dev": "astro dev --host", + "build": "astro build", + "preview": "astro preview", + "astro": "astro" + }, + "dependencies": { + "@astrojs/db": "^0.17.2", + "@astrojs/node": "^9.4.0", + "@atproto/api": "^0.17.2", + "@fujocoded/authproto": "file:..", + "astro": "^5.12.9" + } +} diff --git a/astro-authproto/__examples__/06-custom-scopes/src/pages/index.astro b/astro-authproto/__examples__/06-custom-scopes/src/pages/index.astro new file mode 100644 index 0000000..64b1346 --- /dev/null +++ b/astro-authproto/__examples__/06-custom-scopes/src/pages/index.astro @@ -0,0 +1,83 @@ +--- +import { account } from "@fujocoded/authproto"; +import { Login } from "@fujocoded/authproto/components"; +import { + defaultDevUser, + // Import the public server-side config so your custom form only shows scopes + // this app is allowed to request. + scopes as allScopes, +} from "fujocoded:authproto/config"; + +const user = Astro.locals.loggedInUser; +--- + + + + + + + + Custom Scopes + + + { + user ? ( + <> +

+ Welcome {user.handle} ({user.did}) +

+

You have the following scopes:

+
    + {user.scopes.map((scope) => ( +
  • {scope}
  • + ))} +
+

Not Enough™? Make your own form, choose new ones!

+
+ + {allScopes.map((scope) => ( + + ))} + +
+ + ) : ( + <> +

Choose your login adventure:

+ +

1. Default Scopes

+ + +

2. Minimal scopes (identity only)

+ + +

3. Default scopes + email access

+ + +

4. Roll your own form

+
+ + {allScopes.map((scope) => ( + + ))} + +
+ + ) + } + + diff --git a/astro-authproto/__examples__/06-custom-scopes/tsconfig.json b/astro-authproto/__examples__/06-custom-scopes/tsconfig.json new file mode 100644 index 0000000..bcbf8b5 --- /dev/null +++ b/astro-authproto/__examples__/06-custom-scopes/tsconfig.json @@ -0,0 +1,3 @@ +{ + "extends": "astro/tsconfigs/strict" +} diff --git a/astro-authproto/__examples__/README.md b/astro-authproto/__examples__/README.md index 12801c4..52fa468 100644 --- a/astro-authproto/__examples__/README.md +++ b/astro-authproto/__examples__/README.md @@ -21,6 +21,10 @@ how to use `@fujocoded/authproto` to achieve all your dreams. - restrict pages to logged-in users - restrict pages to e.g. mutuals 5. Redirect configuration +6. Custom scopes => + - Pick exactly which scopes your app asks for, per login + - Re-auth a logged-in user to grant more (or fewer) scopes + - Build your own login form when `` is _Not Enough™_ // FUTURE // Astro DB? diff --git a/astro-authproto/__tests__/config.test.ts b/astro-authproto/__tests__/config.test.ts new file mode 100644 index 0000000..789d294 --- /dev/null +++ b/astro-authproto/__tests__/config.test.ts @@ -0,0 +1,92 @@ +import { describe, expect, test } from "vitest"; + +import { createClientMetadata } from "../src/lib/auth.ts"; +import { getConfig } from "../src/lib/config.ts"; + +const parseClientId = (clientId: string | undefined): URL => { + expect(clientId).toEqual(expect.any(String)); + if (typeof clientId !== "string") { + throw new Error("Expected client_id to be a string"); + } + return new URL(clientId); +}; + +describe("authproto config virtual module", () => { + test("emits parseable JavaScript for quoted string options", () => { + const source = getConfig({ + options: { + applicationName: 'Auth "Proto" Test', + applicationDomain: 'https://fujocoded.test/"quoted"', + driver: { name: "memory", options: undefined }, + }, + isDev: false, + }); + const parseableSource = source + .replace(/^\s*import .*$/gm, "") + .replace(/\bexport const\b/g, "const"); + + // We compile the generated source so we know we haven't fucked up its + // syntax. + // eslint-disable-next-line @typescript-eslint/no-implied-eval + expect(() => new Function(parseableSource)).not.toThrow(); + expect(source).toContain( + 'export const applicationName = "Auth \\"Proto\\" Test";', + ); + expect(source).toContain( + 'export const applicationDomain = "https://fujocoded.test/\\"quoted\\"";', + ); + expect(source).toContain('export const driverName = "memory";'); + expect(source).toContain( + 'export const clientMetadataDomain = process.env.AUTHPROTO_EXTERNAL_DOMAIN ?? "https://fujocoded.test/\\"quoted\\"" ?? "https://fujocoded.test/\\"quoted\\"";', + ); + }); +}); + +describe("createClientMetadata", () => { + test("localhost domain stuffs scope and redirect_uri into the client_id URL", () => { + const metadata = createClientMetadata("http://127.0.0.1:4321"); + + expect(metadata).toMatchObject({ + client_id: expect.any(String), + client_name: "AuthProto Test App", + client_uri: "http://127.0.0.1:4321/", + redirect_uris: ["http://127.0.0.1:4321/oauth/callback"], + jwks_uri: "http://127.0.0.1:4321/jwks.json", + scope: "atproto transition:generic transition:email", + }); + + const clientId = parseClientId(metadata.client_id); + expect(clientId.origin).toBe("http://localhost"); + expect(clientId.searchParams.get("scope")).toBe( + "atproto transition:generic transition:email", + ); + expect(clientId.searchParams.get("redirect_uri")).toBe( + "http://127.0.0.1:4321/oauth/callback", + ); + }); + + test("rewrites the literal 'localhost' hostname to 127.0.0.1 per RFC 8252", () => { + const metadata = createClientMetadata("http://localhost:4321"); + + expect(metadata).toMatchObject({ + client_id: expect.any(String), + client_uri: "http://127.0.0.1:4321/", + redirect_uris: ["http://127.0.0.1:4321/oauth/callback"], + }); + expect( + parseClientId(metadata.client_id).searchParams.get("redirect_uri"), + ).toBe("http://127.0.0.1:4321/oauth/callback"); + }); + + test("non-localhost domain points client_id at /oauth-client-metadata.json", () => { + const metadata = createClientMetadata("https://fujocoded.test"); + + expect(metadata).toMatchObject({ + client_id: "https://fujocoded.test/oauth-client-metadata.json", + client_uri: "https://fujocoded.test/", + redirect_uris: ["https://fujocoded.test/oauth/callback"], + jwks_uri: "https://fujocoded.test/jwks.json", + scope: "atproto transition:generic transition:email", + }); + }); +}); diff --git a/astro-authproto/__tests__/middleware.test.ts b/astro-authproto/__tests__/middleware.test.ts new file mode 100644 index 0000000..8fdf259 --- /dev/null +++ b/astro-authproto/__tests__/middleware.test.ts @@ -0,0 +1,73 @@ +import { describe, expect, test } from "vitest"; + +import { + AUTHPROTO_ERROR_CODE, + persistAuthprotoError, + persistLoginGrant, + readSessionGrant, +} from "../src/lib/session-state.ts"; +import { onRequest } from "../src/routes/middleware.ts"; +import { TEST_ACCOUNT_DID } from "./support/auth-fixtures.ts"; +import { createSession } from "./support/session.ts"; + +const next = async () => new Response("ok"); + +const runMiddleware = async (session: ReturnType) => { + const locals: Record = {}; + await onRequest( + { locals, session } as unknown as Parameters[0], + next, + ); + return locals; +}; + +describe("authproto middleware", () => { + test("shoves a persisted authproto error into locals.authproto", async () => { + const session = createSession(); + await persistAuthprotoError(session, { + code: "MISSING_FIELD", + description: 'Missing required "atproto-id" field in login form data', + }); + + expect(await runMiddleware(session)).toMatchObject({ + authproto: { + errorCode: "MISSING_FIELD", + errorDescription: + 'Missing required "atproto-id" field in login form data', + errorUri: undefined, + }, + }); + expect(await session.get(AUTHPROTO_ERROR_CODE)).toBeUndefined(); + }); + + test("sets authproto and logged-in locals to null on a clean session", async () => { + const locals = await runMiddleware(createSession()); + + expect(locals).toEqual({ + authproto: null, + loggedInUser: null, + loggedInClient: null, + }); + }); + + test("clears the session grant when the OAuth client cannot restore it", async () => { + // SessionStore is empty after resetTestStores(), so restore() throws and + // the middleware should treat the stored DID as stale and wipe it. + const session = createSession(); + await persistLoginGrant(session, { + did: TEST_ACCOUNT_DID, + scopes: ["atproto"], + }); + + const locals = await runMiddleware(session); + + expect(locals).toMatchObject({ + loggedInUser: null, + loggedInClient: null, + }); + await expect(readSessionGrant(session)).resolves.toEqual({ + did: undefined, + scopes: [], + }); + }); +}); diff --git a/astro-authproto/__tests__/oauth-callback.test.ts b/astro-authproto/__tests__/oauth-callback.test.ts new file mode 100644 index 0000000..ee28148 --- /dev/null +++ b/astro-authproto/__tests__/oauth-callback.test.ts @@ -0,0 +1,161 @@ +import { describe, expect, test } from "vitest"; +import { http, HttpResponse } from "msw"; + +import { decodeOAuthState, encodeOAuthState } from "../src/lib/oauth-state.ts"; +import { + AUTHPROTO_DID, + AUTHPROTO_ERROR_CODE, + AUTHPROTO_ERROR_DESCRIPTION, + AUTHPROTO_ERROR_URI, + AUTHPROTO_SCOPES, +} from "../src/lib/session-state.ts"; +import { POST } from "../src/routes/oauth/login.ts"; +import { GET } from "../src/routes/oauth/callback.ts"; +import { + TEST_ACCOUNT_AUTH_SERVER, + TEST_ACCOUNT_PDS, +} from "./support/auth-fixtures.ts"; +import { parRequests, server } from "./support/msw.ts"; +import { createSession, redirect } from "./support/session.ts"; + +const getCallback = async (url: string) => { + const session = createSession(); + const response = await GET({ + request: new Request(url), + session, + redirect, + }); + + return { response, session }; +}; + +describe("decodeOAuthState", () => { + test("round-trips the fields the package owns and ignores unknown ones", () => { + // Hand-rolled payload includes a field the package doesn't own + // ("legacy_csrf") so we can prove the decoder strips it rather than + // surfacing arbitrary keys from the OAuth state param into our code. + const state = Buffer.from( + JSON.stringify({ + legacy_csrf: "test-csrf", + scopes: ["atproto", "transition:generic"], + referer: "http://127.0.0.1:4321/start", + ignored: true, + }), + ).toString("base64url"); + + expect(decodeOAuthState(state)).toEqual({ + scopes: ["atproto", "transition:generic"], + referer: "http://127.0.0.1:4321/start", + }); + }); + + test("survives malformed input by returning an empty object", () => { + // The OAuth state param is attacker-controllable in callback URLs, so + // a bad base64/JSON payload must not throw. + expect(decodeOAuthState("not-base64-json")).toEqual({}); + }); + + test("encode then decode preserves the owned fields", () => { + const original = { + scopes: ["atproto", "transition:generic"], + referer: "http://127.0.0.1:4321/start", + }; + + expect(decodeOAuthState(encodeOAuthState(original))).toEqual(original); + }); +}); + +describe("oauth callback route", () => { + test("preserves provider callback errors before validating OAuth state", async () => { + const url = new URL("http://127.0.0.1:4321/oauth/callback"); + url.search = new URLSearchParams({ + error: "lexError", + error_description: "@atproto/lexicon validation test", + error_uri: "https://auth.fujocoded.test/errors/lexError", + unknown: "ignored", + }).toString(); + + const { response, session } = await getCallback(url.toString()); + + expect(response.status).toBe(302); + expect(response.headers.get("location")).toBe("/"); + expect(await session.get(AUTHPROTO_ERROR_CODE)).toBe("lexError"); + expect(await session.get(AUTHPROTO_ERROR_DESCRIPTION)).toBe( + "@atproto/lexicon validation test", + ); + expect(await session.get(AUTHPROTO_ERROR_URI)).toBe( + "https://auth.fujocoded.test/errors/lexError", + ); + expect(await session.get("unknown")).toBeUndefined(); + }); + + test("stores an invalid callback error when there is neither code nor provider error", async () => { + const { response, session } = await getCallback( + "http://127.0.0.1:4321/oauth/callback", + ); + + expect(response.status).toBe(302); + expect(response.headers.get("location")).toBe("/"); + expect(await session.get(AUTHPROTO_ERROR_CODE)).toBe("INVALID_CALLBACK"); + expect(await session.get(AUTHPROTO_ERROR_DESCRIPTION)).toBe( + 'Missing required "code" parameter in OAuth callback', + ); + }); + + test("preserves provider errors on malformed callbacks with both code and error", async () => { + const { response, session } = await getCallback( + "http://127.0.0.1:4321/oauth/callback?code=test-code&error=access_denied&error_description=Denied", + ); + + expect(response.status).toBe(302); + expect(response.headers.get("location")).toBe("/"); + expect(await session.get(AUTHPROTO_ERROR_CODE)).toBe("access_denied"); + expect(await session.get(AUTHPROTO_ERROR_DESCRIPTION)).toBe("Denied"); + }); + + test("stores an auth error when exchanging a valid callback code fails", async () => { + server.use( + http.post(`${TEST_ACCOUNT_AUTH_SERVER}/oauth/token`, () => + HttpResponse.json( + { + error: "invalid_grant", + error_description: "The authorization code was rejected.", + }, + { status: 400 }, + ), + ), + ); + + const session = createSession(); + await POST({ + request: new Request("http://127.0.0.1:4321/oauth/login", { + method: "POST", + body: new URLSearchParams({ "atproto-id": TEST_ACCOUNT_PDS }), + headers: { + "content-type": "application/x-www-form-urlencoded", + referer: "http://127.0.0.1:4321/start", + }, + }), + session, + redirect, + }); + + const state = parRequests[0]?.get("state"); + expect(state).toBeTruthy(); + + const response = await GET({ + request: new Request( + `http://127.0.0.1:4321/oauth/callback?code=test-code&state=${encodeURIComponent(state!)}`, + ), + session, + redirect, + }); + + expect(response.status).toBe(302); + expect(response.headers.get("location")).toBe("/"); + expect(await session.get(AUTHPROTO_ERROR_CODE)).toBeTruthy(); + expect(await session.get(AUTHPROTO_ERROR_DESCRIPTION)).toBeTruthy(); + expect(await session.get(AUTHPROTO_DID)).toBeUndefined(); + expect(await session.get(AUTHPROTO_SCOPES)).toBeUndefined(); + }); +}); diff --git a/astro-authproto/__tests__/oauth-login.test.ts b/astro-authproto/__tests__/oauth-login.test.ts new file mode 100644 index 0000000..1e05271 --- /dev/null +++ b/astro-authproto/__tests__/oauth-login.test.ts @@ -0,0 +1,156 @@ +import { describe, expect, test } from "vitest"; + +import { decodeOAuthState } from "../src/lib/oauth-state.ts"; +import { + AUTHPROTO_ERROR_CODE, + AUTHPROTO_ERROR_DESCRIPTION, +} from "../src/lib/session-state.ts"; +import { POST } from "../src/routes/oauth/login.ts"; +import { + TEST_ACCOUNT_AUTH_SERVER, + TEST_ACCOUNT_PDS, +} from "./support/auth-fixtures.ts"; +import { parRequests } from "./support/msw.ts"; +import { createSession, redirect } from "./support/session.ts"; +import { SCOPE_RESOLVER_THROWS_ID } from "./virtual/hooks.ts"; +import { getTestStateAppState } from "./virtual/stores.ts"; + +const postLogin = async ({ + atprotoId, + referer = "http://127.0.0.1:4321/start", + redirectTo, + scopes = [], +}: { + atprotoId?: string; + referer?: string; + redirectTo?: string; + scopes?: string[]; +}) => { + const body = new URLSearchParams(); + if (atprotoId !== undefined) { + body.set("atproto-id", atprotoId); + } + if (redirectTo !== undefined) { + body.set("redirect", redirectTo); + } + for (const scope of scopes) { + body.append("scope", scope); + } + + const request = new Request("http://127.0.0.1:4321/oauth/login", { + method: "POST", + body, + headers: { + "content-type": "application/x-www-form-urlencoded", + referer, + }, + }); + + const session = createSession(); + const response = await POST({ + request, + session, + redirect, + } as Parameters[0]); + + return { response, session }; +}; + +describe("oauth login route", () => { + test("starts the ATProto OAuth flow through mocked discovery and PAR endpoints", async () => { + const { response, session } = await postLogin({ + atprotoId: TEST_ACCOUNT_PDS, + }); + const location = response.headers.get("location"); + + expect(response.status).toBe(302); + expect(location).toBeTruthy(); + expect({ + code: await session.get(AUTHPROTO_ERROR_CODE), + description: await session.get(AUTHPROTO_ERROR_DESCRIPTION), + }).toEqual({ code: undefined, description: undefined }); + + const redirectUrl = new URL(location!); + expect(redirectUrl.origin).toBe(TEST_ACCOUNT_AUTH_SERVER); + expect(redirectUrl.pathname).toBe("/oauth/authorize"); + expect(redirectUrl.searchParams.get("request_uri")).toBe( + "urn:ietf:params:oauth:request_uri:test-request", + ); + + expect(parRequests).toHaveLength(1); + expect(parRequests[0]?.get("login_hint")).toBeNull(); + expect(parRequests[0]?.get("scope")).toBe("atproto transition:generic"); + expect(parRequests[0]?.get("redirect_uri")).toBe( + "http://127.0.0.1:4321/oauth/callback", + ); + expect(parRequests[0]?.get("client_id")).toContain("http://localhost"); + expect( + decodeOAuthState(getTestStateAppState(parRequests[0]?.get("state"))), + ).toEqual({ + scopes: ["atproto", "transition:generic"], + referer: "http://127.0.0.1:4321/start", + }); + }); + + test("stores custom redirects in OAuth state instead of the referer", async () => { + await postLogin({ + atprotoId: TEST_ACCOUNT_PDS, + referer: "http://127.0.0.1:4321/events", + redirectTo: "/welcome", + }); + + expect(parRequests).toHaveLength(1); + expect( + decodeOAuthState(getTestStateAppState(parRequests[0]?.get("state"))), + ).toEqual({ + scopes: ["atproto", "transition:generic"], + redirect: "/welcome", + }); + }); + + test("forwards resolved scopes to the PAR scope param and state", async () => { + await postLogin({ + atprotoId: TEST_ACCOUNT_PDS, + scopes: ["transition:email"], + }); + + expect(parRequests).toHaveLength(1); + expect(parRequests[0]?.get("scope")).toBe("atproto transition:email"); + expect( + decodeOAuthState(getTestStateAppState(parRequests[0]?.get("state"))), + ).toEqual({ + scopes: ["atproto", "transition:email"], + referer: "http://127.0.0.1:4321/start", + }); + }); + + test("stores a session error when the login form is missing atproto-id", async () => { + const { response, session } = await postLogin({ atprotoId: undefined }); + + expect(response.status).toBe(302); + expect(response.headers.get("location")).toBe( + "http://127.0.0.1:4321/start", + ); + expect(await session.get(AUTHPROTO_ERROR_CODE)).toBe("MISSING_FIELD"); + expect(await session.get(AUTHPROTO_ERROR_DESCRIPTION)).toBe( + 'Missing required "atproto-id" field in login form data', + ); + expect(parRequests).toHaveLength(0); + }); + + test("stores a session error when the scope resolver fails", async () => { + const { response, session } = await postLogin({ + atprotoId: SCOPE_RESOLVER_THROWS_ID, + }); + + expect(response.status).toBe(302); + expect(response.headers.get("location")).toBe( + "http://127.0.0.1:4321/start", + ); + expect(await session.get(AUTHPROTO_ERROR_CODE)).toBe("Error"); + expect(await session.get(AUTHPROTO_ERROR_DESCRIPTION)).toBe( + "scope resolver failed", + ); + expect(parRequests).toHaveLength(0); + }); +}); diff --git a/astro-authproto/__tests__/oauth-logout.test.ts b/astro-authproto/__tests__/oauth-logout.test.ts new file mode 100644 index 0000000..13a5da3 --- /dev/null +++ b/astro-authproto/__tests__/oauth-logout.test.ts @@ -0,0 +1,92 @@ +import { describe, expect, test, vi } from "vitest"; + +import { + AUTHPROTO_DID, + AUTHPROTO_SCOPES, + persistLoginGrant, +} from "../src/lib/session-state.ts"; +import { POST } from "../src/routes/oauth/logout.ts"; +import { createSession, redirect } from "./support/session.ts"; +import { TEST_ACCOUNT_DID } from "./support/auth-fixtures.ts"; + +describe("oauth logout route", () => { + test("redirects when logout is attempted without a login grant", async () => { + const consoleError = vi + .spyOn(console, "error") + .mockImplementation(() => {}); + + const session = createSession(); + const response = await POST({ + request: new Request("http://127.0.0.1:4321/oauth/logout", { + method: "POST", + body: new URLSearchParams(), + headers: { + "content-type": "application/x-www-form-urlencoded", + referer: "http://127.0.0.1:4321/start", + }, + }), + session, + redirect, + } as Parameters[0]); + + expect(response.status).toBe(302); + expect(response.headers.get("location")).toBe("/"); + expect(consoleError).toHaveBeenCalledWith( + "User is not logged in but logout was attempted.", + ); + + consoleError.mockRestore(); + }); + + test("clears the local session when the OAuth session was already deleted", async () => { + const session = createSession(); + await persistLoginGrant(session, { + did: TEST_ACCOUNT_DID, + scopes: ["atproto"], + }); + + const response = await POST({ + request: new Request("http://127.0.0.1:4321/oauth/logout", { + method: "POST", + body: new URLSearchParams(), + headers: { + "content-type": "application/x-www-form-urlencoded", + referer: "http://127.0.0.1:4321/start", + }, + }), + session, + redirect, + } as Parameters[0]); + + expect(response.status).toBe(302); + expect(response.headers.get("location")).toBe("/"); + expect(await session.get(AUTHPROTO_DID)).toBeUndefined(); + expect(await session.get(AUTHPROTO_SCOPES)).toBeUndefined(); + }); + + test("reads redirect from form body and applies it on logout", async () => { + // Wiring proof only: getRedirectUrl tests are in redirects.test.ts. This + // is testing that the logout route reads the redirect form field and passes + // it to getRedirectUrl. + const session = createSession(); + await persistLoginGrant(session, { + did: TEST_ACCOUNT_DID, + scopes: ["atproto"], + }); + + const response = await POST({ + request: new Request("http://127.0.0.1:4321/oauth/logout", { + method: "POST", + body: new URLSearchParams({ redirect: "/goodbye" }), + headers: { + "content-type": "application/x-www-form-urlencoded", + referer: "http://127.0.0.1:4321/events", + }, + }), + session, + redirect, + } as Parameters[0]); + + expect(response.headers.get("location")).toBe("/goodbye"); + }); +}); diff --git a/astro-authproto/__tests__/permissions.test.ts b/astro-authproto/__tests__/permissions.test.ts new file mode 100644 index 0000000..9d08ab4 --- /dev/null +++ b/astro-authproto/__tests__/permissions.test.ts @@ -0,0 +1,90 @@ +import { describe, expect, test } from "vitest"; + +import { + account, + blob, + identity, + include, + permissionScopes, + repo, + rpc, +} from "../src/lib/permissions.ts"; + +describe("permission scope helpers", () => { + test("builds repo scopes for one or many record lexicons", () => { + expect(repo("test.fujocoded.profile")).toBe("repo:test.fujocoded.profile"); + expect( + repo("test.fujocoded.profile", { + action: ["create", "update", "delete"], + }), + ).toBe( + "repo:test.fujocoded.profile?action=create&action=update&action=delete", + ); + expect(repo(["test.fujocoded.profile", "test.fujocoded.post"])).toBe( + "repo?collection=test.fujocoded.profile&collection=test.fujocoded.post", + ); + }); + + test("builds rpc scopes for service method lexicons", () => { + expect(rpc("test.fujocoded.moderation.createReport", { aud: "*" })).toBe( + "rpc:test.fujocoded.moderation.createReport?aud=*", + ); + expect(rpc("*", { aud: "did:web:api.fujocoded.test#svc_appview" })).toBe( + "rpc:*?aud=did%3Aweb%3Aapi.fujocoded.test%23svc_appview", + ); + expect( + rpc(["test.fujocoded.getFeed", "test.fujocoded.getProfile"], { + aud: "did:web:api.fujocoded.test#svc_appview", + }), + ).toBe( + "rpc?lxm=test.fujocoded.getFeed&lxm=test.fujocoded.getProfile&aud=did%3Aweb%3Aapi.fujocoded.test%23svc_appview", + ); + }); + + test("builds direct blob, account, identity, and include scopes", () => { + expect(blob("*/*")).toBe("blob:*%2F*"); + expect(blob(["video/*", "text/html"])).toBe( + "blob?accept=video%2F*&accept=text%2Fhtml", + ); + expect(account("email")).toBe("account:email"); + expect(account("repo", { action: "manage" })).toBe( + "account:repo?action=manage", + ); + expect(identity("handle")).toBe("identity:handle"); + expect(include("test.fujocoded.authBasicFeatures")).toBe( + "include:test.fujocoded.authBasicFeatures", + ); + expect( + include("test.fujocoded.authBasicFeatures", { + aud: "did:web:api.fujocoded.test#svc_appview", + }), + ).toBe( + "include:test.fujocoded.authBasicFeatures?aud=did%3Aweb%3Aapi.fujocoded.test%23svc_appview", + ); + }); + + test("collects optional scopes without duplicates", () => { + expect( + permissionScopes([ + repo("test.fujocoded.profile"), + false, + null, + undefined, + repo("test.fujocoded.profile"), + blob("*/*"), + ]), + ).toEqual(["repo:test.fujocoded.profile", "blob:*%2F*"]); + }); + + test("rejects unsupported wildcard shapes", () => { + expect(() => repo("test.fujocoded.*")).toThrow( + "repo permission collection does not support partial wildcards", + ); + expect(() => rpc("test.fujocoded.*", { aud: "*" })).toThrow( + "rpc permission lxm does not support partial wildcards", + ); + expect(() => rpc("*", { aud: "*" })).toThrow( + 'rpc permission cannot use both lxm="*" and aud="*".', + ); + }); +}); diff --git a/astro-authproto/__tests__/redirects.test.ts b/astro-authproto/__tests__/redirects.test.ts new file mode 100644 index 0000000..887e898 --- /dev/null +++ b/astro-authproto/__tests__/redirects.test.ts @@ -0,0 +1,138 @@ +import { beforeEach, describe, expect, test, vi } from "vitest"; + +import { encodeOAuthState } from "../src/lib/oauth-state.ts"; +import { getRedirectUrl } from "../src/lib/redirects.ts"; +import { persistLoginGrant } from "../src/lib/session-state.ts"; +import { GET as CALLBACK } from "../src/routes/oauth/callback.ts"; +import { POST as LOGOUT } from "../src/routes/oauth/logout.ts"; +import { + TEST_ACCOUNT_DID, + TEST_ACCOUNT_HANDLE, +} from "./support/auth-fixtures.ts"; +import { createSession, redirect } from "./support/session.ts"; + +let mockOAuthCallbackResult: { + session: { did: string }; + state: string; +} | null = null; + +vi.mock("../src/lib/auth.ts", async (importActual) => { + const actual = await importActual(); + return { + ...actual, + getOAuthClient: async () => { + if (!mockOAuthCallbackResult) { + return actual.getOAuthClient(); + } + const result = mockOAuthCallbackResult; + return { + callback: async () => result, + } as unknown as Awaited>; + }, + }; +}); + +beforeEach(() => { + mockOAuthCallbackResult = null; +}); + +describe("getRedirectUrl template substitution", () => { + test.each([ + { + label: "explicit redirect path", + redirectToBase: "/welcome", + referer: "http://127.0.0.1:4321/events", + expected: "/welcome", + }, + { + label: "{referer} template", + redirectToBase: "{referer}", + referer: "http://127.0.0.1:4321/events?tab=upcoming", + expected: "http://127.0.0.1:4321/events?tab=upcoming", + }, + { + label: "{referer} template with extra params", + redirectToBase: "{referer}?welcome=true", + referer: "http://127.0.0.1:4321/events?tab=upcoming", + expected: "http://127.0.0.1:4321/events?tab=upcoming&welcome=true", + }, + { + label: "{loggedInUser.did} template", + redirectToBase: "/users/{loggedInUser.did}/home", + referer: "http://127.0.0.1:4321/events", + expected: `/users/${TEST_ACCOUNT_DID}/home`, + }, + { + label: "{loggedInUser.handle} template", + redirectToBase: "/users/{loggedInUser.handle}/home", + referer: "http://127.0.0.1:4321/events", + expected: `/users/${TEST_ACCOUNT_HANDLE}/home`, + }, + ])("substitutes $label", async ({ redirectToBase, referer, expected }) => { + await expect( + getRedirectUrl({ redirectToBase, did: TEST_ACCOUNT_DID, referer }), + ).resolves.toBe(expected); + }); + + test("falls back to / when the substituted redirect is empty", async () => { + await expect( + getRedirectUrl({ + redirectToBase: "{referer}", + did: TEST_ACCOUNT_DID, + referer: "", + }), + ).resolves.toBe("/"); + }); +}); + +describe("route wiring", () => { + test("logout route forwards form redirect and referer through getRedirectUrl", async () => { + const session = createSession(); + await persistLoginGrant(session, { + did: TEST_ACCOUNT_DID, + scopes: ["atproto"], + }); + + const response = await LOGOUT({ + request: new Request("http://127.0.0.1:4321/oauth/logout", { + method: "POST", + body: new URLSearchParams({ redirect: "{referer}?reason=logged-out" }), + headers: { + "content-type": "application/x-www-form-urlencoded", + referer: "http://127.0.0.1:4321/events?tab=upcoming", + }, + }), + session, + redirect, + } as Parameters[0]); + + expect(response.headers.get("location")).toBe( + "http://127.0.0.1:4321/events?tab=upcoming&reason=logged-out", + ); + }); + + test("callback route forwards state redirect and referer through getRedirectUrl", async () => { + const appState = encodeOAuthState({ + scopes: ["atproto"], + redirect: "/users/{loggedInUser.handle}/home", + referer: "http://127.0.0.1:4321/start", + }); + mockOAuthCallbackResult = { + session: { did: TEST_ACCOUNT_DID }, + state: appState, + }; + + const session = createSession(); + const response = await CALLBACK({ + request: new Request( + "http://127.0.0.1:4321/oauth/callback?code=test-code&state=anything", + ), + session, + redirect, + } as Parameters[0]); + + expect(response.headers.get("location")).toBe( + `/users/${TEST_ACCOUNT_HANDLE}/home`, + ); + }); +}); diff --git a/astro-authproto/__tests__/scopes.test.ts b/astro-authproto/__tests__/scopes.test.ts new file mode 100644 index 0000000..411d992 --- /dev/null +++ b/astro-authproto/__tests__/scopes.test.ts @@ -0,0 +1,263 @@ +import { describe, expect, test, vi } from "vitest"; + +import { + resolveAuthorizeFormScopes, + resolveLoginFormScopes, + resolveServerLoginScopes, +} from "../src/lib/scopes.ts"; +import { resolveAtprotoIdentity } from "../src/lib/auth.ts"; +import { + TEST_ACCOUNT_DID, + TEST_ACCOUNT_HANDLE, +} from "./support/auth-fixtures.ts"; + +describe("authorize form scopes", () => { + test("returns defaults when no overrides are provided", () => { + expect( + resolveAuthorizeFormScopes({ + defaultScopes: ["transition:generic"], + }), + ).toEqual(["transition:generic"]); + }); + + test("extendDefaultScopes merges with defaults and atproto, deduplicated", () => { + expect( + resolveAuthorizeFormScopes({ + defaultScopes: ["transition:generic"], + extendDefaultScopes: ["transition:email", "transition:generic"], + }), + ).toEqual(["atproto", "transition:generic", "transition:email"]); + }); + + test("lets scopes override defaults entirely", () => { + expect( + resolveAuthorizeFormScopes({ + scopes: ["transition:email"], + defaultScopes: ["transition:generic"], + }), + ).toEqual(["atproto", "transition:email"]); + }); +}); + +describe("login form scopes", () => { + test("returns an empty list when no overrides are provided", () => { + // Bare Login forms omit scope fields so the server route can apply defaultScopes. + expect( + resolveLoginFormScopes({ + defaultScopes: ["transition:generic"], + }), + ).toEqual([]); + }); + + test("extendDefaultScopes still merges defaults even though no-override returns empty", () => { + // extendDefaultScopes extends defaultScopes; it does not replace them. + expect( + resolveLoginFormScopes({ + defaultScopes: ["transition:generic"], + extendDefaultScopes: ["transition:email"], + }), + ).toEqual(["atproto", "transition:generic", "transition:email"]); + }); + + test("lets scopes override defaults entirely", () => { + expect( + resolveLoginFormScopes({ + scopes: ["transition:email"], + defaultScopes: ["transition:generic"], + }), + ).toEqual(["atproto", "transition:email"]); + }); +}); + +describe("server login scopes", () => { + test("skips resolveScopes when atprotoId is missing", async () => { + // Non-empty requested scopes isolates the skip behavior from the + // defaults-fallback path (covered separately below). + const resolveScopes = vi.fn(); + + await expect( + resolveServerLoginScopes({ + requestedScopes: ["transition:generic"], + configuredScopes: ["atproto", "transition:generic"], + defaultScopes: ["transition:email"], + atprotoId: undefined, + resolveScopes, + }), + ).resolves.toEqual(["atproto", "transition:generic"]); + + expect(resolveScopes).not.toHaveBeenCalled(); + }); + + test("filters requestedScopes against configuredScopes before invoking the hook", async () => { + // The "before" filter: requested scopes the app didn't configure are + // dropped *before* the hook runs, so a user-supplied hook never sees + // them. + const resolveScopes = vi.fn(async ({ proposedScopes }) => [ + // We don't resolve further scopes so if extra ones remain, + // it must be because it wasn't filtered by the hook. + ...proposedScopes, + ]); + + await expect( + resolveServerLoginScopes({ + requestedScopes: ["transition:email", "transition:not-configured"], + configuredScopes: ["atproto", "transition:generic", "transition:email"], + defaultScopes: ["transition:generic"], + atprotoId: TEST_ACCOUNT_HANDLE, + resolveScopes, + }), + // "transition:not-configured" is dropped (not in configuredScopes), + // "atproto" is always prepended, and defaultScopes isn't used because the + // request had at least one allowed scope requested. + ).resolves.toEqual(["atproto", "transition:email"]); + + expect(resolveScopes).toHaveBeenCalledWith( + expect.objectContaining({ + // Hook sees the already-filtered list, not the raw requestedScopes. + proposedScopes: ["atproto", "transition:email"], + allowedScopes: ["atproto", "transition:generic", "transition:email"], + defaultScopes: ["atproto", "transition:generic"], + }), + ); + }); + + test("does not let resolveScopes add unconfigured scopes", async () => { + // The "after" filter: even if the hook returns scopes the app didn't + // configure, they are stripped before the OAuth request goes out. + const resolveScopes = vi.fn(async ({ proposedScopes }) => [ + ...proposedScopes, + "transition:admin", // not in configuredScopes, must be dropped + "transition:email", // allowed, kept + "atproto", // duplicate of what's already in proposedScopes, deduped + ]); + + await expect( + resolveServerLoginScopes({ + requestedScopes: ["transition:not-configured", "transition:generic"], + configuredScopes: ["atproto", "transition:generic", "transition:email"], + defaultScopes: ["transition:generic"], + atprotoId: TEST_ACCOUNT_HANDLE, + resolveScopes, + }), + // "transition:admin" dropped (not configured); "atproto" deduped. + // "transition:generic" survived the pre-hook filter of requestedScopes. + ).resolves.toEqual(["atproto", "transition:generic", "transition:email"]); + + expect(resolveScopes).toHaveBeenCalledWith({ + atprotoId: expect.objectContaining({ + handle: TEST_ACCOUNT_HANDLE, + did: undefined, + resolve: expect.any(Function), + }), + proposedScopes: ["atproto", "transition:generic"], + allowedScopes: ["atproto", "transition:generic", "transition:email"], + defaultScopes: ["atproto", "transition:generic"], + }); + }); + + test("falls back to default scopes when requested scopes are all unconfigured", async () => { + await expect( + resolveServerLoginScopes({ + requestedScopes: ["transition:not-configured"], + configuredScopes: ["atproto", "transition:generic", "transition:email"], + defaultScopes: ["transition:email"], + atprotoId: TEST_ACCOUNT_HANDLE, + resolveScopes: null, + }), + ).resolves.toEqual(["atproto", "transition:email"]); + }); + + test("passes a handle-discriminated atprotoId and a working resolve()", async () => { + // A handle login exposes `handle` (did undefined). `resolve()` fills in the + // canonical pair via the MSW-backed production identity resolver. + const handleHook = vi.fn(async ({ atprotoId, proposedScopes }) => { + expect(atprotoId).toMatchObject({ + handle: TEST_ACCOUNT_HANDLE, + did: undefined, + }); + expect(await atprotoId.resolve()).toEqual({ + did: TEST_ACCOUNT_DID, + handle: TEST_ACCOUNT_HANDLE, + }); + return proposedScopes; + }); + + await resolveServerLoginScopes({ + requestedScopes: ["transition:generic"], + configuredScopes: ["atproto", "transition:generic"], + defaultScopes: ["transition:generic"], + atprotoId: TEST_ACCOUNT_HANDLE, + resolveScopes: handleHook, + resolveIdentity: resolveAtprotoIdentity, + }); + + expect(handleHook).toHaveBeenCalledOnce(); + }); + + test("passes a DID-discriminated atprotoId and a working resolve()", async () => { + const didHook = vi.fn(async ({ atprotoId, proposedScopes }) => { + expect(atprotoId).toMatchObject({ + did: TEST_ACCOUNT_DID, + handle: undefined, + }); + expect(await atprotoId.resolve()).toEqual({ + did: TEST_ACCOUNT_DID, + handle: TEST_ACCOUNT_HANDLE, + }); + return proposedScopes; + }); + + await resolveServerLoginScopes({ + requestedScopes: ["transition:generic"], + configuredScopes: ["atproto", "transition:generic"], + defaultScopes: ["transition:generic"], + atprotoId: TEST_ACCOUNT_DID, + resolveScopes: didHook, + resolveIdentity: resolveAtprotoIdentity, + }); + + expect(didHook).toHaveBeenCalledOnce(); + }); + + test("accepts void and null resolveScopes returns as proposed scopes", async () => { + // 1. Hook returns void => package falls back to proposedScopes. + // 2. Hook returns null => same fallback. + // 3. Hook inputs are frozen so a hook cannot mutate the package's + // internal arrays by accident. + const voidResolveScopes = vi.fn((input) => { + // Assertions live inside the hook so we observe the exact object the + // package passed in, before any return value is processed. + expect(input).toMatchObject({ + atprotoId: { handle: TEST_ACCOUNT_HANDLE, did: undefined }, + proposedScopes: ["atproto", "transition:generic"], + allowedScopes: ["atproto", "transition:generic", "transition:email"], + defaultScopes: ["atproto", "transition:email"], + }); + expect(Object.isFrozen(input.proposedScopes)).toBe(true); + expect(Object.isFrozen(input.allowedScopes)).toBe(true); + expect(Object.isFrozen(input.defaultScopes)).toBe(true); + }); + + await expect( + resolveServerLoginScopes({ + requestedScopes: ["transition:generic"], + configuredScopes: ["atproto", "transition:generic", "transition:email"], + defaultScopes: ["transition:email"], + atprotoId: TEST_ACCOUNT_HANDLE, + resolveScopes: voidResolveScopes, + }), + ).resolves.toEqual(["atproto", "transition:generic"]); + + const nullResolveScopes = vi.fn(() => null); + + await expect( + resolveServerLoginScopes({ + requestedScopes: ["transition:generic"], + configuredScopes: ["atproto", "transition:generic"], + defaultScopes: ["transition:generic"], + atprotoId: TEST_ACCOUNT_HANDLE, + resolveScopes: nullResolveScopes, + }), + ).resolves.toEqual(["atproto", "transition:generic"]); + }); +}); diff --git a/astro-authproto/__tests__/session-state.test.ts b/astro-authproto/__tests__/session-state.test.ts new file mode 100644 index 0000000..fd22650 --- /dev/null +++ b/astro-authproto/__tests__/session-state.test.ts @@ -0,0 +1,95 @@ +import { describe, expect, test } from "vitest"; + +import { + AUTHPROTO_DID, + AUTHPROTO_ERROR_CODE, + AUTHPROTO_ERROR_DESCRIPTION, + AUTHPROTO_ERROR_URI, + AUTHPROTO_SCOPES, + clearSessionGrant, + persistAuthprotoError, + persistLoginGrant, + readAndClearAuthprotoError, + readSessionGrant, +} from "../src/lib/session-state.ts"; +import { createSession } from "./support/session.ts"; + +describe("session state", () => { + test("persists and clears login grants", async () => { + const session = createSession(); + + await persistLoginGrant(session, { + did: "did:plc:b0b474nD1d", + scopes: ["atproto", "transition:generic"], + }); + + await expect(readSessionGrant(session)).resolves.toEqual({ + did: "did:plc:b0b474nD1d", + scopes: ["atproto", "transition:generic"], + }); + + expect(await session.get(AUTHPROTO_SCOPES)).toEqual([ + "atproto", + "transition:generic", + ]); + + await clearSessionGrant(session); + await expect(readSessionGrant(session)).resolves.toEqual({ + did: undefined, + scopes: [], + }); + expect(await session.get(AUTHPROTO_DID)).toBeUndefined(); + expect(await session.get(AUTHPROTO_SCOPES)).toBeUndefined(); + }); + + test("does not store non-string scope values in login grants", async () => { + const session = createSession(); + + await persistLoginGrant(session, { + did: "did:plc:b0b474nD1d", + scopes: ["atproto", 123, "transition:generic"], + }); + + expect(await session.get(AUTHPROTO_SCOPES)).toEqual([ + "atproto", + "transition:generic", + ]); + await expect(readSessionGrant(session)).resolves.toEqual({ + did: "did:plc:b0b474nD1d", + scopes: ["atproto", "transition:generic"], + }); + }); + + test("drops non-string scope values left in session storage", async () => { + const session = createSession({ + [AUTHPROTO_DID]: "did:plc:b0b474nD1d", + // Existing session storage may already contain invalid scope values. + [AUTHPROTO_SCOPES]: ["atproto", 123, "transition:generic"], + }); + + await expect(readSessionGrant(session)).resolves.toEqual({ + did: "did:plc:b0b474nD1d", + scopes: ["atproto", "transition:generic"], + }); + }); + + test("reads and clears auth errors for locals input", async () => { + const session = createSession(); + + await persistAuthprotoError(session, { + code: "lexError", + description: "Lexicon validation failed", + uri: "https://auth.fujocoded.test/errors/lexError", + }); + + await expect(readAndClearAuthprotoError(session)).resolves.toEqual({ + code: "lexError", + description: "Lexicon validation failed", + uri: "https://auth.fujocoded.test/errors/lexError", + }); + await expect(readAndClearAuthprotoError(session)).resolves.toBeNull(); + expect(await session.get(AUTHPROTO_ERROR_CODE)).toBeUndefined(); + expect(await session.get(AUTHPROTO_ERROR_DESCRIPTION)).toBeUndefined(); + expect(await session.get(AUTHPROTO_ERROR_URI)).toBeUndefined(); + }); +}); diff --git a/astro-authproto/__tests__/setup.ts b/astro-authproto/__tests__/setup.ts new file mode 100644 index 0000000..6964177 --- /dev/null +++ b/astro-authproto/__tests__/setup.ts @@ -0,0 +1,23 @@ +import { afterAll, afterEach, beforeAll, vi } from "vitest"; + +import { resetParRequests, server } from "./support/msw.ts"; +import { resetTestStores } from "./virtual/stores.ts"; + +vi.mock("node:dns/promises", async (importActual) => { + const { createDnsMock } = await import("@fujocoded/msw-atproto"); + return createDnsMock(importActual); +}); + +beforeAll(() => { + server.listen({ onUnhandledRequest: "error" }); +}); + +afterEach(() => { + server.resetHandlers(); + resetParRequests(); + resetTestStores(); +}); + +afterAll(() => { + server.close(); +}); diff --git a/astro-authproto/__tests__/support/auth-fixtures.ts b/astro-authproto/__tests__/support/auth-fixtures.ts new file mode 100644 index 0000000..c555723 --- /dev/null +++ b/astro-authproto/__tests__/support/auth-fixtures.ts @@ -0,0 +1,4 @@ +export const TEST_ACCOUNT_DID = "did:plc:yk4dd2qkboz2yv6tpubpc6co"; +export const TEST_ACCOUNT_HANDLE = "bobatan.fujocoded.test"; +export const TEST_ACCOUNT_PDS = "https://pds.fujocoded.test"; +export const TEST_ACCOUNT_AUTH_SERVER = "https://auth.fujocoded.test"; diff --git a/astro-authproto/__tests__/support/msw.ts b/astro-authproto/__tests__/support/msw.ts new file mode 100644 index 0000000..68e83ef --- /dev/null +++ b/astro-authproto/__tests__/support/msw.ts @@ -0,0 +1,74 @@ +import { createMockRepoIdentity } from "@fujocoded/msw-atproto"; +import { http, HttpResponse } from "msw"; +import { setupServer } from "msw/node"; + +import { + TEST_ACCOUNT_AUTH_SERVER, + TEST_ACCOUNT_DID, + TEST_ACCOUNT_HANDLE, + TEST_ACCOUNT_PDS, +} from "./auth-fixtures.ts"; + +// Form body of every Pushed Authorization Request the login route sends. +// Tests read this to assert on the scopes and parameters that went out. +// Call resetParRequests() between tests so one test's requests don't leak +// into the next. +export const parRequests: URLSearchParams[] = []; + +export const resetParRequests = () => { + parRequests.length = 0; +}; + +export const server = setupServer( + // MSW handlers that fake the AT Protocol OAuth login flow. + // Before a real login can redirect the user to the authorization page, the + // client needs to: + // 1. resolve the account's handle to its DID, then the DID to its PDS + // 2. ask the PDS which auth server protects it + // 3. read that auth server's metadata + // 4. push the authorization request to the auth server + // We fake every step so the OAuth route tests never hit the network. + ...createMockRepoIdentity({ + did: TEST_ACCOUNT_DID, + handle: TEST_ACCOUNT_HANDLE, + pds: TEST_ACCOUNT_PDS, + }).handlers(), + + http.get(`${TEST_ACCOUNT_PDS}/.well-known/oauth-protected-resource`, () => { + return HttpResponse.json({ + resource: TEST_ACCOUNT_PDS, + authorization_servers: [TEST_ACCOUNT_AUTH_SERVER], + }); + }), + + http.get( + `${TEST_ACCOUNT_AUTH_SERVER}/.well-known/oauth-authorization-server`, + () => { + return HttpResponse.json({ + issuer: TEST_ACCOUNT_AUTH_SERVER, + authorization_endpoint: `${TEST_ACCOUNT_AUTH_SERVER}/oauth/authorize`, + token_endpoint: `${TEST_ACCOUNT_AUTH_SERVER}/oauth/token`, + pushed_authorization_request_endpoint: `${TEST_ACCOUNT_AUTH_SERVER}/oauth/par`, + protected_resources: [TEST_ACCOUNT_PDS], + scopes_supported: ["atproto"], + response_types_supported: ["code"], + response_modes_supported: ["query"], + grant_types_supported: ["authorization_code", "refresh_token"], + code_challenge_methods_supported: ["S256"], + token_endpoint_auth_methods_supported: ["none"], + dpop_signing_alg_values_supported: ["ES256"], + client_id_metadata_document_supported: true, + require_pushed_authorization_requests: true, + }); + }, + ), + + http.post(`${TEST_ACCOUNT_AUTH_SERVER}/oauth/par`, async ({ request }) => { + parRequests.push(new URLSearchParams(await request.text())); + + return HttpResponse.json({ + request_uri: "urn:ietf:params:oauth:request_uri:test-request", + expires_in: 60, + }); + }), +); diff --git a/astro-authproto/__tests__/support/session.ts b/astro-authproto/__tests__/support/session.ts new file mode 100644 index 0000000..7b6e5c5 --- /dev/null +++ b/astro-authproto/__tests__/support/session.ts @@ -0,0 +1,41 @@ +import { + AUTHPROTO_DID, + AUTHPROTO_ERROR_CODE, + AUTHPROTO_ERROR_DESCRIPTION, + AUTHPROTO_ERROR_URI, + AUTHPROTO_SCOPES, + type AuthprotoSession, +} from "../../src/lib/session-state.ts"; + +export type TestSession = AuthprotoSession; + +export const createSession = ( + initialValues: Record = {}, +): TestSession => { + const values: Record = { + [AUTHPROTO_DID]: undefined, + [AUTHPROTO_SCOPES]: undefined, + [AUTHPROTO_ERROR_CODE]: undefined, + [AUTHPROTO_ERROR_DESCRIPTION]: undefined, + [AUTHPROTO_ERROR_URI]: undefined, + ...initialValues, + }; + + return { + async get(key: string) { + return values[key]; + }, + set(key: string, value: unknown) { + values[key] = value; + }, + delete(key: string) { + delete values[key]; + }, + } as TestSession; +}; + +export const redirect = (location: string) => + new Response(null, { + status: 302, + headers: { location }, + }); diff --git a/astro-authproto/__tests__/virtual/config.ts b/astro-authproto/__tests__/virtual/config.ts new file mode 100644 index 0000000..4b03aa9 --- /dev/null +++ b/astro-authproto/__tests__/virtual/config.ts @@ -0,0 +1,16 @@ +// Mpcks the `fujocoded:authproto/config` virtual module via vitest.config.ts. +// Mirrors what the Astro integration emits at build time so src/ can read its +// configuration through the same import shape it uses in production. +export const applicationName = "AuthProto Test App"; +export const applicationDomain = "http://127.0.0.1:4321"; +export const defaultDevUser = null; +export const externalDomain = undefined; +export const storage = null; +export const scopes = ["atproto", "transition:generic", "transition:email"]; +export const defaultScopes = ["atproto", "transition:generic"]; +export const driverName = "memory"; +export const redirectAfterLogin = "/"; +export const redirectAfterLogout = "/"; +export const clientMetadataDomain = "http://127.0.0.1:4321"; +export const isDev = true; +export const isDevServerHostSet = true; diff --git a/astro-authproto/__tests__/virtual/hooks.ts b/astro-authproto/__tests__/virtual/hooks.ts new file mode 100644 index 0000000..6857222 --- /dev/null +++ b/astro-authproto/__tests__/virtual/hooks.ts @@ -0,0 +1,22 @@ +// Mocks the `fujocoded:authproto/hooks` virtual module via vitest.config.ts. +// The login route imports `resolveScopes` from here; the special atprotoId +// values below let oauth-login.test.ts exercise hook failure and extension +// paths without needing to mock the virtual module per test. +import type { ResolveScopesHook } from "../../src/lib/scopes.ts"; + +// atprotoId values that trigger special behavior in the mocked resolveScopes +// hook, letting tests exercise the failure and extension paths. +export const SCOPE_RESOLVER_THROWS_ID = "throws.fujocoded.test"; +export const SCOPE_RESOLVER_ADDS_EMAIL_ID = "with-email.fujocoded.test"; + +export const resolveScopes: ResolveScopesHook = async ({ + atprotoId, + proposedScopes, +}) => { + if (atprotoId.handle === SCOPE_RESOLVER_THROWS_ID) { + throw new Error("scope resolver failed"); + } + if (atprotoId.handle === SCOPE_RESOLVER_ADDS_EMAIL_ID) { + return [...proposedScopes, "transition:email"]; + } +}; diff --git a/astro-authproto/__tests__/virtual/stores.ts b/astro-authproto/__tests__/virtual/stores.ts new file mode 100644 index 0000000..a038592 --- /dev/null +++ b/astro-authproto/__tests__/virtual/stores.ts @@ -0,0 +1,62 @@ +// Backs the `fujocoded:authproto/stores` virtual module via vitest.config.ts. +// auth.ts:createClient instantiates these for the NodeOAuthClient's +// state and session storage. `resetTestStores` is called from setup.ts +// between tests to keep stores isolated. + +type StoreValue = unknown; + +const states = new Map(); +const sessions = new Map(); + +export const getTestStateAppState = ( + state: string | null | undefined, +): string | undefined => { + if (!state) { + return undefined; + } + + const value = states.get(state); + if ( + typeof value === "object" && + value !== null && + "appState" in value && + typeof value.appState === "string" + ) { + return value.appState; + } + + return undefined; +}; + +export const resetTestStores = () => { + states.clear(); + sessions.clear(); +}; + +export class StateStore { + async get(key: string) { + return states.get(key); + } + + async set(key: string, value: StoreValue) { + states.set(key, value); + } + + async del(key: string) { + states.delete(key); + } +} + +export class SessionStore { + async get(did: string) { + return sessions.get(did); + } + + async set(did: string, value: StoreValue) { + sessions.set(did, value); + } + + async del(did: string) { + sessions.delete(did); + } +} diff --git a/astro-authproto/package.json b/astro-authproto/package.json index 53f4965..6aa8c6e 100644 --- a/astro-authproto/package.json +++ b/astro-authproto/package.json @@ -1,7 +1,7 @@ { "name": "@fujocoded/authproto", "version": "0.3.1", - "description": "Astro integration to easily authenticateyour site visitors using ATproto. For Bluesky and beyond.", + "description": "Astro integration to easily authenticate your site visitors using ATproto. For Bluesky and beyond.", "keywords": [ "astro", "astro-integration", @@ -27,6 +27,7 @@ "package.json" ], "type": "module", + "sideEffects": false, "main": "dist/index.js", "exports": { ".": { @@ -71,15 +72,20 @@ "@atproto/api": "^0.17.3", "@atproto/identity": "^0.4.8", "@atproto/jwk-jose": "^0.1.4", + "@atproto/oauth-client": "^0.5.14", "@atproto/oauth-client-node": "^0.3.8", + "@atproto/oauth-types": "^0.6.2", "astro-integration-kit": "^0.20.0", "unstorage": "^1.16.1" }, "devDependencies": { "@astrojs/db": "^0.17.1", + "@fujocoded/msw-atproto": "^0.0.1", "drizzle-orm": "^0.42.0", "glob": "^13.0.6", - "tsdown": "^0.17.2" + "msw": "^2.13.4", + "tsdown": "^0.17.2", + "vitest": "^3.2.4" }, "peerDependencies": { "@astrojs/db": "^0.17.1 || ^0.20.0", diff --git a/astro-authproto/src/components.ts b/astro-authproto/src/components.ts index 3462eb4..c0ac0f3 100644 --- a/astro-authproto/src/components.ts +++ b/astro-authproto/src/components.ts @@ -1 +1,2 @@ +export { default as Authorize } from "./components/Authorize.astro"; export { default as Login } from "./components/Login.astro"; diff --git a/astro-authproto/src/components/Authorize.astro b/astro-authproto/src/components/Authorize.astro new file mode 100644 index 0000000..afaa1ed --- /dev/null +++ b/astro-authproto/src/components/Authorize.astro @@ -0,0 +1,82 @@ +--- +import type { HTMLAttributes } from "astro/types"; +import { defaultDevUser, defaultScopes } from "fujocoded:authproto/config"; +import { resolveAuthorizeFormScopes } from "../lib/scopes.js"; +import type { OAuthScope } from "../lib/config.js"; + +interface Props extends Omit, "action" | "method"> { + /** + * Where to send the user after authorization succeeds. + */ + redirect?: string; + /** + * Placeholder text for the handle input when no user is logged in. + */ + placeholder?: string; + /** + * Exact scopes this authorization form should request instead of + * `defaultScopes`. Authproto always adds `"atproto"` and ignores scopes you + * did not configure. + */ + scopes?: OAuthScope[]; + /** + * Extra scopes to request on top of `defaultScopes` (rather than on top of the + * current session's grant). Automatically ignores scopes you did not configure. + */ + extendDefaultScopes?: OAuthScope[]; +} + +const { + redirect: redirectUrl, + placeholder, + scopes, + extendDefaultScopes, + ...formAttrs +} = Astro.props; + +const currentUser = Astro.locals.loggedInUser; +const uniqueScopes = resolveAuthorizeFormScopes({ + defaultScopes, + extendDefaultScopes, + scopes, +}); +// When the current session already has the requested grant, the component is a +// deliberate no-op: there is nothing new to authorize. +const alreadyAuthorized = + currentUser && + uniqueScopes.length > 0 && + uniqueScopes.every((scope) => currentUser.scopes.includes(scope)); +--- + +{ + alreadyAuthorized ? ( + + ) : ( +
+ {currentUser ? ( + + ) : ( + + )} + {redirectUrl && ( + + )} + {uniqueScopes.map((scope) => ( + + ))} + +
+ ) +} diff --git a/astro-authproto/src/components/Login.astro b/astro-authproto/src/components/Login.astro index 9e7680a..3c2d0ad 100644 --- a/astro-authproto/src/components/Login.astro +++ b/astro-authproto/src/components/Login.astro @@ -1,26 +1,49 @@ --- import type { HTMLAttributes } from "astro/types"; -import { defaultDevUser } from "fujocoded:authproto/config"; +import { defaultDevUser, defaultScopes } from "fujocoded:authproto/config"; +import { resolveLoginFormScopes } from "../lib/scopes.js"; +import type { OAuthScope } from "../lib/config.js"; interface Props extends Omit, "action" | "method"> { - /** Optional redirect URL after successful login/logout */ + /** + * Where to send the user after they log in or log out. + */ redirect?: string; + /** + * Placeholder text for the handle input. + */ placeholder?: string; + /** + * Exact scopes this login form should request instead of `defaultScopes`. + * Authproto always adds `"atproto"` and ignores scopes you did not configure. + */ + scopes?: OAuthScope[]; + /** + * Extra scopes to request on top of `defaultScopes` (rather than on top of the + * current session's grant). Authproto ignores scopes you did not configure. + */ + extendDefaultScopes?: OAuthScope[]; } const { redirect: redirectUrl, - placeholder = "handle.bsky.social", + placeholder, + scopes, + extendDefaultScopes, ...formAttrs } = Astro.props; const currentUser = Astro.locals.loggedInUser; const currentError = Astro.locals.authproto?.errorDescription ?? Astro.locals.authproto?.errorCode; +const resolvedScopes = resolveLoginFormScopes({ + defaultScopes, + extendDefaultScopes, + scopes, +}); --- { currentUser ? ( - // If there's a current user, show the log out button
{redirectUrl && ( @@ -28,7 +51,6 @@ const currentError =
) : ( - // If there's no current user, show the login button
)} + {resolvedScopes.map((scope) => ( + + ))}
) diff --git a/astro-authproto/src/config-module.d.ts b/astro-authproto/src/config-module.d.ts index 19a56b6..2b09847 100644 --- a/astro-authproto/src/config-module.d.ts +++ b/astro-authproto/src/config-module.d.ts @@ -1,48 +1,16 @@ -/** - * Type definitions for "fujocoded:authproto/config". - * - * This file declares types for fujocoded:authproto's virtual modules. These - * modules are "imports" that don't map to real files, but are injected into the - * application by Vite. - * - * We need a sepate type definition file for virtual modules, because TypeScript has - * two separate modes for .d.ts files: - * 1. "Script" mode, which can declare new modules (does NOT support import/export) - * 2. "Module" mode, which can only augment existing modules (supports import/export) - * - * In this case, we need both types, so we add this file (config-module.d.ts) for module - * declarations. - * - * See: https://www.totaltypescript.com/books/total-typescript-essentials/modules-scripts-and-declaration-files#module-augmentation-vs-module-overriding - */ -declare module "fujocoded:authproto/config" { - export const applicationName: string; - export const applicationDomain: string; - export const defaultDevUser: string | null; - export const externalDomain: string | undefined; - export const storage: import("unstorage").Storage | null; - export const scopes: string[]; - export const driverName: string; - export const redirectAfterLogin: string; - export const redirectAfterLogout: string; - export const clientMetadataDomain: string; - export const isDev: boolean; - export const isDevServerHostSet: boolean; -} - -declare module "fujocoded:authproto/stores" { - export { - StateStore, - SessionStore, - } from "@fujocoded/authproto/stores/unstorage"; -} +// The consumer-facing virtual module declarations (config, hooks, stores) live +// in `virtual-modules.d.ts` so they can be both injected into consuming apps via +// `injectTypes` and picked up here for internal typechecking by tsconfig's +// `include` glob. declare module "*.astro" { const Component: import("astro/runtime/server/index.js").AstroComponentFactory; export default Component; } -// astro:db types - @astrojs/db is an optional peer dependency +// Minimal `any`-typed version of the `astro:db` virtual module. In reality, +// types are generated by `astro sync` in the consuming app, but @astrojs/db may +// not be installed here as it's an optional peer dep. declare module "astro:db" { export const db: any; export function eq(column: any, value: any): any; diff --git a/astro-authproto/src/index.ts b/astro-authproto/src/index.ts index 46bb6b3..b05df9f 100644 --- a/astro-authproto/src/index.ts +++ b/astro-authproto/src/index.ts @@ -5,11 +5,24 @@ import { execFileSync } from "node:child_process"; import { addVirtualImports } from "astro-integration-kit"; import { getConfig, + getHooksImport, getStoresImport, type ConfigOptions, } from "./lib/config.js"; import { readFile } from "node:fs/promises"; +export type { OAuthScope } from "./lib/config.js"; +export type { ResolveScopesHook, ResolveScopesInput } from "./lib/scopes.js"; +export { + account, + blob, + identity, + include, + permissionScopes, + repo, + rpc, +} from "./lib/permissions.js"; + /** * Returns whether `git check-ignore` thinks the given absolute path is covered * by the project's gitignore rules. Returns `null` when git can't answer (no @@ -122,9 +135,22 @@ export default ( content: getStoresImport(configOptions.driver?.name), context: "server", }, + { + id: "fujocoded:authproto/hooks", + content: getHooksImport(configOptions.resolveScopesEntrypoint), + context: "server", + }, ], }); + addMiddleware({ + order: "pre", + entrypoint: path.join( + import.meta.dirname, + "./routes/loopback-middleware.js", + ), + }); + // Add request interception that deals with authorizing the users during // the regular requests addMiddleware({ @@ -161,6 +187,33 @@ export default ( `Your Astro output config is "static". The login status is only available on dynamically rendered pages.`, ); } + if ( + config.output === "server" && + config.security?.checkOrigin !== false && + (config.security?.allowedDomains?.length ?? 0) === 0 + ) { + let publicHostname: string | undefined; + let publicProtocol: string | undefined; + try { + const parsed = new URL(configOptions.applicationDomain); + if ( + parsed.protocol === "https:" && + parsed.hostname !== "localhost" && + parsed.hostname !== "127.0.0.1" + ) { + publicHostname = parsed.hostname; + publicProtocol = "https"; + } + } catch { + // Non-URL applicationDomain values cannot produce a targeted hint. + } + const suggestion = publicHostname + ? `Add to astro.config: security: { allowedDomains: [{ hostname: "${publicHostname}", protocol: "${publicProtocol}" }] }` + : `Add your public hostname to security.allowedDomains in astro.config.`; + logger.warn( + `security.allowedDomains is empty. Behind a TLS-terminating proxy, login POSTs can fail with "Cross-site form submissions are forbidden" because Astro can't trust X-Forwarded-Proto. ${suggestion}`, + ); + } if ( configOptions.driver?.name === "fs" || configOptions.driver?.name === "fs-lite" @@ -201,6 +254,18 @@ export default ( }, ), }); + // Ship the virtual module declarations (config/hooks/stores) so apps that + // import from `fujocoded:authproto/config` typecheck. Without this, the + // declarations only exist inside the package's own source. + injectTypes({ + filename: "virtual-modules.d.ts", + content: await readFile( + path.join(import.meta.dirname, "./virtual-modules.d.ts"), + { + encoding: "utf-8", + }, + ), + }); }, "astro:db:setup": ({ extendDb }) => { if (configOptions.driver?.name == "astro:db") { diff --git a/astro-authproto/src/lib/auth.ts b/astro-authproto/src/lib/auth.ts index 9605f94..ef0e54d 100644 --- a/astro-authproto/src/lib/auth.ts +++ b/astro-authproto/src/lib/auth.ts @@ -2,9 +2,10 @@ import { NodeOAuthClient, type NodeOAuthClientOptions, } from "@atproto/oauth-client-node"; +import { requestLocalLock } from "@atproto/oauth-client"; import { JoseKey } from "@atproto/jwk-jose"; -import { DidResolver } from "@atproto/identity"; +import { DidResolver, HandleResolver } from "@atproto/identity"; import { scopes, applicationName, @@ -26,23 +27,20 @@ const REDIRECT_PATH = "/oauth/callback"; export const createClientMetadata = ( domain: string, ): NodeOAuthClientOptions["clientMetadata"] => { - const DOMAIN_URL = new URL(domain); + const ENDPOINT_URL = new URL(domain); const IS_LOCALHOST = - DOMAIN_URL.hostname === "127.0.0.1" || DOMAIN_URL.hostname === "localhost"; - - const ENDPOINT_URL = IS_LOCALHOST - ? (() => { - const loopback = new URL(domain); - loopback.hostname = "127.0.0.1"; - return loopback; - })() - : DOMAIN_URL; + ENDPOINT_URL.hostname === "127.0.0.1" || + ENDPOINT_URL.hostname === "localhost"; + if (IS_LOCALHOST) { + // RFC 8252 requires the redirect_uri to use 127.0.0.1 (not "localhost"). + ENDPOINT_URL.hostname = "127.0.0.1"; + } // In local clients configuration for allowed scopes and redirects // is done through search params. In that case, the client ID must // be "http://localhost" but the redirect_uri must use 127.0.0.1 (RFC 8252). // See: https://atproto.com/specs/oauth#clients - const CLIENT_ID = new URL(IS_LOCALHOST ? "http://localhost" : DOMAIN_URL); + const CLIENT_ID = new URL(IS_LOCALHOST ? "http://localhost" : domain); if (IS_LOCALHOST) { CLIENT_ID.searchParams.set("scope", scopes.join(" ")); CLIENT_ID.searchParams.set( @@ -71,20 +69,48 @@ export const createClientMetadata = ( const createClient = async (domain: string) => { return new NodeOAuthClient({ clientMetadata: createClientMetadata(domain), + fetch: (input, init) => globalThis.fetch(input, init), keyset: [await JoseKey.generate()], stateStore: new Stores.StateStore(), sessionStore: new Stores.SessionStore(), + requestLock: requestLocalLock, }); }; -export const oauthClient = await createClient(clientMetadataDomain); +let oauthClient: Promise | undefined; + +export const getOAuthClient = () => { + oauthClient ??= createClient(clientMetadataDomain); + return oauthClient; +}; const IDENTITY_RESOLVER = new DidResolver({}); +const HANDLE_RESOLVER = new HandleResolver({}); export const didToHandle = async (did: string) => { const atprotoData = await IDENTITY_RESOLVER.resolveAtprotoData(did); return atprotoData.handle; }; +/** + * Resolves whatever the user typed into the login form (a handle or a DID) to + * the canonical DID and handle pair. Instant when the missing half is the only + * lookup; throws when a handle can't be resolved to a DID. + */ +export const resolveAtprotoIdentity = async ( + atprotoId: string, +): Promise<{ did: string; handle: string }> => { + if (atprotoId.startsWith("did:")) { + return { did: atprotoId, handle: await didToHandle(atprotoId) }; + } + const did = await HANDLE_RESOLVER.resolve(atprotoId); + if (!did) { + throw new Error(`Could not resolve handle "${atprotoId}" to a DID`); + } + return { did, handle: atprotoId }; +}; + +// The `"UNKNOWN" | (string & {})` return literal preserves IDE autocomplete +// for the known code while still accepting any other Error.name. export const extractAuthError = ( e: unknown, ): { code: "UNKNOWN" | (string & {}); description: string | undefined } => { diff --git a/astro-authproto/src/lib/config.ts b/astro-authproto/src/lib/config.ts index ffa8e5b..daaf1e0 100644 --- a/astro-authproto/src/lib/config.ts +++ b/astro-authproto/src/lib/config.ts @@ -33,24 +33,64 @@ export type OAuthScope = */ | (string & {}); +export type ScopesOption = + | OAuthScope[] + | { + /** + * Ask for access to the user's email address and confirmation status. + */ + email?: boolean; + /** + * Ask to read and write the user's non-DM ATproto records. + */ + genericData?: boolean; + /** + * Ask to read and write Bluesky direct messages. Requires + * `genericData: true`. + */ + directMessages?: boolean; + /** + * Extra OAuth scope strings for services not covered by the named + * options. + */ + additionalScopes?: OAuthScope[]; + }; + export interface ConfigOptions { applicationName: string; applicationDomain: string; defaultDevUser?: string; externalDomain?: string; driver?: AllDriverOptions | AstroDriverOption; - // atproto => must have - // transition:email => email address + confirmation status - // transition:generic => write/read all data except DMs - // transition:chat.bsky => chat.bsky Lexicons - scopes?: - | OAuthScope[] - | { - email?: boolean; - genericData?: boolean; - directMessages?: boolean; - additionalScopes?: OAuthScope[]; - }; + /** + * Every OAuth scope this app may request. + * + * Authproto always includes `"atproto"`. Custom login forms and + * `resolveScopesEntrypoint` cannot grant scopes you did not list here. + * Omit this to request only `"atproto"`. + */ + scopes?: ScopesOption; + /** + * Scopes Authproto requests when no per-request scopes come in. + * + * This is used when `` has no `scopes` or `extendDefaultScopes`, + * and when a custom form sends no `scope` fields. Defaults to all configured + * `scopes`. + */ + defaultScopes?: ScopesOption; + /** + * Lets you change which scopes a login asks for, per account. + * + * Point this at a module that exports a `resolveScopes(input)` + * function. Authproto calls it after picking scopes + * from the request and dropping scopes you did not configure. Return a string + * array to replace `input.proposedScopes`. Return `undefined` or `null` to + * accept `input.proposedScopes`. + * + * Authproto still keeps only configured scopes and always keeps `"atproto"`. + * Omit this for one scope policy across every account. + */ + resolveScopesEntrypoint?: string; redirects?: { afterLogin?: string; afterLogout?: string; @@ -63,6 +103,13 @@ export interface ConfigOptions { */ } +export const getHooksImport = (resolveScopesEntrypoint?: string) => { + if (resolveScopesEntrypoint) { + return `export { resolveScopes } from ${JSON.stringify(resolveScopesEntrypoint)};`; + } + return `export const resolveScopes = null;`; +}; + export const getStoresImport = (driverName?: string) => { if (driverName === "astro:db") { return `export { StateStore, SessionStore } from "@fujocoded/authproto/stores/db";`; @@ -70,6 +117,28 @@ export const getStoresImport = (driverName?: string) => { return `export { StateStore, SessionStore } from "@fujocoded/authproto/stores/unstorage";`; }; +const resolveScopesOption = (scopesOption?: ScopesOption): OAuthScope[] => { + const resolved: OAuthScope[] = ["atproto"]; + if (Array.isArray(scopesOption)) { + resolved.push(...scopesOption); + } else { + if (scopesOption?.email) { + resolved.push("transition:email"); + } + if (scopesOption?.genericData) { + resolved.push("transition:generic"); + } + if (scopesOption?.directMessages) { + resolved.push("transition:chat.bsky"); + } + resolved.push(...(scopesOption?.additionalScopes ?? [])); + } + return [...new Set(resolved)]; +}; + +const exportConst = (name: string, value: unknown): string => + `export const ${name} = ${JSON.stringify(value)};`; + export const getConfig = ({ options, isDev, @@ -93,21 +162,10 @@ export const getConfig = ({ ? `http://127.0.0.1:${devPort ?? 4321}/` : options.applicationDomain); - const scopes: OAuthScope[] = ["atproto"]; - if (Array.isArray(options.scopes)) { - scopes.push(...options.scopes); - } else { - if (options.scopes?.email) { - scopes.push("transition:email"); - } - if (options.scopes?.genericData) { - scopes.push("transition:generic"); - } - if (options.scopes?.directMessages) { - scopes.push("transition:chat.bsky"); - } - scopes.push(...(options.scopes?.additionalScopes ?? [])); - } + const scopes = resolveScopesOption(options.scopes); + const defaultScopes = options.defaultScopes + ? resolveScopesOption(options.defaultScopes) + : scopes; let driversImport = ""; if (finalDriver.name !== "astro:db") { @@ -129,16 +187,17 @@ export const getConfig = ({ ${driversImport} - export const applicationName = "${options.applicationName}"; - export const applicationDomain = "${options.applicationDomain}"; - export const defaultDevUser = ${JSON.stringify(options.defaultDevUser ?? null)}; - export const scopes = ${JSON.stringify(scopes)}; - export const driverName = "${finalDriver.name}"; - export const redirectAfterLogin = ${JSON.stringify(options.redirects?.afterLogin ?? "/")}; - export const redirectAfterLogout = ${JSON.stringify(options.redirects?.afterLogout ?? "/")}; - export const externalDomain = ${JSON.stringify(externalDomain)}; - export const clientMetadataDomain = process.env.AUTHPROTO_EXTERNAL_DOMAIN ?? ${JSON.stringify(externalDomain)} ?? "${options.applicationDomain}"; - export const isDev = ${JSON.stringify(isDev)}; - export const isDevServerHostSet = ${JSON.stringify(isDevServerHostSet)}; + ${exportConst("applicationName", options.applicationName)} + ${exportConst("applicationDomain", options.applicationDomain)} + ${exportConst("defaultDevUser", options.defaultDevUser ?? null)} + ${exportConst("scopes", scopes)} + ${exportConst("defaultScopes", defaultScopes)} + ${exportConst("driverName", finalDriver.name)} + ${exportConst("redirectAfterLogin", options.redirects?.afterLogin ?? "{referer}")} + ${exportConst("redirectAfterLogout", options.redirects?.afterLogout ?? "{referer}")} + ${exportConst("externalDomain", externalDomain)} + export const clientMetadataDomain = process.env.AUTHPROTO_EXTERNAL_DOMAIN ?? ${JSON.stringify(externalDomain)} ?? ${JSON.stringify(options.applicationDomain)}; + ${exportConst("isDev", isDev)} + ${exportConst("isDevServerHostSet", isDevServerHostSet)} `; }; diff --git a/astro-authproto/src/lib/oauth-state.ts b/astro-authproto/src/lib/oauth-state.ts new file mode 100644 index 0000000..5259d14 --- /dev/null +++ b/astro-authproto/src/lib/oauth-state.ts @@ -0,0 +1,38 @@ +import type { OAuthScope } from "./config.js"; + +export type OAuthState = { + scopes: OAuthScope[]; + redirect?: string; + referer?: string; +}; + +export const encodeOAuthState = (state: OAuthState): string => + Buffer.from(JSON.stringify(state)).toString("base64url"); + +export const decodeOAuthState = ( + encoded: string | null | undefined, +): Partial => { + if (!encoded) { + return {}; + } + + try { + const value = JSON.parse(Buffer.from(encoded, "base64url").toString()) as { + scopes?: unknown; + redirect?: unknown; + referer?: unknown; + }; + return { + ...(Array.isArray(value.scopes) && + value.scopes.every( + (scope): scope is OAuthScope => typeof scope === "string", + ) && { + scopes: value.scopes, + }), + ...(typeof value.redirect === "string" && { redirect: value.redirect }), + ...(typeof value.referer === "string" && { referer: value.referer }), + }; + } catch { + return {}; + } +}; diff --git a/astro-authproto/src/lib/permissions.ts b/astro-authproto/src/lib/permissions.ts new file mode 100644 index 0000000..40c0c21 --- /dev/null +++ b/astro-authproto/src/lib/permissions.ts @@ -0,0 +1,187 @@ +import type { OAuthScope } from "./config.js"; + +type RepoAction = "create" | "update" | "delete"; +type AccountAction = "read" | "manage"; +type AccountAttr = "email" | "repo" | (string & {}); +type IdentityAttr = "handle" | "*"; +type OneOrMany = T | readonly T[]; + +type RepoPermissionOptions = { + action?: OneOrMany; +}; + +type RpcPermissionOptions = { + aud: string; +}; + +type AccountPermissionOptions = { + action?: AccountAction; +}; + +type IncludePermissionOptions = { + aud?: string; +}; + +const encodeValue = (value: string) => encodeURIComponent(value); + +const isReadonlyArray = (value: OneOrMany): value is readonly T[] => + Array.isArray(value); + +const asArray = (value: OneOrMany): T[] => + isReadonlyArray(value) ? [...value] : [value]; + +const unique = (values: readonly T[]): T[] => [...new Set(values)]; + +const appendParams = ( + params: URLSearchParams, + name: string, + value: OneOrMany | undefined, +) => { + if (value === undefined) { + return; + } + for (const item of unique(asArray(value))) { + params.append(name, item); + } +}; + +const buildScope = ({ + resource, + positionalName, + positionalValue, + params, +}: { + resource: string; + positionalName: string; + positionalValue: string | undefined; + params?: Record | undefined>; +}): OAuthScope => { + const searchParams = new URLSearchParams(); + for (const [name, value] of Object.entries(params ?? {})) { + appendParams(searchParams, name, value); + } + + const query = searchParams.toString(); + if (positionalValue !== undefined) { + return `${resource}:${encodeValue(positionalValue)}${query ? `?${query}` : ""}`; + } + if (query) { + return `${resource}?${query}`; + } + throw new Error( + `Cannot build ${resource} permission without ${positionalName} or parameters.`, + ); +}; + +const assertNonEmpty = (resource: string, name: string, values: string[]) => { + if (values.length === 0) { + throw new Error(`Cannot build ${resource} permission without ${name}.`); + } +}; + +const assertNoPartialWildcard = ( + resource: string, + name: string, + values: readonly string[], +) => { + for (const value of values) { + if (value.includes("*") && value !== "*") { + throw new Error( + `${resource} permission ${name} does not support partial wildcards: ${value}`, + ); + } + } +}; + +export const repo = ( + collection: OneOrMany, + options: RepoPermissionOptions = {}, +): OAuthScope => { + const collections = unique(asArray(collection)); + assertNonEmpty("repo", "collection", collections); + assertNoPartialWildcard("repo", "collection", collections); + + return buildScope({ + resource: "repo", + positionalName: "collection", + positionalValue: collections.length === 1 ? collections[0] : undefined, + params: { + collection: collections.length > 1 ? collections : undefined, + action: options.action, + }, + }); +}; + +export const rpc = ( + lxm: OneOrMany, + options: RpcPermissionOptions, +): OAuthScope => { + const lxms = unique(asArray(lxm)); + assertNonEmpty("rpc", "lxm", lxms); + assertNoPartialWildcard("rpc", "lxm", lxms); + if (lxms.includes("*") && options.aud === "*") { + throw new Error('rpc permission cannot use both lxm="*" and aud="*".'); + } + + return buildScope({ + resource: "rpc", + positionalName: "lxm", + positionalValue: lxms.length === 1 ? lxms[0] : undefined, + params: { + lxm: lxms.length > 1 ? lxms : undefined, + aud: options.aud, + }, + }); +}; + +export const blob = (accept: OneOrMany): OAuthScope => { + const accepts = unique(asArray(accept)); + assertNonEmpty("blob", "accept", accepts); + + return buildScope({ + resource: "blob", + positionalName: "accept", + positionalValue: accepts.length === 1 ? accepts[0] : undefined, + params: { + accept: accepts.length > 1 ? accepts : undefined, + }, + }); +}; + +export const account = ( + attr: AccountAttr, + options: AccountPermissionOptions = {}, +): OAuthScope => + buildScope({ + resource: "account", + positionalName: "attr", + positionalValue: attr, + params: { + action: options.action, + }, + }); + +export const identity = (attr: IdentityAttr): OAuthScope => + buildScope({ + resource: "identity", + positionalName: "attr", + positionalValue: attr, + }); + +export const include = ( + nsid: string, + options: IncludePermissionOptions = {}, +): OAuthScope => + buildScope({ + resource: "include", + positionalName: "nsid", + positionalValue: nsid, + params: { + aud: options.aud, + }, + }); + +export const permissionScopes = ( + scopes: readonly (OAuthScope | false | null | undefined)[], +): OAuthScope[] => + unique(scopes.filter((scope): scope is OAuthScope => !!scope)); diff --git a/astro-authproto/src/lib/redirects.ts b/astro-authproto/src/lib/redirects.ts index b91b97d..2a8778f 100644 --- a/astro-authproto/src/lib/redirects.ts +++ b/astro-authproto/src/lib/redirects.ts @@ -74,11 +74,16 @@ export const getRedirectUrl = async ({ }) => { let redirectTo = redirectToBase; - if (referer && redirectTo.includes(REDIRECT_TO_REFERER_TEMPLATE)) { + if (redirectTo.includes(REDIRECT_TO_REFERER_TEMPLATE)) { redirectTo = substituteRefererTemplate(redirectTo, referer); } redirectTo = await substituteUserTemplates(redirectTo, did); + // Empty {referer} substitution or an empty custom redirect should still land somewhere. + if (!redirectTo) { + redirectTo = "/"; + } + return redirectTo; }; diff --git a/astro-authproto/src/lib/scopes.ts b/astro-authproto/src/lib/scopes.ts new file mode 100644 index 0000000..d9b5424 --- /dev/null +++ b/astro-authproto/src/lib/scopes.ts @@ -0,0 +1,187 @@ +import type { OAuthScope } from "./config.js"; + +/** + * The account logging in, as typed into the login form. Exactly one of + * `handle` / `did` is set (whichever the user entered) while the other is + * `undefined`. Call `resolve()` to fetch the canonical DID and handle together. + */ +export type ResolveScopesAtprotoId = { + /** + * Resolves the account to its canonical DID and handle. Does one network + * lookup for the missing half. + */ + resolve: () => Promise<{ did: string; handle: string }>; +} & ({ handle: string; did: undefined } | { handle: undefined; did: string }); + +/** + * Values passed to your `resolveScopes` hook before Authproto sends the user + * to their PDS, the server that stores their ATproto account. + */ +export type ResolveScopesInput = { + /** + * The account logging in. Exactly one of `atprotoId.handle` / + * `atprotoId.did` is set; call `atprotoId.resolve()` for both. + */ + atprotoId: ResolveScopesAtprotoId; + /** + * The scopes Authproto is ready to request. Custom form values have already + * been checked against `allowedScopes`, and `"atproto"` is always present. + */ + proposedScopes: readonly OAuthScope[]; + /** + * Every scope this app is allowed to request. Authproto builds this from your + * configured `scopes` and always includes `"atproto"`. + */ + allowedScopes: readonly OAuthScope[]; + /** + * The scopes Authproto uses when a login form does not ask for anything + * specific. + */ + defaultScopes: readonly OAuthScope[]; +}; + +/** + * Lets you change the requested scopes for one login. + * + * Return a string array to replace `input.proposedScopes`. Return nothing or + * `null` to keep `input.proposedScopes`. Authproto still keeps only scopes + * from `input.allowedScopes`, so the hook cannot grant a scope this app did + * not configure. + */ +export type ResolveScopesHook = ( + input: ResolveScopesInput, +) => OAuthScope[] | void | null | Promise; + +const uniqueScopes = (scopes: readonly OAuthScope[]): OAuthScope[] => [ + ...new Set(scopes), +]; + +/** + * Builds the scope values rendered by the Astro login components. The OAuth + * login route still does the authoritative configured-scope filtering before + * redirecting to the provider. + */ +const resolveFormScopes = ({ + defaultScopes, + extendDefaultScopes, + withDefaults, + scopes, +}: { + defaultScopes: readonly OAuthScope[]; + extendDefaultScopes?: OAuthScope[]; + withDefaults: boolean; + scopes?: OAuthScope[]; +}): OAuthScope[] => { + if (scopes) { + return uniqueScopes(["atproto", ...scopes]); + } + + if (extendDefaultScopes) { + return uniqueScopes(["atproto", ...defaultScopes, ...extendDefaultScopes]); + } + + return withDefaults ? uniqueScopes(defaultScopes) : []; +}; + +export const resolveLoginFormScopes = (input: { + defaultScopes: readonly OAuthScope[]; + extendDefaultScopes?: OAuthScope[]; + scopes?: OAuthScope[]; +}): OAuthScope[] => resolveFormScopes({ ...input, withDefaults: false }); + +export const resolveAuthorizeFormScopes = (input: { + defaultScopes: readonly OAuthScope[]; + extendDefaultScopes?: OAuthScope[]; + scopes?: OAuthScope[]; +}): OAuthScope[] => resolveFormScopes({ ...input, withDefaults: true }); + +const normalizeAllowedScopes = ( + configuredScopes: readonly OAuthScope[], +): OAuthScope[] => [...new Set(["atproto", ...configuredScopes])]; + +const normalizeLoginScopes = ( + scopes: readonly string[], + allowedScopes: readonly OAuthScope[], +): OAuthScope[] => { + const allowedScopesSet: ReadonlySet = new Set(allowedScopes); + const finalScopes = scopes.filter((scope): scope is OAuthScope => + allowedScopesSet.has(scope), + ); + + if (!finalScopes.includes("atproto")) { + finalScopes.unshift("atproto"); + } + + return [...new Set(finalScopes)]; +}; + +/** + * Picks the final OAuth scopes for a login request. + * + * Starts from `requestedScopes` after dropping anything not in + * `configuredScopes`, falls back to `defaultScopes` when the request supplies + * no configured scopes, and always keeps `"atproto"`. If a `resolveScopes` + * hook is configured and we know which account is logging in, the hook can + * rewrite the list from a readonly object containing the proposed, allowed, + * and default scopes. Returning `undefined` or `null` accepts the proposed + * list. We check the hook's output against the configured scopes too, so a hook + * cannot grant something the app did not declare. + */ +export const resolveServerLoginScopes = async ({ + requestedScopes, + configuredScopes, + defaultScopes, + atprotoId, + resolveScopes, + resolveIdentity, +}: { + requestedScopes: readonly string[]; + configuredScopes: readonly OAuthScope[]; + defaultScopes: readonly OAuthScope[]; + atprotoId?: string; + resolveScopes: null | ResolveScopesHook; + resolveIdentity?: ( + atprotoId: string, + ) => Promise<{ did: string; handle: string }>; +}): Promise => { + const allowedScopes = Object.freeze(normalizeAllowedScopes(configuredScopes)); + const resolvedDefaultScopes = Object.freeze( + normalizeLoginScopes(defaultScopes, allowedScopes), + ); + const requestedAllowedScopes = requestedScopes.filter((scope) => + allowedScopes.includes(scope), + ); + const initialScopes = + requestedAllowedScopes.length > 0 + ? requestedAllowedScopes + : resolvedDefaultScopes; + + const proposedScopes = Object.freeze( + normalizeLoginScopes(initialScopes, allowedScopes), + ); + + if (!resolveScopes || !atprotoId) { + return [...proposedScopes]; + } + + const resolve = async () => { + if (!resolveIdentity) { + throw new Error( + "resolveScopes called atprotoId.resolve() but no identity resolver is configured", + ); + } + return resolveIdentity(atprotoId); + }; + const atprotoIdInput: ResolveScopesAtprotoId = atprotoId.startsWith("did:") + ? { did: atprotoId, handle: undefined, resolve } + : { handle: atprotoId, did: undefined, resolve }; + + const resolvedScopes = await resolveScopes({ + atprotoId: atprotoIdInput, + proposedScopes, + allowedScopes, + defaultScopes: resolvedDefaultScopes, + }); + + return normalizeLoginScopes(resolvedScopes ?? proposedScopes, allowedScopes); +}; diff --git a/astro-authproto/src/lib/session-state.ts b/astro-authproto/src/lib/session-state.ts new file mode 100644 index 0000000..1e64d08 --- /dev/null +++ b/astro-authproto/src/lib/session-state.ts @@ -0,0 +1,100 @@ +import { AstroSession } from "astro"; +import type { OAuthScope } from "./config.js"; + +export const AUTHPROTO_ERROR_CODE = "authproto-error-code"; +export const AUTHPROTO_ERROR_DESCRIPTION = "authproto-error-description"; +export const AUTHPROTO_ERROR_URI = "authproto-error-uri"; +export const AUTHPROTO_SCOPES = "atproto-scopes"; +export const AUTHPROTO_DID = "atproto-did"; + +export type AuthprotoSessionData = { + [AUTHPROTO_DID]: string | undefined; + [AUTHPROTO_SCOPES]: OAuthScope[] | undefined; + [AUTHPROTO_ERROR_CODE]: string | undefined; + [AUTHPROTO_ERROR_DESCRIPTION]: string | undefined; + [AUTHPROTO_ERROR_URI]: string | undefined; +}; + +export type AuthprotoSession = Pick; +type AuthprotoWritableSession = Pick; +type AuthprotoReadableSession = Pick; + +type AuthprotoError = { + code?: string; + description?: string; + uri?: string; +}; + +type LoginGrant = { + did: string; + scopes?: readonly unknown[]; +}; + +export const persistAuthprotoError = async ( + session: AuthprotoWritableSession | undefined, + { code, description, uri }: AuthprotoError, +) => { + session?.set(AUTHPROTO_ERROR_CODE, code ?? "UNKNOWN"); + session?.set(AUTHPROTO_ERROR_DESCRIPTION, description ?? code); + session?.set(AUTHPROTO_ERROR_URI, uri); +}; + +export const persistLoginGrant = async ( + session: AuthprotoWritableSession | undefined, + { did, scopes }: LoginGrant, +) => { + session?.set(AUTHPROTO_DID, did); + if (scopes) { + session?.set( + AUTHPROTO_SCOPES, + scopes.filter((scope): scope is OAuthScope => typeof scope === "string"), + ); + } +}; + +export const readAndClearAuthprotoError = async ( + session: AuthprotoReadableSession | undefined, +): Promise => { + const [code, description, uri] = await Promise.all([ + session?.get(AUTHPROTO_ERROR_CODE), + session?.get(AUTHPROTO_ERROR_DESCRIPTION), + session?.get(AUTHPROTO_ERROR_URI), + ]); + + if (!code && !description && !uri) { + return null; + } + + // Clear the error as we read it. It should be present in the one page rendered + // right after a failed login, then disappear, so a later unrelated page does + // not still show it and look like its own login failed. + session?.delete(AUTHPROTO_ERROR_CODE); + session?.delete(AUTHPROTO_ERROR_DESCRIPTION); + session?.delete(AUTHPROTO_ERROR_URI); + + return { + code: typeof code === "string" ? code : undefined, + description: typeof description === "string" ? description : undefined, + uri: typeof uri === "string" ? uri : undefined, + }; +}; + +export const readSessionGrant = async ( + session: AuthprotoReadableSession | undefined, +): Promise<{ did?: string; scopes: OAuthScope[] }> => { + const did = await session?.get(AUTHPROTO_DID); + const scopes = await session?.get(AUTHPROTO_SCOPES); + return { + did: typeof did === "string" ? did : undefined, + scopes: Array.isArray(scopes) + ? scopes.filter((scope): scope is OAuthScope => typeof scope === "string") + : [], + }; +}; + +export const clearSessionGrant = async ( + session: AuthprotoReadableSession | undefined, +) => { + session?.delete(AUTHPROTO_DID); + session?.delete(AUTHPROTO_SCOPES); +}; diff --git a/astro-authproto/src/routes/jwks.json.ts b/astro-authproto/src/routes/jwks.json.ts index 8ea33f9..278b153 100644 --- a/astro-authproto/src/routes/jwks.json.ts +++ b/astro-authproto/src/routes/jwks.json.ts @@ -1,7 +1,8 @@ import type { APIRoute } from "astro"; -import { oauthClient } from "../lib/auth.js"; +import { getOAuthClient } from "../lib/auth.js"; export const GET: APIRoute = async () => { + const oauthClient = await getOAuthClient(); return new Response(JSON.stringify(oauthClient.jwks), { status: 200, headers: { diff --git a/astro-authproto/src/routes/loopback-middleware.ts b/astro-authproto/src/routes/loopback-middleware.ts new file mode 100644 index 0000000..65dd51a --- /dev/null +++ b/astro-authproto/src/routes/loopback-middleware.ts @@ -0,0 +1,22 @@ +import { type MiddlewareHandler } from "astro"; + +const isDev = process.env.NODE_ENV === "development"; + +/** + * Sends `localhost` visitors to `127.0.0.1` during local development. + * + * In dev it's required by ATproto OAuth that we build all OAuth callback URLs + * against `127.0.0.1`. Even if the app is bound to `127.0.0.1`, however, it's + * still possible to open the dev server at `localhost`. This would not match + * those URLs and could not log in, so we redirect to the same address on + * `127.0.0.1` first. Outside dev, or for any other hostname, the request passes + * through unchanged. + */ +export const onRequest: MiddlewareHandler = (context, next) => { + if (isDev && context.url.hostname === "localhost") { + const url = new URL(context.url); + url.hostname = "127.0.0.1"; + return context.redirect(url.toString(), 307); + } + return next(); +}; diff --git a/astro-authproto/src/routes/middleware.ts b/astro-authproto/src/routes/middleware.ts index 71664e9..028a9ca 100644 --- a/astro-authproto/src/routes/middleware.ts +++ b/astro-authproto/src/routes/middleware.ts @@ -1,48 +1,45 @@ -import { didToHandle, oauthClient } from "../lib/auth.js"; +import { didToHandle, getOAuthClient } from "../lib/auth.js"; import { type MiddlewareHandler } from "astro"; - -export const AUTHPROTO_ERROR_CODE = "authproto-error-code"; -export const AUTHPROTO_ERROR_DESCRIPTION = "authproto-error-description"; +import { + clearSessionGrant, + readAndClearAuthprotoError, + readSessionGrant, +} from "../lib/session-state.js"; export const onRequest: MiddlewareHandler = async ( { locals, session }, next, ) => { - const userDid = await session?.get("atproto-did"); - const errorCode = await session?.get(AUTHPROTO_ERROR_CODE); - const errorDescription = await session?.get(AUTHPROTO_ERROR_DESCRIPTION); - if (errorCode || errorDescription) { - locals.authproto = { - // TODO: add input handler - errorCode, - errorDescription, - }; - session?.delete(AUTHPROTO_ERROR_CODE); - session?.delete(AUTHPROTO_ERROR_DESCRIPTION); - } + const grant = await readSessionGrant(session); + const error = await readAndClearAuthprotoError(session); + locals.authproto = error + ? { + errorCode: error.code, + errorDescription: error.description, + errorUri: error.uri, + } + : null; + locals.loggedInUser = null; + locals.loggedInClient = null; - if (!session || !userDid) { - locals.loggedInUser = null; - locals.loggedInClient = null; + if (!session || !grant.did) { return next(); } try { - // TODO: check how we can find out if the log in failed and then - // delete the cookie. - const loggedInClient = await oauthClient.restore(userDid); + const oauthClient = await getOAuthClient(); + const loggedInClient = await oauthClient.restore(grant.did); if (loggedInClient.did) { locals.loggedInUser = { did: loggedInClient.did, handle: await didToHandle(loggedInClient.did), + scopes: grant.scopes, fetchHandler: loggedInClient.fetchHandler.bind(loggedInClient), }; locals.loggedInClient = loggedInClient; } } catch { - session.delete("atproto-did"); - locals.loggedInUser = null; - locals.loggedInClient = null; + await clearSessionGrant(session); } return next(); diff --git a/astro-authproto/src/routes/oauth/callback.ts b/astro-authproto/src/routes/oauth/callback.ts index d8cbe8c..ae29782 100644 --- a/astro-authproto/src/routes/oauth/callback.ts +++ b/astro-authproto/src/routes/oauth/callback.ts @@ -1,60 +1,109 @@ -import type { APIRoute } from "astro"; -import { extractAuthError, oauthClient } from "../../lib/auth.js"; +import type { APIContext } from "astro"; +import { extractAuthError, getOAuthClient } from "../../lib/auth.js"; import { getRedirectUrl } from "../../lib/redirects.js"; import { redirectAfterLogin } from "fujocoded:authproto/config"; +import { decodeOAuthState } from "../../lib/oauth-state.js"; import { - AUTHPROTO_ERROR_DESCRIPTION, - AUTHPROTO_ERROR_CODE, -} from "../middleware.ts"; + AuthprotoSession, + persistAuthprotoError, + persistLoginGrant, +} from "../../lib/session-state.js"; import { type OAuthSession } from "@atproto/oauth-client-node"; -export const GET: APIRoute = async ({ request, redirect, session }) => { - const requestUrl = new URL(request.url); +type CallbackError = { + code: string; + description?: string; + uri?: string; +}; - let oauthSession: OAuthSession | null; - let oauthState: string | null = null; - let error = requestUrl.searchParams.get("error"); - // This falls back to undefined so it will be compatible with the session - // storage signature if not present. - let errorDescription = - requestUrl.searchParams.get("error_description") ?? undefined; +// Restrict the type of the routes to just what we need to make them easier to test +type CallbackRouteContext = Pick & { + session?: AuthprotoSession; +}; + +const readCallbackErrorParams = ( + params: URLSearchParams, +): CallbackError | null => { + const code = params.get("error"); + const description = params.get("error_description") ?? undefined; + const uri = params.get("error_uri") ?? undefined; + if (!code && !description && !uri) { + return null; + } + const fallbackDescription = description ?? code; + return { + code: code ?? "UNKNOWN", + ...(fallbackDescription !== null && { description: fallbackDescription }), + ...(uri !== null && { uri }), + }; +}; + +const tryOAuthCallback = async ( + params: URLSearchParams, +): Promise< + { session: OAuthSession; state: string | null } | { error: CallbackError } +> => { try { - const clientCallback = await oauthClient.callback(requestUrl.searchParams); - oauthSession = clientCallback.session; - oauthState = clientCallback.state; - session?.set("atproto-did", oauthSession.did); + const oauthClient = await getOAuthClient(); + const { session, state } = await oauthClient.callback(params); + return { session, state }; } catch (e) { - // If there is an error during session restoration then it takes precedence - // over the one in the searchParams const authError = extractAuthError(e); - error = authError.code ?? error; - errorDescription = authError.description; - oauthSession = null; + return { + error: { code: authError.code, description: authError.description }, + }; } +}; - if (error || errorDescription) { - session?.set(AUTHPROTO_ERROR_CODE, error ?? "UNKNOWN"); - session?.set(AUTHPROTO_ERROR_DESCRIPTION, errorDescription); +export const GET = async ({ + request, + redirect, + session, +}: CallbackRouteContext) => { + const requestUrl = new URL(request.url); + + const searchParamsError = readCallbackErrorParams(requestUrl.searchParams); + + // A provider-side error means there's nothing trustworthy to exchange — + // record the error and bail before we even talk to the OAuth client. + if (searchParamsError) { + await persistAuthprotoError(session, searchParamsError); + return redirect("/"); + } + + if (!requestUrl.searchParams.has("code")) { + await persistAuthprotoError(session, { + code: "INVALID_CALLBACK", + description: 'Missing required "code" parameter in OAuth callback', + }); + return redirect("/"); } - // The `state` value in the URL is NOT the state we sent during login: the - // OAuth client swaps it for its own internal id. Our original state comes - // back as `clientCallback.state`, so that's what we read. - // CSRF was already validated by oauthClient.callback() above, so if parsing - // fails here it's safe to fall back to the default redirect. - let customRedirect: string | undefined; - let referer: string | undefined; - if (oauthState) { - try { - const stateData = JSON.parse( - Buffer.from(oauthState, "base64url").toString(), - ); - customRedirect = stateData.redirect; - referer = stateData.referer; - } catch { - // If custom redirect parsing fails, use default redirect - // (CSRF protection is still validated by the OAuth client) - } + const callbackResult = await tryOAuthCallback(requestUrl.searchParams); + const oauthFailed = "error" in callbackResult; + const oauthSession = oauthFailed ? null : callbackResult.session; + const oauthState = oauthFailed ? null : callbackResult.state; + + // A provider-side error already returned above, so the only failure that can + // reach here is the OAuth client failing to exchange the code. + if (oauthFailed) { + await persistAuthprotoError(session, callbackResult.error); + } + + // Do not decode `requestUrl.searchParams.get("state")` here. It is not the + // app state we passed to `authorize()` during login: the OAuth client sets + // that callback value and uses it to validate the login attempt. After + // validation, `oauthClient.callback()` returns our app state and carries our + // redirect and granted scopes. If it cannot be decoded, we can still finish + // login and use the default redirect. + const stateData = decodeOAuthState(oauthState); + const { redirect: customRedirect, referer, scopes } = stateData; + + if (oauthSession) { + await persistLoginGrant(session, { + did: oauthSession.did, + scopes, + }); } const redirectTo = oauthSession diff --git a/astro-authproto/src/routes/oauth/login.ts b/astro-authproto/src/routes/oauth/login.ts index c5a726d..4f5908d 100644 --- a/astro-authproto/src/routes/oauth/login.ts +++ b/astro-authproto/src/routes/oauth/login.ts @@ -1,11 +1,27 @@ -import type { APIRoute } from "astro"; -import { extractAuthError, oauthClient } from "../../lib/auth.js"; -import { scopes, isDev, isDevServerHostSet } from "fujocoded:authproto/config"; -import { randomBytes } from "node:crypto"; +import type { APIContext } from "astro"; import { - AUTHPROTO_ERROR_CODE, - AUTHPROTO_ERROR_DESCRIPTION, -} from "../../../src/routes/middleware.ts"; + extractAuthError, + getOAuthClient, + resolveAtprotoIdentity, +} from "../../lib/auth.js"; +import { + defaultScopes, + scopes, + isDev, + isDevServerHostSet, +} from "fujocoded:authproto/config"; +import { resolveScopes } from "fujocoded:authproto/hooks"; +import { + persistAuthprotoError, + type AuthprotoSession, +} from "../../lib/session-state.js"; +import { resolveServerLoginScopes } from "../../lib/scopes.js"; +import { encodeOAuthState, type OAuthState } from "../../lib/oauth-state.js"; + +// Restrict the type of the routes to just what we need to make them easier to test +type LoginRouteContext = Pick & { + session?: AuthprotoSession; +}; const DEV_HOST_WARNING = [ "", @@ -19,7 +35,11 @@ const DEV_HOST_WARNING = [ "", ].join("\n"); -export const POST: APIRoute = async ({ redirect, request, session }) => { +export const POST = async ({ + redirect, + request, + session, +}: LoginRouteContext) => { if (isDev && !isDevServerHostSet) { console.error(DEV_HOST_WARNING); } @@ -31,45 +51,52 @@ export const POST: APIRoute = async ({ redirect, request, session }) => { const redirectValue = body.get("redirect"); const customRedirect = typeof redirectValue === "string" ? redirectValue : undefined; + const requestedScopes = body + .getAll("scope") + .filter((scope): scope is string => typeof scope === "string"); - // Get the referer to redirect back to the same page after login - // if the developer asked us to do so + // Keep the current page as the fallback redirect. A custom `redirect` field + // still wins when a form sends one. const referer = request.headers.get("referer"); - - // Build state that includes both CSRF protection and optional redirect - const stateData = { - csrf: randomBytes(16).toString("base64url"), - ...(customRedirect && { redirect: customRedirect }), - ...(referer && !customRedirect && { referer }), - }; + const errorRedirect = referer || "/"; if (!atprotoId) { - session?.set(AUTHPROTO_ERROR_CODE, "MISSING_FIELD"); - session?.set( - AUTHPROTO_ERROR_DESCRIPTION, - 'Missing required "atproto-id" field in login form data', - ); - return redirect(stateData.referer || "/"); + await persistAuthprotoError(session, { + code: "MISSING_FIELD", + description: 'Missing required "atproto-id" field in login form data', + }); + return redirect(errorRedirect); } try { + const finalScopes = await resolveServerLoginScopes({ + requestedScopes, + configuredScopes: scopes, + defaultScopes, + atprotoId, + resolveScopes, + resolveIdentity: resolveAtprotoIdentity, + }); + const oauthClient = await getOAuthClient(); const url = await oauthClient.authorize(atprotoId, { - scope: scopes.join(" "), - // This random value protects against CSRF (Cross-Site Request - // Forgery) attacks. We send it along our authorization request, and the OAuth - // provider will send it back with the authentication response. By verifying - // it matches what we sent, we can be sure the callback is in response to - // OUR authorization request, not someone else's. - // We also encode the desired redirect URL if provided. - state: Buffer.from(JSON.stringify(stateData)).toString("base64url"), + scope: finalScopes.join(" "), + // The encoded state ties this callback to the login that started it. + // It also carries the redirect and selected scopes through the provider. + state: encodeOAuthState({ + scopes: finalScopes, + ...(customRedirect && { redirect: customRedirect }), + ...(referer && !customRedirect && { referer }), + } satisfies OAuthState), }); return redirect(url.toString()); } catch (e) { const authError = extractAuthError(e); - session?.set(AUTHPROTO_ERROR_CODE, authError.code); - session?.set(AUTHPROTO_ERROR_DESCRIPTION, authError.description); + await persistAuthprotoError(session, { + code: authError.code, + description: authError.description, + }); - return redirect(stateData.referer || "/"); + return redirect(errorRedirect); } }; diff --git a/astro-authproto/src/routes/oauth/logout.ts b/astro-authproto/src/routes/oauth/logout.ts index fea0a40..c0dffff 100644 --- a/astro-authproto/src/routes/oauth/logout.ts +++ b/astro-authproto/src/routes/oauth/logout.ts @@ -1,20 +1,48 @@ -import type { APIRoute } from "astro"; -import { oauthClient } from "../../lib/auth.js"; +import type { APIContext } from "astro"; +import { getOAuthClient } from "../../lib/auth.js"; import { getRedirectUrl } from "../../lib/redirects.js"; import { redirectAfterLogout } from "fujocoded:authproto/config"; +import { + clearSessionGrant, + readSessionGrant, + type AuthprotoSession, +} from "../../lib/session-state.js"; -export const POST: APIRoute = async ({ redirect, session, request }) => { - const userDid = await session?.get("atproto-did"); +// @atproto/oauth-client's SessionGetter throws TokenRefreshError with this +// message when the stored OAuth session was already removed elsewhere. The +// library does not expose a reason code, so keep this paired with the error +// type and DID checks below. +const ALREADY_DELETED_SESSION_MESSAGE = + "The session was deleted by another process"; + +// Restrict the type of the routes to just what we need to make them easier to test +type LogoutRouteContext = Pick & { + session?: AuthprotoSession; +}; + +function isAlreadyDeletedSessionError( + error: unknown, + userDid: string, +): boolean { + return ( + error instanceof Error && + "sub" in error && + error.sub === userDid && + error.message.includes(ALREADY_DELETED_SESSION_MESSAGE) + ); +} + +export const POST = async ({ + redirect, + session, + request, +}: LogoutRouteContext) => { + const { did: userDid } = await readSessionGrant(session); if (!session || !userDid) { console.error("User is not logged in but logout was attempted."); return redirect(redirectAfterLogout); } - const loggedInClient = await oauthClient.restore(userDid); - await loggedInClient.signOut(); - - session.delete("atproto-did"); - // Check if a custom redirect was passed in the form data const body = await request.formData(); const redirectValue = body.get("redirect"); @@ -27,5 +55,20 @@ export const POST: APIRoute = async ({ redirect, session, request }) => { referer: referer ?? "", }); + await clearSessionGrant(session); + + try { + const oauthClient = await getOAuthClient(); + const loggedInClient = await oauthClient.restore(userDid); + await loggedInClient.signOut(); + } catch (error) { + if (!isAlreadyDeletedSessionError(error, userDid)) { + console.warn( + "[authproto] failed to revoke OAuth session during logout", + error, + ); + } + } + return redirect(redirectTo); }; diff --git a/astro-authproto/src/types.d.ts b/astro-authproto/src/types.d.ts index 9c772ec..2157d68 100644 --- a/astro-authproto/src/types.d.ts +++ b/astro-authproto/src/types.d.ts @@ -1,26 +1,58 @@ -import { - AUTHPROTO_ERROR_CODE, - AUTHPROTO_ERROR_DESCRIPTION, -} from "./routes/middleware.ts"; +import type { AuthprotoSessionData } from "./lib/session-state.js"; +import type { OAuthScope } from "./lib/config.js"; declare global { namespace App { - interface SessionData { - "atproto-did": string | undefined; - [AUTHPROTO_ERROR_CODE]: string | undefined; - [AUTHPROTO_ERROR_DESCRIPTION]: string | undefined; - } + interface SessionData extends AuthprotoSessionData {} interface Locals { + /** + * The logged-in ATproto user for this request, or `null` when the visitor + * is logged out. + */ loggedInUser: { + /** + * The user's DID, the stable account id used by ATproto. + */ did: string; + /** + * The user's current handle. + */ handle: string; + /** + * The scopes granted for this session. This can be narrower than the + * scopes you asked for, as it drops undeclared permissions. + */ + scopes: OAuthScope[]; + /** + * Fetches ATproto requests as the logged-in user. + */ fetchHandler: import("@atproto/oauth-client-node").OAuthSession["fetchHandler"]; } | null; + /** + * The raw OAuth session for advanced server-side ATproto calls. + */ loggedInClient: import("@atproto/oauth-client-node").OAuthSession | null; + /** + * One-shot Authproto status for the current request. Login errors appear + * here once, then Authproto clears them from the session. + */ authproto: { + /** + * The handle submitted by the user, when Authproto has one. + */ attemptedHandle?: string; + /** + * Human-readable login failure text. + */ errorDescription?: string; + /** + * Machine-readable login failure code. + */ errorCode?: string; + /** + * Provider documentation URL for the login failure, when available. + */ + errorUri?: string; } | null; } } diff --git a/astro-authproto/src/virtual-modules.d.ts b/astro-authproto/src/virtual-modules.d.ts new file mode 100644 index 0000000..9dff234 --- /dev/null +++ b/astro-authproto/src/virtual-modules.d.ts @@ -0,0 +1,80 @@ +// Type declarations for Authproto's virtual modules. These are the +// consumer-facing declarations: this file is injected into apps via +// `injectTypes` (see `astro:config:done` in `index.ts`) AND referenced by +// `config-module.d.ts` so the package's own source typechecks against the +// same source of truth. +declare module "fujocoded:authproto/config" { + /** + * The display name Authproto puts in OAuth client metadata. + */ + export const applicationName: string; + /** + * The public site URL Authproto uses to build OAuth callback URLs. + */ + export const applicationDomain: string; + /** + * The handle prefilled by `` in dev mode. Use this in custom forms + * when you want the same local-dev helper. + */ + export const defaultDevUser: string | null; + /** + * The public domain used when the app runs behind a proxy or tunnel. + */ + export const externalDomain: string | undefined; + /** + * Authproto's configured session storage, or `null` when storage comes from + * another generated module. + */ + export const storage: import("unstorage").Storage | null; + /** + * Every scope this app is allowed to request. Custom forms can render this + * list directly so they stay matched to `authProto({ scopes })`. + */ + export const scopes: import("@fujocoded/authproto").OAuthScope[]; + /** + * Scopes Authproto requests when a form does not ask for anything specific. + * Defaults to all configured `scopes`. + */ + export const defaultScopes: import("@fujocoded/authproto").OAuthScope[]; + /** + * The configured storage driver name. + */ + export const driverName: string; + /** + * The default path or URL Authproto sends users to after login. + */ + export const redirectAfterLogin: string; + /** + * The default path or URL Authproto sends users to after logout. + */ + export const redirectAfterLogout: string; + /** + * The domain Authproto uses for OAuth client metadata. + */ + export const clientMetadataDomain: string; + /** + * Whether Astro is running in development mode. + */ + export const isDev: boolean; + /** + * Whether the dev server was configured with `server.host`. + */ + export const isDevServerHostSet: boolean; +} + +declare module "fujocoded:authproto/hooks" { + /** + * The `resolveScopes` export of your configured `resolveScopesEntrypoint`, or + * `null` when you did not configure one. + */ + export const resolveScopes: + | null + | import("./lib/scopes.js").ResolveScopesHook; +} + +declare module "fujocoded:authproto/stores" { + export { + StateStore, + SessionStore, + } from "@fujocoded/authproto/stores/unstorage"; +} diff --git a/astro-authproto/tsconfig.json b/astro-authproto/tsconfig.json index c9ff883..141f6d0 100644 --- a/astro-authproto/tsconfig.json +++ b/astro-authproto/tsconfig.json @@ -20,7 +20,7 @@ "allowImportingTsExtensions": true, "noEmit": true }, - "include": ["./src/**/*"], + "include": ["./src/**/*", "./__tests__/**/*"], "exclude": ["node_modules"], "files": ["./src/types.d.ts", "./src/config-module.d.ts"] } diff --git a/astro-authproto/tsdown.config.ts b/astro-authproto/tsdown.config.ts index 359a68e..ede010d 100644 --- a/astro-authproto/tsdown.config.ts +++ b/astro-authproto/tsdown.config.ts @@ -28,6 +28,14 @@ export default defineConfig([ "src/helpers.ts", "src/types.d.ts", "src/db/tables.ts", + "src/lib/scopes.ts", + ], + // Copy the virtual module declarations verbatim instead of bundling them. + // tsdown rewrites inline `import("x")` types into top-level `import * as` + // statements, which turns the file into a module and stops TypeScript from + // registering the `declare module "fujocoded:authproto/*"` ambient blocks. + copy: [ + { from: "src/virtual-modules.d.ts", to: "dist/virtual-modules.d.ts" }, ], external: astroFileExternal, ...COMMON_CONFIG, diff --git a/astro-authproto/vitest.config.ts b/astro-authproto/vitest.config.ts new file mode 100644 index 0000000..6476fc8 --- /dev/null +++ b/astro-authproto/vitest.config.ts @@ -0,0 +1,25 @@ +import { fileURLToPath } from "node:url"; +import { defineConfig } from "vitest/config"; + +export default defineConfig({ + resolve: { + alias: { + "fujocoded:authproto/config": fileURLToPath( + new URL("./__tests__/virtual/config.ts", import.meta.url), + ), + "fujocoded:authproto/stores": fileURLToPath( + new URL("./__tests__/virtual/stores.ts", import.meta.url), + ), + "fujocoded:authproto/hooks": fileURLToPath( + new URL("./__tests__/virtual/hooks.ts", import.meta.url), + ), + "@fujocoded/msw-atproto": fileURLToPath( + new URL("../msw-atproto/src/index.ts", import.meta.url), + ), + }, + }, + test: { + environment: "node", + setupFiles: ["./__tests__/setup.ts"], + }, +}); diff --git a/package-lock.json b/package-lock.json index a7e8997..2cb74f2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -21,7 +21,7 @@ }, "astro-alt-text-toolkit": { "name": "@fujocoded/astro-alt-text-toolkit", - "version": "0.0.1", + "version": "0.0.2", "license": "MIT", "devDependencies": { "tsdown": "^0.14.1" @@ -484,15 +484,20 @@ "@atproto/api": "^0.17.3", "@atproto/identity": "^0.4.8", "@atproto/jwk-jose": "^0.1.4", + "@atproto/oauth-client": "^0.5.14", "@atproto/oauth-client-node": "^0.3.8", + "@atproto/oauth-types": "^0.6.2", "astro-integration-kit": "^0.20.0", "unstorage": "^1.16.1" }, "devDependencies": { "@astrojs/db": "^0.17.1", + "@fujocoded/msw-atproto": "^0.0.1", "drizzle-orm": "^0.42.0", "glob": "^13.0.6", - "tsdown": "^0.17.2" + "msw": "^2.13.4", + "tsdown": "^0.17.2", + "vitest": "^3.2.4" }, "peerDependencies": { "@astrojs/db": "^0.17.1 || ^0.20.0", @@ -508,6 +513,107 @@ } } }, + "astro-authproto/node_modules/@atproto-labs/did-resolver": { + "version": "0.2.6", + "resolved": "https://registry.npmjs.org/@atproto-labs/did-resolver/-/did-resolver-0.2.6.tgz", + "integrity": "sha512-2K1bC04nI2fmgNcvof+yA28IhGlpWn2JKYlPa7To9JTKI45FINCGkQSGiL2nyXlyzDJJ34fZ1aq6/IRFIOIiqg==", + "license": "MIT", + "dependencies": { + "@atproto-labs/fetch": "0.2.3", + "@atproto-labs/pipe": "0.1.1", + "@atproto-labs/simple-store": "0.3.0", + "@atproto-labs/simple-store-memory": "0.1.4", + "@atproto/did": "0.3.0", + "zod": "^3.23.8" + } + }, + "astro-authproto/node_modules/@atproto-labs/handle-resolver": { + "version": "0.3.6", + "resolved": "https://registry.npmjs.org/@atproto-labs/handle-resolver/-/handle-resolver-0.3.6.tgz", + "integrity": "sha512-qnSTXvOBNj1EHhp2qTWSX8MS5q3AwYU5LKlt5fBvSbCjgmTr2j0URHCv+ydrwO55KvsojIkTMgeMOh4YuY4fCA==", + "license": "MIT", + "dependencies": { + "@atproto-labs/simple-store": "0.3.0", + "@atproto-labs/simple-store-memory": "0.1.4", + "@atproto/did": "0.3.0", + "zod": "^3.23.8" + } + }, + "astro-authproto/node_modules/@atproto-labs/identity-resolver": { + "version": "0.3.6", + "resolved": "https://registry.npmjs.org/@atproto-labs/identity-resolver/-/identity-resolver-0.3.6.tgz", + "integrity": "sha512-qoWqBDRobln0NR8L8dQjSp79E0chGkBhibEgxQa2f9WD+JbJdjQ0YvwwO5yeQn05pJoJmAwmI2wyJ45zjU7aWg==", + "license": "MIT", + "dependencies": { + "@atproto-labs/did-resolver": "0.2.6", + "@atproto-labs/handle-resolver": "0.3.6" + } + }, + "astro-authproto/node_modules/@atproto/did": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@atproto/did/-/did-0.3.0.tgz", + "integrity": "sha512-raUPzUGegtW/6OxwCmM8bhZvuIMzxG5t9oWsth6Tp91Kb5fTnHV2h/KKNF1C82doeA4BdXCErTyg7ISwLbQkzA==", + "license": "MIT", + "dependencies": { + "zod": "^3.23.8" + } + }, + "astro-authproto/node_modules/@atproto/oauth-client": { + "version": "0.5.14", + "resolved": "https://registry.npmjs.org/@atproto/oauth-client/-/oauth-client-0.5.14.tgz", + "integrity": "sha512-sPH+vcdq9maTEAhJI0HzmFcFAMrkCS19np+RUssNkX6kS8Xr3OYr57tvYRCbkcnIyYTfYcxKQgpwHKx3RVEaYw==", + "license": "MIT", + "dependencies": { + "@atproto-labs/did-resolver": "0.2.6", + "@atproto-labs/fetch": "0.2.3", + "@atproto-labs/handle-resolver": "0.3.6", + "@atproto-labs/identity-resolver": "0.3.6", + "@atproto-labs/simple-store": "0.3.0", + "@atproto-labs/simple-store-memory": "0.1.4", + "@atproto/did": "0.3.0", + "@atproto/jwk": "0.6.0", + "@atproto/oauth-types": "0.6.2", + "@atproto/xrpc": "0.7.7", + "core-js": "^3", + "multiformats": "^9.9.0", + "zod": "^3.23.8" + } + }, + "astro-authproto/node_modules/@atproto/oauth-types": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/@atproto/oauth-types/-/oauth-types-0.6.2.tgz", + "integrity": "sha512-2cuboM4RQBCYR8NQC5uGRkW6KgCgKyq/B5/+tnMmWZYtZGVUQvsUWQHK/ZiMCnVXbcDNtc/RIEJQJDZ8FXMoxg==", + "license": "MIT", + "dependencies": { + "@atproto/did": "0.3.0", + "@atproto/jwk": "0.6.0", + "zod": "^3.23.8" + } + }, + "astro-authproto/node_modules/@fujocoded/msw-atproto": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/@fujocoded/msw-atproto/-/msw-atproto-0.0.1.tgz", + "integrity": "sha512-eomcjd9amNtXptU//dIrBBEpWgQph0QrkpN0O21GZ1KjVvtotl12NWRE1IGBeGeBoFHlf/ZeuxXz/qKZI5ME7g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@atproto/common-web": "^0.4.7", + "@atproto/crypto": "^0.4.5", + "@atproto/syntax": "^0.4.2", + "multiformats": "^13.4.2" + }, + "peerDependencies": { + "msw": "^2.0.0", + "vitest": "^3.0.0 || ^4.0.0 || ^5.0.0" + } + }, + "astro-authproto/node_modules/@fujocoded/msw-atproto/node_modules/multiformats": { + "version": "13.4.2", + "resolved": "https://registry.npmjs.org/multiformats/-/multiformats-13.4.2.tgz", + "integrity": "sha512-eh6eHCrRi1+POZ3dA+Dq1C6jhP1GNtr9CRINMb67OKzqW9I5DUuZM/3jLPlzhgpGeiNUlEGEbkCYChXMCc/8DQ==", + "dev": true, + "license": "Apache-2.0 OR MIT" + }, "astro-authproto/node_modules/@oxc-project/types": { "version": "0.101.0", "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.101.0.tgz", @@ -881,7 +987,7 @@ }, "astro-smooth-actions": { "name": "@fujocoded/astro-smooth-action", - "version": "0.0.1", + "version": "0.0.2", "license": "MIT", "devDependencies": { "astro": "^5.0.0", @@ -1300,6 +1406,32 @@ "unist-util-visit-parents": "^6.0.1" } }, + "msw-atproto": { + "name": "@fujocoded/msw-atproto", + "version": "0.0.1", + "license": "MIT", + "dependencies": { + "@atproto/common-web": "^0.4.7", + "@atproto/crypto": "^0.4.5", + "@atproto/syntax": "^0.4.2", + "multiformats": "^13.4.2" + }, + "devDependencies": { + "msw": "^2.13.4", + "tsdown": "^0.14.2", + "vitest": "^3.2.4" + }, + "peerDependencies": { + "msw": "^2.0.0", + "vitest": "^3.0.0 || ^4.0.0 || ^5.0.0" + } + }, + "msw-atproto/node_modules/multiformats": { + "version": "13.4.2", + "resolved": "https://registry.npmjs.org/multiformats/-/multiformats-13.4.2.tgz", + "integrity": "sha512-eh6eHCrRi1+POZ3dA+Dq1C6jhP1GNtr9CRINMb67OKzqW9I5DUuZM/3jLPlzhgpGeiNUlEGEbkCYChXMCc/8DQ==", + "license": "Apache-2.0 OR MIT" + }, "node_modules/@astrojs/compiler": { "version": "2.13.0", "resolved": "https://registry.npmjs.org/@astrojs/compiler/-/compiler-2.13.0.tgz", @@ -5816,7 +5948,6 @@ "os": [ "android" ], - "peer": true, "engines": { "node": "^20.19.0 || >=22.12.0" } @@ -5834,7 +5965,6 @@ "os": [ "darwin" ], - "peer": true, "engines": { "node": "^20.19.0 || >=22.12.0" } @@ -5852,7 +5982,6 @@ "os": [ "darwin" ], - "peer": true, "engines": { "node": "^20.19.0 || >=22.12.0" } @@ -5870,7 +5999,6 @@ "os": [ "freebsd" ], - "peer": true, "engines": { "node": "^20.19.0 || >=22.12.0" } @@ -5888,7 +6016,6 @@ "os": [ "linux" ], - "peer": true, "engines": { "node": "^20.19.0 || >=22.12.0" } @@ -5906,7 +6033,6 @@ "os": [ "linux" ], - "peer": true, "engines": { "node": "^20.19.0 || >=22.12.0" } @@ -5924,7 +6050,6 @@ "os": [ "linux" ], - "peer": true, "engines": { "node": "^20.19.0 || >=22.12.0" } @@ -5942,7 +6067,6 @@ "os": [ "linux" ], - "peer": true, "engines": { "node": "^20.19.0 || >=22.12.0" } @@ -5960,7 +6084,6 @@ "os": [ "linux" ], - "peer": true, "engines": { "node": "^20.19.0 || >=22.12.0" } @@ -5978,7 +6101,6 @@ "os": [ "openharmony" ], - "peer": true, "engines": { "node": "^20.19.0 || >=22.12.0" } @@ -5993,7 +6115,6 @@ "dev": true, "license": "MIT", "optional": true, - "peer": true, "dependencies": { "@napi-rs/wasm-runtime": "^1.1.0" }, @@ -6014,7 +6135,6 @@ "os": [ "win32" ], - "peer": true, "engines": { "node": "^20.19.0 || >=22.12.0" } @@ -6032,7 +6152,6 @@ "os": [ "win32" ], - "peer": true, "engines": { "node": "^20.19.0 || >=22.12.0" } @@ -19785,32 +19904,6 @@ "url": "https://github.com/sponsors/wooorm" } }, - "msw-atproto": { - "name": "@fujocoded/msw-atproto", - "version": "0.0.1", - "license": "MIT", - "dependencies": { - "@atproto/common-web": "^0.4.7", - "@atproto/crypto": "^0.4.5", - "@atproto/syntax": "^0.4.2", - "multiformats": "^13.4.2" - }, - "devDependencies": { - "msw": "^2.13.4", - "tsdown": "^0.14.2", - "vitest": "^3.2.4" - }, - "peerDependencies": { - "msw": "^2.0.0", - "vitest": "^3.0.0" - } - }, - "msw-atproto/node_modules/multiformats": { - "version": "13.4.2", - "resolved": "https://registry.npmjs.org/multiformats/-/multiformats-13.4.2.tgz", - "integrity": "sha512-eh6eHCrRi1+POZ3dA+Dq1C6jhP1GNtr9CRINMb67OKzqW9I5DUuZM/3jLPlzhgpGeiNUlEGEbkCYChXMCc/8DQ==", - "license": "Apache-2.0 OR MIT" - }, "rehype-code-params": { "name": "@fujocoded/rehype-code-params", "version": "0.0.5", @@ -19894,7 +19987,7 @@ }, "zod-transform-socials": { "name": "@fujocoded/zod-transform-socials", - "version": "0.1.0-zod4-test.0", + "version": "0.1.0", "license": "MIT", "dependencies": { "social-links": "^1.14.0"