diff --git a/.github/workflows/deploy-stripe-worker.yml b/.github/workflows/deploy-stripe-worker.yml
deleted file mode 100644
index 67e7514..0000000
--- a/.github/workflows/deploy-stripe-worker.yml
+++ /dev/null
@@ -1,40 +0,0 @@
-name: Deploy Stripe Webhook Worker
-
-on:
- push:
- branches:
- - main
- paths:
- - "worker/stripe-webhook/**"
- - ".github/workflows/deploy-stripe-worker.yml"
- workflow_dispatch:
-
-defaults:
- run:
- working-directory: worker/stripe-webhook
-
-jobs:
- deploy:
- runs-on: ubuntu-latest
- steps:
- - uses: actions/checkout@v4
- - uses: actions/setup-node@v4
- with:
- node-version: "20"
- - run: npm ci
-
- - name: Deploy
- run: npx wrangler deploy
- env:
- CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
- CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
-
- - name: Set secrets
- run: |
- echo "${{ secrets.STRIPE_WEBHOOK_SECRET_LIVE }}" | npx wrangler secret put STRIPE_WEBHOOK_SECRET_LIVE
- echo "${{ secrets.STRIPE_WEBHOOK_SECRET_TEST }}" | npx wrangler secret put STRIPE_WEBHOOK_SECRET_TEST
- echo "${{ secrets.RESEND_API_KEY }}" | npx wrangler secret put RESEND_API_KEY
- echo "${{ secrets.KEYGEN_API_KEY }}" | npx wrangler secret put KEYGEN_API_KEY
- env:
- CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
- CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
diff --git a/docs/authentication/google.mdx b/docs/authentication/google.mdx
index 25d52bb..cd05895 100644
--- a/docs/authentication/google.mdx
+++ b/docs/authentication/google.mdx
@@ -2,8 +2,6 @@
title: Google
---
-This feature requires the **Team** or **Enterprise** plan.
-
Allow users to sign in with their Google accounts.
## Prerequisites
diff --git a/docs/authentication/keycloak.mdx b/docs/authentication/keycloak.mdx
index d1c91b2..0ce8430 100644
--- a/docs/authentication/keycloak.mdx
+++ b/docs/authentication/keycloak.mdx
@@ -2,8 +2,6 @@
title: Keycloak
---
-This feature requires the **Enterprise** plan.
-
Allow users to sign in with Keycloak, an open source identity and access management solution.
## Prerequisites
diff --git a/docs/authentication/okta.mdx b/docs/authentication/okta.mdx
index 8b17e94..47f4068 100644
--- a/docs/authentication/okta.mdx
+++ b/docs/authentication/okta.mdx
@@ -2,8 +2,6 @@
title: Okta
---
-This feature requires the **Enterprise** plan.
-
Allow users to sign in with Okta, a cloud identity and access management platform.
## Prerequisites
diff --git a/docs/authentication/overview.mdx b/docs/authentication/overview.mdx
index aeeb49d..4bc5a9c 100644
--- a/docs/authentication/overview.mdx
+++ b/docs/authentication/overview.mdx
@@ -9,7 +9,7 @@ pgconsole supports multiple authentication methods to secure access to your data
| Method | Description | Best For |
|--------|-------------|----------|
| Email/Password | Built-in local authentication | Simple setups, small teams |
-| SSO ([Google](/authentication/google), [Okta](/authentication/okta), [Keycloak](/authentication/keycloak)) | OAuth providers | Enterprise, existing identity providers |
+| SSO ([Google](/authentication/google), [Okta](/authentication/okta), [Keycloak](/authentication/keycloak)) | OAuth providers | Organizations with existing identity providers |
## Configuration
diff --git a/docs/configuration/config.mdx b/docs/configuration/config.mdx
index 2b2b528..06b25b3 100644
--- a/docs/configuration/config.mdx
+++ b/docs/configuration/config.mdx
@@ -27,7 +27,6 @@ pgconsole uses a TOML configuration file for all settings. Pass the config file
```toml pgconsole.toml
[general]
external_url = "https://pgconsole.example.com"
-license = "your-license-key"
[general.banner]
text = "System maintenance scheduled for Sunday 2am UTC"
@@ -142,12 +141,10 @@ permissions = ["read"]
| Field | Description | Required |
|-------|-------------|----------|
| `external_url` | Public URL of the application. See [Configure External Access](/configuration/external-access). | Required for SSO |
-| `license` | License key. See [Manage License](/configuration/license). | |
```toml pgconsole.toml
[general]
external_url = "https://pgconsole.example.com"
-license = "your-license-key"
```
### Announcement Banner
@@ -169,7 +166,7 @@ color = "#7c3aed"
## Branding
-Replace the pgconsole logo with your own. Requires an [Enterprise license](/configuration/license).
+Replace the pgconsole logo with your own.
| Field | Description | Required |
|-------|-------------|----------|
@@ -307,7 +304,7 @@ User entries. Repeat for multiple users. Users with a `password` can sign in wit
|-------|-------------|----------|
| `email` | User email or identifier | Yes |
| `password` | Login password (omit for SSO-only users) | |
-| `owner` | Grants access to subscription management | |
+| `owner` | Marks the user as an owner (shown with an Owner badge) | |
```toml pgconsole.toml
[[users]]
@@ -322,7 +319,7 @@ email = "alice@example.com"
### Owner Role
-Users with `owner = true` can view subscription status and access upgrade options. See [Manage License](/configuration/license). If no user has `owner = true`, the first user entry automatically becomes the owner.
+Users with `owner = true` are marked with an Owner badge in the UI. If no user has `owner = true`, the first user entry automatically becomes the owner.
## Groups
@@ -345,7 +342,7 @@ members = ["admin@example.com", "alice@example.com"]
## Access Control (IAM)
-Rules for controlling access to connections. See [Database Access Control](/features/database-access-control) for a full guide on permissions, patterns, and examples.
+Rules for controlling access to connections. IAM is opt-in: with no `[[iam]]` rules defined, all authenticated users have full access, and enforcement begins once you define the first rule. See [Database Access Control](/features/database-access-control) for a full guide on permissions, patterns, and examples.
| Field | Description | Required |
|-------|-------------|----------|
@@ -410,7 +407,7 @@ base_url = "https://api.groq.com/openai/v1"
## Agents
-Non-human principals that authenticate to the [MCP Server](/features/mcp-server) with a bearer token. An agent is **not** a [user](#users) — it can't log into the UI and doesn't count against your license seats. Repeat for multiple agents.
+Non-human principals that authenticate to the [MCP Server](/features/mcp-server) with a bearer token. An agent is **not** a [user](#users) — it can't log into the UI. Repeat for multiple agents.
| Field | Description | Required |
|-------|-------------|----------|
diff --git a/docs/configuration/license.mdx b/docs/configuration/license.mdx
deleted file mode 100644
index 88a78e5..0000000
--- a/docs/configuration/license.mdx
+++ /dev/null
@@ -1,29 +0,0 @@
----
-title: Manage License
----
-
-pgconsole does not connect to any external license server, so it cannot auto-refresh the license. After purchasing a new subscription or renewing an existing one, a new license key will be emailed to you. Update the key in your configuration file and restart pgconsole for it to take effect.
-
-## Purchasing a License
-
-Purchase directly from the application via the **Subscription** option in the owner's profile menu, or use the links below:
-
-- [Monthly plan — $20/user/month](https://buy.stripe.com/aFa00ifoZ8hHfEDgeN1Fe02)
-- [Annual plan — $16/user/month](https://buy.stripe.com/28E28qfoZ69zcsr2nX1Fe03)
-
-A license key will be emailed to you after purchase.
-
-## Adding a License
-
-Add your license key to the configuration file:
-
-```toml pgconsole.toml
-[general]
-license = "your-license-key"
-```
-
-Restart pgconsole for the license to take effect.
-
-## Billing
-
-For billing issues, contact [billing@pgconsole.com](mailto:billing@pgconsole.com).
diff --git a/docs/docs.json b/docs/docs.json
index 37878d4..f164c90 100644
--- a/docs/docs.json
+++ b/docs/docs.json
@@ -52,7 +52,6 @@
"pages": [
"configuration/server-flags",
"configuration/external-access",
- "configuration/license",
"configuration/config"
]
}
diff --git a/docs/features/audit-log.mdx b/docs/features/audit-log.mdx
index 5a171b7..7e0d493 100644
--- a/docs/features/audit-log.mdx
+++ b/docs/features/audit-log.mdx
@@ -2,8 +2,6 @@
title: Audit Log
---
-This feature requires the **Enterprise** plan.
-
pgconsole emits audit logs as JSON lines to stdout, allowing you to capture and process them with your existing log infrastructure.
## Events
diff --git a/docs/features/database-access-control.mdx b/docs/features/database-access-control.mdx
index 2f4f3cd..4cbd677 100644
--- a/docs/features/database-access-control.mdx
+++ b/docs/features/database-access-control.mdx
@@ -2,10 +2,10 @@
title: Database Access Control
---
-This feature requires the **Team** or **Enterprise** plan.
-
pgconsole provides fine-grained access control for your database connections. You define [IAM rules](/configuration/config#access-control-iam) in your configuration file, and pgconsole enforces them — rejecting unauthorized queries before they reach the database.
+IAM is **opt-in**. With no `[[iam]]` rules defined, every authenticated user has full access to all connections. Enforcement begins the moment you define your first rule — from then on, any user without a matching rule is denied.
+
```mermaid
flowchart LR
U[User] -->|SQL| P[pgconsole]
@@ -19,17 +19,17 @@ Unlike PostgreSQL's built-in role system (`GRANT`/`REVOKE`), pgconsole's access

-Access control in pgconsole works on three principles:
+Once at least one rule is defined, access control works on three principles:
- **Default deny** — users have no access unless a rule explicitly grants it
- **Connection-scoped** — permissions are granted per database connection, not globally
- **Disjoint permissions** — each permission level is independent; `write` does not imply `read`
-When IAM is enabled, users only see connections they have at least one permission for. Connections without any matching rules are hidden entirely.
+While IAM is active, users only see connections they have at least one permission for. Connections without any matching rules are hidden entirely.
## Prerequisites
-- [Authentication](/configuration/config#authentication) must be enabled; otherwise, all users get full access to all connections
+- [Authentication](/configuration/config#authentication) must be enabled, and at least one `[[iam]]` rule must be defined; otherwise, all users get full access to all connections
- Users must be defined in [`[[users]]`](/configuration/config#users)
- Groups (if used) must be defined in [`[[groups]]`](/configuration/config#groups)
diff --git a/docs/features/mcp-server.mdx b/docs/features/mcp-server.mdx
index 16a990f..6ac549b 100644
--- a/docs/features/mcp-server.mdx
+++ b/docs/features/mcp-server.mdx
@@ -24,9 +24,11 @@ Requests without a valid token are rejected with `401`.
## Identity
-Each token belongs to an `[[agents]]` entry — a non-human principal that is **not** a user (no UI login, no license seat). There are two kinds:
+Each token belongs to an `[[agents]]` entry — a non-human principal that is **not** a user (no UI login). There are two kinds:
-- **Pure agent** — a standalone service account (e.g. a CI bot). Authorized by IAM rules whose `members` include `agent:`. Audited as `agent:`.
+If no `[[iam]]` rules are defined, IAM is off and agents have full access to all connections; define at least one rule to enforce least privilege.
+
+- **Pure agent** — a standalone service account (e.g. a CI bot). When IAM is active, authorized by IAM rules whose `members` include `agent:`. Audited as `agent:`.
- **Delegated agent** — acts `on_behalf_of` a user, **inheriting that user's permissions** narrowed by optional `permissions`/`connections` caps. It can never exceed the user and loses access automatically when the user does. Audited as the user, tagged with the agent.
```toml pgconsole.toml
diff --git a/docs/features/white-labeling.mdx b/docs/features/white-labeling.mdx
index 7624334..c3ec62e 100644
--- a/docs/features/white-labeling.mdx
+++ b/docs/features/white-labeling.mdx
@@ -2,8 +2,6 @@
title: White Labeling
---
-This feature requires the **Enterprise** plan.
-
pgconsole supports white labeling, allowing you to rebrand and embed the console into your own product.
- **Custom Logo** — Replace the default logo with your own in the top-right corner.
diff --git a/docs/getting-started/faq.mdx b/docs/getting-started/faq.mdx
index 6c4623d..87e8b91 100644
--- a/docs/getting-started/faq.mdx
+++ b/docs/getting-started/faq.mdx
@@ -20,7 +20,7 @@ No. pgconsole is fully self-hosted and makes no outbound network requests except
## License
-pgconsole is [source available](https://github.com/pgplex/pgconsole/blob/main/LICENSE). The Community plan is free to use. A commercial license is required for the Team or Enterprise plan.
+pgconsole is [source available](https://github.com/pgplex/pgconsole/blob/main/LICENSE) and free to use, with all features included.
## Certifications
diff --git a/pgconsole.example.toml b/pgconsole.example.toml
index 0fc70a4..aa8598b 100644
--- a/pgconsole.example.toml
+++ b/pgconsole.example.toml
@@ -6,7 +6,6 @@
# =============================================================================
# [general]
# external_url = "https://pgconsole.example.com" # Required when OAuth providers are enabled
-# license = "eyJhbGciOiJSUzI1NiJ9..." # License key (JWT). Omit for FREE plan.
#
# # Banner (optional) - displays at the top of the page, cannot be dismissed
# [general.banner]
@@ -15,9 +14,9 @@
# color = "#7c3aed" # Optional
# =============================================================================
-# Branding (Enterprise only)
+# Branding
# =============================================================================
-# Replace the pgconsole logo with your own. Requires an Enterprise license.
+# Replace the pgconsole logo with your own.
#
# [branding]
# logo = "https://example.com/your-logo.svg"
diff --git a/server/auth-routes.ts b/server/auth-routes.ts
index 166c2b6..b364120 100644
--- a/server/auth-routes.ts
+++ b/server/auth-routes.ts
@@ -1,19 +1,11 @@
import { Router, type Request, type Response } from 'express'
import crypto from 'crypto'
import { createToken, verifyToken, authenticateBasic } from './lib/auth'
-import { getAuthConfig, isAuthEnabled, getGroupsForUser, getPlan, isOwner, getUsers } from './lib/config'
+import { getAuthConfig, isAuthEnabled, getGroupsForUser, isOwner, getUsers } from './lib/config'
import { auditLogin, auditLogout } from './lib/audit'
import { registerGoogleOAuth } from './lib/oauth/google'
import { registerKeycloakOAuth } from './lib/oauth/keycloak'
import { registerOktaOAuth } from './lib/oauth/okta'
-import { feature, requiredPlan } from '../src/lib/plan'
-import type { Feature } from '../src/lib/plan'
-
-const SSO_FEATURE: Record = {
- google: 'SSO_GOOGLE',
- keycloak: 'SSO_KEYCLOAK',
- okta: 'SSO_OKTA',
-}
const router = Router()
@@ -123,28 +115,6 @@ const oauthOpts = {
generateState,
getClientIp,
}
-// Gate OAuth routes by plan
-router.use(['/google', '/google/callback'], (req: Request, res: Response, next) => {
- if (!feature('SSO_GOOGLE', getPlan())) {
- return res.status(403).json({ error: 'Google SSO requires Team plan or higher' })
- }
- next()
-})
-
-router.use(['/keycloak', '/keycloak/callback'], (req: Request, res: Response, next) => {
- if (!feature('SSO_KEYCLOAK', getPlan())) {
- return res.status(403).json({ error: 'Keycloak SSO requires Enterprise plan' })
- }
- next()
-})
-
-router.use(['/okta', '/okta/callback'], (req: Request, res: Response, next) => {
- if (!feature('SSO_OKTA', getPlan())) {
- return res.status(403).json({ error: 'Okta SSO requires Enterprise plan' })
- }
- next()
-})
-
registerGoogleOAuth(router, oauthOpts)
registerKeycloakOAuth(router, oauthOpts)
registerOktaOAuth(router, oauthOpts)
@@ -156,16 +126,10 @@ router.get('/providers', (_req: Request, res: Response) => {
}
const config = getAuthConfig()
- const plan = getPlan()
- const providers: Array<{ name: string; requiredPlan?: string }> = []
+ const providers: Array<{ name: string }> = []
if (getUsers().some(u => u.password)) providers.push({ name: 'basic' })
for (const provider of config?.providers ?? []) {
- const feat = SSO_FEATURE[provider.type]
- if (feat && !feature(feat, plan)) {
- providers.push({ name: provider.type, requiredPlan: requiredPlan(feat) })
- } else {
- providers.push({ name: provider.type })
- }
+ providers.push({ name: provider.type })
}
return res.json({ providers })
diff --git a/server/index.ts b/server/index.ts
index 43fbb17..2c1a731 100644
--- a/server/index.ts
+++ b/server/index.ts
@@ -4,10 +4,9 @@ import cookieParser from 'cookie-parser'
import { authRouter } from './auth-routes'
import { connectRouter } from './connect'
import { mcpRouter, MCP_PATH } from './mcp'
-import { loadConfig, loadConfigFromString, loadDemoConfig, isDemoMode, getBanner, getBranding, getExternalUrl, getPlan, getLicenseExpiry, getUsers, getLicenseMaxUsers, getLicenseEmail, getAgents } from './lib/config'
+import { loadConfig, loadConfigFromString, loadDemoConfig, isDemoMode, getBanner, getBranding, getExternalUrl, getAgents } from './lib/config'
import { startDemoDatabase, stopDemoDatabase } from './lib/demo'
import { testAllConnections } from './lib/test-connections'
-import { feature } from '../src/lib/plan'
// __dirname is provided by esbuild banner
declare const __dirname: string
@@ -40,15 +39,9 @@ app.use(connectRouter)
// Public settings endpoint (no auth required)
app.get('/api/setting', (_req, res) => {
- const plan = getPlan()
res.json({
- banner: feature('BANNER', plan) ? getBanner() : undefined,
- branding: feature('BRANDING', plan) ? getBranding() : undefined,
- plan,
- licenseExpiry: getLicenseExpiry(),
- licenseEmail: getLicenseEmail(),
- maxUsers: getLicenseMaxUsers(),
- userCount: getUsers().length,
+ banner: getBanner(),
+ branding: getBranding(),
demo: isDemoMode(),
})
})
@@ -99,21 +92,6 @@ async function start() {
console.log(`✓ Demo database started on port ${demoPort}`)
}
- // Check license expiration
- const licenseExpiry = getLicenseExpiry()
- if (licenseExpiry) {
- const now = Math.floor(Date.now() / 1000)
- if (licenseExpiry < now) {
- console.warn('\n⚠️ WARNING: Your license has expired. Some features may be restricted.\n Renew at https://docs.pgconsole.com/configuration/license#purchasing-a-license\n')
- }
- }
-
- // Log plan and user seat info
- const plan = getPlan()
- const maxUsers = getLicenseMaxUsers()
- const userCount = getUsers().length
- console.log(`✓ Plan: ${plan}, User seat: ${userCount}/${maxUsers}`)
-
// Test all connections to populate cache
try {
await testAllConnections()
diff --git a/server/lib/audit.ts b/server/lib/audit.ts
index d982155..23ba896 100644
--- a/server/lib/audit.ts
+++ b/server/lib/audit.ts
@@ -1,7 +1,4 @@
// Audit logging - emits JSON lines to stdout
-import { feature } from '../../src/lib/plan'
-import { getPlan } from './config'
-
interface BaseEvent {
type: 'audit'
ts: string
@@ -49,7 +46,6 @@ interface DataExportEvent extends BaseEvent {
type AuditEvent = AuthLoginEvent | AuthLogoutEvent | SQLExecuteEvent | DataExportEvent
function emit(event: AuditEvent): void {
- if (!feature('AUDIT_LOG', getPlan())) return
console.log(JSON.stringify(event))
}
diff --git a/server/lib/config.ts b/server/lib/config.ts
index 556e3b9..ebd9169 100644
--- a/server/lib/config.ts
+++ b/server/lib/config.ts
@@ -1,8 +1,5 @@
import { parse } from 'smol-toml'
import { readFileSync } from 'fs'
-import { checkLicense } from './license'
-import { feature } from '../../src/lib/plan'
-import type { PlanTier } from '../../src/lib/plan'
import type { Vendor } from '../ai/vendors'
export interface LabelConfig {
@@ -103,7 +100,6 @@ export interface IAMRule {
interface Config {
external_url?: string
- license?: string
banner?: BannerConfig
branding?: BrandingConfig
users: UserConfig[]
@@ -114,10 +110,6 @@ interface Config {
ai?: AIConfig
agents: AgentConfig[]
iam: IAMRule[]
- plan: PlanTier
- licenseExpiry?: number
- licenseMaxUsers: number
- licenseEmail?: string
}
const validSslModes = ['disable', 'prefer', 'require', 'verify-full']
@@ -144,7 +136,7 @@ function parsePermissionList(raw: unknown, label: string): Permission[] {
return permissions
}
-const DEFAULT_CONFIG: Config = { users: [], groups: [], labels: [], connections: [], auth: undefined, ai: undefined, agents: [], banner: undefined, branding: undefined, license: undefined, iam: [], plan: 'FREE', licenseExpiry: undefined, licenseMaxUsers: 1, licenseEmail: undefined }
+const DEFAULT_CONFIG: Config = { users: [], groups: [], labels: [], connections: [], auth: undefined, ai: undefined, agents: [], banner: undefined, branding: undefined, iam: [] }
let loadedConfig: Config = { ...DEFAULT_CONFIG }
let demoMode = false
@@ -183,7 +175,6 @@ export async function loadConfigFromString(content: string): Promise {
// Parse [general] section
let external_url: string | undefined = undefined
- let license: string | undefined = undefined
let banner: BannerConfig | undefined = undefined
if (parsed.general) {
const g = parsed.general
@@ -201,13 +192,6 @@ export async function loadConfigFromString(content: string): Promise {
}
}
- if (g.license !== undefined) {
- if (typeof g.license !== 'string') {
- throw new Error('general.license must be a string')
- }
- license = g.license
- }
-
// Parse [general.banner] section
const rawBanner = g.banner as Record | undefined
if (rawBanner) {
@@ -763,28 +747,7 @@ export async function loadConfigFromString(content: string): Promise {
iam.push({ connection, permissions, members })
}
- // Resolve license → plan tier and maxUsers before assigning config
- let plan: PlanTier = 'FREE'
- let licenseExpiry: number | undefined
- let licenseMaxUsers = 1
- let licenseEmail: string | undefined
- if (license) {
- const result = await checkLicense(license)
- plan = result.plan
- licenseExpiry = result.expiry
- licenseMaxUsers = result.maxUsers
- licenseEmail = result.email
- }
-
- loadedConfig = { external_url, license, banner, branding, users, groups, labels, connections, auth, ai, agents, iam, plan, licenseExpiry, licenseMaxUsers, licenseEmail }
-
- // Validate user count against license limit
- if (auth) {
- const limit = licenseMaxUsers
- if (users.length > limit) {
- throw new Error(`Too many [[users]] entries: ${users.length} configured but current license only allows ${limit}. Remove users or upgrade at https://docs.pgconsole.com/configuration/license#purchasing-a-license`)
- }
- }
+ loadedConfig = { external_url, banner, branding, users, groups, labels, connections, auth, ai, agents, iam }
}
export function getLabels(): LabelConfig[] {
@@ -800,7 +763,6 @@ export function getGroupById(id: string): GroupConfig | undefined {
}
export function getGroupsForUser(email: string): GroupConfig[] {
- if (!feature('GROUPS', getPlan())) return []
return loadedConfig.groups.filter((g) => g.members.includes(email))
}
@@ -877,26 +839,6 @@ export function getIAMRules(): IAMRule[] {
return loadedConfig.iam
}
-export function getLicense(): string | undefined {
- return loadedConfig.license
-}
-
-export function getPlan(): PlanTier {
- return loadedConfig.plan
-}
-
-export function getLicenseExpiry(): number | undefined {
- return loadedConfig.licenseExpiry
-}
-
-export function getLicenseMaxUsers(): number {
- return loadedConfig.licenseMaxUsers
-}
-
-export function getLicenseEmail(): string | undefined {
- return loadedConfig.licenseEmail
-}
-
export function loadDemoConfig(port: number): void {
demoMode = true
loadedConfig = {
diff --git a/server/lib/iam.ts b/server/lib/iam.ts
index c951d8b..6745136 100644
--- a/server/lib/iam.ts
+++ b/server/lib/iam.ts
@@ -1,6 +1,5 @@
import { ConnectError, Code } from '@connectrpc/connect'
-import { getIAMRules, getGroupsForUser, isAuthEnabled, getPlan } from './config'
-import { feature } from '../../src/lib/plan'
+import { getIAMRules, getGroupsForUser, isAuthEnabled } from './config'
import type { Permission, AgentConfig, IAMRule } from './config'
export type { Permission }
@@ -63,13 +62,15 @@ export function requireAnyPermission(
}
// Union the permissions from every IAM rule that applies to this connection and whose
-// members satisfy `matches`. Auth-disabled / IAM-not-licensed short-circuit to full access.
+// members satisfy `matches`. IAM is opt-in: with auth disabled or no [[iam]] rules
+// defined, every principal gets full access. Defining the first rule turns enforcement on.
function resolvePermissions(connectionId: string, matches: (rule: IAMRule) => boolean): Set {
- if (!isAuthEnabled() || !feature('IAM', getPlan())) {
+ const rules = getIAMRules()
+ if (!isAuthEnabled() || rules.length === 0) {
return new Set(ALL_PERMISSIONS)
}
const permissions = new Set()
- for (const rule of getIAMRules()) {
+ for (const rule of rules) {
if (rule.connection !== '*' && rule.connection !== connectionId) {
continue
}
@@ -88,7 +89,7 @@ function resolvePermissions(connectionId: string, matches: (rule: IAMRule) => bo
*/
export function getUserPermissions(email: string, connectionId: string): Set {
// Resolve the user's groups lazily — skipped entirely when resolvePermissions
- // short-circuits (auth off / IAM unlicensed) or when no rule has a group: member.
+ // short-circuits (auth off / no IAM rules) or when no rule has a group: member.
let groupIds: Set | undefined
return resolvePermissions(connectionId, rule =>
rule.members.some(member => {
@@ -112,9 +113,8 @@ export function hasPermission(email: string, connectionId: string, permission: P
/**
* Get an agent's effective permissions for a connection. Like `getUserPermissions`, this
- * grants full access when auth is disabled or the plan doesn't include IAM (the shared
- * `resolvePermissions` short-circuit — IAM is a paid feature, applied uniformly to every
- * principal). When IAM is active:
+ * grants full access when auth is disabled or no IAM rules are defined (the shared
+ * `resolvePermissions` short-circuit). Once IAM is active:
* - Pure agent: matched ONLY by explicit `agent:` rules — `*`/`group:`/`user:` rules
* never apply, so no agent silently inherits a broad "everyone" grant.
* - Delegated agent (`onBehalfOf`): the user's grant, narrowed by the connection/permission
diff --git a/server/lib/license.ts b/server/lib/license.ts
deleted file mode 100644
index 31f8470..0000000
--- a/server/lib/license.ts
+++ /dev/null
@@ -1,46 +0,0 @@
-import { jwtVerify, importSPKI } from 'jose'
-import type { PlanTier } from '../../src/lib/plan'
-
-export interface LicenseResult {
- plan: PlanTier
- expiry?: number
- maxUsers: number
- email?: string
-}
-
-const LICENSE_ISSUER = 'pgconsole/license'
-
-let KEYGEN_PUBLIC_KEY = `-----BEGIN PUBLIC KEY-----
-MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAuNGFvPtpvyhT7eYc1x5Y
-ir/nW5CAH3kLYL3F70xN5bMYxsx9h9H/4xBIAk3ddm/maOqcua2E+PI2Z1w8lEtR
-GhAXSJtykKGuPXIDudnMVXbYqYhvvVYTH4NXRtTuS5NdTD0ZURr6X8X01dIsJVve
-QUp/TXODV0GHTRvkRdisak3NUnub9Mv20XirYWPed1OnDWLuE57T1FaD6ZYhC0is
-loNCg5i7KfmNxigW/iABe7Pbvafuq5O5UBkN9l7x+kcnc68oY/ceBUnyHa8Hj3p6
-B5IyYLTs5y7dD1IS22hJiteOQEmmdOQpYCXyOhVRXcuVHzHYkQNNvlW9vDVTE+m7
-rQIDAQAB
------END PUBLIC KEY-----`
-
-export function setPublicKeyForTesting(pem: string): void {
- KEYGEN_PUBLIC_KEY = pem
-}
-
-export async function checkLicense(license: string): Promise {
- try {
- const publicKey = await importSPKI(KEYGEN_PUBLIC_KEY, 'RS256')
- const { payload } = await jwtVerify(license, publicKey, {
- issuer: LICENSE_ISSUER,
- requiredClaims: ['exp'],
- })
- const plan = payload.plan as string
- const maxUsers = typeof payload.userSeat === 'number' ? payload.userSeat : 1
- const email = typeof payload.email === 'string' ? payload.email : undefined
- if (plan === 'team' || plan === 'enterprise') {
- return { plan: plan.toUpperCase() as PlanTier, expiry: payload.exp, maxUsers, email }
- }
- console.warn('License JWT has unrecognized plan claim:', plan)
- return { plan: 'FREE', maxUsers: 1 }
- } catch (err) {
- console.warn('License verification failed:', err instanceof Error ? err.message : err)
- return { plan: 'FREE', maxUsers: 1 }
- }
-}
diff --git a/src/App.tsx b/src/App.tsx
index b2a8195..93e2ba3 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -12,8 +12,6 @@ import { useEditorNavigation } from './hooks/useEditorNavigation';
import { Button } from './components/ui/button';
import { Banner } from './components/Banner';
import { useSetting } from './hooks/useSetting';
-import { SubscriptionModalProvider } from './components/SubscriptionModal';
-import { LicenseExpiryBanner } from './components/LicenseExpiryBanner';
import { DemoBanner } from './components/DemoBanner';
function AppLayout() {
@@ -103,44 +101,41 @@ function AppLayout() {
return (
-
-
- {!isSignInRoute && demo &&
}
- {!isSignInRoute && banner?.text && (
-
- )}
- {!isSignInRoute &&
}
- {!isSignInRoute &&
}
-
-
-
- } />
-
-
-
+
+ {!isSignInRoute && demo &&
}
+ {!isSignInRoute && banner?.text && (
+
+ )}
+ {!isSignInRoute &&
}
+
+
+
} />
- } />
-
-
+
+
+
+ } />
+
} />
+
-
+
);
}
diff --git a/src/components/AuthForm.tsx b/src/components/AuthForm.tsx
index d0b7cc1..ee2f09c 100644
--- a/src/components/AuthForm.tsx
+++ b/src/components/AuthForm.tsx
@@ -46,7 +46,7 @@ export default function AuthForm({ onSuccess }: AuthFormProps) {
const hasBasic = providers.some((p) => p.name === 'basic');
const oauthEntries = providers
.filter((p) => p.name !== 'basic' && p.name in oauthProviders)
- .map((p) => ({ key: p.name, requiredPlan: p.requiredPlan, ...oauthProviders[p.name] }));
+ .map((p) => ({ key: p.name, ...oauthProviders[p.name] }));
const hasOAuth = oauthEntries.length > 0;
if (providers.length === 0) {
@@ -55,23 +55,17 @@ export default function AuthForm({ onSuccess }: AuthFormProps) {
return (
- {oauthEntries.map(({ key, icon, label, path, requiredPlan }) => (
+ {oauthEntries.map(({ key, icon, label, path }) => (
{ if (!requiredPlan) window.location.href = path; }}
+ onClick={() => { window.location.href = path; }}
variant="outline"
className="w-full"
size="lg"
- disabled={!!requiredPlan}
>
{icon}
{label}
- {requiredPlan && (
-
- Requires {requiredPlan} plan — Get a license
-
- )}
))}
diff --git a/src/components/Header.tsx b/src/components/Header.tsx
index 5517247..343ea58 100644
--- a/src/components/Header.tsx
+++ b/src/components/Header.tsx
@@ -1,9 +1,8 @@
import { useNavigate } from 'react-router-dom';
-import { LogOut, CreditCard } from 'lucide-react';
+import { LogOut } from 'lucide-react';
import { ConnectionSwitcher } from './ConnectionSwitcher';
import { Menu, MenuTrigger, MenuPopup, MenuItem } from './ui/menu';
import { useSession, signOut } from '@/lib/auth-client';
-import { useSubscriptionModal } from '@/hooks/useSubscriptionModal';
import { useOwner } from '@/hooks/useOwner';
import { useSetting } from '@/hooks/useSetting';
import { useConnections } from '@/hooks/useQuery';
@@ -26,7 +25,6 @@ function expandHex(hex: string): string {
export default function Header({ selectedConnectionId }: HeaderProps) {
const navigate = useNavigate();
const { user, authEnabled } = useSession();
- const subscriptionModal = useSubscriptionModal();
const isOwner = useOwner();
const { branding } = useSetting();
const { data: connections } = useConnections();
@@ -91,12 +89,6 @@ export default function Header({ selectedConnectionId }: HeaderProps) {
{isOwner &&
(Owner) }
- {isOwner && (
- subscriptionModal.open()}>
-
- Subscription
-
- )}
Sign Out
diff --git a/src/components/LicenseExpiryBanner.tsx b/src/components/LicenseExpiryBanner.tsx
deleted file mode 100644
index 8a91f6e..0000000
--- a/src/components/LicenseExpiryBanner.tsx
+++ /dev/null
@@ -1,47 +0,0 @@
-import { useSetting } from '@/hooks/useSetting'
-import { useOwner } from '@/hooks/useOwner'
-import { useSubscriptionModal } from '@/hooks/useSubscriptionModal'
-
-// Number of days before expiry to show warning banner
-const EXPIRY_WARNING_DAYS = 7
-
-export function LicenseExpiryBanner() {
- const { licenseExpiry } = useSetting()
- const isOwner = useOwner()
- const subscriptionModal = useSubscriptionModal()
-
- if (!licenseExpiry) return null
-
- const now = Date.now()
- const expiryMs = licenseExpiry * 1000
- const daysUntilExpiry = Math.ceil((expiryMs - now) / (1000 * 60 * 60 * 24))
-
- // Don't show if not expiring soon (expired = negative days, so always shows)
- if (daysUntilExpiry > EXPIRY_WARNING_DAYS) return null
-
- const message =
- daysUntilExpiry < 0
- ? 'Your license has expired.'
- : daysUntilExpiry === 0
- ? 'Your license expires today.'
- : daysUntilExpiry === 1
- ? 'Your license expires tomorrow.'
- : `Your license expires in ${daysUntilExpiry} days.`
-
- if (isOwner) {
- return (
- subscriptionModal.open()}
- className="flex h-10 w-full items-center justify-center gap-1.5 bg-primary text-sm font-medium text-primary-foreground hover:opacity-90"
- >
- {message} Click to renew.
-
- )
- }
-
- return (
-
- {message}
-
- )
-}
diff --git a/src/components/SubscriptionModal.tsx b/src/components/SubscriptionModal.tsx
deleted file mode 100644
index fd973dd..0000000
--- a/src/components/SubscriptionModal.tsx
+++ /dev/null
@@ -1,177 +0,0 @@
-import { useState, useCallback, useMemo } from 'react'
-import { Users, Calendar, Shield, Mail } from 'lucide-react'
-import { useOwner } from '@/hooks/useOwner'
-import {
- Dialog,
- DialogPopup,
- DialogHeader,
- DialogTitle,
- DialogDescription,
- DialogPanel,
- DialogFooter,
-} from '@/components/ui/dialog'
-import { Button } from '@/components/ui/button'
-import { Badge } from '@/components/ui/badge'
-import { useSetting } from '@/hooks/useSetting'
-import { SubscriptionModalContext } from '@/hooks/useSubscriptionModal'
-import type { PlanTier } from '@/lib/plan'
-
-const PLAN_LABELS: Record = {
- FREE: 'Free',
- TEAM: 'Team',
- ENTERPRISE: 'Enterprise',
-}
-
-const PLAN_COLORS: Record = {
- FREE: 'bg-gray-100 text-gray-700',
- TEAM: 'bg-blue-100 text-blue-700',
- ENTERPRISE: 'bg-purple-100 text-purple-700',
-}
-
-const PAYMENT_LINKS = {
- monthly: 'https://buy.stripe.com/aFa00ifoZ8hHfEDgeN1Fe02',
- annual: 'https://buy.stripe.com/28E28qfoZ69zcsr2nX1Fe03',
-}
-
-const CUSTOMER_PORTAL_LINK = 'https://billing.stripe.com/p/login/8x23cu1y969z643e6F1Fe00'
-
-function formatDate(timestamp: number): string {
- return new Date(timestamp * 1000).toLocaleDateString(undefined, {
- year: 'numeric',
- month: 'long',
- day: 'numeric',
- })
-}
-
-function isExpired(timestamp: number): boolean {
- return timestamp * 1000 < Date.now()
-}
-
-type BillingCycle = 'monthly' | 'annual'
-
-export function SubscriptionModalProvider({ children }: { children: React.ReactNode }) {
- const [isOpen, setIsOpen] = useState(false)
- const [reason, setReason] = useState()
- const { plan, licenseExpiry, licenseEmail, maxUsers, userCount } = useSetting()
- const isOwner = useOwner()
- const [billingCycle, setBillingCycle] = useState('annual')
-
- const open = useCallback((r?: string) => {
- setReason(r)
- setIsOpen(true)
- }, [])
-
- const contextValue = useMemo(() => ({ open }), [open])
-
- const expired = licenseExpiry ? isExpired(licenseExpiry) : false
-
- return (
-
- {children}
-
-
-
- Subscription
- {reason && (
- {reason}
- )}
-
-
-
-
-
-
- Plan
-
-
{PLAN_LABELS[plan]}
-
-
- {licenseEmail && (
-
-
-
- Licensee
-
-
{licenseEmail}
-
- )}
-
- {licenseExpiry && (
-
-
-
- Expires
-
-
- {expired ? 'Expired' : formatDate(licenseExpiry)}
-
-
- )}
-
-
-
-
- Users
-
-
- {userCount} / {maxUsers}
-
-
-
- {isOwner && plan === 'FREE' && (
-
-
Select billing cycle:
-
-
setBillingCycle('monthly')}
- className={`p-3 rounded-lg border text-left transition-colors ${
- billingCycle === 'monthly'
- ? 'border-blue-500 bg-blue-50'
- : 'border-gray-200 hover:border-gray-300'
- }`}
- >
- Monthly
- $20/user/month
-
-
setBillingCycle('annual')}
- className={`p-3 rounded-lg border text-left transition-colors ${
- billingCycle === 'annual'
- ? 'border-blue-500 bg-blue-50'
- : 'border-gray-200 hover:border-gray-300'
- }`}
- >
-
- Annual
- Save 20%
-
- $16/user/month
-
-
-
- )}
-
-
-
- setIsOpen(false)}>
- Close
-
- {isOwner && (
- {
- let url = plan === 'FREE' ? PAYMENT_LINKS[billingCycle] : CUSTOMER_PORTAL_LINK
- if (plan !== 'FREE' && licenseEmail) {
- url += `?prefilled_email=${encodeURIComponent(licenseEmail)}`
- }
- window.open(url, '_blank')
- }}>
- {plan === 'FREE' ? 'Upgrade' : 'Manage'}
-
- )}
-
-
-
-
- )
-}
diff --git a/src/hooks/useSetting.ts b/src/hooks/useSetting.ts
index a4a08d6..0160b18 100644
--- a/src/hooks/useSetting.ts
+++ b/src/hooks/useSetting.ts
@@ -1,5 +1,4 @@
import { useQuery } from '@tanstack/react-query'
-import type { PlanTier } from '@/lib/plan'
interface BannerConfig {
text: string
@@ -15,11 +14,6 @@ interface BrandingConfig {
interface SettingResponse {
branding?: BrandingConfig
banner?: BannerConfig
- plan: PlanTier
- licenseExpiry?: number
- licenseEmail?: string
- maxUsers: number
- userCount: number
demo?: boolean
}
@@ -36,11 +30,6 @@ export function useSetting() {
return {
branding: data?.branding,
banner: data?.banner,
- plan: data?.plan ?? 'FREE',
- licenseExpiry: data?.licenseExpiry,
- licenseEmail: data?.licenseEmail,
- maxUsers: data?.maxUsers ?? 1,
- userCount: data?.userCount ?? 0,
demo: data?.demo ?? false,
isLoading,
}
diff --git a/src/hooks/useSubscriptionModal.ts b/src/hooks/useSubscriptionModal.ts
deleted file mode 100644
index 406227f..0000000
--- a/src/hooks/useSubscriptionModal.ts
+++ /dev/null
@@ -1,15 +0,0 @@
-import { createContext, useContext } from 'react'
-
-export interface SubscriptionModalContextValue {
- open: (reason?: string) => void
-}
-
-export const SubscriptionModalContext = createContext(null)
-
-export function useSubscriptionModal(): SubscriptionModalContextValue {
- const context = useContext(SubscriptionModalContext)
- if (!context) {
- throw new Error('useSubscriptionModal must be used within SubscriptionModalProvider')
- }
- return context
-}
diff --git a/src/lib/auth-client.ts b/src/lib/auth-client.ts
index 6912f50..d2e9011 100644
--- a/src/lib/auth-client.ts
+++ b/src/lib/auth-client.ts
@@ -80,7 +80,6 @@ export async function signOut() {
export interface AuthProvider {
name: string
- requiredPlan?: string
}
export async function getProviders(): Promise {
diff --git a/src/lib/plan.ts b/src/lib/plan.ts
deleted file mode 100644
index a05b5f7..0000000
--- a/src/lib/plan.ts
+++ /dev/null
@@ -1,28 +0,0 @@
-export type PlanTier = 'FREE' | 'TEAM' | 'ENTERPRISE'
-
-const PLAN_ORDER: Record = {
- FREE: 0,
- TEAM: 1,
- ENTERPRISE: 2,
-}
-
-const FEATURE_PLAN = {
- GROUPS: 'TEAM',
- IAM: 'TEAM',
- SSO_GOOGLE: 'TEAM',
- SSO_KEYCLOAK: 'ENTERPRISE',
- SSO_OKTA: 'ENTERPRISE',
- BANNER: 'TEAM',
- BRANDING: 'ENTERPRISE',
- AUDIT_LOG: 'ENTERPRISE',
-} as const
-
-export type Feature = keyof typeof FEATURE_PLAN
-
-export function feature(name: Feature, plan: PlanTier): boolean {
- return PLAN_ORDER[plan] >= PLAN_ORDER[FEATURE_PLAN[name]]
-}
-
-export function requiredPlan(name: Feature): PlanTier {
- return FEATURE_PLAN[name]
-}
diff --git a/tests/iam.test.ts b/tests/iam.test.ts
index 20dc606..dc68ee4 100644
--- a/tests/iam.test.ts
+++ b/tests/iam.test.ts
@@ -1,25 +1,17 @@
import { describe, it, expect, beforeEach, vi } from 'vitest'
import { getUserPermissions, hasPermission, getAccessibleConnectionIds, getAgentPermissions } from '../server/lib/iam'
import * as config from '../server/lib/config'
-import { feature as featureCheck } from '../src/lib/plan'
// Mock config module
vi.mock('../server/lib/config', () => ({
getIAMRules: vi.fn(),
getGroupsForUser: vi.fn(),
isAuthEnabled: vi.fn(),
- getPlan: vi.fn(),
-}))
-
-vi.mock('../src/lib/plan', () => ({
- feature: vi.fn(),
}))
describe('getUserPermissions', () => {
beforeEach(() => {
vi.resetAllMocks()
- // Default: IAM feature enabled (existing tests assume this)
- vi.mocked(featureCheck).mockReturnValue(true)
})
it('returns all permissions when auth is disabled', () => {
@@ -31,11 +23,22 @@ describe('getUserPermissions', () => {
expect(perms).toEqual(new Set(['read', 'write', 'ddl', 'admin', 'explain', 'execute', 'export']))
})
- it('returns empty set when no rules match', () => {
+ it('returns all permissions when no IAM rules are defined (IAM off by default)', () => {
vi.mocked(config.isAuthEnabled).mockReturnValue(true)
vi.mocked(config.getIAMRules).mockReturnValue([])
vi.mocked(config.getGroupsForUser).mockReturnValue([])
+ const perms = getUserPermissions('alice', 'prod')
+ expect(perms).toEqual(new Set(['read', 'write', 'ddl', 'admin', 'explain', 'execute', 'export']))
+ })
+
+ it('returns empty set when rules are defined but none match', () => {
+ vi.mocked(config.isAuthEnabled).mockReturnValue(true)
+ vi.mocked(config.getIAMRules).mockReturnValue([
+ { connection: 'prod', permissions: ['read'], members: ['user:someone-else'] },
+ ])
+ vi.mocked(config.getGroupsForUser).mockReturnValue([])
+
const perms = getUserPermissions('alice', 'prod')
expect(perms).toEqual(new Set())
})
@@ -118,23 +121,11 @@ describe('getUserPermissions', () => {
const perms = getUserPermissions('alice', 'prod')
expect(perms).toEqual(new Set(['read', 'write']))
})
-
- it('returns all permissions when IAM feature is not enabled by plan', () => {
- vi.mocked(config.isAuthEnabled).mockReturnValue(true)
- vi.mocked(featureCheck).mockReturnValue(false)
- vi.mocked(config.getIAMRules).mockReturnValue([])
- vi.mocked(config.getGroupsForUser).mockReturnValue([])
-
- const perms = getUserPermissions('alice', 'prod')
- expect(perms).toEqual(new Set(['read', 'write', 'ddl', 'admin', 'explain', 'execute', 'export']))
- })
})
describe('hasPermission', () => {
beforeEach(() => {
vi.resetAllMocks()
- // Default: IAM feature enabled (existing tests assume this)
- vi.mocked(featureCheck).mockReturnValue(true)
})
it('returns true when user has the permission', () => {
@@ -153,7 +144,6 @@ describe('hasPermission', () => {
describe('getAgentPermissions', () => {
beforeEach(() => {
vi.resetAllMocks()
- vi.mocked(featureCheck).mockReturnValue(true)
vi.mocked(config.isAuthEnabled).mockReturnValue(true)
vi.mocked(config.getGroupsForUser).mockReturnValue([])
})
@@ -211,8 +201,6 @@ describe('getAgentPermissions', () => {
describe('getAccessibleConnectionIds', () => {
beforeEach(() => {
vi.resetAllMocks()
- // Default: IAM feature enabled (existing tests assume this)
- vi.mocked(featureCheck).mockReturnValue(true)
})
it('returns all connections when auth is disabled', () => {
@@ -236,9 +224,11 @@ describe('getAccessibleConnectionIds', () => {
expect(result).toEqual(['conn1', 'conn3'])
})
- it('returns empty array when no permissions', () => {
+ it('returns empty array when rules are defined but none match', () => {
vi.mocked(config.isAuthEnabled).mockReturnValue(true)
- vi.mocked(config.getIAMRules).mockReturnValue([])
+ vi.mocked(config.getIAMRules).mockReturnValue([
+ { connection: 'conn1', permissions: ['read'], members: ['user:someone-else'] },
+ ])
vi.mocked(config.getGroupsForUser).mockReturnValue([])
const result = getAccessibleConnectionIds('alice', ['conn1', 'conn2'])
diff --git a/tests/license.test.ts b/tests/license.test.ts
deleted file mode 100644
index 3df4852..0000000
--- a/tests/license.test.ts
+++ /dev/null
@@ -1,121 +0,0 @@
-import { describe, it, expect, beforeAll } from 'vitest'
-import { SignJWT, exportSPKI, generateKeyPair } from 'jose'
-import { checkLicense, setPublicKeyForTesting } from '../server/lib/license'
-
-let privateKey: CryptoKey
-let publicKey: CryptoKey
-
-async function mintJWT(claims: Record, opts?: { key?: CryptoKey; expiresIn?: string }) {
- const key = opts?.key ?? privateKey
- let builder = new SignJWT(claims)
- .setProtectedHeader({ alg: 'RS256' })
- .setIssuer('pgconsole/license')
- .setSubject('lic_test-uuid')
- if (opts?.expiresIn) {
- builder = builder.setExpirationTime(opts.expiresIn)
- } else {
- builder = builder.setExpirationTime('1h')
- }
- return builder.sign(key)
-}
-
-describe('checkLicense', () => {
- beforeAll(async () => {
- const pair = await generateKeyPair('RS256')
- privateKey = pair.privateKey
- publicKey = pair.publicKey
- const pem = await exportSPKI(publicKey)
- setPublicKeyForTesting(pem)
- })
-
- it('returns TEAM for valid TEAM license', async () => {
- const jwt = await mintJWT({ plan: 'team', email: 'a@b.com' })
- const result = await checkLicense(jwt)
- expect(result.plan).toBe('TEAM')
- expect(result.expiry).toBeTypeOf('number')
- })
-
- it('returns ENTERPRISE for valid ENTERPRISE license', async () => {
- const jwt = await mintJWT({ plan: 'enterprise', email: 'a@b.com' })
- expect((await checkLicense(jwt)).plan).toBe('ENTERPRISE')
- })
-
- it('returns FREE for expired license', async () => {
- const jwt = await mintJWT({ plan: 'team', email: 'a@b.com' }, { expiresIn: '-1s' })
- expect((await checkLicense(jwt)).plan).toBe('FREE')
- })
-
- it('returns FREE for invalid signature', async () => {
- const wrongPair = await generateKeyPair('RS256')
- const jwt = await mintJWT({ plan: 'team', email: 'a@b.com' }, { key: wrongPair.privateKey })
- expect((await checkLicense(jwt)).plan).toBe('FREE')
- })
-
- it('returns FREE for unknown plan value', async () => {
- const jwt = await mintJWT({ plan: 'BOGUS', email: 'a@b.com' })
- expect((await checkLicense(jwt)).plan).toBe('FREE')
- })
-
- it('returns FREE for missing plan claim', async () => {
- const jwt = await mintJWT({ email: 'a@b.com' })
- expect((await checkLicense(jwt)).plan).toBe('FREE')
- })
-
- it('returns FREE for garbage string', async () => {
- expect((await checkLicense('not-a-jwt')).plan).toBe('FREE')
- })
-
- it('returns FREE for empty string', async () => {
- expect((await checkLicense('')).plan).toBe('FREE')
- })
-
- // maxUsers tests
- it('returns maxUsers from user claim', async () => {
- const jwt = await mintJWT({ plan: 'team', userSeat: 5 })
- const result = await checkLicense(jwt)
- expect(result.maxUsers).toBe(5)
- })
-
- it('returns maxUsers=1 when user claim is missing', async () => {
- const jwt = await mintJWT({ plan: 'team' })
- const result = await checkLicense(jwt)
- expect(result.maxUsers).toBe(1)
- })
-
- it('returns maxUsers=1 for invalid license', async () => {
- const result = await checkLicense('invalid-jwt')
- expect(result.plan).toBe('FREE')
- expect(result.maxUsers).toBe(1)
- })
-
- it('returns maxUsers=1 when user claim is non-numeric string', async () => {
- const jwt = await mintJWT({ plan: 'team', userSeat: 'five' })
- const result = await checkLicense(jwt)
- expect(result.maxUsers).toBe(1)
- })
-
- it('returns maxUsers=0 when user claim is zero', async () => {
- const jwt = await mintJWT({ plan: 'team', userSeat: 0 })
- const result = await checkLicense(jwt)
- expect(result.maxUsers).toBe(0)
- })
-
- it('returns negative maxUsers when user claim is negative', async () => {
- const jwt = await mintJWT({ plan: 'team', userSeat: -1 })
- const result = await checkLicense(jwt)
- expect(result.maxUsers).toBe(-1)
- })
-
- // email tests
- it('returns email from email claim', async () => {
- const jwt = await mintJWT({ plan: 'team', email: 'customer@example.com' })
- const result = await checkLicense(jwt)
- expect(result.email).toBe('customer@example.com')
- })
-
- it('returns undefined email when email claim is missing', async () => {
- const jwt = await mintJWT({ plan: 'team' })
- const result = await checkLicense(jwt)
- expect(result.email).toBeUndefined()
- })
-})
diff --git a/tests/mcp.test.ts b/tests/mcp.test.ts
index 11504ae..85492b9 100644
--- a/tests/mcp.test.ts
+++ b/tests/mcp.test.ts
@@ -127,8 +127,8 @@ members = ["${member}"]
})
describe('Principal permission resolution', () => {
- // On the FREE plan IAM is not enforced, so getUserPermissions returns the full set —
- // which lets us verify that a delegated agent's caps actually narrow that base.
+ // With no [[iam]] rules defined, IAM is off and alice's base is full access — so the
+ // delegated agent's caps are what narrow it.
it('delegated caps narrow the user grant (permission cap)', async () => {
await loadConfigFromString(`${BASE}
[[agents]]
@@ -142,19 +142,6 @@ permissions = ["read", "explain"]
expect(p.auditActor).toBe('alice@example.com')
})
- it('delegated connection cap blocks other connections', async () => {
- await loadConfigFromString(`${BASE}
-[[agents]]
-id = "alice-claude"
-token = "t"
-on_behalf_of = "alice@example.com"
-connections = ["prod"]
-`)
- const p = new Principal(getAgentByToken('t')!)
- expect(p.permissions('prod').size).toBeGreaterThan(0)
- expect(p.permissions('staging')).toEqual(new Set())
- })
-
it('a pure agent audits as agent:', async () => {
await loadConfigFromString(`${BASE}
[[agents]]
@@ -167,8 +154,9 @@ token = "t"
describe('dispatchTool enforcement', () => {
// These exercise the permission/per-statement gating, which throws before any DB I/O.
- // On the FREE plan a pure agent has all permissions, so rejections come purely from the
- // tool's statement-kind rule; a delegated read-only agent exercises the cap-based denials.
+ // With no [[iam]] rules defined, IAM is off so the pure agent and alice have full access;
+ // rejections come purely from the tool's statement-kind rule, and a delegated read-only
+ // agent exercises the cap-based denials.
const AGENTS = `
[[agents]]
id = "pure"
diff --git a/tests/plan.test.ts b/tests/plan.test.ts
deleted file mode 100644
index 55e22cc..0000000
--- a/tests/plan.test.ts
+++ /dev/null
@@ -1,50 +0,0 @@
-import { describe, it, expect } from 'vitest'
-import { feature, requiredPlan } from '../src/lib/plan'
-
-describe('feature', () => {
- it('FREE plan has no gated features', () => {
- expect(feature('GROUPS', 'FREE')).toBe(false)
- expect(feature('IAM', 'FREE')).toBe(false)
- expect(feature('SSO_GOOGLE', 'FREE')).toBe(false)
- expect(feature('SSO_KEYCLOAK', 'FREE')).toBe(false)
- expect(feature('SSO_OKTA', 'FREE')).toBe(false)
- expect(feature('BANNER', 'FREE')).toBe(false)
- expect(feature('AUDIT_LOG', 'FREE')).toBe(false)
- })
-
- it('TEAM plan includes TEAM features', () => {
- expect(feature('GROUPS', 'TEAM')).toBe(true)
- expect(feature('IAM', 'TEAM')).toBe(true)
- expect(feature('SSO_GOOGLE', 'TEAM')).toBe(true)
- expect(feature('BANNER', 'TEAM')).toBe(true)
- })
-
- it('TEAM plan excludes ENTERPRISE features', () => {
- expect(feature('SSO_KEYCLOAK', 'TEAM')).toBe(false)
- expect(feature('SSO_OKTA', 'TEAM')).toBe(false)
- expect(feature('AUDIT_LOG', 'TEAM')).toBe(false)
- })
-
- it('ENTERPRISE plan includes all features', () => {
- expect(feature('GROUPS', 'ENTERPRISE')).toBe(true)
- expect(feature('IAM', 'ENTERPRISE')).toBe(true)
- expect(feature('SSO_GOOGLE', 'ENTERPRISE')).toBe(true)
- expect(feature('SSO_KEYCLOAK', 'ENTERPRISE')).toBe(true)
- expect(feature('SSO_OKTA', 'ENTERPRISE')).toBe(true)
- expect(feature('BANNER', 'ENTERPRISE')).toBe(true)
- expect(feature('AUDIT_LOG', 'ENTERPRISE')).toBe(true)
- })
-})
-
-describe('requiredPlan', () => {
- it('returns minimum plan for each feature', () => {
- expect(requiredPlan('GROUPS')).toBe('TEAM')
- expect(requiredPlan('IAM')).toBe('TEAM')
- expect(requiredPlan('SSO_GOOGLE')).toBe('TEAM')
- expect(requiredPlan('SSO_KEYCLOAK')).toBe('ENTERPRISE')
- expect(requiredPlan('SSO_OKTA')).toBe('ENTERPRISE')
- expect(requiredPlan('BANNER')).toBe('TEAM')
- expect(requiredPlan('AUDIT_LOG')).toBe('ENTERPRISE')
- })
-})
-
diff --git a/website/src/app/pricing/page.tsx b/website/src/app/pricing/page.tsx
deleted file mode 100644
index c145eea..0000000
--- a/website/src/app/pricing/page.tsx
+++ /dev/null
@@ -1,115 +0,0 @@
-import { SoftButtonLink } from '@/components/elements/button'
-import { Main } from '@/components/elements/main'
-import { LINKS } from '@/lib/links'
-import { FAQsTwoColumnAccordion, Faq } from '@/components/sections/faqs-two-column-accordion'
-import { Plan, PricingHeroMultiTier } from '@/components/sections/pricing-hero-multi-tier'
-import { CTA } from '@/components/shared/cta'
-import { Footer } from '@/components/shared/footer'
-import { Navbar } from '@/components/shared/navbar'
-
-function plans(option: string) {
- return (
- <>
- For individuals and small projects getting started
}
- features={['1 user', 'Unlimited connections', 'SQL editor and intellisense', 'Inline data editing', 'Schema inspection', 'AI assistant']}
- cta={
-
- Get started
-
- }
- />
- For teams who need collaboration}
- features={[
- 'Everything in Personal',
- 'Unlimited users',
- 'User groups',
- 'Database access control',
- 'Google SSO',
- 'Custom banner',
- ]}
- cta={
-
- Get started
-
- }
- />
- For organizations that need security and governance}
- features={[
- 'Everything in Team',
- 'Enterprise SSO (Okta, Keycloak, etc.)',
- 'Audit logging',
- 'OEM / White-labeling',
- 'SOC 2 report',
- 'Dedicated support channel',
- ]}
- cta={
-
- Contact us
-
- }
- />
- >
- )
-}
-
-export default function PricingPage() {
- return (
- <>
-
-
-
- {/* Pricing */}
-
- Start in minutes and scale as you grow.
-
- }
- options={['Monthly', 'Yearly']}
- plans={{ Monthly: plans('Monthly'), Yearly: plans('Yearly') }}
- />
-
- {/* FAQs */}
-
-
-
-
-
-
-
- {/* Call To Action */}
-
-
-
-
- >
- )
-}
diff --git a/website/src/app/sitemap.ts b/website/src/app/sitemap.ts
index af7f19c..824d357 100644
--- a/website/src/app/sitemap.ts
+++ b/website/src/app/sitemap.ts
@@ -8,7 +8,6 @@ export default function sitemap(): MetadataRoute.Sitemap {
return [
{ url: BASE_URL, lastModified: new Date() },
- { url: `${BASE_URL}/pricing`, lastModified: new Date() },
{ url: `${BASE_URL}/blog`, lastModified: posts[0]?.date ?? new Date() },
...posts.map((post) => ({
url: `${BASE_URL}/blog/${post.slug}`,
diff --git a/website/src/components/sections/plan-comparison-table.tsx b/website/src/components/sections/plan-comparison-table.tsx
deleted file mode 100644
index ed30668..0000000
--- a/website/src/components/sections/plan-comparison-table.tsx
+++ /dev/null
@@ -1,137 +0,0 @@
-import { ElTabGroup, ElTabList, ElTabPanels } from '@tailwindplus/elements/react'
-import { clsx } from 'clsx/lite'
-import { type ComponentProps, type ReactNode } from 'react'
-import { Container } from '../elements/container'
-import { CheckmarkIcon } from '../icons/checkmark-icon'
-import { MinusIcon } from '../icons/minus-icon'
-
-function FeatureGroup({
- group,
- plans,
-}: {
- group: {
- title: ReactNode
- features: { name: ReactNode; value: ReactNode | Record }[]
- }
- plans: Plan[]
-}) {
- return (
-
-
-
- {group.title}
-
-
- {group.features.map((feature) => (
-
-
- {feature.name}
-
- {plans.map((plan) => {
- const value = ((value: any): value is Record =>
- typeof value === 'object' && value !== null && plan in value)(feature.value)
- ? feature.value[plan]
- : feature.value
-
- return (
-
- {value === true ? (
-
- ) : value === false ? (
-
- ) : (
- value
- )}
-
- )
- })}
-
- ))}
-
- )
-}
-
-export function PlanComparisonTable({
- plans,
- features,
- className,
- ...props
-}: {
- plans: Plan[]
- features: {
- title: ReactNode
- features: { name: ReactNode; value: ReactNode | Record }[]
- }[]
-} & ComponentProps<'section'>) {
- return (
-
-
-
-
-
- {plans.map((plan) => (
-
- ))}
-
-
-
-
- Compare features
-
- {plans.map((plan, index) => (
-
- {plan}
-
- ))}
-
-
- {features.map((group, index) => (
-
- ))}
-
-
-
-
-
- {plans.map((plan) => (
-
- {plan}
-
- ))}
-
-
- {plans.map((plan) => (
-
-
-
-
-
- {features.map((group, index) => (
-
- ))}
-
- ))}
-
-
-
-
-
- )
-}
diff --git a/website/src/components/sections/pricing-hero-multi-tier.tsx b/website/src/components/sections/pricing-hero-multi-tier.tsx
deleted file mode 100644
index 72848ad..0000000
--- a/website/src/components/sections/pricing-hero-multi-tier.tsx
+++ /dev/null
@@ -1,117 +0,0 @@
-import { ElTabGroup, ElTabList, ElTabPanels } from '@tailwindplus/elements/react'
-import { clsx } from 'clsx/lite'
-import type { ComponentProps, ReactNode } from 'react'
-import { Container } from '../elements/container'
-import { Heading } from '../elements/heading'
-import { Text } from '../elements/text'
-import { CheckmarkIcon } from '../icons/checkmark-icon'
-
-export function Plan({
- name,
- price,
- period,
- subheadline,
- badge,
- features,
- cta,
- className,
-}: {
- name: ReactNode
- price: ReactNode
- period?: ReactNode
- subheadline: ReactNode
- badge?: ReactNode
- features: ReactNode[]
- cta: ReactNode
-} & ComponentProps<'div'>) {
- return (
-
-
-
- {badge && (
-
- {badge}
-
- )}
-
-
{name}
-
-
- {price}
- {period && {period} }
-
-
{subheadline}
-
- {features.map((feature, index) => (
-
-
- {feature}
-
- ))}
-
-
- {cta}
-
- )
-}
-
-export function PricingHeroMultiTier({
- eyebrow,
- headline,
- subheadline,
- options,
- plans,
- footer,
- className,
- ...props
-}: {
- eyebrow?: ReactNode
- headline: ReactNode
- subheadline: ReactNode
- options: readonly T[]
- plans: Record
- footer?: ReactNode
-} & ComponentProps<'section'>) {
- return (
-
-
-
-
- {eyebrow}
- {headline}
-
- {subheadline}
-
-
- {options.map((option) => (
-
- {option}
-
- ))}
-
-
-
- {options.map((option) => (
-
- {plans[option]}
-
- ))}
-
- {footer}
-
-
-
- )
-}
diff --git a/website/src/components/sections/pricing-multi-tier.tsx b/website/src/components/sections/pricing-multi-tier.tsx
deleted file mode 100644
index 46d215f..0000000
--- a/website/src/components/sections/pricing-multi-tier.tsx
+++ /dev/null
@@ -1,73 +0,0 @@
-import { clsx } from 'clsx/lite'
-import type { ComponentProps, ReactNode } from 'react'
-import { Section } from '../elements/section'
-import { CheckmarkIcon } from '../icons/checkmark-icon'
-
-export function Plan({
- name,
- price,
- period,
- subheadline,
- badge,
- features,
- cta,
- className,
-}: {
- name: ReactNode
- price: ReactNode
- period?: ReactNode
- subheadline: ReactNode
- badge?: ReactNode
- features: ReactNode[]
- cta: ReactNode
-} & ComponentProps<'div'>) {
- return (
-
-
-
- {badge && (
-
- {badge}
-
- )}
-
-
{name}
-
-
- {price}
- {period && {period} }
-
-
{subheadline}
-
- {features.map((feature, index) => (
-
-
- {feature}
-
- ))}
-
-
- {cta}
-
- )
-}
-
-export function PricingMultiTier({
- plans,
- ...props
-}: {
- plans: ReactNode
-} & ComponentProps) {
- return (
-
- )
-}
diff --git a/website/src/components/sections/pricing-single-tier-two-column.tsx b/website/src/components/sections/pricing-single-tier-two-column.tsx
deleted file mode 100644
index 088d39b..0000000
--- a/website/src/components/sections/pricing-single-tier-two-column.tsx
+++ /dev/null
@@ -1,58 +0,0 @@
-import { clsx } from 'clsx/lite'
-import type { ComponentProps, ReactNode } from 'react'
-import { Container } from '../elements/container'
-import { Subheading } from '../elements/subheading'
-import { Text } from '../elements/text'
-import { CheckmarkIcon } from '../icons/checkmark-icon'
-
-export function PricingSingleTierTwoColumn({
- headline,
- subheadline,
- price,
- period,
- features,
- cta,
- className,
- ...props
-}: {
- headline: ReactNode
- subheadline: ReactNode
- price: ReactNode
- period?: ReactNode
- features: ReactNode[]
- cta: ReactNode
-} & ComponentProps<'section'>) {
- return (
-
-
-
-
-
- {headline}
- {subheadline}
-
- {cta}
-
-
-
-
- {price}
-
-
{period}
-
-
- {features.map((feature, index) => (
-
-
-
-
- {feature}
-
- ))}
-
-
-
-
-
- )
-}
diff --git a/website/src/components/shared/navbar.tsx b/website/src/components/shared/navbar.tsx
index b2b8a79..a6f7b8f 100644
--- a/website/src/components/shared/navbar.tsx
+++ b/website/src/components/shared/navbar.tsx
@@ -19,7 +19,6 @@ export function Navbar() {
links={
<>
Docs
- Pricing
Blog
Live demo 👈
diff --git a/worker/stripe-webhook/package-lock.json b/worker/stripe-webhook/package-lock.json
deleted file mode 100644
index c3825d5..0000000
--- a/worker/stripe-webhook/package-lock.json
+++ /dev/null
@@ -1,1875 +0,0 @@
-{
- "name": "stripe-webhook-worker",
- "lockfileVersion": 3,
- "requires": true,
- "packages": {
- "": {
- "name": "stripe-webhook-worker",
- "dependencies": {
- "stripe": "^17"
- },
- "devDependencies": {
- "wrangler": "^3"
- }
- },
- "node_modules/@cloudflare/kv-asset-handler": {
- "version": "0.3.4",
- "resolved": "https://registry.npmjs.org/@cloudflare/kv-asset-handler/-/kv-asset-handler-0.3.4.tgz",
- "integrity": "sha512-YLPHc8yASwjNkmcDMQMY35yiWjoKAKnhUbPRszBRS0YgH+IXtsMp61j+yTcnCE3oO2DgP0U3iejLC8FTtKDC8Q==",
- "dev": true,
- "license": "MIT OR Apache-2.0",
- "dependencies": {
- "mime": "^3.0.0"
- },
- "engines": {
- "node": ">=16.13"
- }
- },
- "node_modules/@cloudflare/unenv-preset": {
- "version": "2.0.2",
- "resolved": "https://registry.npmjs.org/@cloudflare/unenv-preset/-/unenv-preset-2.0.2.tgz",
- "integrity": "sha512-nyzYnlZjjV5xT3LizahG1Iu6mnrCaxglJ04rZLpDwlDVDZ7v46lNsfxhV3A/xtfgQuSHmLnc6SVI+KwBpc3Lwg==",
- "dev": true,
- "license": "MIT OR Apache-2.0",
- "peerDependencies": {
- "unenv": "2.0.0-rc.14",
- "workerd": "^1.20250124.0"
- },
- "peerDependenciesMeta": {
- "workerd": {
- "optional": true
- }
- }
- },
- "node_modules/@cloudflare/workerd-darwin-64": {
- "version": "1.20250718.0",
- "resolved": "https://registry.npmjs.org/@cloudflare/workerd-darwin-64/-/workerd-darwin-64-1.20250718.0.tgz",
- "integrity": "sha512-FHf4t7zbVN8yyXgQ/r/GqLPaYZSGUVzeR7RnL28Mwj2djyw2ZergvytVc7fdGcczl6PQh+VKGfZCfUqpJlbi9g==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "license": "Apache-2.0",
- "optional": true,
- "os": [
- "darwin"
- ],
- "engines": {
- "node": ">=16"
- }
- },
- "node_modules/@cloudflare/workerd-darwin-arm64": {
- "version": "1.20250718.0",
- "resolved": "https://registry.npmjs.org/@cloudflare/workerd-darwin-arm64/-/workerd-darwin-arm64-1.20250718.0.tgz",
- "integrity": "sha512-fUiyUJYyqqp4NqJ0YgGtp4WJh/II/YZsUnEb6vVy5Oeas8lUOxnN+ZOJ8N/6/5LQCVAtYCChRiIrBbfhTn5Z8Q==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "license": "Apache-2.0",
- "optional": true,
- "os": [
- "darwin"
- ],
- "engines": {
- "node": ">=16"
- }
- },
- "node_modules/@cloudflare/workerd-linux-64": {
- "version": "1.20250718.0",
- "resolved": "https://registry.npmjs.org/@cloudflare/workerd-linux-64/-/workerd-linux-64-1.20250718.0.tgz",
- "integrity": "sha512-5+eb3rtJMiEwp08Kryqzzu8d1rUcK+gdE442auo5eniMpT170Dz0QxBrqkg2Z48SFUPYbj+6uknuA5tzdRSUSg==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "license": "Apache-2.0",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=16"
- }
- },
- "node_modules/@cloudflare/workerd-linux-arm64": {
- "version": "1.20250718.0",
- "resolved": "https://registry.npmjs.org/@cloudflare/workerd-linux-arm64/-/workerd-linux-arm64-1.20250718.0.tgz",
- "integrity": "sha512-Aa2M/DVBEBQDdATMbn217zCSFKE+ud/teS+fFS+OQqKABLn0azO2qq6ANAHYOIE6Q3Sq4CxDIQr8lGdaJHwUog==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "license": "Apache-2.0",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=16"
- }
- },
- "node_modules/@cloudflare/workerd-windows-64": {
- "version": "1.20250718.0",
- "resolved": "https://registry.npmjs.org/@cloudflare/workerd-windows-64/-/workerd-windows-64-1.20250718.0.tgz",
- "integrity": "sha512-dY16RXKffmugnc67LTbyjdDHZn5NoTF1yHEf2fN4+OaOnoGSp3N1x77QubTDwqZ9zECWxgQfDLjddcH8dWeFhg==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "license": "Apache-2.0",
- "optional": true,
- "os": [
- "win32"
- ],
- "engines": {
- "node": ">=16"
- }
- },
- "node_modules/@cspotcode/source-map-support": {
- "version": "0.8.1",
- "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz",
- "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@jridgewell/trace-mapping": "0.3.9"
- },
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/@emnapi/runtime": {
- "version": "1.8.1",
- "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.8.1.tgz",
- "integrity": "sha512-mehfKSMWjjNol8659Z8KxEMrdSJDDot5SXMq00dM8BN4o+CLNXQ0xH2V7EchNHV4RmbZLmmPdEaXZc5H2FXmDg==",
- "dev": true,
- "license": "MIT",
- "optional": true,
- "dependencies": {
- "tslib": "^2.4.0"
- }
- },
- "node_modules/@esbuild-plugins/node-globals-polyfill": {
- "version": "0.2.3",
- "resolved": "https://registry.npmjs.org/@esbuild-plugins/node-globals-polyfill/-/node-globals-polyfill-0.2.3.tgz",
- "integrity": "sha512-r3MIryXDeXDOZh7ih1l/yE9ZLORCd5e8vWg02azWRGj5SPTuoh69A2AIyn0Z31V/kHBfZ4HgWJ+OK3GTTwLmnw==",
- "dev": true,
- "license": "ISC",
- "peerDependencies": {
- "esbuild": "*"
- }
- },
- "node_modules/@esbuild-plugins/node-modules-polyfill": {
- "version": "0.2.2",
- "resolved": "https://registry.npmjs.org/@esbuild-plugins/node-modules-polyfill/-/node-modules-polyfill-0.2.2.tgz",
- "integrity": "sha512-LXV7QsWJxRuMYvKbiznh+U1ilIop3g2TeKRzUxOG5X3YITc8JyyTa90BmLwqqv0YnX4v32CSlG+vsziZp9dMvA==",
- "dev": true,
- "license": "ISC",
- "dependencies": {
- "escape-string-regexp": "^4.0.0",
- "rollup-plugin-node-polyfills": "^0.2.1"
- },
- "peerDependencies": {
- "esbuild": "*"
- }
- },
- "node_modules/@esbuild/android-arm": {
- "version": "0.17.19",
- "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.17.19.tgz",
- "integrity": "sha512-rIKddzqhmav7MSmoFCmDIb6e2W57geRsM94gV2l38fzhXMwq7hZoClug9USI2pFRGL06f4IOPHHpFNOkWieR8A==",
- "cpu": [
- "arm"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "android"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/@esbuild/android-arm64": {
- "version": "0.17.19",
- "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.17.19.tgz",
- "integrity": "sha512-KBMWvEZooR7+kzY0BtbTQn0OAYY7CsiydT63pVEaPtVYF0hXbUaOyZog37DKxK7NF3XacBJOpYT4adIJh+avxA==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "android"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/@esbuild/android-x64": {
- "version": "0.17.19",
- "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.17.19.tgz",
- "integrity": "sha512-uUTTc4xGNDT7YSArp/zbtmbhO0uEEK9/ETW29Wk1thYUJBz3IVnvgEiEwEa9IeLyvnpKrWK64Utw2bgUmDveww==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "android"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/@esbuild/darwin-arm64": {
- "version": "0.17.19",
- "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.17.19.tgz",
- "integrity": "sha512-80wEoCfF/hFKM6WE1FyBHc9SfUblloAWx6FJkFWTWiCoht9Mc0ARGEM47e67W9rI09YoUxJL68WHfDRYEAvOhg==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "darwin"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/@esbuild/darwin-x64": {
- "version": "0.17.19",
- "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.17.19.tgz",
- "integrity": "sha512-IJM4JJsLhRYr9xdtLytPLSH9k/oxR3boaUIYiHkAawtwNOXKE8KoU8tMvryogdcT8AU+Bflmh81Xn6Q0vTZbQw==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "darwin"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/@esbuild/freebsd-arm64": {
- "version": "0.17.19",
- "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.17.19.tgz",
- "integrity": "sha512-pBwbc7DufluUeGdjSU5Si+P3SoMF5DQ/F/UmTSb8HXO80ZEAJmrykPyzo1IfNbAoaqw48YRpv8shwd1NoI0jcQ==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "freebsd"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/@esbuild/freebsd-x64": {
- "version": "0.17.19",
- "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.17.19.tgz",
- "integrity": "sha512-4lu+n8Wk0XlajEhbEffdy2xy53dpR06SlzvhGByyg36qJw6Kpfk7cp45DR/62aPH9mtJRmIyrXAS5UWBrJT6TQ==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "freebsd"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/@esbuild/linux-arm": {
- "version": "0.17.19",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.17.19.tgz",
- "integrity": "sha512-cdmT3KxjlOQ/gZ2cjfrQOtmhG4HJs6hhvm3mWSRDPtZ/lP5oe8FWceS10JaSJC13GBd4eH/haHnqf7hhGNLerA==",
- "cpu": [
- "arm"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/@esbuild/linux-arm64": {
- "version": "0.17.19",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.17.19.tgz",
- "integrity": "sha512-ct1Tg3WGwd3P+oZYqic+YZF4snNl2bsnMKRkb3ozHmnM0dGWuxcPTTntAF6bOP0Sp4x0PjSF+4uHQ1xvxfRKqg==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/@esbuild/linux-ia32": {
- "version": "0.17.19",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.17.19.tgz",
- "integrity": "sha512-w4IRhSy1VbsNxHRQpeGCHEmibqdTUx61Vc38APcsRbuVgK0OPEnQ0YD39Brymn96mOx48Y2laBQGqgZ0j9w6SQ==",
- "cpu": [
- "ia32"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/@esbuild/linux-loong64": {
- "version": "0.17.19",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.17.19.tgz",
- "integrity": "sha512-2iAngUbBPMq439a+z//gE+9WBldoMp1s5GWsUSgqHLzLJ9WoZLZhpwWuym0u0u/4XmZ3gpHmzV84PonE+9IIdQ==",
- "cpu": [
- "loong64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/@esbuild/linux-mips64el": {
- "version": "0.17.19",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.17.19.tgz",
- "integrity": "sha512-LKJltc4LVdMKHsrFe4MGNPp0hqDFA1Wpt3jE1gEyM3nKUvOiO//9PheZZHfYRfYl6AwdTH4aTcXSqBerX0ml4A==",
- "cpu": [
- "mips64el"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/@esbuild/linux-ppc64": {
- "version": "0.17.19",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.17.19.tgz",
- "integrity": "sha512-/c/DGybs95WXNS8y3Ti/ytqETiW7EU44MEKuCAcpPto3YjQbyK3IQVKfF6nbghD7EcLUGl0NbiL5Rt5DMhn5tg==",
- "cpu": [
- "ppc64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/@esbuild/linux-riscv64": {
- "version": "0.17.19",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.17.19.tgz",
- "integrity": "sha512-FC3nUAWhvFoutlhAkgHf8f5HwFWUL6bYdvLc/TTuxKlvLi3+pPzdZiFKSWz/PF30TB1K19SuCxDTI5KcqASJqA==",
- "cpu": [
- "riscv64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/@esbuild/linux-s390x": {
- "version": "0.17.19",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.17.19.tgz",
- "integrity": "sha512-IbFsFbxMWLuKEbH+7sTkKzL6NJmG2vRyy6K7JJo55w+8xDk7RElYn6xvXtDW8HCfoKBFK69f3pgBJSUSQPr+4Q==",
- "cpu": [
- "s390x"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/@esbuild/linux-x64": {
- "version": "0.17.19",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.17.19.tgz",
- "integrity": "sha512-68ngA9lg2H6zkZcyp22tsVt38mlhWde8l3eJLWkyLrp4HwMUr3c1s/M2t7+kHIhvMjglIBrFpncX1SzMckomGw==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/@esbuild/netbsd-x64": {
- "version": "0.17.19",
- "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.17.19.tgz",
- "integrity": "sha512-CwFq42rXCR8TYIjIfpXCbRX0rp1jo6cPIUPSaWwzbVI4aOfX96OXY8M6KNmtPcg7QjYeDmN+DD0Wp3LaBOLf4Q==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "netbsd"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/@esbuild/openbsd-x64": {
- "version": "0.17.19",
- "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.17.19.tgz",
- "integrity": "sha512-cnq5brJYrSZ2CF6c35eCmviIN3k3RczmHz8eYaVlNasVqsNY+JKohZU5MKmaOI+KkllCdzOKKdPs762VCPC20g==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "openbsd"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/@esbuild/sunos-x64": {
- "version": "0.17.19",
- "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.17.19.tgz",
- "integrity": "sha512-vCRT7yP3zX+bKWFeP/zdS6SqdWB8OIpaRq/mbXQxTGHnIxspRtigpkUcDMlSCOejlHowLqII7K2JKevwyRP2rg==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "sunos"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/@esbuild/win32-arm64": {
- "version": "0.17.19",
- "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.17.19.tgz",
- "integrity": "sha512-yYx+8jwowUstVdorcMdNlzklLYhPxjniHWFKgRqH7IFlUEa0Umu3KuYplf1HUZZ422e3NU9F4LGb+4O0Kdcaag==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "win32"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/@esbuild/win32-ia32": {
- "version": "0.17.19",
- "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.17.19.tgz",
- "integrity": "sha512-eggDKanJszUtCdlVs0RB+h35wNlb5v4TWEkq4vZcmVt5u/HiDZrTXe2bWFQUez3RgNHwx/x4sk5++4NSSicKkw==",
- "cpu": [
- "ia32"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "win32"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/@esbuild/win32-x64": {
- "version": "0.17.19",
- "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.17.19.tgz",
- "integrity": "sha512-lAhycmKnVOuRYNtRtatQR1LPQf2oYCkRGkSFnseDAKPl8lu5SOsK/e1sXe5a0Pc5kHIHe6P2I/ilntNv2xf3cA==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "win32"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/@fastify/busboy": {
- "version": "2.1.1",
- "resolved": "https://registry.npmjs.org/@fastify/busboy/-/busboy-2.1.1.tgz",
- "integrity": "sha512-vBZP4NlzfOlerQTnba4aqZoMhE/a9HY7HRqoOPaETQcSQuWEIyZMHGfVu6w9wGtGK5fED5qRs2DteVCjOH60sA==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=14"
- }
- },
- "node_modules/@img/sharp-darwin-arm64": {
- "version": "0.33.5",
- "resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.33.5.tgz",
- "integrity": "sha512-UT4p+iz/2H4twwAoLCqfA9UH5pI6DggwKEGuaPy7nCVQ8ZsiY5PIcrRvD1DzuY3qYL07NtIQcWnBSY/heikIFQ==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "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.0.4"
- }
- },
- "node_modules/@img/sharp-darwin-x64": {
- "version": "0.33.5",
- "resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.33.5.tgz",
- "integrity": "sha512-fyHac4jIc1ANYGRDxtiqelIbdWkIuQaI84Mv45KvGRRxSAa7o7d1ZKAOBaYbnepLC1WqxfpimdeWfvqqSGwR2Q==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "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.0.4"
- }
- },
- "node_modules/@img/sharp-libvips-darwin-arm64": {
- "version": "1.0.4",
- "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.0.4.tgz",
- "integrity": "sha512-XblONe153h0O2zuFfTAbQYAX2JhYmDHeWikp1LM9Hul9gVPjFY427k6dFEcOL72O01QxQsWi761svJ/ev9xEDg==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "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.0.4",
- "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.0.4.tgz",
- "integrity": "sha512-xnGR8YuZYfJGmWPvmlunFaWJsb9T/AO2ykoP3Fz/0X5XV2aoYBPkX6xqCQvUTKKiLddarLaxpzNe+b1hjeWHAQ==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "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.0.5",
- "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.0.5.tgz",
- "integrity": "sha512-gvcC4ACAOPRNATg/ov8/MnbxFDJqf/pDePbBnuBDcjsI8PssmjoKMAz4LtLaVi+OnSb5FK/yIOamqDwGmXW32g==",
- "cpu": [
- "arm"
- ],
- "dev": true,
- "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.0.4",
- "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.0.4.tgz",
- "integrity": "sha512-9B+taZ8DlyyqzZQnoeIvDVR/2F4EbMepXMc/NdVbkzsJbzkUjhXv/70GQJ7tdLA4YJgNP25zukcxpX2/SueNrA==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "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.0.4",
- "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.0.4.tgz",
- "integrity": "sha512-u7Wz6ntiSSgGSGcjZ55im6uvTrOxSIS8/dgoVMoiGE9I6JAfU50yH5BoDlYA1tcuGS7g/QNtetJnxA6QEsCVTA==",
- "cpu": [
- "s390x"
- ],
- "dev": true,
- "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.0.4",
- "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.0.4.tgz",
- "integrity": "sha512-MmWmQ3iPFZr0Iev+BAgVMb3ZyC4KeFc3jFxnNbEPas60e1cIfevbtuyf9nDGIzOaW9PdnDciJm+wFFaTlj5xYw==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "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.0.4",
- "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.0.4.tgz",
- "integrity": "sha512-9Ti+BbTYDcsbp4wfYib8Ctm1ilkugkA/uscUn6UXK1ldpC1JjiXbLfFZtRlBhjPZ5o1NCLiDbg8fhUPKStHoTA==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "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.0.4",
- "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.0.4.tgz",
- "integrity": "sha512-viYN1KX9m+/hGkJtvYYp+CCLgnJXwiQB39damAO7WMdKWlIhmYTfHjwSbQeUK/20vY154mwezd9HflVFM1wVSw==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "license": "LGPL-3.0-or-later",
- "optional": true,
- "os": [
- "linux"
- ],
- "funding": {
- "url": "https://opencollective.com/libvips"
- }
- },
- "node_modules/@img/sharp-linux-arm": {
- "version": "0.33.5",
- "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.33.5.tgz",
- "integrity": "sha512-JTS1eldqZbJxjvKaAkxhZmBqPRGmxgu+qFKSInv8moZ2AmT5Yib3EQ1c6gp493HvrvV8QgdOXdyaIBrhvFhBMQ==",
- "cpu": [
- "arm"
- ],
- "dev": true,
- "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.0.5"
- }
- },
- "node_modules/@img/sharp-linux-arm64": {
- "version": "0.33.5",
- "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.33.5.tgz",
- "integrity": "sha512-JMVv+AMRyGOHtO1RFBiJy/MBsgz0x4AWrT6QoEVVTyh1E39TrCUpTRI7mx9VksGX4awWASxqCYLCV4wBZHAYxA==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "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.0.4"
- }
- },
- "node_modules/@img/sharp-linux-s390x": {
- "version": "0.33.5",
- "resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.33.5.tgz",
- "integrity": "sha512-y/5PCd+mP4CA/sPDKl2961b+C9d+vPAveS33s6Z3zfASk2j5upL6fXVPZi7ztePZ5CuH+1kW8JtvxgbuXHRa4Q==",
- "cpu": [
- "s390x"
- ],
- "dev": true,
- "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.0.4"
- }
- },
- "node_modules/@img/sharp-linux-x64": {
- "version": "0.33.5",
- "resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.33.5.tgz",
- "integrity": "sha512-opC+Ok5pRNAzuvq1AG0ar+1owsu842/Ab+4qvU879ippJBHvyY5n2mxF1izXqkPYlGuP/M556uh53jRLJmzTWA==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "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.0.4"
- }
- },
- "node_modules/@img/sharp-linuxmusl-arm64": {
- "version": "0.33.5",
- "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.33.5.tgz",
- "integrity": "sha512-XrHMZwGQGvJg2V/oRSUfSAfjfPxO+4DkiRh6p2AFjLQztWUuY/o8Mq0eMQVIY7HJ1CDQUJlxGGZRw1a5bqmd1g==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "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.0.4"
- }
- },
- "node_modules/@img/sharp-linuxmusl-x64": {
- "version": "0.33.5",
- "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.33.5.tgz",
- "integrity": "sha512-WT+d/cgqKkkKySYmqoZ8y3pxx7lx9vVejxW/W4DOFMYVSkErR+w7mf2u8m/y4+xHe7yY9DAXQMWQhpnMuFfScw==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "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.0.4"
- }
- },
- "node_modules/@img/sharp-wasm32": {
- "version": "0.33.5",
- "resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.33.5.tgz",
- "integrity": "sha512-ykUW4LVGaMcU9lu9thv85CbRMAwfeadCJHRsg2GmeRa/cJxsVY9Rbd57JcMxBkKHag5U/x7TSBpScF4U8ElVzg==",
- "cpu": [
- "wasm32"
- ],
- "dev": true,
- "license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT",
- "optional": true,
- "dependencies": {
- "@emnapi/runtime": "^1.2.0"
- },
- "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.33.5",
- "resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.33.5.tgz",
- "integrity": "sha512-T36PblLaTwuVJ/zw/LaH0PdZkRz5rd3SmMHX8GSmR7vtNSP5Z6bQkExdSK7xGWyxLw4sUknBuugTelgw2faBbQ==",
- "cpu": [
- "ia32"
- ],
- "dev": true,
- "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.33.5",
- "resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.33.5.tgz",
- "integrity": "sha512-MpY/o8/8kj+EcnxwvrP4aTJSWw/aZ7JIGR4aBeZkZw5B7/Jn+tY9/VNwtcoGmdT7GfggGIU4kygOMSbYnOrAbg==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "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/resolve-uri": {
- "version": "3.1.2",
- "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
- "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=6.0.0"
- }
- },
- "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==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/@jridgewell/trace-mapping": {
- "version": "0.3.9",
- "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz",
- "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@jridgewell/resolve-uri": "^3.0.3",
- "@jridgewell/sourcemap-codec": "^1.4.10"
- }
- },
- "node_modules/@types/node": {
- "version": "25.2.3",
- "resolved": "https://registry.npmjs.org/@types/node/-/node-25.2.3.tgz",
- "integrity": "sha512-m0jEgYlYz+mDJZ2+F4v8D1AyQb+QzsNqRuI7xg1VQX/KlKS0qT9r1Mo16yo5F/MtifXFgaofIFsdFMox2SxIbQ==",
- "license": "MIT",
- "dependencies": {
- "undici-types": "~7.16.0"
- }
- },
- "node_modules/acorn": {
- "version": "8.14.0",
- "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.0.tgz",
- "integrity": "sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==",
- "dev": true,
- "license": "MIT",
- "bin": {
- "acorn": "bin/acorn"
- },
- "engines": {
- "node": ">=0.4.0"
- }
- },
- "node_modules/acorn-walk": {
- "version": "8.3.2",
- "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.2.tgz",
- "integrity": "sha512-cjkyv4OtNCIeqhHrfS81QWXoCBPExR/J62oyEqepVw8WaQeSqpW2uhuLPh1m9eWhDuOo/jUXVTlifvesOWp/4A==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=0.4.0"
- }
- },
- "node_modules/as-table": {
- "version": "1.0.55",
- "resolved": "https://registry.npmjs.org/as-table/-/as-table-1.0.55.tgz",
- "integrity": "sha512-xvsWESUJn0JN421Xb9MQw6AsMHRCUknCe0Wjlxvjud80mU4E6hQf1A6NzQKcYNmYw62MfzEtXc+badstZP3JpQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "printable-characters": "^1.0.42"
- }
- },
- "node_modules/blake3-wasm": {
- "version": "2.1.5",
- "resolved": "https://registry.npmjs.org/blake3-wasm/-/blake3-wasm-2.1.5.tgz",
- "integrity": "sha512-F1+K8EbfOZE49dtoPtmxUQrpXaBIl3ICvasLh+nJta0xkz+9kF/7uet9fLnwKqhDrmj6g+6K3Tw9yQPUg2ka5g==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/call-bind-apply-helpers": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
- "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==",
- "license": "MIT",
- "dependencies": {
- "es-errors": "^1.3.0",
- "function-bind": "^1.1.2"
- },
- "engines": {
- "node": ">= 0.4"
- }
- },
- "node_modules/call-bound": {
- "version": "1.0.4",
- "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz",
- "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==",
- "license": "MIT",
- "dependencies": {
- "call-bind-apply-helpers": "^1.0.2",
- "get-intrinsic": "^1.3.0"
- },
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/color": {
- "version": "4.2.3",
- "resolved": "https://registry.npmjs.org/color/-/color-4.2.3.tgz",
- "integrity": "sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==",
- "dev": true,
- "license": "MIT",
- "optional": true,
- "dependencies": {
- "color-convert": "^2.0.1",
- "color-string": "^1.9.0"
- },
- "engines": {
- "node": ">=12.5.0"
- }
- },
- "node_modules/color-convert": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
- "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
- "dev": true,
- "license": "MIT",
- "optional": true,
- "dependencies": {
- "color-name": "~1.1.4"
- },
- "engines": {
- "node": ">=7.0.0"
- }
- },
- "node_modules/color-name": {
- "version": "1.1.4",
- "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
- "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
- "dev": true,
- "license": "MIT",
- "optional": true
- },
- "node_modules/color-string": {
- "version": "1.9.1",
- "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.9.1.tgz",
- "integrity": "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==",
- "dev": true,
- "license": "MIT",
- "optional": true,
- "dependencies": {
- "color-name": "^1.0.0",
- "simple-swizzle": "^0.2.2"
- }
- },
- "node_modules/cookie": {
- "version": "0.7.2",
- "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz",
- "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">= 0.6"
- }
- },
- "node_modules/data-uri-to-buffer": {
- "version": "2.0.2",
- "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-2.0.2.tgz",
- "integrity": "sha512-ND9qDTLc6diwj+Xe5cdAgVTbLVdXbtxTJRXRhli8Mowuaan+0EJOtdqJ0QCHNSSPyoXGx9HX2/VMnKeC34AChA==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/defu": {
- "version": "6.1.4",
- "resolved": "https://registry.npmjs.org/defu/-/defu-6.1.4.tgz",
- "integrity": "sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==",
- "dev": true,
- "license": "MIT"
- },
- "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==",
- "dev": true,
- "license": "Apache-2.0",
- "optional": true,
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/dunder-proto": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
- "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==",
- "license": "MIT",
- "dependencies": {
- "call-bind-apply-helpers": "^1.0.1",
- "es-errors": "^1.3.0",
- "gopd": "^1.2.0"
- },
- "engines": {
- "node": ">= 0.4"
- }
- },
- "node_modules/es-define-property": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
- "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==",
- "license": "MIT",
- "engines": {
- "node": ">= 0.4"
- }
- },
- "node_modules/es-errors": {
- "version": "1.3.0",
- "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
- "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
- "license": "MIT",
- "engines": {
- "node": ">= 0.4"
- }
- },
- "node_modules/es-object-atoms": {
- "version": "1.1.1",
- "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz",
- "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==",
- "license": "MIT",
- "dependencies": {
- "es-errors": "^1.3.0"
- },
- "engines": {
- "node": ">= 0.4"
- }
- },
- "node_modules/esbuild": {
- "version": "0.17.19",
- "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.17.19.tgz",
- "integrity": "sha512-XQ0jAPFkK/u3LcVRcvVHQcTIqD6E2H1fvZMA5dQPSOWb3suUbWbfbRf94pjc0bNzRYLfIrDRQXr7X+LHIm5oHw==",
- "dev": true,
- "hasInstallScript": true,
- "license": "MIT",
- "bin": {
- "esbuild": "bin/esbuild"
- },
- "engines": {
- "node": ">=12"
- },
- "optionalDependencies": {
- "@esbuild/android-arm": "0.17.19",
- "@esbuild/android-arm64": "0.17.19",
- "@esbuild/android-x64": "0.17.19",
- "@esbuild/darwin-arm64": "0.17.19",
- "@esbuild/darwin-x64": "0.17.19",
- "@esbuild/freebsd-arm64": "0.17.19",
- "@esbuild/freebsd-x64": "0.17.19",
- "@esbuild/linux-arm": "0.17.19",
- "@esbuild/linux-arm64": "0.17.19",
- "@esbuild/linux-ia32": "0.17.19",
- "@esbuild/linux-loong64": "0.17.19",
- "@esbuild/linux-mips64el": "0.17.19",
- "@esbuild/linux-ppc64": "0.17.19",
- "@esbuild/linux-riscv64": "0.17.19",
- "@esbuild/linux-s390x": "0.17.19",
- "@esbuild/linux-x64": "0.17.19",
- "@esbuild/netbsd-x64": "0.17.19",
- "@esbuild/openbsd-x64": "0.17.19",
- "@esbuild/sunos-x64": "0.17.19",
- "@esbuild/win32-arm64": "0.17.19",
- "@esbuild/win32-ia32": "0.17.19",
- "@esbuild/win32-x64": "0.17.19"
- }
- },
- "node_modules/escape-string-regexp": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
- "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=10"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/estree-walker": {
- "version": "0.6.1",
- "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-0.6.1.tgz",
- "integrity": "sha512-SqmZANLWS0mnatqbSfRP5g8OXZC12Fgg1IwNtLsyHDzJizORW4khDfjPqJZsemPWBB2uqykUah5YpQ6epsqC/w==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/exit-hook": {
- "version": "2.2.1",
- "resolved": "https://registry.npmjs.org/exit-hook/-/exit-hook-2.2.1.tgz",
- "integrity": "sha512-eNTPlAD67BmP31LDINZ3U7HSF8l57TxOY2PmBJ1shpCvpnxBF93mWCE8YHBnXs8qiUZJc9WDcWIeC3a2HIAMfw==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=6"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/exsolve": {
- "version": "1.0.8",
- "resolved": "https://registry.npmjs.org/exsolve/-/exsolve-1.0.8.tgz",
- "integrity": "sha512-LmDxfWXwcTArk8fUEnOfSZpHOJ6zOMUJKOtFLFqJLoKJetuQG874Uc7/Kki7zFLzYybmZhp1M7+98pfMqeX8yA==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/fsevents": {
- "version": "2.3.3",
- "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
- "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
- "dev": true,
- "hasInstallScript": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "darwin"
- ],
- "engines": {
- "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
- }
- },
- "node_modules/function-bind": {
- "version": "1.1.2",
- "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
- "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
- "license": "MIT",
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/get-intrinsic": {
- "version": "1.3.0",
- "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
- "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==",
- "license": "MIT",
- "dependencies": {
- "call-bind-apply-helpers": "^1.0.2",
- "es-define-property": "^1.0.1",
- "es-errors": "^1.3.0",
- "es-object-atoms": "^1.1.1",
- "function-bind": "^1.1.2",
- "get-proto": "^1.0.1",
- "gopd": "^1.2.0",
- "has-symbols": "^1.1.0",
- "hasown": "^2.0.2",
- "math-intrinsics": "^1.1.0"
- },
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/get-proto": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz",
- "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==",
- "license": "MIT",
- "dependencies": {
- "dunder-proto": "^1.0.1",
- "es-object-atoms": "^1.0.0"
- },
- "engines": {
- "node": ">= 0.4"
- }
- },
- "node_modules/get-source": {
- "version": "2.0.12",
- "resolved": "https://registry.npmjs.org/get-source/-/get-source-2.0.12.tgz",
- "integrity": "sha512-X5+4+iD+HoSeEED+uwrQ07BOQr0kEDFMVqqpBuI+RaZBpBpHCuXxo70bjar6f0b0u/DQJsJ7ssurpP0V60Az+w==",
- "dev": true,
- "license": "Unlicense",
- "dependencies": {
- "data-uri-to-buffer": "^2.0.0",
- "source-map": "^0.6.1"
- }
- },
- "node_modules/glob-to-regexp": {
- "version": "0.4.1",
- "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz",
- "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==",
- "dev": true,
- "license": "BSD-2-Clause"
- },
- "node_modules/gopd": {
- "version": "1.2.0",
- "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
- "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==",
- "license": "MIT",
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/has-symbols": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
- "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==",
- "license": "MIT",
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/hasown": {
- "version": "2.0.2",
- "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
- "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
- "license": "MIT",
- "dependencies": {
- "function-bind": "^1.1.2"
- },
- "engines": {
- "node": ">= 0.4"
- }
- },
- "node_modules/is-arrayish": {
- "version": "0.3.4",
- "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.4.tgz",
- "integrity": "sha512-m6UrgzFVUYawGBh1dUsWR5M2Clqic9RVXC/9f8ceNlv2IcO9j9J/z8UoCLPqtsPBFNzEpfR3xftohbfqDx8EQA==",
- "dev": true,
- "license": "MIT",
- "optional": true
- },
- "node_modules/magic-string": {
- "version": "0.25.9",
- "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.25.9.tgz",
- "integrity": "sha512-RmF0AsMzgt25qzqqLc1+MbHmhdx0ojF2Fvs4XnOqz2ZOBXzzkEwc/dJQZCYHAn7v1jbVOjAZfK8msRn4BxO4VQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "sourcemap-codec": "^1.4.8"
- }
- },
- "node_modules/math-intrinsics": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
- "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==",
- "license": "MIT",
- "engines": {
- "node": ">= 0.4"
- }
- },
- "node_modules/mime": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/mime/-/mime-3.0.0.tgz",
- "integrity": "sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A==",
- "dev": true,
- "license": "MIT",
- "bin": {
- "mime": "cli.js"
- },
- "engines": {
- "node": ">=10.0.0"
- }
- },
- "node_modules/miniflare": {
- "version": "3.20250718.3",
- "resolved": "https://registry.npmjs.org/miniflare/-/miniflare-3.20250718.3.tgz",
- "integrity": "sha512-JuPrDJhwLrNLEJiNLWO7ZzJrv/Vv9kZuwMYCfv0LskQDM6Eonw4OvywO3CH/wCGjgHzha/qyjUh8JQ068TjDgQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@cspotcode/source-map-support": "0.8.1",
- "acorn": "8.14.0",
- "acorn-walk": "8.3.2",
- "exit-hook": "2.2.1",
- "glob-to-regexp": "0.4.1",
- "stoppable": "1.1.0",
- "undici": "^5.28.5",
- "workerd": "1.20250718.0",
- "ws": "8.18.0",
- "youch": "3.3.4",
- "zod": "3.22.3"
- },
- "bin": {
- "miniflare": "bootstrap.js"
- },
- "engines": {
- "node": ">=16.13"
- }
- },
- "node_modules/mustache": {
- "version": "4.2.0",
- "resolved": "https://registry.npmjs.org/mustache/-/mustache-4.2.0.tgz",
- "integrity": "sha512-71ippSywq5Yb7/tVYyGbkBggbU8H3u5Rz56fH60jGFgr8uHwxs+aSKeqmluIVzM0m0kB7xQjKS6qPfd0b2ZoqQ==",
- "dev": true,
- "license": "MIT",
- "bin": {
- "mustache": "bin/mustache"
- }
- },
- "node_modules/object-inspect": {
- "version": "1.13.4",
- "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz",
- "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==",
- "license": "MIT",
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/ohash": {
- "version": "2.0.11",
- "resolved": "https://registry.npmjs.org/ohash/-/ohash-2.0.11.tgz",
- "integrity": "sha512-RdR9FQrFwNBNXAr4GixM8YaRZRJ5PUWbKYbE5eOsrwAjJW0q2REGcf79oYPsLyskQCZG1PLN+S/K1V00joZAoQ==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/path-to-regexp": {
- "version": "6.3.0",
- "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-6.3.0.tgz",
- "integrity": "sha512-Yhpw4T9C6hPpgPeA28us07OJeqZ5EzQTkbfwuhsUg0c237RomFoETJgmp2sa3F/41gfLE6G5cqcYwznmeEeOlQ==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/pathe": {
- "version": "2.0.3",
- "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz",
- "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/printable-characters": {
- "version": "1.0.42",
- "resolved": "https://registry.npmjs.org/printable-characters/-/printable-characters-1.0.42.tgz",
- "integrity": "sha512-dKp+C4iXWK4vVYZmYSd0KBH5F/h1HoZRsbJ82AVKRO3PEo8L4lBS/vLwhVtpwwuYcoIsVY+1JYKR268yn480uQ==",
- "dev": true,
- "license": "Unlicense"
- },
- "node_modules/qs": {
- "version": "6.15.0",
- "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.0.tgz",
- "integrity": "sha512-mAZTtNCeetKMH+pSjrb76NAM8V9a05I9aBZOHztWy/UqcJdQYNsf59vrRKWnojAT9Y+GbIvoTBC++CPHqpDBhQ==",
- "license": "BSD-3-Clause",
- "dependencies": {
- "side-channel": "^1.1.0"
- },
- "engines": {
- "node": ">=0.6"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/rollup-plugin-inject": {
- "version": "3.0.2",
- "resolved": "https://registry.npmjs.org/rollup-plugin-inject/-/rollup-plugin-inject-3.0.2.tgz",
- "integrity": "sha512-ptg9PQwzs3orn4jkgXJ74bfs5vYz1NCZlSQMBUA0wKcGp5i5pA1AO3fOUEte8enhGUC+iapTCzEWw2jEFFUO/w==",
- "deprecated": "This package has been deprecated and is no longer maintained. Please use @rollup/plugin-inject.",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "estree-walker": "^0.6.1",
- "magic-string": "^0.25.3",
- "rollup-pluginutils": "^2.8.1"
- }
- },
- "node_modules/rollup-plugin-node-polyfills": {
- "version": "0.2.1",
- "resolved": "https://registry.npmjs.org/rollup-plugin-node-polyfills/-/rollup-plugin-node-polyfills-0.2.1.tgz",
- "integrity": "sha512-4kCrKPTJ6sK4/gLL/U5QzVT8cxJcofO0OU74tnB19F40cmuAKSzH5/siithxlofFEjwvw1YAhPmbvGNA6jEroA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "rollup-plugin-inject": "^3.0.0"
- }
- },
- "node_modules/rollup-pluginutils": {
- "version": "2.8.2",
- "resolved": "https://registry.npmjs.org/rollup-pluginutils/-/rollup-pluginutils-2.8.2.tgz",
- "integrity": "sha512-EEp9NhnUkwY8aif6bxgovPHMoMoNr2FulJziTndpt5H9RdwC47GSGuII9XxpSdzVGM0GWrNPHV6ie1LTNJPaLQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "estree-walker": "^0.6.1"
- }
- },
- "node_modules/semver": {
- "version": "7.7.4",
- "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz",
- "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==",
- "dev": true,
- "license": "ISC",
- "optional": true,
- "bin": {
- "semver": "bin/semver.js"
- },
- "engines": {
- "node": ">=10"
- }
- },
- "node_modules/sharp": {
- "version": "0.33.5",
- "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.33.5.tgz",
- "integrity": "sha512-haPVm1EkS9pgvHrQ/F3Xy+hgcuMV0Wm9vfIBSiwZ05k+xgb0PkBQpGsAA/oWdDobNaZTH5ppvHtzCFbnSEwHVw==",
- "dev": true,
- "hasInstallScript": true,
- "license": "Apache-2.0",
- "optional": true,
- "dependencies": {
- "color": "^4.2.3",
- "detect-libc": "^2.0.3",
- "semver": "^7.6.3"
- },
- "engines": {
- "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
- },
- "funding": {
- "url": "https://opencollective.com/libvips"
- },
- "optionalDependencies": {
- "@img/sharp-darwin-arm64": "0.33.5",
- "@img/sharp-darwin-x64": "0.33.5",
- "@img/sharp-libvips-darwin-arm64": "1.0.4",
- "@img/sharp-libvips-darwin-x64": "1.0.4",
- "@img/sharp-libvips-linux-arm": "1.0.5",
- "@img/sharp-libvips-linux-arm64": "1.0.4",
- "@img/sharp-libvips-linux-s390x": "1.0.4",
- "@img/sharp-libvips-linux-x64": "1.0.4",
- "@img/sharp-libvips-linuxmusl-arm64": "1.0.4",
- "@img/sharp-libvips-linuxmusl-x64": "1.0.4",
- "@img/sharp-linux-arm": "0.33.5",
- "@img/sharp-linux-arm64": "0.33.5",
- "@img/sharp-linux-s390x": "0.33.5",
- "@img/sharp-linux-x64": "0.33.5",
- "@img/sharp-linuxmusl-arm64": "0.33.5",
- "@img/sharp-linuxmusl-x64": "0.33.5",
- "@img/sharp-wasm32": "0.33.5",
- "@img/sharp-win32-ia32": "0.33.5",
- "@img/sharp-win32-x64": "0.33.5"
- }
- },
- "node_modules/side-channel": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz",
- "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==",
- "license": "MIT",
- "dependencies": {
- "es-errors": "^1.3.0",
- "object-inspect": "^1.13.3",
- "side-channel-list": "^1.0.0",
- "side-channel-map": "^1.0.1",
- "side-channel-weakmap": "^1.0.2"
- },
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/side-channel-list": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz",
- "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==",
- "license": "MIT",
- "dependencies": {
- "es-errors": "^1.3.0",
- "object-inspect": "^1.13.3"
- },
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/side-channel-map": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz",
- "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==",
- "license": "MIT",
- "dependencies": {
- "call-bound": "^1.0.2",
- "es-errors": "^1.3.0",
- "get-intrinsic": "^1.2.5",
- "object-inspect": "^1.13.3"
- },
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/side-channel-weakmap": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz",
- "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==",
- "license": "MIT",
- "dependencies": {
- "call-bound": "^1.0.2",
- "es-errors": "^1.3.0",
- "get-intrinsic": "^1.2.5",
- "object-inspect": "^1.13.3",
- "side-channel-map": "^1.0.1"
- },
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/simple-swizzle": {
- "version": "0.2.4",
- "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.4.tgz",
- "integrity": "sha512-nAu1WFPQSMNr2Zn9PGSZK9AGn4t/y97lEm+MXTtUDwfP0ksAIX4nO+6ruD9Jwut4C49SB1Ws+fbXsm/yScWOHw==",
- "dev": true,
- "license": "MIT",
- "optional": true,
- "dependencies": {
- "is-arrayish": "^0.3.1"
- }
- },
- "node_modules/source-map": {
- "version": "0.6.1",
- "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
- "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
- "dev": true,
- "license": "BSD-3-Clause",
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/sourcemap-codec": {
- "version": "1.4.8",
- "resolved": "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz",
- "integrity": "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==",
- "deprecated": "Please use @jridgewell/sourcemap-codec instead",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/stacktracey": {
- "version": "2.1.8",
- "resolved": "https://registry.npmjs.org/stacktracey/-/stacktracey-2.1.8.tgz",
- "integrity": "sha512-Kpij9riA+UNg7TnphqjH7/CzctQ/owJGNbFkfEeve4Z4uxT5+JapVLFXcsurIfN34gnTWZNJ/f7NMG0E8JDzTw==",
- "dev": true,
- "license": "Unlicense",
- "dependencies": {
- "as-table": "^1.0.36",
- "get-source": "^2.0.12"
- }
- },
- "node_modules/stoppable": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/stoppable/-/stoppable-1.1.0.tgz",
- "integrity": "sha512-KXDYZ9dszj6bzvnEMRYvxgeTHU74QBFL54XKtP3nyMuJ81CFYtABZ3bAzL2EdFUaEwJOBOgENyFj3R7oTzDyyw==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=4",
- "npm": ">=6"
- }
- },
- "node_modules/stripe": {
- "version": "17.7.0",
- "resolved": "https://registry.npmjs.org/stripe/-/stripe-17.7.0.tgz",
- "integrity": "sha512-aT2BU9KkizY9SATf14WhhYVv2uOapBWX0OFWF4xvcj1mPaNotlSc2CsxpS4DS46ZueSppmCF5BX1sNYBtwBvfw==",
- "license": "MIT",
- "dependencies": {
- "@types/node": ">=8.1.0",
- "qs": "^6.11.0"
- },
- "engines": {
- "node": ">=12.*"
- }
- },
- "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==",
- "dev": true,
- "license": "0BSD",
- "optional": true
- },
- "node_modules/ufo": {
- "version": "1.6.3",
- "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.6.3.tgz",
- "integrity": "sha512-yDJTmhydvl5lJzBmy/hyOAA0d+aqCBuwl818haVdYCRrWV84o7YyeVm4QlVHStqNrrJSTb6jKuFAVqAFsr+K3Q==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/undici": {
- "version": "5.29.0",
- "resolved": "https://registry.npmjs.org/undici/-/undici-5.29.0.tgz",
- "integrity": "sha512-raqeBD6NQK4SkWhQzeYKd1KmIG6dllBOTt55Rmkt4HtI9mwdWtJljnrXjAFUBLTSN67HWrOIZ3EPF4kjUw80Bg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@fastify/busboy": "^2.0.0"
- },
- "engines": {
- "node": ">=14.0"
- }
- },
- "node_modules/undici-types": {
- "version": "7.16.0",
- "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz",
- "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==",
- "license": "MIT"
- },
- "node_modules/unenv": {
- "version": "2.0.0-rc.14",
- "resolved": "https://registry.npmjs.org/unenv/-/unenv-2.0.0-rc.14.tgz",
- "integrity": "sha512-od496pShMen7nOy5VmVJCnq8rptd45vh6Nx/r2iPbrba6pa6p+tS2ywuIHRZ/OBvSbQZB0kWvpO9XBNVFXHD3Q==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "defu": "^6.1.4",
- "exsolve": "^1.0.1",
- "ohash": "^2.0.10",
- "pathe": "^2.0.3",
- "ufo": "^1.5.4"
- }
- },
- "node_modules/workerd": {
- "version": "1.20250718.0",
- "resolved": "https://registry.npmjs.org/workerd/-/workerd-1.20250718.0.tgz",
- "integrity": "sha512-kqkIJP/eOfDlUyBzU7joBg+tl8aB25gEAGqDap+nFWb+WHhnooxjGHgxPBy3ipw2hnShPFNOQt5lFRxbwALirg==",
- "dev": true,
- "hasInstallScript": true,
- "license": "Apache-2.0",
- "bin": {
- "workerd": "bin/workerd"
- },
- "engines": {
- "node": ">=16"
- },
- "optionalDependencies": {
- "@cloudflare/workerd-darwin-64": "1.20250718.0",
- "@cloudflare/workerd-darwin-arm64": "1.20250718.0",
- "@cloudflare/workerd-linux-64": "1.20250718.0",
- "@cloudflare/workerd-linux-arm64": "1.20250718.0",
- "@cloudflare/workerd-windows-64": "1.20250718.0"
- }
- },
- "node_modules/wrangler": {
- "version": "3.114.17",
- "resolved": "https://registry.npmjs.org/wrangler/-/wrangler-3.114.17.tgz",
- "integrity": "sha512-tAvf7ly+tB+zwwrmjsCyJ2pJnnc7SZhbnNwXbH+OIdVas3zTSmjcZOjmLKcGGptssAA3RyTKhcF9BvKZzMUycA==",
- "dev": true,
- "license": "MIT OR Apache-2.0",
- "dependencies": {
- "@cloudflare/kv-asset-handler": "0.3.4",
- "@cloudflare/unenv-preset": "2.0.2",
- "@esbuild-plugins/node-globals-polyfill": "0.2.3",
- "@esbuild-plugins/node-modules-polyfill": "0.2.2",
- "blake3-wasm": "2.1.5",
- "esbuild": "0.17.19",
- "miniflare": "3.20250718.3",
- "path-to-regexp": "6.3.0",
- "unenv": "2.0.0-rc.14",
- "workerd": "1.20250718.0"
- },
- "bin": {
- "wrangler": "bin/wrangler.js",
- "wrangler2": "bin/wrangler.js"
- },
- "engines": {
- "node": ">=16.17.0"
- },
- "optionalDependencies": {
- "fsevents": "~2.3.2",
- "sharp": "^0.33.5"
- },
- "peerDependencies": {
- "@cloudflare/workers-types": "^4.20250408.0"
- },
- "peerDependenciesMeta": {
- "@cloudflare/workers-types": {
- "optional": true
- }
- }
- },
- "node_modules/ws": {
- "version": "8.18.0",
- "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz",
- "integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==",
- "dev": true,
- "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/youch": {
- "version": "3.3.4",
- "resolved": "https://registry.npmjs.org/youch/-/youch-3.3.4.tgz",
- "integrity": "sha512-UeVBXie8cA35DS6+nBkls68xaBBXCye0CNznrhszZjTbRVnJKQuNsyLKBTTL4ln1o1rh2PKtv35twV7irj5SEg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "cookie": "^0.7.1",
- "mustache": "^4.2.0",
- "stacktracey": "^2.1.8"
- }
- },
- "node_modules/zod": {
- "version": "3.22.3",
- "resolved": "https://registry.npmjs.org/zod/-/zod-3.22.3.tgz",
- "integrity": "sha512-EjIevzuJRiRPbVH4mGc8nApb/lVLKVpmUhAaR5R5doKGfAnGJ6Gr3CViAVjP+4FWSxCsybeWQdcgCtbX+7oZug==",
- "dev": true,
- "license": "MIT",
- "funding": {
- "url": "https://github.com/sponsors/colinhacks"
- }
- }
- }
-}
diff --git a/worker/stripe-webhook/package.json b/worker/stripe-webhook/package.json
deleted file mode 100644
index ffcada1..0000000
--- a/worker/stripe-webhook/package.json
+++ /dev/null
@@ -1,14 +0,0 @@
-{
- "name": "stripe-webhook-worker",
- "private": true,
- "scripts": {
- "dev": "wrangler dev",
- "deploy": "wrangler deploy"
- },
- "dependencies": {
- "stripe": "^17"
- },
- "devDependencies": {
- "wrangler": "^3"
- }
-}
diff --git a/worker/stripe-webhook/src/index.js b/worker/stripe-webhook/src/index.js
deleted file mode 100644
index c11287d..0000000
--- a/worker/stripe-webhook/src/index.js
+++ /dev/null
@@ -1,360 +0,0 @@
-import Stripe from 'stripe';
-
-const cryptoProvider = Stripe.createSubtleCryptoProvider();
-
-// Stripe client for webhook verification (API key not used, but required for init)
-const stripe = new Stripe('sk_none', {
- httpClient: Stripe.createFetchHttpClient(),
-});
-
-// Resend API endpoint
-const RESEND_API_URL = 'https://api.resend.com/emails';
-
-// Keygen configuration
-const KEYGEN_API_URL = 'https://api.keygen.sh/v1/accounts/bytebase';
-const KEYGEN_POLICY_ID = '2ca585ae-4ba4-4ce9-9056-8d51ba55b50a';
-
-// Product configuration: productId -> planName
-const PRODUCTS = {
- // Test
- 'prod_TvEOD3fbORZgV5': 'Team',
- // Production
- 'prod_TvIPxbQUfqXquA': 'Team',
-};
-
-export default {
- async fetch(request, env) {
- if (request.method !== 'POST') {
- return new Response('Method not allowed', { status: 405 });
- }
-
- const signature = request.headers.get('stripe-signature');
- if (!signature) {
- return new Response('Missing stripe-signature header', { status: 400 });
- }
-
- const body = await request.text();
-
- // Try to verify with live secret first, then test
- let event = await verifyWebhook(body, signature, env.STRIPE_WEBHOOK_SECRET_LIVE);
- if (!event) {
- event = await verifyWebhook(body, signature, env.STRIPE_WEBHOOK_SECRET_TEST);
- }
- if (!event) {
- return new Response('Invalid signature', { status: 400 });
- }
-
- // Handle the event
- if (event.type === 'invoice.paid') {
- const invoice = event.data.object;
- // Only handle subscription invoices (not one-time payments)
- const subscriptionId = invoice.subscription || invoice.parent?.subscription_details?.subscription;
- if (subscriptionId) {
- const result = processInvoice(invoice, event.livemode, subscriptionId);
- const prefix = result.mode === 'test' ? '[test] ' : '';
- if (result.error) {
- console.log(`${prefix}Skipped: ${result.error}`);
- } else {
- // Log proration (mid-cycle upgrade) details
- if (result.billingReason === 'subscription_update') {
- console.log(
- `${prefix}Mid-cycle upgrade: ${result.customerEmail} (${result.customerName || 'N/A'}) ` +
- `upgraded to ${result.userSeatCount} seats on ${result.planName} plan. ` +
- `Amount charged: $${(result.amountPaid / 100).toFixed(2)} ${result.currency.toUpperCase()}`
- );
- }
- try {
- result.licenseKey = await generateLicense(result, env);
- console.log(`${prefix}Invoice paid:`, JSON.stringify(result, null, 2));
- await sendLicenseEmail(result, env);
- } catch (error) {
- console.log(`${prefix}License generation failed:`, error.message);
- }
- }
- } else {
- console.log('Skipped: Not a subscription invoice');
- }
- } else {
- console.log(`Unhandled event type: ${event.type}`);
- }
-
- return new Response(JSON.stringify({ received: true }), {
- status: 200,
- headers: { 'Content-Type': 'application/json' },
- });
- },
-};
-
-function processInvoice(invoice, livemode, subscriptionId) {
- const mode = livemode ? 'live' : 'test';
- const isProration = invoice.billing_reason === 'subscription_update';
-
- // For proration invoices (mid-cycle upgrades), find the NEW state line item (positive amount)
- // For regular invoices (renewals, initial), use the first line item
- const lineItem = isProration
- ? invoice.lines?.data?.find((line) => line.amount > 0)
- : invoice.lines?.data?.[0];
- if (!lineItem) {
- return { mode, error: 'No line items found' };
- }
-
- const priceDetails = lineItem.pricing?.price_details;
- if (!priceDetails) {
- return { mode, error: 'No price details found' };
- }
-
- // Look up plan name by product ID
- const productId = priceDetails.product;
- const planName = PRODUCTS[productId];
- if (!planName) {
- return { mode, error: `Unknown product: ${productId}` };
- }
-
- // Split customer name into first and last name
- const { firstName, lastName } = splitName(invoice.customer_name);
-
- return {
- mode,
- invoiceId: invoice.id,
- subscriptionId,
- customerId: invoice.customer,
- customerEmail: invoice.customer_email,
- customerName: invoice.customer_name,
- customerFirstName: firstName,
- customerLastName: lastName,
- customerCountry: invoice.customer_address?.country,
- planName,
- userSeatCount: lineItem.quantity,
- periodStart: lineItem.period.start,
- periodEnd: lineItem.period.end,
- amountPaid: invoice.amount_paid,
- currency: invoice.currency,
- billingReason: invoice.billing_reason,
- };
-}
-
-async function verifyWebhook(payload, signature, secret) {
- if (!secret) return null;
-
- try {
- return await stripe.webhooks.constructEventAsync(
- payload,
- signature,
- secret,
- undefined,
- cryptoProvider
- );
- } catch {
- return null;
- }
-}
-
-// Split full name into first and last name
-function splitName(fullName) {
- if (!fullName) return {};
- // Normalize: trim and collapse multiple spaces into one
- const parts = fullName.trim().split(/\s+/);
- if (parts.length === 0 || parts[0] === '') return {};
- if (parts.length === 1) {
- return { firstName: parts[0] };
- }
- return {
- firstName: parts[0],
- lastName: parts.slice(1).join(' '),
- };
-}
-
-// Upsert user in Keygen: retrieve by email, create if not found
-async function upsertKeygenUser(params, env) {
- const { email, customerId, customerFirstName, customerLastName, customerCountry } = params;
- const keygenApiUrl = KEYGEN_API_URL;
- const headers = {
- Authorization: `Bearer ${env.KEYGEN_API_KEY}`,
- 'Content-Type': 'application/vnd.api+json',
- Accept: 'application/vnd.api+json',
- };
-
- // Try to retrieve user by email (email can be used as ID in the URL path)
- const retrieveUrl = `${keygenApiUrl}/users/${email.toLowerCase()}`;
- const retrieveResponse = await fetch(retrieveUrl, { headers });
-
- if (retrieveResponse.ok) {
- const userData = await retrieveResponse.json();
- return { id: userData.data.id };
- }
-
- // User not found (404), create new user
- const metadata = {
- stripeCustomerId: customerId,
- };
- if (customerCountry) metadata.country = customerCountry;
-
- const attributes = {
- email: email.toLowerCase(),
- metadata,
- };
- if (customerFirstName) attributes.firstName = customerFirstName;
- if (customerLastName) attributes.lastName = customerLastName;
-
- const createResponse = await fetch(`${keygenApiUrl}/users`, {
- method: 'POST',
- headers,
- body: JSON.stringify({
- data: {
- type: 'users',
- attributes,
- },
- }),
- });
-
- if (!createResponse.ok) {
- const errorData = await createResponse.json();
- throw new Error(`Failed to create Keygen user: ${JSON.stringify(errorData)}`);
- }
-
- const createData = await createResponse.json();
- return { id: createData.data.id };
-}
-
-// Create license in Keygen
-async function createKeygenLicense(params, env) {
- const { userId, customerId, email, plan, userSeatCount, periodStart, periodEnd } = params;
- const keygenApiUrl = KEYGEN_API_URL;
- const keygenPolicyId = KEYGEN_POLICY_ID;
-
- // Build the license key JSON (all values lowercase)
- const licenseKeyData = {
- iss: 'pgconsole/license',
- sub: customerId,
- aud: 'pgconsole-prod',
- iat: periodStart,
- exp: periodEnd,
- plan: plan.toLowerCase(),
- userSeat: userSeatCount,
- email: email.toLowerCase(),
- };
-
- const expiryDate = new Date(periodEnd * 1000).toISOString();
-
- const response = await fetch(`${keygenApiUrl}/licenses`, {
- method: 'POST',
- headers: {
- Authorization: `Bearer ${env.KEYGEN_API_KEY}`,
- 'Content-Type': 'application/vnd.api+json',
- Accept: 'application/vnd.api+json',
- },
- body: JSON.stringify({
- data: {
- type: 'licenses',
- attributes: {
- key: JSON.stringify(licenseKeyData),
- expiry: expiryDate,
- },
- relationships: {
- policy: {
- data: { type: 'policies', id: keygenPolicyId },
- },
- user: {
- data: { type: 'users', id: userId },
- },
- },
- },
- }),
- });
-
- if (!response.ok) {
- const errorData = await response.json();
- throw new Error(`Failed to create Keygen license: ${JSON.stringify(errorData)}`);
- }
-
- const data = await response.json();
- return data.data.attributes.key;
-}
-
-// Generate license via Keygen
-async function generateLicense(result, env) {
- if (!env.KEYGEN_API_KEY) {
- throw new Error('Missing KEYGEN_API_KEY');
- }
-
- // Upsert user
- const user = await upsertKeygenUser(
- {
- email: result.customerEmail,
- customerId: result.customerId,
- customerFirstName: result.customerFirstName,
- customerLastName: result.customerLastName,
- customerCountry: result.customerCountry,
- },
- env
- );
-
- // Create license
- const licenseKey = await createKeygenLicense(
- {
- userId: user.id,
- customerId: result.customerId,
- email: result.customerEmail,
- plan: result.planName,
- userSeatCount: result.userSeatCount,
- periodStart: result.periodStart,
- periodEnd: result.periodEnd,
- },
- env
- );
-
- return licenseKey;
-}
-
-function formatDate(timestamp) {
- return new Date(timestamp * 1000).toISOString().split('T')[0];
-}
-
-async function sendLicenseEmail(result, env) {
- const prefix = result.mode === 'test' ? '[test] ' : '';
-
- if (!env.RESEND_API_KEY) {
- console.log(`${prefix}Email skipped: Missing RESEND_API_KEY`);
- return;
- }
-
- const variables = {
- plan: result.planName,
- license_key: result.licenseKey,
- user_seat: String(result.userSeatCount),
- issue_date: formatDate(result.periodStart),
- expire_date: formatDate(result.periodEnd),
- };
- if (result.customerFirstName) {
- variables.customer_name = result.customerFirstName;
- }
-
- const payload = {
- to: [result.customerEmail],
- template: {
- id: 'send-license',
- variables,
- },
- };
-
- try {
- const response = await fetch(RESEND_API_URL, {
- method: 'POST',
- headers: {
- 'Content-Type': 'application/json',
- Authorization: `Bearer ${env.RESEND_API_KEY}`,
- },
- body: JSON.stringify(payload),
- });
-
- const data = await response.json();
-
- if (response.ok) {
- console.log(`${prefix}Email sent to ${result.customerEmail}:`, data.id);
- } else {
- console.log(`${prefix}Email failed:`, JSON.stringify(data));
- }
- } catch (error) {
- console.log(`${prefix}Email error:`, error.message);
- }
-}
diff --git a/worker/stripe-webhook/wrangler.toml b/worker/stripe-webhook/wrangler.toml
deleted file mode 100644
index 8539eca..0000000
--- a/worker/stripe-webhook/wrangler.toml
+++ /dev/null
@@ -1,7 +0,0 @@
-name = "stripe-webhook-worker"
-main = "src/index.js"
-compatibility_date = "2024-12-01"
-
-[observability]
-enabled = true
-head_sampling_rate = 1