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 `
+```
+
+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!
+
+ >
+ ) : (
+ <>
+ Choose your login adventure:
+
+ 1. Default Scopes
+
+
+ 2. Minimal scopes (identity only)
+
+
+ 3. Default scopes + email access
+
+
+ 4. Roll your own form
+
+ >
+ )
+ }
+
+
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 ? (
+
+ ) : (
+
+ )
+}
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
) : (
- // If there's no current user, show the login button
)
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"