From 388d8dea91bcaf2d4ce24b50502085d67365321d Mon Sep 17 00:00:00 2001 From: Kevin Brown Date: Wed, 18 Mar 2026 16:54:38 +0300 Subject: [PATCH 01/20] Update styling in List and Tables for platform-architecture-control-plane-data-plane-separation article --- _TODO.md | 6 +- src/components/List/ListItem.astro | 9 ++ .../List/__fixtures__/mixedApi.fixture.astro | 16 ++ .../List/__fixtures__/richItems.fixture.astro | 13 ++ src/components/List/__tests__/index.spec.ts | 23 +++ src/components/List/index.astro | 53 +++++-- src/components/List/server/slotItems.ts | 65 ++++++++ .../pdf.mdx | 47 +++--- .../pdf.mdx | 149 +++++++++++------- src/layouts/MarkdownLayout.astro | 2 + 10 files changed, 282 insertions(+), 101 deletions(-) create mode 100644 src/components/List/ListItem.astro create mode 100644 src/components/List/__fixtures__/mixedApi.fixture.astro create mode 100644 src/components/List/__fixtures__/richItems.fixture.astro create mode 100644 src/components/List/server/slotItems.ts diff --git a/_TODO.md b/_TODO.md index 392bb4c0..4730e659 100644 --- a/_TODO.md +++ b/_TODO.md @@ -226,8 +226,6 @@ https://mermaid.js.org/config/directives.html - Improve `` styling: https://codepen.io/ire/pen/NoqWpm -- Moving the scroll bar up quickly with the mouse seems to make the header logic break - the Switcher component and Breadcrumbs are hidden under the header - ## List Component - Task list checked variant Markdown in dark theme is awkward, it has a dark shadow @@ -240,6 +238,8 @@ https://mermaid.js.org/config/directives.html - Need to improve the "squish" animation where the header reduces in size on scroll down, and returns to full size on scroll up. Maybe reduce and expand the text and search / themepicker / hamburger menu sizes in place, and then slide them horizontally. +- Moving the scroll bar up quickly with the mouse seems to make the header logic break - the Switcher component and Breadcrumbs are hidden under the header + ## Content Issues - Need an article on OpenStack @@ -252,7 +252,7 @@ https://mermaid.js.org/config/directives.html - Add a "Preview Special" item to our Download CTA that lets the user know the Deep Dive content can be previewed in HTML format, and offer a switch to it. -- How can we handle footnotes in List components? src/content/articles/kubernetes-multi-cluster-fleet-management-configuration/pdf.mdx line 80 +- Tags should break more evenly across two lines when there's a lot of them, instead of forcing the author name and date to break across two lines: platform-engineering-metrics-lead-time-developer-friction/index.mdx - Need to update Case Studies with lists and tables too diff --git a/src/components/List/ListItem.astro b/src/components/List/ListItem.astro new file mode 100644 index 00000000..d47944b9 --- /dev/null +++ b/src/components/List/ListItem.astro @@ -0,0 +1,9 @@ +--- +export type Props = { + lead?: string +} + +const { lead } = Astro.props as Props +--- + + \ No newline at end of file diff --git a/src/components/List/__fixtures__/mixedApi.fixture.astro b/src/components/List/__fixtures__/mixedApi.fixture.astro new file mode 100644 index 00000000..5824f4e0 --- /dev/null +++ b/src/components/List/__fixtures__/mixedApi.fixture.astro @@ -0,0 +1,16 @@ +--- +import List from '@components/List/index.astro' +import ListItem from '@components/List/ListItem.astro' +--- + + + This should never render. + \ No newline at end of file diff --git a/src/components/List/__fixtures__/richItems.fixture.astro b/src/components/List/__fixtures__/richItems.fixture.astro new file mode 100644 index 00000000..0c5d84c0 --- /dev/null +++ b/src/components/List/__fixtures__/richItems.fixture.astro @@ -0,0 +1,13 @@ +--- +import List from '@components/List/index.astro' +import ListItem from '@components/List/ListItem.astro' +--- + + + + Separate VPCs1 can provide stronger boundaries. + + + Dedicated node pools reduce resource contention. + + \ No newline at end of file diff --git a/src/components/List/__tests__/index.spec.ts b/src/components/List/__tests__/index.spec.ts index c34c5ad0..c085ff1c 100644 --- a/src/components/List/__tests__/index.spec.ts +++ b/src/components/List/__tests__/index.spec.ts @@ -1,6 +1,8 @@ import { beforeEach, describe, expect, test } from 'vitest' import { experimental_AstroContainer as AstroContainer } from 'astro/container' import { withJsdomEnvironment } from '@test/unit/helpers/litRuntime' +import MixedApiFixture from '@components/List/__fixtures__/mixedApi.fixture.astro' +import RichItemsFixture from '@components/List/__fixtures__/richItems.fixture.astro' describe('List (Astro)', () => { let container: AstroContainer @@ -232,4 +234,25 @@ describe('List (Astro)', () => { expect(title?.textContent).toContain('Resource metrics') }) }) + + test('renders rich ListItem children through the existing layout item API', async () => { + const renderedHtml = await container.renderToString(RichItemsFixture) + + await withJsdomEnvironment(async ({ window }) => { + window.document.body.innerHTML = renderedHtml + + const items = Array.from(window.document.querySelectorAll('ul > li')) + expect(items).toHaveLength(2) + expect(items[0]?.querySelector('em')?.textContent).toContain('Network isolation') + expect(items[0]?.querySelector('sup[data-footnote-ref="slot-demo"]')?.textContent).toBe('1') + expect(items[1]?.textContent).toContain('Dedicated node pools reduce resource contention.') + expect(window.document.querySelector('wsb-list-item')).toBeNull() + }) + }) + + test('throws a BuildError when items and ListItem children are both provided', async () => { + await expect(container.renderToString(MixedApiFixture)).rejects.toThrow( + 'List: received both the `items` prop and ListItem children. Use one API or the other.' + ) + }) }) \ No newline at end of file diff --git a/src/components/List/index.astro b/src/components/List/index.astro index 4bbdb63c..574dc9a9 100644 --- a/src/components/List/index.astro +++ b/src/components/List/index.astro @@ -1,4 +1,5 @@ --- +import { BuildError } from '@lib/errors/BuildError' import AccentBorderLeftList from '@components/List/layouts/AccentBorderLeftList.astro' import BadgeList from '@components/List/layouts/BadgeList.astro' import CardGridList from '@components/List/layouts/CardGridList.astro' @@ -14,9 +15,10 @@ import ThreeColumnIconList from '@components/List/layouts/ThreeColumnIconList.as import TwoColumnCheckIconsList from '@components/List/layouts/TwoColumnCheckIconsList.astro' import TwoColumnIconList from '@components/List/layouts/TwoColumnIconList.astro' import ZebraList from '@components/List/layouts/ZebraList.astro' +import { getListItemsFromSlotMarkup } from '@components/List/server/slotItems' export type Props = { - items: { + items?: { title?: string lead?: string text: string @@ -45,14 +47,33 @@ export type Props = { style?: Record } -const { items, size, color, startNumber, variant = 'default', classes, style } = Astro.props +const { items, size, color, startNumber, variant = 'default', classes, style } = Astro.props as Props +const hasItemsProp = Object.prototype.hasOwnProperty.call(Astro.props, 'items') +const hasDefaultSlot = Astro.slots.has('default') + +if (hasItemsProp && hasDefaultSlot) { + throw new BuildError( + 'List: received both the `items` prop and ListItem children. Use one API or the other.', + { + phase: 'compilation', + filePath: 'src/components/List/index.astro', + tool: 'list', + } + ) +} + +const slotMarkup = hasDefaultSlot ? await Astro.slots.render('default') : '' +const normalizedItems = hasDefaultSlot + ? getListItemsFromSlotMarkup(slotMarkup, variant) + : (items ?? []) + const classesProps = classes ? { classes } : {} const numericSizeProps = typeof size === 'number' ? { size } : {} const chatBubbleSizeProps = typeof size === 'string' ? { size } : {} const colorProps = typeof color === 'string' && color.trim().length > 0 ? { color } : {} const startNumberProps = typeof startNumber === 'number' ? { startNumber } : {} const styleProps = style ? { style } : {} -const itemsWithColor = items as Array<{ +const itemsWithColor = normalizedItems as Array<{ title?: string lead?: string text: string @@ -61,32 +82,32 @@ const itemsWithColor = items as Array<{ inverseColor?: string bgColor?: string }> -const itemsWithIconAndColor = items as Array<{ +const itemsWithIconAndColor = normalizedItems as Array<{ text: string icon: string color: string inverseColor?: string bgColor?: string }> -const plainIconItems = items.filter((item): item is Props['items'][number] & { icon: string } => { +const plainIconItems = normalizedItems.filter((item): item is NonNullable[number] & { icon: string } => { return typeof item.icon === 'string' && item.icon.trim().length > 0 }) ---
- {variant === 'accent-border-left-list' && } - {variant === 'badge-list' && } - {variant === 'card-grid-list' && } - {variant === 'chat-bubbles' && } - {variant === 'check-icons-list' && } - {variant === 'chevron-list' && } + {variant === 'accent-border-left-list' && } + {variant === 'badge-list' && } + {variant === 'card-grid-list' && } + {variant === 'chat-bubbles' && } + {variant === 'check-icons-list' && } + {variant === 'chevron-list' && } {variant === 'colored-marker-list' && } - {variant === 'numbered-with-background-list' && } + {variant === 'numbered-with-background-list' && } {variant === 'plain-icon-list' && } - {variant === 'side-by-side-list' && } - {variant === 'timeline-list' && } - {variant === 'two-column-check-icons-list' && } + {variant === 'side-by-side-list' && } + {variant === 'timeline-list' && } + {variant === 'two-column-check-icons-list' && } {variant === 'two-column-icon-list' && } {variant === 'three-column-icon-list' && } - {variant === 'zebra-list' && } + {variant === 'zebra-list' && }
diff --git a/src/components/List/server/slotItems.ts b/src/components/List/server/slotItems.ts new file mode 100644 index 00000000..fdc2b631 --- /dev/null +++ b/src/components/List/server/slotItems.ts @@ -0,0 +1,65 @@ +import { JSDOM } from 'jsdom' +import { BuildError } from '@lib/errors/BuildError' +import type { Props as ListProps } from '@components/List/index.astro' + +type ListItemShape = NonNullable[number] + +const unsupportedRichSlotVariants = new Set([ + 'plain-icon-list', + 'two-column-icon-list', + 'three-column-icon-list', +]) + +const buildErrorContext = { + phase: 'compilation', + filePath: 'src/components/List/index.astro', + tool: 'list', +} as const + +/** + * Parse rendered ListItem slot markup into the existing List items shape. + */ +export function getListItemsFromSlotMarkup(markup: string, variant: string): ListItemShape[] { + if (unsupportedRichSlotVariants.has(variant)) { + throw new BuildError( + `List: rich ListItem children are not supported for the \`${variant}\` variant. Use the \`items\` prop for icon-based list variants.`, + buildErrorContext + ) + } + + const document = new JSDOM(`${markup}`).window.document + const meaningfulNodes = Array.from(document.body.childNodes).filter((node) => { + if (node.nodeType === node.TEXT_NODE) { + return node.textContent?.trim().length + } + + return node.nodeType === node.ELEMENT_NODE + }) + + if (meaningfulNodes.length === 0) { + throw new BuildError( + 'List: expected one or more ListItem children when using rich slot content.', + buildErrorContext + ) + } + + const invalidNode = meaningfulNodes.find((node) => { + return node.nodeType !== node.ELEMENT_NODE || (node as Element).tagName.toLowerCase() !== 'wsb-list-item' + }) + + if (invalidNode) { + throw new BuildError( + 'List: rich slot content must contain only ListItem children. Use either the `items` prop or `` children.', + buildErrorContext + ) + } + + return meaningfulNodes.map((node) => { + const element = node as Element + + return { + lead: element.getAttribute('data-lead') ?? undefined, + text: element.innerHTML.trim(), + } + }) +} \ No newline at end of file diff --git a/src/content/articles/kubernetes-multi-cluster-fleet-management-configuration/pdf.mdx b/src/content/articles/kubernetes-multi-cluster-fleet-management-configuration/pdf.mdx index abfb8a33..23a433c7 100644 --- a/src/content/articles/kubernetes-multi-cluster-fleet-management-configuration/pdf.mdx +++ b/src/content/articles/kubernetes-multi-cluster-fleet-management-configuration/pdf.mdx @@ -78,27 +78,32 @@ Organizations arrive at multi-cluster architectures for different reasons, and t Every multi-cluster strategy sits somewhere on a spectrum from "all clusters are identical" to "each cluster is independent." Neither extreme works well in practice. - + + + Means all clusters get exactly the same configuration. This is simple to reason about and guarantees + reproducibility, but it's inflexible. You end up over-provisioning dev and staging to match + production, or you can't tune production for its actual load patterns. This approach only works for + small fleets with simple applications. + + + Uses a base configuration with environment-specific overrides. This is the sweet spot for most + organizations. You define what's shared (security policies, monitoring stack, core add-ons) and + what varies (replica counts, resource limits, feature flags). Kustomize overlays and Helm values + files are the typical implementation. The risk is "override explosion"—so many layers of patches + that nobody can trace what a cluster actually runs. + + + Configuration sets guardrails rather than identical configs. Teams can customize their clusters + within bounds enforced by OPA/Gatekeeper[^opa-gatekeeper] or Kyverno[^kyverno]. This enables + innovation and team autonomy, but drift happens within the policy bounds. Debugging becomes harder + because clusters are intentionally different. + + + Means each cluster is independent, with its own GitOps repo and configuration. Maximum autonomy, + maximum chaos. Security risks emerge when teams deviate from baselines. This only works with truly + autonomous teams who don't need cross-cluster consistency. + + [^opa-gatekeeper]: OPA (Open Policy Agent) is an open-source, general-purpose policy engine that unifies policy enforcement across the stack, using a declarative language called Rego to define complex rules. Gatekeeper is a specialized project that integrates OPA into Kubernetes — it acts as a validating admission controller, intercepting requests to the Kubernetes API and checking them against OPA policies before resources are created or modified. diff --git a/src/content/articles/platform-architecture-control-plane-data-plane-separation/pdf.mdx b/src/content/articles/platform-architecture-control-plane-data-plane-separation/pdf.mdx index 707393c1..98583039 100644 --- a/src/content/articles/platform-architecture-control-plane-data-plane-separation/pdf.mdx +++ b/src/content/articles/platform-architecture-control-plane-data-plane-separation/pdf.mdx @@ -36,12 +36,12 @@ Problems emerge. A control plane upgrade requires scheduling maintenance windows The team refactors: dedicated control plane cluster, separate data plane clusters per environment, GitOps for configuration sync. Now control plane upgrades happen independently. Data plane issues are isolated. Each plane scales according to its specific needs. -The lesson isn't that separation is always necessary from day one — it's that separation should be _designed for_ from day one. The abstractions matter more than the deployment topology. You can deploy together initially, but the APIs, resource boundaries, and tenancy models need to support eventual separation. This matters most for multi-tenancy: once you're supporting multiple teams, the separation enables isolation patterns that would be impossible with tightly coupled planes. - The most common platform architecture mistake: building for single-tenant simplicity, then retrofitting multi-tenancy. Design separation into your abstractions from the start, even if you deploy everything together initially. +The lesson isn't that separation is always necessary from day one — it's that separation should be _designed for_ from day one. The abstractions matter more than the deployment topology. You can deploy together initially, but the APIs, resource boundaries, and tenancy models need to support eventual separation. This matters most for multi-tenancy: once you're supporting multiple teams, the separation enables isolation patterns that would be impossible with tightly coupled planes. + ## Architectural Concepts ### Control Plane vs Data Plane Defined @@ -53,7 +53,7 @@ The data plane is where actual work happens. It executes workloads, routes traff The characteristics differ in ways that matter for architecture: -
+ + The boundaries between layers matter. Infrastructure-to-orchestration uses cloud APIs and Terraform — loose coupling means infrastructure can be replaced. Orchestration-to-platform uses Kubernetes APIs and CRDs — medium coupling since most platforms depend on Kubernetes primitives. Platform-to-developer uses platform APIs and GitOps — loose coupling keeps developers insulated from internals. @@ -179,7 +187,7 @@ The fundamental question: how much isolation do tenants need, and what are you w Four patterns cover most platforms, each trading isolation strength against cost and operational complexity.
+ variant="chevron-list" + classes={{ + wrapper: "sm:ml-6 sm:mr-18 mb-6", + }} +> + + Prevents tenants from communicating unless explicitly allowed. Network policies default-deny + cross-namespace traffic. Service mesh mTLS ensures traffic is encrypted and authenticated. For + stronger isolation, separate VPCs[^vpc] or subnets provide network-level boundaries that don't + depend on Kubernetes enforcement. + + + + Prevents resource contention. Resource quotas limit how much CPU, memory, and storage a tenant can + consume. For stronger isolation, dedicated node pools with taints ensure tenant workloads only run + on designated nodes. At the extreme, dedicated clusters eliminate any compute sharing. + + + + Protects data. Per-tenant storage classes can enforce encryption and access controls. Per-tenant + encryption keys mean one tenant's compromise doesn't expose another's data. For the strongest + isolation, completely separate storage backends. + + + + Controls who can do what. RBAC rules scope permissions to specific namespaces. Service accounts are + namespace-scoped by default. Integration with external identity providers (OIDC, LDAP) enables + per-tenant authentication policies. + + [^vpc]: A Virtual Private Cloud (VPC) is an isolated section within a public cloud where you define your own virtual network. You control the IP address ranges, subnets, route tables, and network gateways — effectively a private data center's network topology with cloud scalability. Placing tenant workloads in separate VPCs provides network isolation at the cloud provider level, independent of Kubernetes. This matters because VPC isolation is enforced by the cloud provider's network fabric, not by software running in the cluster. A misconfigured NetworkPolicy can't accidentally expose cross-tenant traffic when tenants are in separate VPCs. The tradeoff is complexity: cross-VPC communication requires VPC peering or transit gateways, adding latency and configuration overhead. @@ -351,7 +373,7 @@ Tenant count introduces overhead beyond the resources tenants create. Each tenan The architectural response depends on scale:
@@ -415,7 +438,7 @@ Scaling decisions affect failure domains — a single large cluster has differen Different failure types have different blast radii. Understanding each helps you design appropriate containment. Date: Wed, 18 Mar 2026 17:19:00 +0300 Subject: [PATCH 02/20] Update styling in List and Tables for platform-engineering-metrics-lead-time-developer-friction article --- .../index.mdx | 33 ++++++--- .../pdf.mdx | 68 +++++++++++++------ 2 files changed, 71 insertions(+), 30 deletions(-) diff --git a/src/content/articles/platform-engineering-metrics-lead-time-developer-friction/index.mdx b/src/content/articles/platform-engineering-metrics-lead-time-developer-friction/index.mdx index 52451310..2322719c 100644 --- a/src/content/articles/platform-engineering-metrics-lead-time-developer-friction/index.mdx +++ b/src/content/articles/platform-engineering-metrics-lead-time-developer-friction/index.mdx @@ -14,14 +14,16 @@ featured: true How do you prove an internal platform creates value? Product teams measure revenue or user growth. Platform teams serve internal customers and enable outcomes rather than producing them directly. This creates a measurement gap — platform work is easy to fund when it's novel and hard to justify when it's mature. -I've watched this play out. A platform team builds an internal developer portal with self-service infrastructure provisioning. Six months in, leadership asks for success metrics. The team reports: 500 developers onboarded, 10,000 API calls per month, 99.9% uptime. Leadership responds: "That's nice, but did we save money? Are developers faster?" The team can't answer because they measured what the platform __does__, not what it __enables__. - -The pivot is simple but fundamental: friction-focused metrics. Time to first deployment dropped from 2 weeks to 2 hours. Infrastructure tickets per developer dropped 80%. Developer NPS rose from -20 to +45. Now the narrative is clear: "Developers are 10x faster to get started and need 80% less support." That story justifies continued investment. +I've watched this play out. A platform team builds an internal developer portal with self-service infrastructure provisioning. Six months in, leadership asks for success metrics. The team reports: 500 developers onboarded, 10,000 API calls per month, 99.9% uptime. Leadership responds: "That's nice, but did we save money? Are developers faster?" The team can't answer because they measured what the platform _does_, not what it _enables_. The most common platform metrics mistake: measuring platform activity (requests served, uptime) instead of developer outcomes (time saved, friction reduced). A platform can be highly available and completely useless. +The pivot is simple but fundamental: friction-focused metrics. Time to first deployment dropped from 2 weeks to 2 hours. Infrastructure tickets per developer dropped 80%. Developer NPS rose from -20 to +45. Now the narrative is clear: "Developers are 10x faster to get started and need 80% less support." That story justifies continued investment. + + + Let's look at the metrics that actually demonstrate platform value. ## Core Platform Metrics @@ -31,7 +33,10 @@ Let's look at the metrics that actually demonstrate platform value. Lead time metrics measure how long things take. They're the clearest indicators of platform friction because they directly answer "how fast can developers move?"
+Lead time tells you _how long_. Friction metrics tell you _how hard_. They measure the cognitive and operational burden the platform imposes. The most common platform metrics mistake: measuring platform activity (requests served, uptime) instead of developer outcomes (time saved, friction reduced). A platform can be highly available and completely useless. +Meaningful platform metrics answer one question: is the platform making developers more productive? How long do common tasks take? How often do developers need help? How much cognitive load does the platform impose? Vanity metrics — adoption counts, API calls, uptime percentages — look impressive in slide decks but don't demonstrate value. + Let's look at the metrics that actually demonstrate platform value. ## Core Platform Metrics @@ -41,7 +41,10 @@ Let's look at the metrics that actually demonstrate platform value. Lead time metrics measure how long things take. They're the clearest indicators of platform friction because they directly answer "how fast can developers move?"
-These metrics align with DORA's[^dora] research-backed framework. +These metrics align with DORA's[^dora] research-backed framework, which identified deployment frequency, lead time for changes, change failure rate, and time to restore service as the four key predictors of software delivery performance. DORA's annual State of DevOps reports consistently show that elite performers deploy multiple times per day with lead times under an hour — putting concrete numbers behind what "good" looks like at each maturity level. [^dora]: DORA (DevOps Research and Assessment) is a research program that identified four key metrics predicting software delivery performance: deployment frequency, lead time for changes, change failure rate, and time to restore service. Their annual State of DevOps reports provide industry benchmarks. Elite performers deploy multiple times per day with lead times under an hour. If you're measuring in weeks, you're not just slow — you're losing competitive ground. ### Developer Friction Metrics -Lead time tells you __how long__. Friction metrics tell you __how hard__. They measure the cognitive and operational burden the platform imposes. +Lead time tells you _how long_. Friction metrics tell you _how hard_. They measure the cognitive and operational burden the platform imposes.
@@ -273,7 +287,11 @@ Automated metrics tell you __what__ is happening. Surveys tell you __why__ — a Three survey types cover the developer lifecycle:
Metrics are a means to an end. The goal isn't impressive dashboards — it's understanding whether the platform reduces friction and enabling decisions about where to invest next. If metrics don't change behavior, they're not worth collecting. + +The ultimate goal is a clear narrative: "Before the platform, onboarding took two weeks. After, it takes two hours. We saved 200 developer-hours this quarter." That story, backed by data, justifies continued investment and guides roadmap decisions. From ad839b4b34e911a724bb9c9debf72c8c1b1288f6 Mon Sep 17 00:00:00 2001 From: Kevin Brown Date: Wed, 18 Mar 2026 21:53:53 +0300 Subject: [PATCH 03/20] Update styling in List and Tables for postgresql-connection-pooling-saturation-sizing article --- src/components/List/layouts/BadgeList.astro | 2 +- .../index.mdx | 56 ++++-- .../pdf.mdx | 178 +++++++++++------- 3 files changed, 151 insertions(+), 85 deletions(-) diff --git a/src/components/List/layouts/BadgeList.astro b/src/components/List/layouts/BadgeList.astro index 141ded47..08fa2c95 100644 --- a/src/components/List/layouts/BadgeList.astro +++ b/src/components/List/layouts/BadgeList.astro @@ -34,7 +34,7 @@ const emClass = ["text-content font-bold not-italic mr-2", classes?.em]
  • {item.title && ( - + )} diff --git a/src/content/articles/postgresql-connection-pooling-saturation-sizing/index.mdx b/src/content/articles/postgresql-connection-pooling-saturation-sizing/index.mdx index dac4c0b3..bc4a1728 100644 --- a/src/content/articles/postgresql-connection-pooling-saturation-sizing/index.mdx +++ b/src/content/articles/postgresql-connection-pooling-saturation-sizing/index.mdx @@ -17,13 +17,18 @@ I've watched this exact scenario play out multiple times. The frustrating part? PostgreSQL uses a process-per-connection model. When a client connects, the main postgres process forks a new backend process dedicated to that session. Each backend consumes 5-10MB of memory just by existing, plus additional `work_mem` allocation when executing queries. More backend processes means more OS scheduler overhead — context switching between hundreds of processes burns CPU even when most are idle. -The counterintuitive truth: fewer connections often means __better__ performance. A database with 50 active connections will outperform one with 500, even at the same query volume, because there's less contention for shared resources and less scheduler thrashing. +The counterintuitive truth: fewer connections often means _better_ performance. A database with 50 active connections will outperform one with 500, even at the same query volume, because there's less contention for shared resources and less scheduler thrashing. The conventional formula: `max_connections = CPU cores × 4` for SSDs. But staying conservative (50-100) with an external connection pooler like PgBouncer in front is usually the better approach. External poolers sit between your applications and the database, multiplexing many application connections onto fewer database connections. This decouples horizontal scaling from connection limits — your app can scale to 100 instances while the database sees a fixed 50 connections.
  • max_connections', 'Use Case', 'Memory Overhead'], }, @@ -43,7 +48,6 @@ The conventional formula: `max_connections = CPU cores × 4` for SSDs. But stayi }, ], }, - figure: 'max_connections settings and their implications.', }} /> @@ -61,15 +65,22 @@ Here's where it gets tricky: Little's Law uses averages, but averages hide varia The practical formula: -```text + pool_size = ceil(peak_qps × p99_query_time × burst_factor) -``` + For an application instance handling 50 queries per second at peak, with P99 query time of 100ms and a 2× burst factor: `ceil(50 × 0.1 × 2) = 10` connections per instance.
    @@ -120,19 +130,23 @@ Code: Node.js pg-pool configuration with key settings. Even with correct calculations, common mistakes undermine pool sizing: pool_size = 100 "just in case" means 10 instances create 1000 connections. Size based on actual need, not fear.', + title: 'Oversized pools', + lead: 'Setting pool_size = 100 "just in case" means 10 instances create 1000 connections. Size based on actual need, not fear.', }, { - lead: 'No max lifetime', - text: 'Connections held forever accumulate memory leaks and stale state. Recycle every 30 minutes.', + title: 'No max lifetime', + lead: 'Connections held forever accumulate memory leaks and stale state. Recycle every 30 minutes.', }, { - lead: 'No idle timeout', - text: 'Idle connections waste resources and hide leaks. Timeout after 10 minutes of inactivity.', + title: 'No idle timeout', + lead: 'Idle connections waste resources and hide leaks. Timeout after 10 minutes of inactivity.', }, ]} /> @@ -155,7 +169,10 @@ WHERE backend_type = 'client backend'; Code: Connection utilization query.
    @@ -220,6 +242,6 @@ If you're using PgBouncer (and at scale, you should be), it exposes its own satu Connection management comes down to three principles: connections are expensive so keep them minimal, size pools with math instead of guesswork, and monitor saturation before it becomes exhaustion. -But knowing __what__ to monitor is only half the battle. When saturation does hit — and it will — you need to understand the cascade that follows and have recovery strategies ready. Connection exhaustion doesn't fail gracefully. It cascades through retry storms, health check failures, and orchestrator restarts that compound the problem. +But knowing _what_ to monitor is only half the battle. When saturation does hit — and it will — you need to understand the cascade that follows and have recovery strategies ready. Connection exhaustion doesn't fail gracefully. It cascades through retry storms, health check failures, and orchestrator restarts that compound the problem. The difference between a slow day and a multi-hour outage often comes down to whether you've implemented circuit breakers and retry budgets before you needed them. diff --git a/src/content/articles/postgresql-connection-pooling-saturation-sizing/pdf.mdx b/src/content/articles/postgresql-connection-pooling-saturation-sizing/pdf.mdx index f13a4b2f..d6321835 100644 --- a/src/content/articles/postgresql-connection-pooling-saturation-sizing/pdf.mdx +++ b/src/content/articles/postgresql-connection-pooling-saturation-sizing/pdf.mdx @@ -12,6 +12,7 @@ featured: true import connectionEstablishmentDiagram from "./diagrams/connection-establishment-sequence.jpg" import connectionSaturationDiagram from "./diagrams/connection-saturation-progression.jpg" import pgbouncerDiagram from "./diagrams/pgbouncer-connection-multiplexing.jpg" +import ListItem from "@components/List/ListItem.astro" *[HPA]: Horizontal Pod Autoscaler *[IOPS]: Input/Output Operations Per Second @@ -27,12 +28,12 @@ Every PostgreSQL connection costs memory — roughly 5-10MB per connection — a I've seen this pattern repeatedly: a team runs PostgreSQL with `max_connections=200`, comfortable at typical usage of 50. Traffic spikes, instances autoscale from 5 to 20, each with a pool of 20. Four hundred connections hit a 200 limit. Rejections cascade — threads block, health checks fail, more scaling triggers more connection requests. -After adding PgBouncer, properly sizing pools, and implementing connection backpressure, the next traffic spike looks different. Database connections stay at 200. The application queues requests appropriately. Latency increases, but nothing fails. The lesson: connection limits aren't about capacity — they're about graceful degradation. - The most common PostgreSQL scaling mistake: configuring `max_connections` high "just in case." Each connection costs memory and CPU. A database with 1000 `max_connections` will perform worse than one with 200 — even at the same actual connection count — because of reservation overhead. +After adding PgBouncer, properly sizing pools, and implementing connection backpressure, the next traffic spike looks different. Database connections stay at 200. The application queues requests appropriately. Latency increases, but nothing fails. The lesson: connection limits aren't about capacity — they're about graceful degradation. + ## Connection Fundamentals ### How PostgreSQL Handles Connections @@ -45,6 +46,7 @@ Each backend process consumes resources independent of whether it's actively run @@ -54,8 +56,9 @@ The `max_connections` setting determines how many backend processes can exist si [^spindle]: Effective spindle count represents the hardware's capacity for concurrent I/O requests. A traditional HDD has one spindle and can usually only handle one I/O request at a time. For modern SSD-based storage, this metric is often considered 0 or ignored in the original formula. Because SSDs handle many parallel I/O requests, some modern benchmarks suggest using I/O queue depth as a more relevant replacement.
    max_connections settings and their implications.', thead: { th: ['max_connections', 'Use Case', 'Memory Overhead', 'Notes'], }, @@ -75,7 +78,6 @@ The `max_connections` setting determines how many backend processes can exist si }, ], }, - figure: 'max_connections settings and their implications.', }} /> @@ -123,8 +125,10 @@ Code: Connection monitoring queries for PostgreSQL. Connection state tells you what each backend is doing. Most connections in a healthy pooled application should be `idle`—waiting for the next query. `Active` connections are executing queries; a high percentage of active connections indicates either long-running queries or genuine load.
    @@ -161,7 +164,11 @@ Connection state tells you what each backend is doing. Most connections in a hea Connection pooling comes in two flavors: application-side pools and external poolers. Both solve the same problem — avoiding the overhead of creating new connections — but they scale differently.
    External poolers support different __pooling modes__ that determine when connections are returned to the pool: default_pool_size = PostgreSQL max_connections / number_of_pools. If PostgreSQL allows 100 connections and you have 5 user + database combinations (pools), each pool gets 20 connections. The reserve_pool handles burst traffic — set it to 10-25% of default_pool_size.', }, { - lead: 'max_client_conn', - text: "determines how many application connections PgBouncer accepts. This can be much higher than PostgreSQL's `max_connections` because PgBouncer queues requests when all server connections are busy. Set this based on peak concurrent application connections with headroom for spikes.", + lead: 'max_client_conn', + text: 'Determines how many application connections PgBouncer accepts. This can be much higher than PostgreSQL\'s max_connections because PgBouncer queues requests when all server connections are busy. Set this based on peak concurrent application connections with headroom for spikes.', }, ]} />
    max_client_conn', 'default_pool_size', 'PostgreSQL max_connections'], }, tbody: { tr: [ @@ -298,7 +313,6 @@ Code: PgBouncer configuration for a typical web application. }, ], }, - figure: 'PgBouncer sizing by deployment scale.', }} /> @@ -314,15 +328,21 @@ This is why the practical formula substitutes P99 query time for average: you wa The practical formula for application pool sizing: -```text + pool_size = ceil(peak_qps × p99_query_time × burst_factor) -``` + For an application instance handling 50 queries per second at peak, with P99 query time of 100ms and a 2× burst factor: `ceil(50 × 0.1 × 2) = 10` connections per instance.
    __Common anti-patterns__ undermine even correct calculations: pool_size = 100 "just in case" means 10 instances create 1000 connections. Size based on actual need.', + title: 'Oversized pools:', + lead: 'Setting pool_size = 100 "just in case" means 10 instances create 1000 connections. Size based on actual need.', }, { - lead: 'No max lifetime', - text: 'Connections held forever accumulate memory leaks and stale state. Recycle every 30 minutes.', + title: 'No max lifetime:', + lead: 'Connections held forever accumulate memory leaks and stale state. Recycle every 30 minutes.', }, { - lead: 'No idle timeout', - text: 'Idle connections waste resources and hide leaks. Timeout after 10 minutes of inactivity.', + title: 'No idle timeout:', + lead: 'Idle connections waste resources and hide leaks. Timeout after 10 minutes of inactivity.', }, ]} /> @@ -468,8 +490,10 @@ Code: HikariCP configuration for PostgreSQL. Regardless of language or pool library, the same principles apply. These settings represent battle-tested defaults that work for most applications:
    @@ -518,7 +541,7 @@ With pools configured, the next question is: how do you know if they're working? Connection saturation doesn't announce itself with a single metric. It's a constellation of signals that, together, tell you whether your system has headroom or is approaching collapse. The goal is catching saturation early — before "too many connections" errors appear in logs. cl_waiting. In application pools, check waitingCount or equivalent. Any sustained queue indicates saturation — the pool can\'t keep up with demand.', }, { lead: 'Idle in transaction', - text: 'connections are the silent killers. They hold connections hostage without doing work. Query for sessions stuck in this state for more than a minute — these are usually application bugs (missing commits, unclosed transactions in error paths).', + text: 'Connections are the silent killers. They hold connections hostage without doing work. Query for sessions stuck in this state for more than a minute — these are usually application bugs (missing commits, unclosed transactions in error paths).', }, ]} /> @@ -570,8 +594,14 @@ Code: Find idle-in-transaction sessions. Each metric has different thresholds for when to pay attention versus when to wake someone up at 3am. These thresholds are starting points — adjust based on your application's tolerance for latency and your database's capacity:
    @@ -619,6 +648,7 @@ The progression from healthy to critical follows a predictable pattern: @@ -630,7 +660,10 @@ The progression from healthy to critical follows a predictable pattern: Connection exhaustion rarely happens in isolation. It cascades. Understanding the stages helps you recognize where you are in the failure progression and what interventions are still possible. +Once you reach Stage 5, the instinct is to restart everything at once. Resist it. A "thundering herd" recovery — where every application instance reconnects simultaneously — recreates the exact conditions that caused the collapse. Each instance tries to fill its connection pool, slamming the database with hundreds of concurrent connection requests before it has finished cleaning up the previous mess. The recovery playbook matters as much as the prevention strategy, and it follows a specific sequence: + SELECT pg_terminate_backend(pid) FROM pg_stat_activity WHERE state = \'idle in transaction\' AND state_change < now() - interval \'5 minutes\'; to kill stuck sessions.', }, { lead: 'Restart applications gradually', @@ -672,15 +707,15 @@ Connection exhaustion rarely happens in isolation. It cascades. Understanding th }, { lead: 'Monitor during recovery', - text: 'Watch `pg_stat_activity` connection count as you add instances. Stop scaling if you approach 80% utilization.', + text: 'Watch pg_stat_activity connection count as you add instances. Stop scaling if you approach 80% utilization.', }, ]} /> -Prevention requires defense at multiple layers: +Recovery gets you back to operational, but it doesn't prevent the next incident. The cascade pattern repeats because the underlying architecture lacks defensive layers — there's nothing between "working fine" and "total collapse." Building resilience means adding checkpoints where the system can absorb pressure instead of transmitting it. Each layer below addresses a different failure mode, and they compound: connection limits cap the blast radius, circuit breakers stop the bleeding, backpressure prevents the bleeding from starting, and graceful degradation keeps users served even when the database is under strain. @@ -785,29 +821,35 @@ The key insights from this article: + classes={{ + wrapper: "sm:ml-6 sm:mr-12", + }} +> + + Each connection costs 5-10MB of memory, consumes CPU through context switching, and competes for + shared resources. The counterintuitive truth: fewer connections often means better performance. + Keep `max_connections` low and use pooling. + + + PgBouncer (or similar external poolers) lets you scale application instances without + proportionally increasing database connections. Transaction pooling mode handles most workloads + well. + + + Little's Law ($$L = \lambda \times W$$) gives you a starting point. Account for variance with a + burst factor. Anti-patterns like oversized pools ("just in case") create more problems than they + solve. + + + By the time you see "too many connections" errors, you're already in a cascade. Set alerts on + the utilization thresholds discussed earlier. Watch for idle-in-transaction sessions — they're + connection leaks waiting to bite you. + + + Circuit breakers, retry budgets, and backpressure turn connection exhaustion from a hard failure + into a soft degradation. Build these patterns before you need them. + + The goal isn't to handle unlimited connections — it's to handle connection constraints gracefully, so saturation causes slowdowns, not outages. @@ -815,6 +857,8 @@ The goal isn't to handle unlimited connections — it's to handle connection con Connection management is about building slack into the system. A database running at 90% connection capacity has no room for variance, spikes, or slow queries. Target 50-70% utilization to leave room for reality to differ from expectations. +
    + ## Reference Queries These SQL queries are useful for monitoring and debugging PostgreSQL connection issues. Run them against your database during incidents or as part of regular health checks. From a4429d4d7b6972bb0f223830f9654577800bc3e4 Mon Sep 17 00:00:00 2001 From: Kevin Brown Date: Wed, 18 Mar 2026 22:46:35 +0300 Subject: [PATCH 04/20] Update styling in List and Tables for private-networking-dns-routing-tls-debugging article --- .../index.mdx | 11 +- .../pdf.mdx | 182 ++++++++++++------ 2 files changed, 135 insertions(+), 58 deletions(-) diff --git a/src/content/articles/private-networking-dns-routing-tls-debugging/index.mdx b/src/content/articles/private-networking-dns-routing-tls-debugging/index.mdx index d7a8b063..188a2c27 100644 --- a/src/content/articles/private-networking-dns-routing-tls-debugging/index.mdx +++ b/src/content/articles/private-networking-dns-routing-tls-debugging/index.mdx @@ -25,7 +25,7 @@ The migration was supposed to take 30 minutes. We were moving from a publicly-ac Three days and four distinct failures later, we had a working connection — and a debugging playbook we've used for every migration since. -Private networking isn't "the same thing, but internal." It's a fundamentally different debugging domain where familiar tools give unfamiliar results and "connection refused" could mean six different things depending on which layer actually failed. The trick is knowing where to look. +Private networking isn't "the same thing, but internal." It's a fundamentally different debugging domain where familiar tools give unfamiliar results and "connection refused" could mean six different things depending on which layer actually failed. The trick is knowing where to look. ## The Debugging Playbook @@ -34,7 +34,7 @@ Every private networking issue follows the same debugging sequence. Each layer d The sequence: __DNS → routing → connectivity → TLS → application__.
    getent hosts \ (sees what your app sees) or `dig @\ (bypasses cache)', + 'getent hosts \ (sees what your app sees) or dig @\ (bypasses cache)', 'Returns expected private IP, not public', ], }, @@ -98,7 +98,10 @@ The most common DNS failure: a private hosted zone exists, the records are corre Split-horizon DNS creates similar confusion. You have a public zone for `api.example.com` that resolves to a public load balancer, and a private zone for the same name that resolves to an internal endpoint. If the private zone isn't associated with your VPC, queries fall through to public DNS and return the public IP. Traffic then goes out through NAT, across the internet, and back in — adding latency and potentially failing security group checks.
    This article assumes familiarity with cloud networking fundamentals: VPCs, subnets, route tables, and basic Linux command-line tools. + +This article assumes familiarity with cloud networking fundamentals: VPCs, subnets, route tables, and basic Linux command-line tools. + Moving workloads to private networks makes sense from a security perspective — no public IPs, no internet exposure, traffic stays within your cloud provider's boundaries. But private networking introduces failure modes that don't exist with public connectivity. DNS resolution that worked fine over the internet fails with private endpoints. Routing that seemed automatic now requires explicit configuration. TLS certificates that validated publicly get rejected privately. @@ -54,7 +57,10 @@ Kubernetes adds another layer. CoreDNS intercepts queries and applies search dom The most common private DNS failures:
    @@ -110,7 +118,8 @@ VPC endpoints add another wrinkle. When you create an interface endpoint for an For hybrid environments connecting AWS to on-premises networks, Route 53 Resolver endpoints bridge the gap. Inbound endpoints let on-premises DNS servers forward queries to AWS. Outbound endpoints let AWS workloads resolve on-premises DNS names. The configuration involves security groups, subnet placement, and forwarding rules — each a potential failure point.
    + + Routes 0.0.0.0/0 to the internet (requires public IP or NAT) + + + Outbound internet from private subnets (the NAT gateway itself must be in a public subnet) + + + Direct connection to another VPC's CIDR (requires routes on both sides, no CIDR overlap)[^peering] + + + Hub-and-spoke for multiple VPCs (handles transitive routing)[^tgw] + + + Routes to AWS services via prefix lists[^prefix] + + [^peering]: VPC Peering creates a direct point-to-point connection between two VPCs. If you have 4 VPCs and want them all to communicate, you must create 6 separate peering connections (a full mesh). Critically, peering does _not_ support transitive routing — if VPC A peers with B, and B peers with C, A cannot reach C through B. Each pair needs its own peering connection. For 10 VPCs, you'd need 45 peering links; for 100 VPCs, nearly 5,000. @@ -172,30 +193,31 @@ Route selection follows the "most specific route wins" principle. If you have ro Routing failures in private networks present differently than on traditional networks. You won't see clear error messages — timeouts look identical whether the cause is a missing route, a security group block, or a service that's down. This ambiguity makes systematic debugging essential. These are the patterns you'll encounter:
    @@ -216,7 +237,8 @@ One gotcha that catches people: MTU issues. VPNs and tunnels often have lower MT I work through connectivity issues in a fixed order — each layer must pass before moving to the next:
    @@ -416,8 +446,9 @@ __Private CAs__ (AWS Private CA, HashiCorp Vault, step-ca, CFSSL[^cfssl]) issue [^privateca]: Public CAs like Let's Encrypt will not issue certificates with the `Basic Constraints: CA:TRUE` flag to third parties. This flag indicates that the certificate can sign other certificates — giving you the power to issue publicly trusted certificates for any domain. Allowing this would completely bypass their security controls. Private CAs exist precisely because you need this capability for internal certificate management.
    /etc/ssl/certs/ and run `update-ca-certificates'], }, { th: 'macOS', td: [ - 'Run `security add-trusted-cert -d -r trustRoot -k /Library/Keychains/System.keychain ca.crt`', + 'Run security add-trusted-cert -d -r trustRoot -k /Library/Keychains/System.keychain ca.crt', ], }, { th: 'Node.js', - td: ['Set `NODE_EXTRA_CA_CERTS=/path/to/ca.crt`'], + td: ['Set NODE_EXTRA_CA_CERTS=/path/to/ca.crt'], }, { th: 'Python', - td: ['Set `REQUESTS_CA_BUNDLE=/path/to/ca.crt`'], + td: ['Set REQUESTS_CA_BUNDLE=/path/to/ca.crt'], }, { th: 'Java', td: [ - 'Import with `keytool -import -trustcacerts -file ca.crt -keystore truststore.jks`', + 'Import with keytool -import -trustcacerts -file ca.crt -keystore truststore.jks', ], }, { th: 'Go', - td: ['Set `SSL_CERT_FILE=/path/to/ca-bundle.crt`'], + td: ['Set SSL_CERT_FILE=/path/to/ca-bundle.crt'], }, ], }, - figure: 'CA trust configuration by runtime.', }} /> @@ -472,7 +502,10 @@ The previous sections covered individual failure modes. This section puts them t Every private networking issue follows the same debugging sequence. Each layer depends on the previous one working correctly — there's no point checking TLS if packets aren't reaching the server. div]:!pt-2 mb-6", + }} items={[ { - text: 'Confirm all source IPs can reach the private endpoint', + lead: 'Confirm all source IPs can reach the private endpoint', }, { - text: 'Check that latency improved (it should — no internet hops)', + lead: 'Check that latency improved (it should — no internet hops)', }, { - text: 'Verify VPC flow logs show no public IP traffic to the service', + lead: 'Verify VPC flow logs show no public IP traffic to the service', }, { - text: 'Disable or restrict the public endpoint', + lead: 'Disable or restrict the public endpoint', }, { - text: 'Update monitoring and runbooks for the new architecture', + lead: 'Update monitoring and runbooks for the new architecture', }, ]} /> @@ -586,18 +623,52 @@ __Post-migration verification:__ When services span multiple VPCs, you have three main options: peering, transit gateway, or Private Link. The choice depends on your topology and constraints. -__VPC Peering__ works for simple topologies with 2-3 VPCs. It's a direct connection — fast and cheap. The limitations: no transitive routing (if A peers with B and B peers with C, A can't reach C through B), CIDRs can't overlap, and cross-region peering adds latency. Both sides need routes added to their route tables. For DNS, enable resolution in the peering connection settings and associate private hosted zones with both VPCs. - -__Transit Gateway__ is the right choice when you have many VPCs or need transitive routing. Each VPC connects once to the gateway, which acts as a central router. You can also attach VPN connections or Direct Connect[^directconnect], giving on-premises networks access to all VPCs. The complexity is in route table management — transit gateway has its own route tables separate from VPC route tables, and misconfiguration causes blackholes. + + + Works for simple topologies with 2-3 VPCs. It's a direct connection — fast and cheap. The + limitations: no transitive routing (if A peers with B and B peers with C, A can't reach C + through B), CIDRs can't overlap, and cross-region peering adds latency. Both sides need routes + added to their route tables. For DNS, enable resolution in the peering connection settings and + associate private hosted zones with both VPCs. + + + Is the right choice when you have many VPCs or need transitive routing. Each VPC connects once + to the gateway, which acts as a central router. You can also attach VPN connections or Direct + Connect[^directconnect], giving on-premises networks access to all VPCs. The complexity is in + route table management — transit gateway has its own route tables separate from VPC route + tables, and misconfiguration causes blackholes. + + + (AWS PrivateLink or equivalent) exposes a specific service to other VPCs without any routing + changes. The provider creates a Network Load Balancer and an endpoint service. Consumers create + interface endpoints that appear as ENIs for AWS or the equivalent on other cloud providers in + their VPC. This approach handles CIDR overlaps gracefully — the consumer never sees the + provider's IP space. The tradeoff: it's service-by-service rather than network-wide + connectivity. + + + Provides encrypted connectivity over the public internet — useful when dedicated connections + aren't available or cost-justified. Site-to-site VPN connects on-premises networks to cloud + VPCs; client VPN gives individual users access. VPN's main drawbacks are latency (traffic still + traverses the internet) and bandwidth limits. Whether VPN supports transitive routing depends on + your setup: a VPN attached to a transit gateway gets transitive access to all attached VPCs, + but a VPN attached directly to a single VPC doesn't. + + [^directconnect]: Direct Connect is AWS's dedicated physical network connection between your data center and AWS, bypassing the public internet for lower latency and more consistent bandwidth. Other providers offer equivalents: Google Cloud has Cloud Interconnect (Dedicated or Partner), Azure has ExpressRoute, and OpenStack environments typically use provider-specific solutions or MPLS circuits configured through the network operator. -__Private Link__ (AWS PrivateLink or equivalent) exposes a specific service to other VPCs without any routing changes. The provider creates a Network Load Balancer and an endpoint service. Consumers create interface endpoints that appear as ENIs for AWS or the equivalent on other cloud providers in their VPC. This approach handles CIDR overlaps gracefully — the consumer never sees the provider's IP space. The tradeoff: it's service-by-service rather than network-wide connectivity. - -__VPN__ provides encrypted connectivity over the public internet — useful when dedicated connections aren't available or cost-justified. Site-to-site VPN connects on-premises networks to cloud VPCs; client VPN gives individual users access. VPN's main drawbacks are latency (traffic still traverses the internet) and bandwidth limits. Whether VPN supports transitive routing depends on your setup: a VPN attached to a transit gateway gets transitive access to all attached VPCs, but a VPN attached directly to a single VPC doesn't. -
    From b1cc27a7637f312590beca47e0794d9ce5750e51 Mon Sep 17 00:00:00 2001 From: Kevin Brown Date: Wed, 18 Mar 2026 23:08:46 +0300 Subject: [PATCH 05/20] Update styling in List and Tables for prometheus-high-cardinality-metrics-label-design article --- .../index.mdx | 38 ++++++----- .../pdf.mdx | 63 +++++++++++-------- 2 files changed, 62 insertions(+), 39 deletions(-) diff --git a/src/content/articles/prometheus-high-cardinality-metrics-label-design/index.mdx b/src/content/articles/prometheus-high-cardinality-metrics-label-design/index.mdx index 2fe8861d..5e78f393 100644 --- a/src/content/articles/prometheus-high-cardinality-metrics-label-design/index.mdx +++ b/src/content/articles/prometheus-high-cardinality-metrics-label-design/index.mdx @@ -12,13 +12,15 @@ featured: true *[OOM]: Out of Memory *[TSDB]: Time Series Database -> Just add a label for debugging. +> Just add a label for debugging +> +> — A senior engineer during sprint planning Those five words killed our monitoring during the worst possible moment. A team I worked with instrumented their API with response time histograms and added labels for endpoint, status code, and `user_id` — that last one "for debugging." With 50 endpoints, 10 status codes, and 100,000 users, they'd created 50 million potential time series. Initially, only active users generated metrics. Maybe 10,000 series. Prometheus hummed along. Over months, more users became active. Memory usage crept up until a marketing campaign drove a traffic spike. Memory jumped. Prometheus OOM'd. Monitoring went dark during the incident they needed to debug. -The fix took five minutes: remove `user_id` from labels, add it to traces instead. The lesson took three days of firefighting to learn. +The fix took five minutes: remove user_id from labels, add it to traces instead. The lesson took three days of firefighting to learn. A single unbounded label can destroy your Prometheus deployment. User IDs, request IDs, email addresses, IP addresses — any label that grows with your data will eventually exhaust memory. Design labels for known, bounded sets of values. @@ -26,12 +28,16 @@ A single unbounded label can destroy your Prometheus deployment. User IDs, reque ## The Math That Kills Your Prometheus -Every unique combination of metric name and label values creates a separate time series. Labels don't add — they __multiply__. +Every unique combination of metric name and label values creates a separate time series. Labels don't add — they _multiply_. Consider a basic HTTP metrics setup: 5 HTTP methods × 20 endpoints × 10 status codes = 1,000 series. Manageable. Add a `user_id` label with 100,000 possible values? Now you're looking at 10 billion potential series. The following table shows how quickly labels multiply:
    http_method`', + th: 'http_method', td: [ - 'GET, POST, PUT, DELETE, PATCH', + 'GET, POST, PUT, DELETE, PATCH', '~7', 'Fixed set defined by HTTP spec', ], }, { - th: 'environment`', + th: 'environment', td: [ 'production, staging, development', '3', @@ -93,14 +104,14 @@ If you can list them exhaustively, it's probably safe. If the value set grows wi ], }, { - th: 'status_class`', + th: 'status_class', td: ['2xx, 3xx, 4xx, 5xx', '4', 'Bucketed from individual codes', ], }, { - th: '`region`', + th: 'region', td: [ 'us-east-1, us-west-2, eu-west-1', '~10', @@ -108,7 +119,7 @@ If you can list them exhaustively, it's probably safe. If the value set grows wi ], }, { - th: '`service`', + th: 'service', td: [ 'api, worker, scheduler', '~20', @@ -117,15 +128,15 @@ If you can list them exhaustively, it's probably safe. If the value set grows wi }, ], }, - figure: 'Good label examples with bounded cardinality.', }} /> Bad labels grow without bound:
    diff --git a/src/content/articles/prometheus-high-cardinality-metrics-label-design/pdf.mdx b/src/content/articles/prometheus-high-cardinality-metrics-label-design/pdf.mdx index 876c2853..5897025d 100644 --- a/src/content/articles/prometheus-high-cardinality-metrics-label-design/pdf.mdx +++ b/src/content/articles/prometheus-high-cardinality-metrics-label-design/pdf.mdx @@ -20,18 +20,18 @@ import singleUnboundedLabelDiagram from "./diagrams/a-single-unbounded-label-tra *[UUID]: Universally Unique Identifier *[WAL]: Write-Ahead Log -"Just add a label for debugging" is the most dangerous sentence in observability. +_"Just add a label for debugging"_ is the most dangerous sentence in observability. Every unique combination of metric name and label values creates a separate time series in Prometheus. A metric with three labels — each having 100 possible values — creates up to one million time series (100 × 100 × 100). Prometheus stores each time series independently, keeping recent data in memory. Cardinality isn't about the number of metrics you have; it's about the combinatorial explosion of label values. I watched a team learn this the hard way. They instrumented their API with response time histograms and added labels for endpoint, status code, and `user_id` "for debugging." With 50 endpoints, 10 status codes, and 100,000 users, they'd created 50 million potential time series. Initially, only active users generated metrics — maybe 10,000 series. Prometheus hummed along. Over months, more users became active. Memory usage crept up. Then a marketing campaign drove a traffic spike. Memory usage jumped. Prometheus OOM'd. Monitoring went dark during the incident. -The fix took five minutes: remove `user_id` from the metric labels, add it to traces instead, implement cardinality limits. Prometheus stabilized at 50,000 series. The lesson took three days of firefighting to learn: labels are multiplicative, not additive. - A single unbounded label can destroy your Prometheus deployment. User IDs, request IDs, email addresses, IP addresses — any label that grows with your data will eventually exhaust memory. Design labels for known, bounded sets of values. +The fix took five minutes: remove `user_id` from the metric labels, add it to traces instead, implement cardinality limits. Prometheus stabilized at 50,000 series. The lesson took three days of firefighting to learn: labels are multiplicative, not additive. + ## Understanding Cardinality ### Time Series Math @@ -53,7 +53,8 @@ Three series. Now multiply: 5 HTTP methods × 20 endpoints × 10 status codes = The math is straightforward but the implications aren't intuitive. Here's how quickly things escalate:
    @@ -101,7 +104,7 @@ Queries compound the problem. A query like `sum(rate(http_requests_total[5m])) b The failure modes are predictable once you understand the architecture:
    http_method`', + th: 'http_method', td: [ - 'GET, POST, PUT, DELETE, PATCH', + 'GET, POST, PUT, DELETE, PATCH', '~7', 'Fixed set defined by HTTP spec', ], }, { - th: 'environment`', + th: 'environment', td: [ 'production, staging, development', '3', @@ -170,14 +178,14 @@ Good labels share four characteristics: bounded cardinality (you know the finite ], }, { - th: 'status_class`', + th: 'status_class', td: ['2xx, 3xx, 4xx, 5xx', '4', 'Bucketed from individual codes', ], }, { - th: '`region`', + th: 'region', td: [ 'us-east-1, us-west-2, eu-west-1', '~10', @@ -185,7 +193,7 @@ Good labels share four characteristics: bounded cardinality (you know the finite ], }, { - th: '`service`', + th: 'service', td: [ 'api, worker, scheduler', '~20', @@ -194,15 +202,15 @@ Good labels share four characteristics: bounded cardinality (you know the finite }, ], }, - figure: 'Good label examples with bounded cardinality.', }} /> Bad labels grow without bound. User IDs scale with your user base. Request IDs are unique per request — literally infinite cardinality. IP addresses span billions of possibilities. Error messages are free-form strings. Any of these as labels will eventually kill your Prometheus instance.
    @@ -480,7 +487,10 @@ The raw-data approach has trade-offs. You're storing higher cardinality in Prome [^relabel-storage]: This statement requires nuance. If you use `metric_relabel_configs` to transform labels (rather than recording rules), Prometheus applies the relabeling __before__ writing to the TSDB — only the normalized version hits disk. However, the high-cardinality data still creates operational issues during ingestion. For the duration of each scrape, Prometheus must hold the raw, un-normalized data in memory to process the relabeling rules. If thousands of targets send hundreds of unique status codes, this transient data can spike memory and CPU usage during the scrape phase. Complex regex patterns across millions of samples per second are CPU-intensive; if normalization logic is too complex, Prometheus may struggle to keep up, causing scrape gaps. The key distinction: __persistent storage__ sees only normalized data, but __ingestion memory__ briefly sees everything.
    Date: Thu, 19 Mar 2026 00:09:26 +0300 Subject: [PATCH 06/20] Update styling in List and Tables for rate-limiting-token-bucket-leaky-bucket-implementation article --- .../index.mdx | 32 +++-- .../pdf.mdx | 117 +++++++++++------- 2 files changed, 95 insertions(+), 54 deletions(-) diff --git a/src/content/articles/rate-limiting-token-bucket-leaky-bucket-implementation/index.mdx b/src/content/articles/rate-limiting-token-bucket-leaky-bucket-implementation/index.mdx index 4138d8fe..ca5e3dc5 100644 --- a/src/content/articles/rate-limiting-token-bucket-leaky-bucket-implementation/index.mdx +++ b/src/content/articles/rate-limiting-token-bucket-leaky-bucket-implementation/index.mdx @@ -18,15 +18,16 @@ I watched this play out at a company that implemented per-IP rate limiting at 10 The fix? They combined __sliding window counter__ (to eliminate the boundary problem) with __token bucket__ for burst control. Same traffic spike: requests distributed smoothly, everyone got served, the backend hummed along at capacity without falling over. -The naive approach—"block anything over N requests"—fails because it treats rate limiting as a wall instead of a valve. The algorithms matter, but __where__ you limit and __how__ you identify clients matter more. +The naive approach — _"block anything over N requests"_ — fails because it treats rate limiting as a wall instead of a valve. The algorithms matter, but _where_ you limit and _how_ you identify clients matter more. ## Where to Rate Limit Most rate limiting articles jump straight to algorithms. But the strategic question — where in your stack to enforce limits — often matters more than which algorithm you choose.
    -But rate limiting has limits. It __slows__ attacks; it doesn't prevent them. Determined attackers distribute across IPs and rotate credentials. Rate limiting buys time — authentication, authorization, and WAF rules provide actual security. Similarly, rate limiting __sheds__ load; it doesn't handle it. You still need scaling for legitimate traffic. And rate limiting __helps__ availability; it doesn't guarantee it. Backend failures still cause errors regardless of how well you've throttled incoming requests. +But rate limiting has limits. It _slows_ attacks; it doesn't prevent them. Determined attackers distribute across IPs and rotate credentials. Rate limiting buys time — authentication, authorization, and WAF rules provide actual security. Similarly, rate limiting _sheds_ load; it doesn't handle it. You still need scaling for legitimate traffic. And rate limiting _helps_ availability; it doesn't guarantee it. Backend failures still cause errors regardless of how well you've throttled incoming requests. ### Algorithm Overview Four algorithms dominate production rate limiting, each with different tradeoffs:
    @@ -145,35 +149,39 @@ Token bucket is the most versatile algorithm for API rate limiting. It allows bu ## Where to Rate Limit -Before choosing an algorithm, decide __where__ in your stack to enforce limits. Each layer has different tradeoffs: +Before choosing an algorithm, decide _where_ in your stack to enforce limits. Each layer has different tradeoffs:
    @@ -318,7 +326,8 @@ Code: Token bucket core logic. Here's how traffic patterns play out with a 10-token bucket refilling at 1 token per second:
    @@ -392,7 +406,8 @@ The key difference: token bucket serves bursts immediately then makes you wait. Use leaky bucket when you need to protect a downstream service that can't handle bursts — like a payment processor with strict per-second limits. The added latency is the tradeoff for guaranteed smooth output.
    @@ -470,7 +486,8 @@ For a complete TypeScript implementation with Redis support, see the [sliding wi The rate limiting key — how you identify who's making requests — is as important as the algorithm. Choose wrong, and you'll either punish legitimate users or fail to stop abuse.
    RateLimit-Limit', + td: ['Maximum requests allowed', '100'], }, { - th: '`RateLimit-Remaining`', - td: ['Requests left in window', '`47`'], + th: 'RateLimit-Remaining', + td: ['Requests left in window', '47'], }, { - th: '`RateLimit-Reset`', - td: ['Seconds until reset', '`30`'], + th: 'RateLimit-Reset', + td: ['Seconds until reset', '30'], }, ], }, - figure: 'IETF rate limit headers.', }} /> @@ -656,7 +674,10 @@ For 429 responses, always include `Retry-After` (RFC 7231) telling the client wh ### Response Codes Date: Thu, 19 Mar 2026 00:45:06 +0300 Subject: [PATCH 07/20] Update styling in List and Tables for release-quality-gates-automated-deployment-validation article --- .../index.mdx | 20 ++-- .../pdf.mdx | 98 +++++++++++++------ 2 files changed, 83 insertions(+), 35 deletions(-) diff --git a/src/content/articles/release-quality-gates-automated-deployment-validation/index.mdx b/src/content/articles/release-quality-gates-automated-deployment-validation/index.mdx index 91d3da23..0045637b 100644 --- a/src/content/articles/release-quality-gates-automated-deployment-validation/index.mdx +++ b/src/content/articles/release-quality-gates-automated-deployment-validation/index.mdx @@ -13,16 +13,19 @@ I once helped a team that had implemented the "full stack" of quality gates: tes What happened? The security scanner added new rules and flagged a dependency vulnerability from 2019 that wasn't exploitable in their context. All deployments blocked. Someone added an exception. Then coverage dropped 0.1% because a refactor deleted dead code — blocked again. Exception added. Performance gate triggered on a cold-start test run — exception. By month three, engineers assumed every gate failure was another false positive and bypassed without investigating. -Here's the paradox: a gate with a 10% false positive rate will block legitimate deployments constantly, training engineers to bypass it. A gate that never fires provides no protection. Somewhere between "block everything" and "block nothing" is the sweet spot where gates catch real failures without becoming obstacles. +Here's the paradox: a gate with a 10% false positive rate will block legitimate deployments constantly, training engineers to bypass it. A gate that never fires provides no protection. Somewhere between "block everything" and "block nothing" is the sweet spot where gates catch real failures without becoming obstacles. The measure of a good gate isn't how many deployments it blocks — it's how many real incidents it prevents relative to how many good deployments it delays. ## What Makes a Gate Worth Having -Not all checks belong in a deployment pipeline, and not all pipeline checks should block deployments. A well-designed gate has four characteristics: it's __actionable__, __deterministic__, __fast__, and __proportional__. +Not all checks belong in a deployment pipeline, and not all pipeline checks should block deployments. A well-designed gate has four characteristics: it's _actionable_, _deterministic_, _fast_, and _proportional_. Your first step: audit your current gates. For each blocking gate, check its bypass rate over the last month. Any gate with a bypass rate above 20% is a candidate for demotion to advisory status — or removal entirely. + +The goal isn't zero-risk deployments — that leads to zero deployments. The goal is catching the failures that matter while maintaining the velocity your business requires. diff --git a/src/content/articles/release-quality-gates-automated-deployment-validation/pdf.mdx b/src/content/articles/release-quality-gates-automated-deployment-validation/pdf.mdx index 7818d029..f8c5bb29 100644 --- a/src/content/articles/release-quality-gates-automated-deployment-validation/pdf.mdx +++ b/src/content/articles/release-quality-gates-automated-deployment-validation/pdf.mdx @@ -25,26 +25,29 @@ Here's the paradox: a gate with a 10% false positive rate will block legitimate Consider a team that implemented the "full stack" of quality gates: test pass rate, code coverage thresholds, security scans, and performance benchmarks. On day one, everything's green and ships in five minutes. A month later, the security scanner adds new rules and flags a dependency vulnerability from 2019 that's not exploitable in their context. All deployments blocked. Someone adds an exception. Then coverage drops 0.1% because a refactor deleted dead code — blocked again. Exception added. Performance gate triggers on a cold-start test run — exception. -Within three months, the gate configuration had so many exceptions it caught nothing. Worse, when a gate __did__ fire, engineers assumed it was another false positive and bypassed without investigating. +Within three months, the gate configuration had so many exceptions it caught nothing. Worse, when a gate _did_ fire, engineers assumed it was another false positive and bypassed without investigating. They rebuilt the system with a different philosophy: __required gates__ (critical tests, security CVEs with CVSS 9+) versus __advisory gates__ (coverage trends, performance baselines). Required gates blocked deployments. Advisory gates logged warnings and alerted, but didn't block. False positives dropped 90%, and when a required gate fired, people actually investigated because they trusted it meant something. The measure of a good gate isn't how many deployments it blocks — it's how many real incidents it prevents relative to how many good deployments it delays. Quality gates are probabilistic safety nets, not deterministic guarantees. The goal isn't zero risk; it's catching the failures that matter while letting good deployments through quickly. -This article covers gate design principles, implementation across pre- and post-deployment phases, configuration patterns for progressive delivery, and safe bypass mechanisms. - The most dangerous quality gate is one with so many false positives that teams stop trusting it. Gate fatigue leads to bypass culture, which means real failures slip through. Tune for precision over recall — it's better to miss some problems than to cry wolf constantly. +This article covers gate design principles, implementation across pre- and post-deployment phases, configuration patterns for progressive delivery, and safe bypass mechanisms. + ## Gate Design Principles ### What Makes a Good Gate -Not all checks belong in a deployment pipeline, and not all pipeline checks should block deployments. A well-designed gate has four characteristics: it's __actionable__, __deterministic__, __fast__, and __proportional__. +Not all checks belong in a deployment pipeline, and not all pipeline checks should block deployments. A well-designed gate has four characteristics: it's _actionable_, _deterministic_, _fast_, and _proportional_. -The key distinction: __blocking gates must have high precision__. If a gate blocks deployments, every failure should represent a real problem worth stopping for. Gates with lower precision — security scanners that flag unexploitable vulnerabilities, performance tests with inherent variance, integration tests that depend on external services — should be advisory. They surface useful information, but they don't stop the pipeline. +The key distinction: _blocking gates must have high precision_. If a gate blocks deployments, every failure should represent a real problem worth stopping for. Gates with lower precision — security scanners that flag unexploitable vulnerabilities, performance tests with inherent variance, integration tests that depend on external services — should be advisory. They surface useful information, but they don't stop the pipeline. ### Gate Anti-Patterns Some gate configurations sound reasonable but cause problems in practice:
    @@ -654,7 +684,8 @@ Code: Argo Rollouts business metrics template. The templates above cover the most common gate patterns: absolute thresholds for error rates and latency, and baseline comparisons for business metrics. Together with the progressive rollout strategy, they form a complete automated validation pipeline. The failure limits and iteration counts let you tune sensitivity — tighter limits catch problems faster but are more prone to false positives from metric noise.
    The ultimate test of a quality gate system: when a gate fails, do engineers investigate or bypass? If they investigate, you've built trust. If they bypass, you've built friction. Design for the former. + +Measure gate effectiveness by the ratio of incidents prevented to false positives generated. If that ratio is high, your gates are earning their keep. If it's low, you're paying in friction for safety you're not receiving. Track it, tune it, and be willing to remove gates that aren't carrying their weight. From af1fb8e462a4cbcb62a84aa08f06d44bc9ce811d Mon Sep 17 00:00:00 2001 From: Kevin Brown Date: Thu, 19 Mar 2026 01:45:59 +0300 Subject: [PATCH 08/20] Update styling in List and Tables for reverse-engineering-documentation-legacy-systems article --- src/components/Icon/icons/dollar.astro | 32 +++++ .../index.mdx | 60 ++++++++- .../pdf.mdx | 122 ++++++++++++------ 3 files changed, 164 insertions(+), 50 deletions(-) create mode 100644 src/components/Icon/icons/dollar.astro diff --git a/src/components/Icon/icons/dollar.astro b/src/components/Icon/icons/dollar.astro new file mode 100644 index 00000000..4375ab3d --- /dev/null +++ b/src/components/Icon/icons/dollar.astro @@ -0,0 +1,32 @@ +--- +export type Props = { + color: string + classes?: string + size: number + accessible: boolean + focusable: boolean + isListMarker?: boolean + id?: string +} + +const { color, classes, size, accessible, focusable, isListMarker, id } = Astro.props +--- + + + {accessible && Dollar Icon} + + diff --git a/src/content/articles/reverse-engineering-documentation-legacy-systems/index.mdx b/src/content/articles/reverse-engineering-documentation-legacy-systems/index.mdx index b5b93f4d..65b9d68e 100644 --- a/src/content/articles/reverse-engineering-documentation-legacy-systems/index.mdx +++ b/src/content/articles/reverse-engineering-documentation-legacy-systems/index.mdx @@ -11,9 +11,9 @@ featured: true *[ADR]: Architecture Decision Record -You've inherited a system with a README that was last updated three years ago. The architecture diagrams reference services that no longer exist. The wiki has seventeen conflicting pages about deployment, and no one's sure which ones are current. The original architects left two reorganizations ago. +You've inherited a system with a `README` that was last updated three years ago. The architecture diagrams reference services that no longer exist. The wiki has seventeen conflicting pages about deployment, and no one's sure which ones are current. The original architects left two reorganizations ago. -Here's the uncomfortable truth: outdated documentation isn't just unhelpful — it's actively harmful. When a new team member reads that architecture diagram and forms a mental model of how the system works, they're building on outdated assumptions that will take months to unlearn. When an on-call engineer follows a runbook during an incident, they might make things worse by following steps that no longer apply. +Here's the uncomfortable truth: outdated documentation isn't just unhelpful — it's actively harmful. When a new team member reads that architecture diagram and forms a mental model of how the system works, they're building on outdated assumptions that will take months to unlearn. When an on-call engineer follows a runbook during an incident, they might make things worse by following steps that no longer apply. But there's good news. The codebase itself contains more reliable documentation than any wiki page ever will. Git history records what changed, when, and often why. Tests that pass demonstrate working behavior that prose documentation might get wrong. And the engineers who've kept the system alive hold knowledge that's never been written down. @@ -161,16 +161,29 @@ This knowledge has an expiration date: when the person leaves. Extracting it req For __architectural knowledge__: @@ -178,16 +191,29 @@ For __architectural knowledge__: For __operational knowledge__: @@ -195,22 +221,39 @@ For __operational knowledge__: For __business rules encoded in code__:
    @@ -280,7 +290,7 @@ Magic numbers are another signal. When you see `if (daysOverdue > 30)` or `const ## Runtime Observation: Watching the System -Static analysis tells you what the code __could__ do. Runtime observation tells you what it __actually__ does. The difference matters more than you'd expect — dead code paths, unused endpoints, and theoretical integrations that never fire in production. Observing the running system reveals the real architecture. +Static analysis tells you what the code _could_ do. Runtime observation tells you what it _actually_ does. The difference matters more than you'd expect — dead code paths, unused endpoints, and theoretical integrations that never fire in production. Observing the running system reveals the real architecture. ### Traffic Analysis and Request Mapping @@ -316,7 +326,7 @@ The analysis doesn't need to be sophisticated. Even a simple log of method, path What you're looking for: @@ -485,6 +497,7 @@ For systems without distributed tracing, service mesh telemetry (Istio, Linkerd) @@ -497,7 +510,7 @@ Documentation rots. Tests break. That asymmetry makes tests the most reliable fo ### Characterization Tests -Characterization tests capture what the system __actually does__, without making judgments about whether that behavior is correct. They're particularly valuable when you're inheriting code and don't know whether observed behavior is intentional or accidental. +Characterization tests capture what the system _actually does_, without making judgments about whether that behavior is correct. They're particularly valuable when you're inheriting code and don't know whether observed behavior is intentional or accidental. The pattern is simple: poke the system with inputs, record the outputs, then assert that future runs produce the same outputs. You're not testing that the code is right — you're testing that it hasn't changed. @@ -659,19 +672,23 @@ Unstructured conversations yield unstructured results. I use targeted question t For __architectural knowledge__, I ask questions that reveal how components interact: @@ -679,19 +696,23 @@ For __architectural knowledge__, I ask questions that reveal how components inte For __operational knowledge__, I focus on failure modes and recovery: @@ -699,19 +720,23 @@ For __operational knowledge__, I focus on failure modes and recovery: For __historical knowledge__, I dig into decisions and evolution: @@ -719,22 +744,26 @@ For __historical knowledge__, I dig into decisions and evolution: For __business rules__, I look for logic encoded in code but not in requirements:
    __Topic__: Order sync timing dependency -> -> __Category__: Gotcha -> -> __Description__: Orders must be synced to the warehouse management system before 11:59 PM EST for same-day processing. The sync job runs at 11:45 PM but can take up to 20 minutes during high volume periods. If orders aren't in WMS by midnight, they get pushed to the next business day regardless of the promised delivery date. This has caused customer complaints when Black Friday orders arrived late. -> -> __Source__: Jane Smith (Operations), January 2024 -> -> __Affected components__: order-service, wms-sync-job -> -> __Related tickets__: OPS-1234, INCIDENT-567 +```markdown +# ADR + +## Topic: Order sync timing dependency + +## Category: Gotcha + +## Description: Orders must be synced to the warehouse management system before 11:59 PM EST for same-day processing. The sync job runs at 11:45 PM but can take up to 20 minutes during high volume periods. If orders aren't in WMS by midnight, they get pushed to the next business day regardless of the promised delivery date. This has caused customer complaints when Black Friday orders arrived late. + +## Source: Jane Smith (Operations), January 2024 + +## Affected components: order-service, wms-sync-job + +## Related tickets: OPS-1234, INCIDENT-567 +``` The verification status matters. Tribal knowledge can be outdated — someone "knows" something that was true three years ago but changed since. Verify before relying on captured knowledge, and update the status when you do. @@ -858,7 +891,7 @@ The documentation you create during reverse-engineering needs to stay accurate a ### Architecture Decision Records -ADRs capture the __why__ behind decisions — the context, constraints, and alternatives considered. They're invaluable when someone asks "why did we do it this way?" two years later, and everyone who remembers has moved on. +ADRs capture the _why_ behind decisions — the context, constraints, and alternatives considered. They're invaluable when someone asks "why did we do it this way?" two years later, and everyone who remembers has moved on. The format is simple: Status, Context, Decision, Consequences, Alternatives Considered. Keep them in the repository alongside the code they describe, usually in a `docs/adr` directory. @@ -907,7 +940,7 @@ Risks: Code: Architecture Decision Record documenting API technology choice. -The key is capturing the decision __when it's made__, while context is fresh. Retroactive ADRs are better than nothing, but they're reconstructed history rather than primary sources. +The key is capturing the decision _when it's made_, while context is fresh. Retroactive ADRs are better than nothing, but they're reconstructed history rather than primary sources. ### Documentation-as-Code Patterns @@ -989,6 +1022,9 @@ The goal isn't comprehensive documentation of everything. That's neither achieva Date: Thu, 19 Mar 2026 02:49:58 +0300 Subject: [PATCH 09/20] Update styling in List and Tables for service-catalog-metadata-schema-ownership-tracking article --- .../Table/layouts/striped-rows.astro | 2 +- .../index.mdx | 22 ++- .../pdf.mdx | 182 +++++++++++------- 3 files changed, 130 insertions(+), 76 deletions(-) diff --git a/src/components/Table/layouts/striped-rows.astro b/src/components/Table/layouts/striped-rows.astro index cb1ac36f..58f83a5f 100644 --- a/src/components/Table/layouts/striped-rows.astro +++ b/src/components/Table/layouts/striped-rows.astro @@ -34,7 +34,7 @@ const { content, classes, fullWidth = true }: Props = Astro.props const tableClass = `text-left${fullWidth ? ' w-full' : ''}` const captionClass = 'text-left px-5 py-3 text-content-offset' const headerClass = 'px-5 py-3 font-semibold' -const theadClass = 'bg-primary text-primary-inverse' +const theadClass = 'bg-note-offset text-primary-inverse' const tbodyClass = '' const tfootClass = '' --- diff --git a/src/content/articles/service-catalog-metadata-schema-ownership-tracking/index.mdx b/src/content/articles/service-catalog-metadata-schema-ownership-tracking/index.mdx index 80640ec0..7a4f0f56 100644 --- a/src/content/articles/service-catalog-metadata-schema-ownership-tracking/index.mdx +++ b/src/content/articles/service-catalog-metadata-schema-ownership-tracking/index.mdx @@ -16,7 +16,7 @@ featured: true Service catalogs follow a depressingly predictable arc. At launch, you've got 90% coverage and 95% accuracy — teams are entering their services because the initiative has leadership attention. By year two, coverage has dropped to 45%, accuracy to 30%, and the catalog has become a punchline in onboarding jokes. Engineers ask in Slack instead of checking the catalog because they've learned they can't trust it. -Here's what makes this worse: a catalog with 80% accurate data is more dangerous than no catalog at all. It gives you false confidence. You page the listed owner at 3 AM, confident you've got the right team, and waste twenty minutes before discovering they handed off the service six months ago. Every minute spent paging the wrong team is a minute your users are affected. +Here's what makes this worse: a catalog with 80% accurate data is more dangerous than no catalog at all. It gives you false confidence. You page the listed owner at 3 AM, confident you've got the right team, and waste twenty minutes before discovering they handed off the service six months ago. Every minute spent paging the wrong team is a minute your users are affected. The fix isn't discipline or better training — it's ownership modeling that captures how teams actually work, combined with enforcement automation that keeps data accurate without relying on anyone remembering to update it. @@ -31,8 +31,9 @@ But "ownership" is deceptively simple. A service might have a development team t The ownership model that works distinguishes between different types of responsibility: a __primary owner__ who's responsible for the service's existence and development, plus __role-specific contacts__ for specialized functions.
    @@ -189,8 +189,13 @@ Code: GitHub Action for catalog validation on PRs. For tier-1 and tier-2 services, make validation failures blocking. For tier-3 and tier-4, warn but allow the PR to merge — you want to reduce friction for less critical services while maintaining strict standards for critical ones.
    @@ -256,7 +260,11 @@ Drift detection requires API access to your identity provider, oncall system, an A catalog without health metrics will silently decay. Measuring catalog health requires tracking three dimensions: coverage (what percentage is cataloged), accuracy (does the data reflect reality), and freshness (when was it last updated).
    Here's the thing that makes this worse: a catalog with 80% accurate data is more dangerous than no catalog at all. It gives you false confidence. You page the listed owner at 3 AM, confident you've got the right team, and waste twenty minutes before discovering they handed off the service six months ago.
    @@ -106,40 +111,40 @@ Here's the thing that makes this worse: a catalog with 80% accurate data is more Before designing a schema, you need to know what questions the catalog should answer. I've found that catalogs justify their maintenance cost when they enable specific workflows that would otherwise require manual investigation. Each workflow has different accuracy requirements, and getting this wrong means either over-engineering (requiring fields nobody uses) or under-engineering (missing data when it matters).
    @@ -155,11 +160,15 @@ Service catalogs often get confused with CMDBs or service mesh observability. Th A CMDB is an IT operations tool, typically focused on hardware and infrastructure components with manual entry and periodic audits. CMDBs excel at tracking physical assets and their relationships but struggle with the pace of change in microservices environments. A service catalog is developer-facing: it tracks logical services, their ownership, and how they relate to each other. -Service mesh observability (from tools like Istio or Linkerd) provides real-time traffic data but doesn't know about ownership, business context, or services that aren't currently receiving traffic. It tells you __what's calling what__ right now, not __who's responsible__ or __what should be calling what__. +Service mesh observability (from tools like Istio or Linkerd) provides real-time traffic data but doesn't know about ownership, business context, or services that aren't currently receiving traffic. It tells you _what's calling what_ right now, not _who's responsible_ or _what should be calling what_.
    @@ -206,8 +214,9 @@ Schema design is where most catalog initiatives go wrong. Teams either start wit A service catalog schema centers on the __service__ as the primary entity, with relationships to teams, other services, repositories, and runtime environments. The relationships matter as much as the entities themselves.
    The service entity needs several categories of fields:
    @@ -308,7 +316,7 @@ The biggest adoption killer is requiring too many fields upfront. I've seen cata For incident routing (the most common primary use case), you need exactly five required fields: owner:', @@ -336,8 +344,13 @@ For incident routing (the most common primary use case), you need exactly five r Everything else can be recommended or optional at launch. Once you hit 90%+ coverage on those five fields, you can start requiring additional fields like runbooks for tier-1 services or dependency declarations.
    @@ -434,8 +446,9 @@ But "ownership" is deceptively simple. A service might have a development team t The ownership model needs to distinguish between different types of responsibility. I've found that a two-tier approach works well: a __primary owner__ who's responsible for the service's existence and development, plus __role-specific contacts__ for specialized functions.
    @@ -504,19 +516,22 @@ Services change hands. Teams get reorganized, people leave, priorities shift. Wi The transfer workflow needs to ensure that: @@ -524,8 +539,12 @@ The transfer workflow needs to ensure that: A two-week transition period where both teams receive pages has saved me more than once. The new team gets exposure to real incidents while the old team is still available to help. It surfaces knowledge gaps before they become 3 AM surprises.
    The knowledge transfer checklist should cover:
    @@ -602,7 +620,10 @@ Services become orphaned when their owning team dissolves, empties out, or goes Run orphan detection weekly. The rules should catch: @@ -665,11 +686,12 @@ Dependencies are the second most valuable data in a service catalog, after owner ### Dependency Types and Metadata -Not all dependencies are equal. A service that can't function without its database has a __critical__ dependency. A service that falls back to cached data when a recommendation engine is down has an __optional__ dependency. Capturing this distinction matters for incident response and maintenance planning. +Not all dependencies are equal. A service that can't function without its database has a _critical_ dependency. A service that falls back to cached data when a recommendation engine is down has an _optional_ dependency. Capturing this distinction matters for incident response and maintenance planning.
    @@ -738,8 +759,12 @@ Declared dependencies are only half the picture. Services add dependencies all t The best approach combines multiple data sources:
    @@ -776,7 +800,10 @@ The best approach combines multiple data sources: The reconciliation logic compares declared dependencies against observed traffic. Four outcomes are possible: -Undeclared dependencies are the dangerous ones. They represent hidden coupling that won't show up in impact analysis. When you're planning maintenance on a service, you need to know __everything__ that depends on it, not just what's documented. +Undeclared dependencies are the dangerous ones. They represent hidden coupling that won't show up in impact analysis. When you're planning maintenance on a service, you need to know _everything_ that depends on it, not just what's documented. ```yaml title="prometheus-dependency-alert.yaml" # Alert on undeclared dependencies detected via service mesh @@ -823,24 +850,25 @@ Code: Prometheus alert for undeclared dependencies. Once you have dependency data, the catalog needs to answer questions about it. The most common queries: @@ -848,8 +876,9 @@ Once you have dependency data, the catalog needs to answer questions about it. T Blast radius is particularly useful for incident response and maintenance planning. When a tier-1 database goes down, you want to immediately know how many services are affected and which teams need to be notified.
    @@ -926,7 +954,10 @@ Code: Catalog entry in the service repository. The benefits of catalog-as-code: @@ -1029,7 +1060,10 @@ Even with CI validation, catalog entries go stale. A service's dependencies chan Run freshness checks weekly. The rules should enforce: @@ -1129,8 +1167,13 @@ A catalog that exists in isolation is just a document repository. The catalog be Backstage provides a catalog API out of the box, but understanding the API surface helps whether you're using Backstage or building something custom. The core operations fall into three categories: entity CRUD, relationship queries, and search.
    @@ -1326,9 +1368,13 @@ dashboard.new( Code: Complete Grafonnet template for catalog-driven dashboards. +In practice, each of these integrations solves a different problem and operates on a different cadence. PagerDuty needs real-time accuracy — a stale on-call mapping at 2 AM is worse than no mapping at all. Datadog and Grafana are more forgiving; a daily or on-change sync keeps dashboards and monitors aligned without hammering either API. Terraform tagging is the easiest to get right because it runs in your deploy pipeline, where the catalog data is already available. Slack is the wild card: it's where engineers actually _discover_ ownership, so even a simple bot that resolves "who owns this?" queries pays for itself fast. The table below summarizes the trade-offs. +
    @@ -1366,7 +1411,7 @@ A catalog without health metrics will silently decay. You'll assume coverage is ### Catalog Coverage Metrics -Coverage measures how much of your infrastructure exists in the catalog. Start by defining the denominator — what __should__ be cataloged. For services, the Kubernetes API or your deployment system provides ground truth. Compare that against what's actually in the catalog. +Coverage measures how much of your infrastructure exists in the catalog. Start by defining the denominator — what _should_ be cataloged. For services, the Kubernetes API or your deployment system provides ground truth. Compare that against what's actually in the catalog. ```yaml # Prometheus recording rules for catalog coverage @@ -1385,11 +1430,13 @@ groups: Code: Prometheus recording rules for coverage metrics. -Beyond simple counts, measure _completeness_—what percentage of cataloged services have all required fields populated. A service that exists in the catalog but lacks an owner isn't really cataloged for incident response purposes. +Beyond simple counts, measure _completeness_ — what percentage of cataloged services have all required fields populated. A service that exists in the catalog but lacks an owner isn't really cataloged for incident response purposes.
    From 112dcf82ed165ad963fe234077612d2bb3a3e13b Mon Sep 17 00:00:00 2001 From: Kevin Brown Date: Thu, 19 Mar 2026 03:20:11 +0300 Subject: [PATCH 10/20] Update styling in List and Tables for service-decommissioning-scream-test-shutdown article --- .../index.mdx | 26 ++-- .../pdf.mdx | 131 +++++++++++------- 2 files changed, 99 insertions(+), 58 deletions(-) diff --git a/src/content/articles/service-decommissioning-scream-test-shutdown/index.mdx b/src/content/articles/service-decommissioning-scream-test-shutdown/index.mdx index 68b212f5..7a69d753 100644 --- a/src/content/articles/service-decommissioning-scream-test-shutdown/index.mdx +++ b/src/content/articles/service-decommissioning-scream-test-shutdown/index.mdx @@ -9,16 +9,19 @@ tags: ["system-modernization","aws","kubernetes"] featured: true --- -Creating a new service has bounded risk — it either works or it doesn't. Deleting one has unbounded risk — you won't know what breaks until it breaks. This asymmetry explains why every organization accumulates zombie services that nobody's sure about. They __might__ be dead. They receive occasional traffic that could be health checks or could be production workloads. The owning team dissolved in a reorg, but surely someone took over. +Creating a new service has bounded risk — it either works or it doesn't. Deleting one has unbounded risk — you won't know what breaks until it breaks. This asymmetry explains why every organization accumulates zombie services that nobody's sure about. They _might_ be dead. They receive occasional traffic that could be health checks or could be production workloads. The owning team dissolved in a reorg, but surely someone took over. -The default is always "leave it running" because turning something off requires courage and knowledge that leaving it alone doesn't. And everyone remembers the one time someone deleted a "dead" service that turned out to power a VP's quarterly dashboard. The scream test is how you get that knowledge: degrade a service in controlled phases, wait for someone to scream, and by the time you flip the switch you've already discovered every consumer that matters. +The default is always "leave it running" because turning something off requires courage and knowledge that leaving it alone doesn't. And everyone remembers the one time someone deleted a "dead" service that turned out to power a VP's quarterly dashboard. The scream test is how you get that knowledge: degrade a service in controlled phases, wait for someone to scream, and by the time you flip the switch you've already discovered every consumer that matters. ## The Scream Test A well-designed scream test has four phases, each lasting about a week. The goal is progressive degradation that surfaces dependencies without causing lasting damage.
    kubectl get deployment $SERVICE returns NotFound'], }, { th: 'Kubernetes service', - td: ['Deleted', '`kubectl get service $SERVICE` returns NotFound'], + td: ['Deleted', 'kubectl get service $SERVICE returns NotFound'], }, { th: 'Database', @@ -778,7 +806,7 @@ Services leave behind more artifacts than you expect. A week after shutdown, run }, { th: 'DNS records', - td: ['Deleted or redirected', '`dig $SERVICE.example.com` returns NXDOMAIN'], + td: ['Deleted or redirected', 'dig $SERVICE.example.com returns NXDOMAIN'], }, { th: 'SSL certificates', @@ -798,7 +826,6 @@ Services leave behind more artifacts than you expect. A week after shutdown, run }, ], }, - figure: 'Post-decommissioning cleanup verification checklist.', }} /> @@ -813,7 +840,8 @@ Decommissioning takes effort. You need to prove the ROI to justify the next one. Pull the service's cost data from the three months before decommissioning. Compare to the current cost (which should be just archive storage, close to zero). The difference is your monthly savings.
    Total', + td: ['$2,150', '$15', '$2,135'], }, ], }, @@ -857,7 +885,10 @@ Every decommissioning teaches you something. Capture it while it's fresh. A retrospective document should cover: Date: Thu, 19 Mar 2026 04:04:26 +0300 Subject: [PATCH 11/20] Update styling in List and Tables for slo-error-budget-practical-guide article --- .../index.mdx | 24 ++- .../slo-error-budget-practical-guide/pdf.mdx | 142 ++++++++++++------ 2 files changed, 111 insertions(+), 55 deletions(-) diff --git a/src/content/articles/slo-error-budget-practical-guide/index.mdx b/src/content/articles/slo-error-budget-practical-guide/index.mdx index 7038498e..31aafec5 100644 --- a/src/content/articles/slo-error-budget-practical-guide/index.mdx +++ b/src/content/articles/slo-error-budget-practical-guide/index.mdx @@ -20,7 +20,7 @@ import fromSliDiagram from "./diagrams/from-SLI-measurement-to-error-budget-deci Every deployment becomes a political negotiation. "Is this change safe enough?" gets answered by whoever argues loudest or has more organizational capital. Product wants to ship. Engineering wants to experiment. Operations — or SRE, as the discipline has evolved — wants stability. Without a shared framework, these conversations become battles of opinion where the most senior person in the room wins — not the most informed decision. -Error budgets change the game. Instead of "should we ship this risky change?" the question becomes "do we have budget to spend on this risk?" The answer is a number, not an opinion. And that number creates alignment where politics used to create friction. +Error budgets change the game. Instead of "should we ship this risky change?" the question becomes "do we have budget to spend on this risk?" The answer is a number, not an opinion. And that number creates alignment where politics used to create friction. 100% reliability is neither achievable nor desirable. Every additional nine costs exponentially more than the last, while providing diminishing returns to users. The question isn't "how do we prevent all failures?" but "how much unreliability can we tolerate?" @@ -33,16 +33,22 @@ The hierarchy works like this: SLIs measure what matters to users. SLOs set targ The math is straightforward. If your SLO is 99.9% availability over a 30-day window (using a simplified month for easy calculation): @@ -53,6 +59,8 @@ The mental shift matters more than the math: you're not trying to prevent all fa @@ -72,8 +80,10 @@ Poor investments include untested deployments (unpredictable budget impact), Fri This is where error budgets prove their worth. Product wants to launch a new payment flow next week. Engineering estimates the change is medium-risk — it touches the checkout path and historically similar changes cause 5-10 minutes of elevated errors during rollout. SRE checks the dashboard: 25 minutes of budget remaining this month.
    @@ -105,11 +114,13 @@ The answer might still be yes — but now it's an informed yes, with shared unde ## When Budget Gets Low -An error budget without a policy is just a dashboard. The policy defines what happens when budget gets low — and critically, it defines this __before__ you're in crisis mode making decisions under pressure. +An error budget without a policy is just a dashboard. The policy defines what happens when budget gets low — and critically, it defines this _before_ you're in crisis mode making decisions under pressure.
    diff --git a/src/content/articles/slo-error-budget-practical-guide/pdf.mdx b/src/content/articles/slo-error-budget-practical-guide/pdf.mdx index 043c77ad..bafa3884 100644 --- a/src/content/articles/slo-error-budget-practical-guide/pdf.mdx +++ b/src/content/articles/slo-error-budget-practical-guide/pdf.mdx @@ -48,8 +48,9 @@ This creates a hidden tax on every team that depends on unreliable internal serv Every SLO conversation surfaces the same objections. Knowing what they really mean helps you respond effectively:
    @@ -86,18 +86,21 @@ Every SLO conversation surfaces the same objections. Knowing what they really me The trick is translating reliability into terms each stakeholder actually cares about. "99.9% availability" means nothing to a product manager, but "deployments fail unpredictably because internal services are unstable" gets attention. @@ -160,7 +163,10 @@ Before adopting any metric as an SLI, run it through these questions: These metrics look like SLIs but fail the user-centric test: @@ -406,6 +424,8 @@ Those 43 minutes are yours to spend however you want. A 10-minute deployment tha @@ -419,8 +439,10 @@ Most services have multiple SLOs, which means multiple budgets to track. A servi These budgets aren't independent. A single incident can consume from multiple budgets simultaneously. A database outage might consume availability budget (service returning errors), latency budget (slow queries during failover), and error rate budget (failed requests during recovery). One bad hour can blow multiple budgets at once.
    @@ -463,15 +484,20 @@ Traditional threshold alerts ("error rate > 1%") catch acute incidents but miss You need two types of burn rate alerts: @@ -510,15 +536,16 @@ The combination catches problems that either alert type would miss alone. Fast b ## Error Budget Policies -An error budget without a policy is just a dashboard. The policy defines what happens when budget gets low — and critically, it defines this __before__ you're in crisis mode making decisions under pressure. +An error budget without a policy is just a dashboard. The policy defines what happens when budget gets low — and critically, it defines this _before_ you're in crisis mode making decisions under pressure. ### Threshold-Based Actions Define clear thresholds and the actions that trigger at each level. Write these down, get stakeholder agreement, and publish them where everyone can see.
    @@ -598,19 +624,28 @@ Policies define the rules, but someone still has to apply them. The real test of Error budgets provide a shared language for the three parties who most often conflict over reliability: @@ -630,7 +665,10 @@ Not all budget consumption is equal. Some spending generates value; some is just Good investments of error budget: Below that, list planned budget consumption for the coming week: a database migration on Tuesday (~10% estimated), a feature flag rollout on Thursday (~5% estimated). With the current 15% used (taking the higher of the two SLO budgets as the constraint), you have 85% remaining. After 15% planned consumption, you'll end the week around 70%. -This "planned consumption" section is particularly valuable. It forces teams to estimate the reliability cost of their work __before__ doing it, and it gives stakeholders visibility into upcoming risk. When that database migration consumes 25% instead of the estimated 10%, the next planning conversation includes that data point. +This "planned consumption" section is particularly valuable. It forces teams to estimate the reliability cost of their work _before_ doing it, and it gives stakeholders visibility into upcoming risk. When that database migration consumes 25% instead of the estimated 10%, the next planning conversation includes that data point. Send these updates to a consistent audience: engineering leadership, product managers for affected services, and the on-call rotation. Keep them short — the goal is a glanceable status, not a detailed report. Save the details for when someone asks. ## Bootstrapping Observability -"We can't set SLOs because we don't have observability" is usually backwards. You don't need comprehensive observability to start with SLOs — you need __enough__ data to measure one or two SLIs for one service. Start there, demonstrate value, then use that success to justify observability investment. +"We can't set SLOs because we don't have observability" is usually backwards. You don't need comprehensive observability to start with SLOs — you need _enough_ data to measure one or two SLIs for one service. Start there, demonstrate value, then use that success to justify observability investment. ### Start with What Exists Teams without observability often have more data than they realize. Before adding any new instrumentation, inventory what you already collect.
    @@ -814,22 +857,25 @@ After your first quarter, watch for these signals. If you're constantly breachin __Key principles:__ From 2a265d37c3876ed2c43bdc8a55af18ba5c758b6e Mon Sep 17 00:00:00 2001 From: Kevin Brown Date: Thu, 19 Mar 2026 19:36:08 +0300 Subject: [PATCH 12/20] Styling fixes to table and list components in slsa-build-provenance-artifact-signing-supply-chain article, add Troubleshooter component --- package.json | 24 +- .../Table/layouts/striped-rows.astro | 2 +- src/components/Troubleshooter/index.astro | 134 ++++++++ .../index.mdx | 20 +- .../pdf.mdx | 311 +++++++++++------- src/layouts/MarkdownLayout.astro | 2 + src/pages/testing/comps/scratchpad.astro | 39 +-- src/styles/index.css | 20 ++ 8 files changed, 376 insertions(+), 176 deletions(-) create mode 100644 src/components/Troubleshooter/index.astro diff --git a/package.json b/package.json index 702c0835..b17f6b44 100644 --- a/package.json +++ b/package.json @@ -68,9 +68,9 @@ "dependencies": { "@adobe/remark-gridtables": "^3.0.18", "@astrojs/check": "0.9.8", - "@astrojs/db": "^0.20.0", - "@astrojs/mdx": "5.0.1", - "@astrojs/preact": "5.0.1", + "@astrojs/db": "^0.20.1", + "@astrojs/mdx": "5.0.2", + "@astrojs/preact": "5.0.2", "@astrojs/rss": "4.0.17", "@astrojs/sitemap": "^3.7.1", "@astrojs/vercel": "^10.0.1", @@ -87,12 +87,12 @@ "@playwright/browser-chromium": "^1.58.2", "@playwright/test": "1.58.2", "@semantic-ui/astro-lit": "^5.3.0", - "@sentry/astro": "^10.43.0", - "@sentry/browser": "^10.43.0", + "@sentry/astro": "^10.44.0", + "@sentry/browser": "^10.44.0", "@shikijs/transformers": "^4.0.2", "@tailwindcss/forms": "0.5.11", "@tailwindcss/typography": "0.5.19", - "@tailwindcss/vite": "^4.2.1", + "@tailwindcss/vite": "^4.2.2", "@testing-library/dom": "10.4.1", "@testing-library/preact": "3.2.4", "@testing-library/user-event": "14.6.1", @@ -129,7 +129,7 @@ "@vitest/coverage-v8": "^4.1.0", "@webcomponents/template-shadowroot": "^0.2.1", "alex": "^11.0.1", - "astro": "6.0.5", + "astro": "6.0.6", "astro-link-validator": "github:rodgtr1/astro-link-validator", "astro-og-canvas": "^0.10.1", "astro-vtbot": "^2.1.12", @@ -171,7 +171,7 @@ "md-attr-parser": "^1.3.0", "mermaid": "^11.13.0", "nanostores": "^1.2.0", - "nodemailer": "^8.0.2", + "nodemailer": "^8.0.3", "npm": "^11.11.1", "playwright-lighthouse": "^4.0.0", "postcss": "8.5.8", @@ -207,7 +207,7 @@ "retext": "^9.0.0", "retext-smartypants": "^6.2.0", "rimraf": "6.1.3", - "sanitize-html": "^2.17.1", + "sanitize-html": "^2.17.2", "schema-dts": "^1.1.5", "sharp": "^0.34.5", "shiki": "^4.0.2", @@ -216,7 +216,7 @@ "stylelint-config-standard": "^40.0.0", "stylelint-declaration-block-no-ignored-properties": "3.0.0", "stylelint-order": "8.1.1", - "tailwindcss": "^4.2.1", + "tailwindcss": "^4.2.2", "temp-dir": "3.0.0", "timezones-ical-library": "^2.1.3", "title-case": "4.3.2", @@ -230,8 +230,8 @@ "unist-util-is": "^6.0.1", "unist-util-visit": "^5.1.0", "uuid": "^13.0.0", - "vercel": "^50.32.5", - "vite": "^8.0.0", + "vercel": "^50.33.1", + "vite": "^8.0.1", "vitest": "4.1.0", "vitest-axe": "0.1.0", "workbox-build": "7.4.0", diff --git a/src/components/Table/layouts/striped-rows.astro b/src/components/Table/layouts/striped-rows.astro index 58f83a5f..cf82dc0d 100644 --- a/src/components/Table/layouts/striped-rows.astro +++ b/src/components/Table/layouts/striped-rows.astro @@ -61,7 +61,7 @@ const tfootClass = '' {content.tbody.tr.map((row, rowIndex) => ( {row.th && ( diff --git a/src/components/Troubleshooter/index.astro b/src/components/Troubleshooter/index.astro new file mode 100644 index 00000000..3223ddf2 --- /dev/null +++ b/src/components/Troubleshooter/index.astro @@ -0,0 +1,134 @@ +--- +import Icon from '@components/Icon/index.astro' + +type TroubleshooterItem = { + text: string +} + +type TroubleshooterSection = { + lead?: string + items: TroubleshooterItem[] +} + +export type Props = { + items: { + symptoms: TroubleshooterSection + causes: TroubleshooterSection + diagnosis: TroubleshooterSection + fixes: TroubleshooterSection + } +} + +const { items } = Astro.props as Props + +const sectionMeta = { + symptoms: { icon: 'error-open', color: 'danger', label: 'Symptoms' }, + causes: { icon: 'question', color: 'warning', label: 'Cause' }, + diagnosis: { icon: 'folder-magnifying-glass', color: 'info', label: 'Diagnosis' }, + fixes: { icon: 'wrench', color: 'success', label: 'Fix' }, +} satisfies Record + +function getSectionClassNames(color: string) { + return { + headerBg: `bg-${color}-inverse`, + headerText: `text-${color}`, + headerTopBorder: `border-t-${color}`, + markerBg: `bg-${color}`, + } +} +--- + + + +
    +
    +
    +
    +

    Troubleshooting

    + +
    +
    + +
    +
    +
    + +
    + {([['symptoms', 'causes'], ['diagnosis', 'fixes']] as const).map((pair, rowIndex) => { + const cards = pair.map((key) => ({ + meta: sectionMeta[key], + items: items[key], + })) + + return ( +
    + {cards.map((card, colIndex) => { + const colorClasses = getSectionClassNames(card.meta.color) + + return ( +
    +
    + +
    {card.meta.label}
    +
    +
    + {!card.items.lead && card.items.items.length === 0 ? ( + None specified + ) : null} + + {card.items.lead && ( +

    0 ? 'mb-3' : '', + ]} set:html={card.items.lead} /> + )} + + {!card.items.lead && card.items.items.length === 1 && ( +

    + )} + + {card.items.items.length > 0 && (card.items.items.length > 1 || Boolean(card.items.lead)) && ( +

      + {card.items.items.map((item) => ( +
    • + + +
    • + ))} +
    + )} +
    +
    + ) + })} +
    + ) + })} +
    +
    diff --git a/src/content/articles/slsa-build-provenance-artifact-signing-supply-chain/index.mdx b/src/content/articles/slsa-build-provenance-artifact-signing-supply-chain/index.mdx index 7979a7aa..07394d58 100644 --- a/src/content/articles/slsa-build-provenance-artifact-signing-supply-chain/index.mdx +++ b/src/content/articles/slsa-build-provenance-artifact-signing-supply-chain/index.mdx @@ -25,7 +25,7 @@ In December 2020, attackers compromised SolarWinds' build process and shipped ma These weren't exotic zero-days. They exploited a fundamental assumption: that code, builds, and artifacts are what they claim to be. When that assumption breaks down, the blast radius is enormous. -Here's the good news: you can implement practical supply chain protection __today__ without managing keys, standing up infrastructure, or paying for expensive tooling. The barrier has dropped dramatically in the past few years, and the core protection — cryptographic signing with identity-based verification — is now free. If you're already comfortable with GitHub Actions, you can add signing to an existing pipeline in about 15 minutes. If you're newer to these tools, budget an hour to understand the OIDC flow and test your configuration. +Here's the good news: you can implement practical supply chain protection _today_ without managing keys, standing up infrastructure, or paying for expensive tooling. The barrier has dropped dramatically in the past few years, and the core protection — cryptographic signing with identity-based verification — is now free. If you're already comfortable with GitHub Actions, you can add signing to an existing pipeline in about 15 minutes. If you're newer to these tools, budget an hour to understand the OIDC flow and test your configuration. ## What You're Actually Protecting Against @@ -34,8 +34,13 @@ Before investing in countermeasures, it helps to understand the attack surface. [SLSA](https://slsa.dev) (Supply-chain Levels for Software Artifacts, pronounced "salsa") is a framework that addresses a specific slice of this problem. It focuses on __provenance_ — proving that an artifact came from a specific source through a specific build process. This matters because it lets you verify that what you're deploying matches what you reviewed.
    @@ -200,23 +204,23 @@ What I've covered here — keyless signing with Cosign and basic verification ga From here, you can layer in additional protections. Here's what each provides: diff --git a/src/content/articles/slsa-build-provenance-artifact-signing-supply-chain/pdf.mdx b/src/content/articles/slsa-build-provenance-artifact-signing-supply-chain/pdf.mdx index 60c83fdd..0625d1fb 100644 --- a/src/content/articles/slsa-build-provenance-artifact-signing-supply-chain/pdf.mdx +++ b/src/content/articles/slsa-build-provenance-artifact-signing-supply-chain/pdf.mdx @@ -45,7 +45,10 @@ Before investing in countermeasures, you need to understand what you're defendin The past few years have given us a painful education in supply chain attacks. Each major incident revealed a different weak point in the pipeline from source code to production deployment. @@ -87,8 +92,13 @@ Each stage in the pipeline requires different defenses. SLSA focuses on the midd SLSA (Supply-chain Levels for Software Artifacts) addresses specific threats, not all threats. Understanding this boundary prevents both under-investment and false confidence.
    @@ -135,7 +144,10 @@ SLSA proves that an artifact came from a specific source through a specific buil ### Level Requirements Overview
    @@ -191,7 +203,10 @@ SLSA proves that an artifact came from a specific source through a specific buil Not every artifact needs the same protection. Choose your target level based on what you're building and who might want to compromise it.
    @@ -811,7 +826,7 @@ For production use, consider wrapping this pattern in a Terraform module so team ## SBOM Integration -Provenance tells you __how__ an artifact was built. An SBOM tells you __what's inside__. Together, they give you complete visibility into your software supply chain. When a new CVE drops, you can answer "are we affected?" in minutes instead of days. +Provenance tells you _how_ an artifact was built. An SBOM tells you _what's inside_. Together, they give you complete visibility into your software supply chain. When a new CVE drops, you can answer "are we affected?" in minutes instead of days. ### Generating SBOMs @@ -916,6 +931,7 @@ For programmatic querying across many images, tools like Grype can consume SBOMs @@ -931,7 +947,10 @@ The tools and concepts above can feel overwhelming. Here's a phased approach tha Before adding security tooling, you need visibility into what you're protecting. @@ -1038,25 +1060,49 @@ Don't try to implement everything at once. Start with signing one artifact type, You'll hit these problems. Here's how to diagnose and fix them. -### "No matching signatures" during verification - -cosign verify returns an error, admission webhook denies pods, deployments blocked.', + }, + ], }, - { - lead: 'Cause:', - text: "The image was signed, but the signing identity doesn't match your verification policy. This is the most common issue.", + causes: { + items: [ + { + text: "The image was signed, but the signing identity doesn't match your verification policy. This is the most common issue.", + }, + ], }, - { - lead: 'Diagnosis:', - text: 'Check what identity actually signed the image:', + diagnosis: { + lead: 'Check what identity actually signed the image. Compare the certificate identity in the output with what your policy expects. Common mismatches:', + items: [ + { + text: 'You signed from refs/heads/main but the policy expects refs/tags/v*', + }, + { + text: 'You renamed or moved your workflow file', + }, + { + text: 'Policy expects GitHub but you are using GitLab', + }, + ], }, - ]} -/> + fixes: { + items: [ + { + text: 'Update either your signing workflow or your verification policy to match.', + }, + ], + }, + }} +> + +### "No matching signatures" during verification + + ```bash cosign verify --output text ghcr.io/myorg/myapp:v1.0.0 2>&1 | head -20 @@ -1064,47 +1110,46 @@ cosign verify --output text ghcr.io/myorg/myapp:v1.0.0 2>&1 | head -20 Code: Inspecting Cosign verification output. -Compare the certificate identity in the output with what your policy expects. Common mismatches: - -id-token: write and that a job-level permissions block is not overriding it.', + }, + { + text: 'Make sure the signing step is running in the same job that has the OIDC permission.', + }, + { + text: 'If you recently tightened default repository permissions, confirm the workflow still has explicit package and contents access where needed.', + }, + ], }, - ]} -/> - -__Fix:__ Update either your signing workflow or your verification policy to match. + fixes: { + lead: 'Add the permission block to your workflow:', + items: [], + }, + }} +> ### OIDC token not available in GitHub Actions - + ```yaml permissions: @@ -1115,69 +1160,99 @@ permissions: Code: GitHub Actions OIDC permissions block. -### Provenance attestation not found - -slsa-verifier fails, attestation download returns empty.', + }, + ], }, - ]} -/> - -slsa-github-generator (not @main)', + }, + { + text: 'Confirm the artifact digest passed to the generator matches what you are verifying', + }, + ], }, - { - text: 'Confirm the artifact digest passed to the generator matches what you are verifying', + fixes: { + items: [ + { + text: 'Most often, this is a digest mismatch. The digest output from your build step must exactly match what you pass to the provenance generator.', + }, + ], }, - ]} -/> + }} +> -__Fix:__ Most often, this is a digest mismatch. The digest output from your build step must exactly match what you pass to the provenance generator. +### Provenance attestation not found -### SBOM generation takes too long + - + }} +> + +### SBOM generation takes too long + + ## Conclusion diff --git a/src/layouts/MarkdownLayout.astro b/src/layouts/MarkdownLayout.astro index bd31a1a6..30be0658 100644 --- a/src/layouts/MarkdownLayout.astro +++ b/src/layouts/MarkdownLayout.astro @@ -36,6 +36,7 @@ import Shares from '@components/Social/Shares/index.astro' import Table from '@components/Table/index.astro' import Time from '@components/Time/index.astro' import Testimonials from '@components/Testimonials/index.astro' +import Troubleshooter from '@components/Troubleshooter/index.astro' import CodeTabs from '@components/Code/CodeTabs/index.astro' import '@components/Social/Highlighter/index.css' @@ -65,6 +66,7 @@ const Components = { Testimonials, Table, Time, + Troubleshooter, } export interface Props { diff --git a/src/pages/testing/comps/scratchpad.astro b/src/pages/testing/comps/scratchpad.astro index bf118570..11bfe3ff 100644 --- a/src/pages/testing/comps/scratchpad.astro +++ b/src/pages/testing/comps/scratchpad.astro @@ -1,48 +1,13 @@ --- import BaseLayout from '@layouts/BaseLayout.astro' -import List from '@components/List/index.astro' const pageTitle = 'Scratchpad' -const pageDescription = 'Styling Variants' +const pageDescription = 'Troubleshooting Component Variants' const path = '/testing/comps/scratchpad' -const chatBubbleItems = [ - { - lead: 'Why did the system go down?', - text: 'Because a config change broke it.', - }, - { - lead: 'Why did the config change break it?', - text: 'Because the engineer made a mistake.', - }, - { - lead: 'Why did the engineer make a mistake?', - text: 'Because they were careless.', - }, -] --- -
    - -

    Variant 6 — Chat Bubbles

    - - - -

    Variant 8 — Elevated Hover Cards

    -
    -
    -
    Why did the system go down?
    -
    Because a config change broke it.
    -
    -
    -
    Why did the config change break it?
    -
    Because the engineer made a mistake.
    -
    -
    -
    Why did the engineer make a mistake?
    -
    Because they were careless.
    -
    -
    +
    diff --git a/src/styles/index.css b/src/styles/index.css index 8890a3a7..8a92fa8a 100644 --- a/src/styles/index.css +++ b/src/styles/index.css @@ -43,6 +43,26 @@ @source inline("bg-orange-600"); @source inline("bg-yellow-500"); @source inline("bg-yellow-600"); +@source inline("bg-warning"); +@source inline("bg-warning-inverse"); +@source inline("bg-info"); +@source inline("bg-info-inverse"); +@source inline("bg-danger"); +@source inline("bg-danger-inverse"); +@source inline("bg-success"); +@source inline("bg-success-inverse"); +@source inline("border-warning"); +@source inline("border-info"); +@source inline("border-danger"); +@source inline("border-success"); +@source inline("border-t-warning"); +@source inline("border-t-info"); +@source inline("border-t-danger"); +@source inline("border-t-success"); +@source inline("text-warning"); +@source inline("text-info"); +@source inline("text-danger"); +@source inline("text-success"); /** New CSS directive in v4 to import Tailwind plugins */ @plugin "@tailwindcss/forms"; From fda05226441cd82282e245738d489435abc5b4b2 Mon Sep 17 00:00:00 2001 From: Kevin Brown Date: Thu, 19 Mar 2026 21:00:59 +0300 Subject: [PATCH 13/20] Styling fixes to table and list components in strangler-fig-migration-complete-guide article --- src/components/Troubleshooter/index.astro | 25 ++- .../index.mdx | 20 +- .../pdf.mdx | 210 +++++++++++++----- 3 files changed, 184 insertions(+), 71 deletions(-) diff --git a/src/components/Troubleshooter/index.astro b/src/components/Troubleshooter/index.astro index 3223ddf2..e8b5b4fa 100644 --- a/src/components/Troubleshooter/index.astro +++ b/src/components/Troubleshooter/index.astro @@ -1,4 +1,6 @@ --- +import { randomUUID } from 'node:crypto' + import Icon from '@components/Icon/index.astro' type TroubleshooterItem = { @@ -28,6 +30,8 @@ const sectionMeta = { fixes: { icon: 'wrench', color: 'success', label: 'Fix' }, } satisfies Record +const sectionIdBase = `troubleshooter-${randomUUID()}` + function getSectionClassNames(color: string) { return { headerBg: `bg-${color}-inverse`, @@ -49,7 +53,7 @@ function getSectionClassNames(color: string) {

    Troubleshooting

    -
    @@ -123,7 +127,12 @@ Once shadow testing proves your new service matches legacy behavior, you're read A progressive traffic shift schedule balances validation time against migration velocity. Moving too fast risks missing problems that only appear under sustained load. Moving too slowly extends the period where you're maintaining two systems.
    Migrate what you can observe; observe before you migrate. +> — Anonymous migration proverb The strangler fig pattern succeeds because it trades big-bang risk for incremental progress. This guide walks through the complete lifecycle — from baseline instrumentation through traffic shifting to legacy decommissioning — using authentication extraction as a concrete running example. @@ -40,15 +41,21 @@ The strangler fig pattern succeeds because it trades big-bang risk for increment The strangler fig pattern gets its name from the strangler fig tree, which grows around a host tree and gradually replaces it while the original structure remains standing. That's exactly what we're doing with migrations: building the new system around the old one, shifting traffic incrementally, and only decommissioning the legacy system when we've proven the new one works. -The core insight is simple: __you can't migrate what you can't observe__. Before touching any traffic, you need to know what "normal" looks like. That baseline becomes your comparison point throughout the migration. +The core insight is simple: _you can't migrate what you can't observe_. Before touching any traffic, you need to know what "normal" looks like. That baseline becomes your comparison point throughout the migration. ### Why Strangler Fig Works The pattern eliminates the single point of failure that kills most migrations. With a big-bang approach, you're betting everything on one deployment. If it fails, you're scrambling to roll back while users pile into your support queue. With strangler fig, each increment is a small bet. A failure at 5% traffic is annoying; a failure at 100% traffic is a much bigger problem.
    RequestCounter = Meter.CreateCounter("http_requests_total"); + private static readonly Counter RequestCounter = Meter.CreateCounter( + "http_requests_total" + ); private static readonly Histogram RequestDuration = Meter.CreateHistogram("http_request_duration_ms", "ms"); public void Init(HttpApplication context) { @@ -123,7 +134,9 @@ public class LegacyInstrumentationModule : IHttpModule { var path = app.Request.Path; // Start Span (Activity) - var activity = ActivitySource.StartActivity($"{app.Request.HttpMethod} {path}"); + var activity = ActivitySource.StartActivity( + $"{app.Request.HttpMethod} {path}" + ); activity?.SetTag("http.method", app.Request.HttpMethod); activity?.SetTag("migration.system", "legacy"); @@ -140,8 +153,12 @@ public class LegacyInstrumentationModule : IHttpModule { var status = app.Response.StatusCode.ToString(); // Record Metrics - RequestCounter.Add(1, new("method", app.Request.HttpMethod), new("status", status)); - RequestDuration.Record(duration, new("method", app.Request.HttpMethod), new("status", status)); + RequestCounter.Add( + 1, new("method", app.Request.HttpMethod), new("status", status) + ); + RequestDuration.Record( + duration, new("method", app.Request.HttpMethod), new("status", status) + ); // End Span activity?.SetTag("http.status_code", app.Response.StatusCode); @@ -183,6 +200,8 @@ Code: OpenTelemetry startup registration. Code: Web.config module registration. +Code: Legacy system instrumentation setup. + If you're working with a system that can't easily add middleware — maybe it's a compiled binary or a third-party service — you can instrument at the proxy layer instead. AWS ALB access logs or NGINX logs can be parsed into metrics, though you lose the ability to capture response bodies. ### Capturing Response Signatures @@ -207,7 +226,9 @@ public class ResponseSignature { } public class MigrationLoggingHandler : DelegatingHandler { - protected override async Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) { + protected override async Task SendAsync( + HttpRequestMessage request, CancellationToken cancellationToken + ) { var stopwatch = Stopwatch.StartNew(); var timestamp = DateTime.UtcNow; @@ -281,7 +302,9 @@ sum(rate(http_requests_total{system="legacy",status=~"5.."}[5m])) by (path) sum(rate(http_requests_total{system="legacy"}[5m])) by (path) # P99 latency by endpoint -histogram_quantile(0.99, sum(rate(http_request_duration_ms_bucket{system="legacy"}[5m])) by (le, path)) +histogram_quantile( + 0.99, sum(rate(http_request_duration_ms_bucket{system="legacy"}[5m])) by (le, path) +) ``` Code: Baseline Prometheus queries. @@ -300,6 +323,8 @@ The basic architecture routes production requests to the legacy system as normal @@ -315,7 +340,9 @@ public class ShadowTrafficHandler : DelegatingHandler { private readonly IComparisonService _comparisonService; private readonly string _newServiceBaseUrl; - public ShadowTrafficHandler(string newServiceBaseUrl, IComparisonService comparisonService) { + public ShadowTrafficHandler( + string newServiceBaseUrl, IComparisonService comparisonService + ) { _newServiceBaseUrl = newServiceBaseUrl; _comparisonService = comparisonService; _shadowClient = new HttpClient { Timeout = TimeSpan.FromSeconds(5) }; @@ -339,7 +366,13 @@ public class ShadowTrafficHandler : DelegatingHandler { // Fire-and-forget shadow request (don't block the response) _ = Task.Run(async () => { try { - await SendShadowRequest(request, requestBody, legacyResponse.StatusCode, legacyBody, legacyLatency); + await SendShadowRequest( + request, + requestBody, + legacyResponse.StatusCode, + legacyBody, + legacyLatency + ); } catch (Exception ex) { // Log but never affect production traffic @@ -350,8 +383,13 @@ public class ShadowTrafficHandler : DelegatingHandler { return legacyResponse; } - private async Task SendShadowRequest(HttpRequestMessage originalRequest, string requestBody, - HttpStatusCode legacyStatus, string legacyBody, long legacyLatencyMs) { + private async Task SendShadowRequest( + HttpRequestMessage originalRequest, + string requestBody, + HttpStatusCode legacyStatus, + string legacyBody, + long legacyLatencyMs + ) { var shadowRequest = new HttpRequestMessage(originalRequest.Method, _newServiceBaseUrl + originalRequest.RequestUri.PathAndQuery); @@ -363,7 +401,9 @@ public class ShadowTrafficHandler : DelegatingHandler { shadowRequest.Headers.Add("X-Shadow-Request", "true"); if (requestBody != null) { - shadowRequest.Content = new StringContent(requestBody, Encoding.UTF8, "application/json"); + shadowRequest.Content = new StringContent( + requestBody, Encoding.UTF8, "application/json" + ); } var stopwatch = Stopwatch.StartNew(); @@ -407,12 +447,18 @@ app = func.FunctionApp() VOLATILE_FIELDS = {'timestamp', 'requestId', 'traceId', 'serverTime'} -@app.cosmos_db_trigger(arg_name="signatures", container_name="signatures", - database_name="migration", connection="CosmosConnection", - lease_container_name="leases") +@app.cosmos_db_trigger( + arg_name="signatures", + container_name="signatures", + database_name="migration", + connection="CosmosConnection", + lease_container_name="leases" +) async def compare_response_signatures(signatures: func.DocumentList): cosmos = CosmosClient.from_connection_string(os.environ["CosmosConnection"]) - container = cosmos.get_database_client("migration").get_container_client("signatures") + container = cosmos.get_database_client( + "migration" + ).get_container_client("signatures") telemetry = TelemetryClient(os.environ["APPINSIGHTS_INSTRUMENTATIONKEY"]) for sig in signatures: @@ -420,8 +466,10 @@ async def compare_response_signatures(signatures: func.DocumentList): # Query for the matching signature from the other system query = "SELECT * FROM c WHERE c.requestId = @rid AND c.system = @sys" - params = [{"name": "@rid", "value": sig["requestId"]}, - {"name": "@sys", "value": partner_system}] + params = [ + {"name": "@rid", "value": sig["requestId"]}, + {"name": "@sys", "value": partner_system} + ] partners = list(container.query_items(query, parameters=params)) if not partners: @@ -542,19 +590,22 @@ Shadow traffic works for read operations but is dangerous for writes. Duplicatin Run shadow traffic for at least a week before considering real traffic migration. You're looking for: @@ -570,8 +621,9 @@ With validation complete, you shift from testing to construction. Shadow traffic Design the interface before writing implementation code. For an auth service, the contract typically includes public endpoints for login, logout, and password management, plus internal endpoints for token validation that other services will call.
    GenerateTokenPairAsync(User user, IEnumerable permissions) { + public async Task GenerateTokenPairAsync( + User user, IEnumerable permissions + ) { var now = DateTime.UtcNow; var claims = new List { new Claim(JwtRegisteredClaimNames.Sub, user.Id), new Claim(JwtRegisteredClaimNames.Email, user.Email), - new Claim(JwtRegisteredClaimNames.Iat, new DateTimeOffset(now).ToUnixTimeSeconds().ToString(), - ClaimValueTypes.Integer64), + new Claim( + JwtRegisteredClaimNames.Iat, + new DateTimeOffset(now).ToUnixTimeSeconds().ToString(), + ClaimValueTypes.Integer64 + ), new Claim("aud", "api"), new Claim("aud", "internal") }; @@ -662,13 +719,15 @@ public class TokenService { var signingCredentials = new SigningCredentials( new RsaSecurityKey(_privateKey), SecurityAlgorithms.RsaSha256); - var accessToken = new JwtSecurityTokenHandler().WriteToken(new JwtSecurityToken( + var accessToken = new JwtSecurityTokenHandler().WriteToken( + new JwtSecurityToken( issuer: "auth-service", claims: claims, notBefore: now, expires: now.Add(_accessTokenTtl), signingCredentials: signingCredentials - )); + ) + ); var refreshToken = await CreateRefreshTokenAsync(user.Id, now); @@ -691,9 +750,13 @@ public class TokenService { ClockSkew = TimeSpan.FromSeconds(30) }; - var principal = handler.ValidateToken(token, parameters, out var validatedToken); + var principal = handler.ValidateToken( + token, parameters, out var validatedToken + ); var userId = principal.FindFirst(JwtRegisteredClaimNames.Sub)?.Value; - var issuedAt = long.Parse(principal.FindFirst(JwtRegisteredClaimNames.Iat)?.Value ?? "0"); + var issuedAt = long.Parse( + principal.FindFirst(JwtRegisteredClaimNames.Iat)?.Value ?? "0" + ); // Check token revocation (user changed password, logged out, etc.) if (await IsTokenRevokedAsync(userId, issuedAt)) { @@ -703,7 +766,9 @@ public class TokenService { return new ValidationResult { Valid = true, UserId = userId, - Permissions = principal.FindAll("permission").Select(c => c.Value).ToList(), + Permissions = principal.FindAll( + "permission" + ).Select(c => c.Value).ToList(), ExpiresAt = ((JwtSecurityToken)validatedToken).ValidTo }; } @@ -750,7 +815,11 @@ public class DualWriteUserService { } catch (Exception ex) { // Log but don't fail — legacy write succeeded - _logger.LogError(ex, "Failed to sync user {UserId} to auth service", user.Id); + _logger.LogError( + ex, + "Failed to sync user {UserId} to auth service", + user.Id + ); await _syncQueue.EnqueueAsync(new SyncUserJob { UserId = user.Id }); } } @@ -769,8 +838,14 @@ public class DualWriteUserService { await _authService.UpdatePasswordHashAsync(userId, hash); } catch (Exception ex) { - _logger.LogError(ex, "Failed to sync password for user {UserId}", userId); - await _syncQueue.EnqueueAsync(new SyncPasswordJob { UserId = userId, Hash = hash }); + _logger.LogError( + ex, + "Failed to sync password for user {UserId}", + userId + ); + await _syncQueue.EnqueueAsync( + new SyncPasswordJob { UserId = userId, Hash = hash } + ); } } } @@ -826,7 +901,8 @@ public class UserMigrationJob { } report.Migrated++; - await Task.Delay(config.RateLimitMs); // Be gentle with the new service + // Be gentle with the new service + await Task.Delay(config.RateLimitMs); } catch (Exception ex) { report.Failed++; @@ -857,8 +933,10 @@ Shadow traffic validated the new system; now you're moving real users. This is w The most common approach: route a percentage of requests to the new system, increasing the percentage as confidence grows. Start small (1%), wait for metrics to stabilize, then increase.
    @@ -941,22 +1018,22 @@ Percentage-based splitting is random — any user might hit either system on any Cohort-based migration routes entire user segments rather than random requests. Start with low-risk cohorts: { new Claim(ClaimTypes.NameIdentifier, validation.UserId) }; - claims.AddRange(validation.Permissions.Select(p => new Claim("permission", p))); + claims.AddRange( + validation.Permissions.Select(p => new Claim("permission", p)) + ); var identity = new ClaimsIdentity(claims, "JWT"); HttpContext.Current.User = new ClaimsPrincipal(identity); @@ -1076,7 +1158,10 @@ public class HybridAuthMiddleware : IHttpModule { result = new ValidationResult { Valid = true, UserId = principal.FindFirst(JwtRegisteredClaimNames.Sub)?.Value, - Permissions = principal.FindAll("permission").Select(c => c.Value).ToList() + Permissions = principal + .FindAll("permission") + .Select(c => c.Value) + .ToList() }; } catch (SecurityTokenException) { @@ -1115,8 +1200,10 @@ When metrics degrade during traffic shifting, you need to roll back immediately Define clear thresholds before you start shifting traffic:
    @@ -1265,8 +1351,9 @@ You've reached 100% traffic on the new system. The migration isn't complete unti Don't declare victory too soon. Before decommissioning the legacy system, verify:
    @@ -1303,8 +1389,14 @@ Don't declare victory too soon. Before decommissioning the legacy system, verify Decommissioning happens in phases, each designed to catch problems before they become emergencies:
    @@ -1374,7 +1465,8 @@ public class AuthReconciliationJob { UserId = monolithUser.Id, Differences = differences }); - await _authService.SyncUserAsync(monolithUser); // Legacy system is source of truth + // Legacy system is source of truth + await _authService.SyncUserAsync(monolithUser); report.Fixed++; _metrics.Increment("reconciliation.data_mismatch"); } @@ -1412,15 +1504,15 @@ When the reconciliation job reports zero mismatches for a week straight, you can After decommissioning, hold a retrospective while the details are fresh. Track these metrics to improve future migrations: Date: Thu, 19 Mar 2026 21:50:42 +0300 Subject: [PATCH 14/20] Styling fixes to table and list components in structured-logging-correlation-ids-log-schema-design article --- .../index.mdx | 58 +++++---- .../pdf.mdx | 123 +++++++++++------- 2 files changed, 112 insertions(+), 69 deletions(-) diff --git a/src/content/articles/structured-logging-correlation-ids-log-schema-design/index.mdx b/src/content/articles/structured-logging-correlation-ids-log-schema-design/index.mdx index 05d1eb00..2a87c836 100644 --- a/src/content/articles/structured-logging-correlation-ids-log-schema-design/index.mdx +++ b/src/content/articles/structured-logging-correlation-ids-log-schema-design/index.mdx @@ -11,11 +11,11 @@ featured: true It's 3 AM. A payment is stuck somewhere between your API gateway, order service, and payment processor. You start searching logs. -You try `grep 'userId'`. Nothing. Maybe it's `grep 'user_id'`? A few hits, but not from the payment service. `grep 'user.id'`? Different results again. Five queries later, you've pieced together __most__ of the request path, but you're still not sure if you've found everything. +You try `grep 'userId'`. Nothing. Maybe it's `grep 'user_id'`? A few hits, but not from the payment service. `grep 'user.id'`? Different results again. Five queries later, you've pieced together _most_ of the request path, but you're still not sure if you've found everything. Now imagine a different scenario: `user.id:12345 AND event.action:payment_initiated`. One query. Every service. Every log. Complete picture in seconds. -The difference isn't better tooling. It's discipline in how you emit logs. Structured logging with consistent schemas and correlation IDs transforms distributed debugging from archaeology into routine work. Here's how to implement it. +The difference isn't better tooling. It's discipline in how you emit logs. Structured logging with consistent schemas and correlation IDs transforms distributed debugging from archaeology into routine work. Here's how to implement it. ## Why Schema Matters @@ -47,23 +47,26 @@ Code: ECS-compliant log entry. The key field groups to adopt immediately: service.\*', - text: 'which service emitted the log (service.name, service.version)', + title: 'service.\*', + lead: 'Which service emitted the log (service.name, service.version)', }, { - lead: 'trace.\*', - text: 'correlation context (trace.id, span.id)', + title: 'trace.\*', + lead: 'Correlation context (trace.id, span.id)', }, { - lead: 'event.\*', - text: 'what happened (event.action, event.outcome)', + title: 'event.\*', + lead: 'What happened (event.action, event.outcome)', }, { - lead: 'error.\*', - text: 'failure details (error.type, error.message, error.stack_trace)', + title: 'error.\*', + lead: 'Failure details (error.type, error.message, error.stack_trace)', }, ]} /> @@ -83,8 +86,9 @@ A consistent schema lets you query individual services reliably. Correlation IDs Not all correlation IDs serve the same purpose. Here's the hierarchy you need:
    4bf92f3577b34da6a...'], }, { th: 'Span ID', - td: ['Single service hop', 'Distinguish parent/child operations', '`00f067aa0ba902b7`'], + td: ['Single service hop', 'Distinguish parent/child operations', '00f067aa0ba902b7'], }, { th: 'Request ID', - td: ['Single HTTP request', 'Correlate with load balancer logs', '`req_abc123`'], + td: ['Single HTTP request', 'Correlate with load balancer logs', 'req_abc123'], }, { th: 'Transaction ID', - td: ['Business operation', 'Group related requests (e.g., checkout flow)', '`order-12345`'], + td: ['Business operation', 'Group related requests (e.g., checkout flow)', 'order-12345'], }, ], }, - figure: 'Correlation ID types and scopes.', }} /> -The key insight: __trace ID stays constant__ across every service that handles the request. When you query `trace.id:4bf92f3577b34da6a`, you get logs from the API gateway, order service, payment service, inventory service, and notification service — everything involved in that single user action. +The key insight: _trace ID stays constant_ across every service that handles the request. When you query `trace.id:4bf92f3577b34da6a`, you get logs from the API gateway, order service, payment service, inventory service, and notification service — everything involved in that single user action. @@ -122,9 +125,14 @@ Correlation IDs are useless if they don't travel with requests. Every service-to For HTTP, use the W3C Trace Context standard. The `traceparent` header carries trace and span IDs in a single string: -```text + traceparent: 00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01 -``` + The format is `version-traceId-spanId-flags`. Most APM tools (OpenTelemetry, Jaeger, Zipkin) support this natively. @@ -195,7 +203,10 @@ Every HTTP client in your codebase must propagate correlation headers. A single These patterns break correlation in production. Each creates gaps in your trace data: -Finding all logins for user 12345 now requires five different search patterns. Worse, you __don't know what you're missing__. Did you account for the service that uses `customer` instead of `user`? The one that logs to a different index? The one with the typo in the field name? +Finding all logins for user 12345 now requires five different search patterns. Worse, you _don't know what you're missing_. Did you account for the service that uses `customer` instead of `user`? The one that logs to a different index? The one with the typo in the field name? -The storage savings come from better compression — consistent field names compress dramatically better than arbitrary strings. The query accuracy improvement comes from knowing __exactly__ which field to search. The time savings compound: every query, every dashboard, every alert benefits from the consistency. +The storage savings come from better compression — consistent field names compress dramatically better than arbitrary strings. The query accuracy improvement comes from knowing _exactly_ which field to search. The time savings compound: every query, every dashboard, every alert benefits from the consistency. -JSON is a format, not a schema. Two services can emit valid JSON logs with completely incompatible field names. Structured logging requires both: a machine-readable format __and__ a shared vocabulary. +JSON is a format, not a schema. Two services can emit valid JSON logs with completely incompatible field names. Structured logging requires both: a machine-readable format _and_ a shared vocabulary. So how do you design a schema that actually sticks? @@ -131,7 +139,7 @@ Every schema decision you make in month one will constrain your queries for year Six rules that prevent the most common schema regrets:
    -The flat-over-nested rule surprises people. Dot notation like `http.request.method` __looks__ nested but stores flat in Elasticsearch. Actual nested JSON objects require special nested field mappings, slow down queries, and complicate aggregations. Use dots in field names, not actual object nesting. +The flat-over-nested rule surprises people. Dot notation like `http.request.method` _looks_ nested but stores flat in Elasticsearch. Actual nested JSON objects require special nested field mappings, slow down queries, and complicate aggregations. Use dots in field names, not actual object nesting. ### Adopting Elastic Common Schema (ECS) @@ -223,40 +231,40 @@ Code: ECS request logging example. The key ECS namespaces you'll use most often:
    service.*', + td: ['Service identification', 'service.name, service.version, service.environment'], }, { - th: '`http.*`', - td: ['HTTP request/response', '`http.request.method`, `http.response.status_code`'], + th: 'http.*', + td: ['HTTP request/response', 'http.request.method, http.response.status_code'], }, { - th: '`error.*`', - td: ['Error details', '`error.type`, `error.message`, `error.stack_trace`'], + th: 'error.*', + td: ['Error details', 'error.type, error.message, error.stack_trace'], }, { - th: '`user.*`', - td: ['User context', '`user.id`, `user.name`, `user.roles`'], + th: 'user.*', + td: ['User context', 'user.id, user.name, user.roles'], }, { - th: '`trace.*`', - td: ['Distributed tracing', '`trace.id`, `span.id`'], + th: 'trace.*', + td: ['Distributed tracing', 'trace.id, span.id'], }, { - th: '`event.*`', - td: ['Event categorization', '`event.action`, `event.outcome`, `event.category`'], + th: 'event.*', + td: ['Event categorization', 'event.action, event.outcome, event.category'], }, ], }, - figure: 'Common ECS namespaces.', }} /> @@ -287,9 +295,9 @@ Custom namespaces should be rare. Before adding `order.shipping_method`, check i ## Correlation ID Implementation -A schema gets you consistent field names. But when a request fails, you need more than consistency — you need to see __every log from every service__ that touched that request. That's where correlation IDs come in. +A schema gets you consistent field names. But when a request fails, you need more than consistency — you need to see _every log from every service_ that touched that request. That's where correlation IDs come in. -A single user action — placing an order, uploading a file, logging in — touches multiple services: API gateways, business logic services, payment processors, notification systems, databases. Without correlation IDs, each service's logs are islands. You know __something__ failed, but stitching together the sequence of events across service boundaries requires manual timestamp correlation and guesswork. +A single user action — placing an order, uploading a file, logging in — touches multiple services: API gateways, business logic services, payment processors, notification systems, databases. Without correlation IDs, each service's logs are islands. You know _something_ failed, but stitching together the sequence of events across service boundaries requires manual timestamp correlation and guesswork. Correlation IDs solve this by threading a common identifier through every log entry related to a single logical operation. Query by that ID, and you get the complete story — regardless of how many services participated. @@ -298,7 +306,7 @@ Correlation IDs solve this by threading a common identifier through every log en Not all correlation happens at the same scope. A single user session might span hundreds of requests, each request might fan out to dozens of service calls, and async operations might continue long after the original request completes. Different ID types address different correlation needs:
    trace.id', td: ['Distributed trace', 'Edge/first service', 'Single request tree', 'Debugging request flow'], }, { - th: '`span.id`', + th: 'span.id', td: ['Single operation', 'Each service', 'One operation', 'Identifying service involvement'], }, { - th: '`request.id`', + th: 'request.id', td: ['HTTP request', 'API gateway', 'Single HTTP request', 'Correlating gateway with downstream'], }, { - th: '`session.id`', + th: 'session.id', td: ['User session', 'Auth system', 'Session duration', 'Tracking user activity across requests'], }, { - th: '`transaction.id`', + th: 'transaction.id', td: ['Business operation', 'Domain service', 'Until completion', 'Tracking sagas and workflows'], }, { - th: '`causation.id`', + th: 'causation.id', td: ['Event chain', 'Message producer', 'One hop', 'Understanding event triggers'], }, ], @@ -393,11 +401,13 @@ When a user places an order, the request flows through multiple services. Each s -The key insight: `trace.id` stays constant across all services for a single request tree. Query `trace.id:4bf92f*` and you see __every__ log from __every__ service involved in that order placement — API gateway, order service, payment service, inventory service, notification service. +The key insight: `trace.id` stays constant across all services for a single request tree. Query `trace.id:4bf92f*` and you see _every_ log from _every_ service involved in that order placement — API gateway, order service, payment service, inventory service, notification service. The W3C Trace Context standard defines how to propagate trace and span IDs in HTTP headers via the `traceparent` header. Use it — it's supported by OpenTelemetry, Jaeger, Zipkin, and most APM tools. Don't invent your own trace header format. @@ -557,8 +567,9 @@ Code: Message queue correlation pattern. I've seen all of these break correlation in production. Each one creates gaps in your trace data that make debugging harder:
    traceparent; generate only if missing'], }, { th: 'Logging outside AsyncLocalStorage', - td: ['Logs lack correlation context', 'Run all request handlers within `contextStorage.run()`'], + td: ['Logs lack correlation context', 'Run all request handlers within contextStorage.run()'], }, { th: 'Direct HTTP client usage', - td: ["Outbound calls don't propagate headers", 'Use `fetchWithCorrelation()` or OpenTelemetry auto-instrumentation'], + td: ["Outbound calls don't propagate headers", 'Use fetchWithCorrelation() or OpenTelemetry auto-instrumentation'], }, { th: 'Fire-and-forget async', @@ -582,11 +593,10 @@ I've seen all of these break correlation in production. Each one creates gaps in }, { th: 'Batched message processing', - td: ['All messages share one correlation', 'Process each message in its own `contextStorage.run()` scope'], + td: ['All messages share one correlation', 'Process each message in its own contextStorage.run() scope'], }, ], }, - figure: 'Correlation anti-patterns and fixes.', }} /> @@ -838,6 +848,7 @@ Modern log architectures have three tiers: shippers that collect logs from appli @@ -849,15 +860,21 @@ Not all logs deserve storage. Health checks fire every few seconds and rarely ma This framework helps decide how to handle different log patterns. The "Connection events" row uses aggregation — the same technique shown in the code example that follows:
    /health, /ready)', td: ['Drop', 'High volume, rarely useful for debugging'], }, { @@ -886,7 +903,6 @@ This framework helps decide how to handle different log patterns. The "Connectio }, ], }, - figure: 'Noise classification framework.', }} /> @@ -917,7 +933,7 @@ const aggregatedConnectionLog: AggregatedLog = { Code: Aggregated log example. -Be cautious with sampling and dropping. Start conservative — drop only the logs you're __certain__ have no debugging value. You can always drop more later, but you can't recover logs you never stored. +Be cautious with sampling and dropping. Start conservative — drop only the logs you're _certain_ have no debugging value. You can always drop more later, but you can't recover logs you never stored. ## Schema Governance and Evolution @@ -929,7 +945,12 @@ A schema is only useful if everyone follows it. Without governance, you'll have You need a central source of truth for field definitions — what fields exist, their types, who owns them, and whether they're deprecated. Unfortunately, there's no dominant open source "log schema registry" the way there's a Confluent Schema Registry for Kafka. Your options: -The key insight: the __format__ of your registry matters less than __having one at all__ and __enforcing it in CI__. A YAML file that every service validates against beats a sophisticated registry that nobody uses. +The key insight: the _format_ of your registry matters less than _having one at all_ and _enforcing it in CI_. A YAML file that every service validates against beats a sophisticated registry that nobody uses. If you're using ECS, you already have a schema — the [ECS field reference](https://www.elastic.co/guide/en/ecs/current/ecs-field-reference.html) __is__ your registry for standard fields. You only need to document custom fields you've added. @@ -957,8 +978,14 @@ If you're using ECS, you already have a schema — the [ECS field reference](htt Schemas change — new features need new fields, old patterns get replaced. The key is managing change without breaking consumers. Here's the evolution process that's worked for me:
    @@ -1106,7 +1132,10 @@ Code: Correlation propagation tests. In production, monitor log quality the same way you monitor application health. Three Prometheus counters give you visibility into schema compliance and correlation coverage: Date: Fri, 20 Mar 2026 01:57:58 +0300 Subject: [PATCH 15/20] Styling fixes to table and list components in symptom-based-alerting-runbooks-alert-design article --- src/components/List/server/slotItems.ts | 33 +--- .../index.mdx | 29 ++-- .../pdf.mdx | 133 +++++++++++----- src/pages/testing/comps/scratchpad.astro | 147 +++++++++++++++++- src/styles/themes/dark.css | 40 ++--- 5 files changed, 286 insertions(+), 96 deletions(-) diff --git a/src/components/List/server/slotItems.ts b/src/components/List/server/slotItems.ts index fdc2b631..0757e543 100644 --- a/src/components/List/server/slotItems.ts +++ b/src/components/List/server/slotItems.ts @@ -28,38 +28,17 @@ export function getListItemsFromSlotMarkup(markup: string, variant: string): Lis } const document = new JSDOM(`${markup}`).window.document - const meaningfulNodes = Array.from(document.body.childNodes).filter((node) => { - if (node.nodeType === node.TEXT_NODE) { - return node.textContent?.trim().length - } + const listItemElements = Array.from(document.body.querySelectorAll('wsb-list-item')) - return node.nodeType === node.ELEMENT_NODE - }) - - if (meaningfulNodes.length === 0) { + if (listItemElements.length === 0) { throw new BuildError( 'List: expected one or more ListItem children when using rich slot content.', buildErrorContext ) } - const invalidNode = meaningfulNodes.find((node) => { - return node.nodeType !== node.ELEMENT_NODE || (node as Element).tagName.toLowerCase() !== 'wsb-list-item' - }) - - if (invalidNode) { - throw new BuildError( - 'List: rich slot content must contain only ListItem children. Use either the `items` prop or `` children.', - buildErrorContext - ) - } - - return meaningfulNodes.map((node) => { - const element = node as Element - - return { - lead: element.getAttribute('data-lead') ?? undefined, - text: element.innerHTML.trim(), - } - }) + return listItemElements.map((element) => ({ + lead: element.getAttribute('data-lead') ?? undefined, + text: element.innerHTML.trim(), + })) } \ No newline at end of file diff --git a/src/content/articles/symptom-based-alerting-runbooks-alert-design/index.mdx b/src/content/articles/symptom-based-alerting-runbooks-alert-design/index.mdx index a47c1889..c28ede34 100644 --- a/src/content/articles/symptom-based-alerting-runbooks-alert-design/index.mdx +++ b/src/content/articles/symptom-based-alerting-runbooks-alert-design/index.mdx @@ -15,16 +15,19 @@ Your on-call engineer gets paged at 2 AM for high CPU on a batch processing node In the morning, they discover the payment service was down for 45 minutes. Customers couldn't complete purchases. The alert was real; the engineer had just been trained to ignore it. -This is alert fatigue in action. When everything alerts, nothing alerts. The fix isn't better discipline — it's better alert design. +This is alert fatigue in action. When everything alerts, nothing alerts. The fix isn't better discipline — it's better alert design. ## The Symptom vs Cause Distinction -The most common alerting mistake is alerting on __causes__ rather than __symptoms__. High CPU, low disk space, elevated connection counts — these are causes. They __might__ affect users, or they might not. Request latency, error rates, failed transactions — these are symptoms. They tell you users are __actually__ experiencing problems right now. +The most common alerting mistake is alerting on _causes_ rather than _symptoms_. High CPU, low disk space, elevated connection counts — these are causes. They _might_ affect users, or they might not. Request latency, error rates, failed transactions — these are symptoms. They tell you users are _actually_ experiencing problems right now. Consider three classic cause-based alerts and their symptom-based alternatives: -The pattern is consistent: causes are __potential__ problems; symptoms are __actual__ problems. This leads to a simple rule: page on symptoms, ticket on causes. +The pattern is consistent: causes are _potential_ problems; symptoms are _actual_ problems. This leads to a simple rule: page on symptoms, ticket on causes. text: 'If you can\'t write down how to diagnose and remediate an alert, you don\'t understand it well enough to wake someone up for it. The runbook doesn\'t need to be exhaustive-a summary of user impact, links to relevant dashboards, common causes ranked by likelihood, and escalation criteria are enough. The point is that a 2 AM responder shouldn\'t have to reverse-engineer what the alert means.', This creates a natural hierarchy for categorizing alerts: @@ -53,7 +56,7 @@ This creates a natural hierarchy for categorizing alerts: ## SLO-Based Burn Rate Alerting -Symptom-based alerting answers __what__ to alert on. Burn rates answer __when__. +Symptom-based alerting answers _what_ to alert on. Burn rates answer _when_. The problem with raw thresholds is they lack context. A 1% error rate sounds scary, but is it? If your SLO allows 0.1% errors over a month, that 1% rate means you're burning through your error budget 10x faster than sustainable. You have about 3 days before you exhaust your monthly budget. Urgent, but not a 2 AM emergency. @@ -64,8 +67,10 @@ Burn rate measures how fast you're consuming your error budget relative to a sus The math: if your monthly budget is 0.1% errors and you're currently seeing 1.44% errors (14.4 × 0.1%), you're burning 14.4x faster than sustainable. At that rate, your 30-day budget disappears in 30 days ÷ 14.4 ≈ 2 hours.
    @@ -115,9 +119,9 @@ groups: Code: Prometheus burn rate alert. -One refinement makes burn rate alerts even more reliable: multi-window alerting. Single-window alerts have a problem — short windows catch spikes but also false-positive on brief blips; long windows miss fast-moving incidents. The solution is requiring __both__ a short and long window to breach before alerting. +One refinement makes burn rate alerts even more reliable: multi-window alerting. Single-window alerts have a problem — short windows catch spikes but also false-positive on brief blips; long windows miss fast-moving incidents. The solution is requiring _both_ a short and long window to breach before alerting. -For example, a 14.4x burn rate alert might require both conditions to be true: the 5-minute error rate exceeds the threshold __and__ the 1-hour error rate exceeds the threshold. If a 30-second traffic spike pushes errors to 2% but the hourly rate is still 0.05%, the alert doesn't fire — the spike isn't sustained. Conversely, if an incident resolved 20 minutes ago, the 1-hour window might still show elevated errors, but the 5-minute window is clean — no alert, because the problem is already over. +For example, a 14.4x burn rate alert might require both conditions to be true: the 5-minute error rate exceeds the threshold _and_ the 1-hour error rate exceeds the threshold. If a 30-second traffic spike pushes errors to 2% but the hourly rate is still 0.05%, the alert doesn't fire — the spike isn't sustained. Conversely, if an incident resolved 20 minutes ago, the 1-hour window might still show elevated errors, but the 5-minute window is clean — no alert, because the problem is already over. ## Sustaining Alert Quality @@ -136,7 +140,12 @@ The symptom vs cause distinction and burn rate math are the technical foundation /> runbook_url annotation must exist.', *[MTTR]: Mean Time to Resolve *[NOC]: Network Operations Center *[P1]: Priority 1 (Critical) @@ -36,18 +36,21 @@ At first, you have two or three alerts per day, and 90% require real action. Eng -Breaking this cycle requires three changes. First, alert on __symptoms__ that affect users rather than __causes__ that might affect them — this dramatically reduces false positives. Second, require runbooks for every alert so responders can act quickly and confidently. Third, conduct regular alert hygiene reviews to prune noise and retire alerts that no longer earn their keep. These aren't optional practices for mature teams; they're prerequisites for an alerting system that actually works. +Breaking this cycle requires three changes. First, alert on _symptoms_ that affect users rather than _causes_ that might affect them — this dramatically reduces false positives. Second, require runbooks for every alert so responders can act quickly and confidently. Third, conduct regular alert hygiene reviews to prune noise and retire alerts that no longer earn their keep. These aren't optional practices for mature teams; they're prerequisites for an alerting system that actually works. ### Measuring Alert Quality You can't fix what you don't measure. Before optimizing your alerts, establish baseline metrics that reveal whether your alerting helps or hurts.
    -The pattern: causes are __potential__ problems; symptoms are __actual__ problems. Page on symptoms, ticket on causes. +The pattern: causes are _potential_ problems; symptoms are _actual_ problems. Page on symptoms, ticket on causes. @@ -143,7 +150,12 @@ The math: if your monthly budget is 0.1% errors and you're currently seeing 1.44 This framing changes how you think about alerts. A 1% error rate might sound scary in isolation, but if your SLO allows 0.1% errors, that's a 10x burn rate — you have 3 days before budget exhaustion. Urgent, but not a 2 AM page. A 0.3% error rate is only 3x burn, giving you 10 days. That's a ticket for business hours.
    + + Your primary symptom signal. Alert on P99 latency[^1] exceeding your SLO, not average latency + (which hides tail issues affecting your worst-served users). Page when P99 exceeds 2x your SLO + for more than 5 minutes; create a ticket when it exceeds 1.5x for more than 30 minutes. + + + Unusual — you typically alert on _drops_, not increases. A 50% traffic drop + compared to predictions suggests an outage that's preventing users from reaching your service. + High traffic is usually good news; capacity concerns belong on dashboards, not alerts. + + + Should trigger based on burn rate against your error budget, not absolute counts. A single error + isn't actionable; an error rate that will exhaust your monthly budget in 2 hours is. Use the + burn rate thresholds from the previous section. + + + The trickiest signal. High CPU or memory usage alone doesn't warrant a page — many systems + run hot by design. Alert on saturation only when it's _causing_ symptoms: when + queue depth correlates with latency spikes, or when memory pressure triggers OOM kills. Keep raw + saturation metrics on dashboards for diagnosis, not as alert triggers. + + [^1]: P99, P95, and P50 are percentile metrics. P99 means the value below which 99% of observations fall — in other words, only 1% of requests are slower than this value. P99 captures the experience of your worst-served users without being skewed by rare extreme outliers. P50 (the median) shows typical experience, P95 catches most bad cases, and P99 catches nearly all of them. For alerting, P99 is usually the right choice: it's sensitive to real degradation but robust to occasional slow requests. -__Traffic__ is unusual — you typically alert on __drops__, not increases. A 50% traffic drop compared to predictions suggests an outage that's preventing users from reaching your service. High traffic is usually good news; capacity concerns belong on dashboards, not alerts. - -__Errors__ should trigger based on burn rate against your error budget, not absolute counts. A single error isn't actionable; an error rate that will exhaust your monthly budget in 2 hours is. Use the burn rate thresholds from the previous section. - -__Saturation__ is the trickiest signal. High CPU or memory usage alone doesn't warrant a page — many systems run hot by design. Alert on saturation only when it's __causing__ symptoms: when queue depth correlates with latency spikes, or when memory pressure triggers OOM kills. Keep raw saturation metrics on dashboards for diagnosis, not as alert triggers. -
    runbook_url annotation must exist', + lead: 'The runbook_url annotation must exist', }, { - text: 'The URL must return a 200 (the runbook exists)', + lead: 'The URL must return a 200 (the runbook exists)', }, { - text: 'The runbook must have been updated within the last 90 days', + lead: 'The runbook must have been updated within the last 90 days', }, ]} /> @@ -712,11 +756,14 @@ Code: Alertmanager routing configuration. Key routing principles: group_by: [\'alertname\', \'service\'] prevents a cascading failure from generating 50 separate pages. One page with context is better than an inbox flood.', }, { lead: 'Set appropriate repeat intervals:', @@ -736,7 +783,10 @@ Escalation ensures that unacknowledged alerts reach someone who can respond. The A typical escalation chain for critical production issues:
    @@ -284,15 +284,16 @@ The `Faker.seed()` call ensures you get the same records every time, which matte ## Production Data Anonymization -Sometimes you genuinely need production data — debugging a specific customer issue, reproducing a complex data pattern, or performance testing with realistic distributions. The answer isn't "never use production data." It's "never use __identifiable__ production data." Anonymization lets you keep the structure and relationships while removing the liability. +Sometimes you genuinely need production data — debugging a specific customer issue, reproducing a complex data pattern, or performance testing with realistic distributions. The answer isn't "never use production data." It's "never use _identifiable_ production data." Anonymization lets you keep the structure and relationships while removing the liability. ### Choosing the Right Technique Different data types need different anonymization approaches. The goal is preserving what matters for testing while destroying what identifies individuals.
    -The critical insight is __consistency__: the same real value must always map to the same fake value. Without this, referential integrity breaks. If `john@real.com` appears in both the `users` and `orders` tables, both occurrences need to become the same anonymized value. +The critical insight is _consistency_: the same real value must always map to the same fake value. Without this, referential integrity breaks. If `john@real.com` appears in both the `users` and `orders` tables, both occurrences need to become the same anonymized value. ### Deterministic Anonymization @@ -457,25 +457,55 @@ Test data isn't a one-time problem. Schemas evolve, test scenarios multiply, and The most maintainable approach separates fixtures by their role in testing. Base fixtures provide the minimal data every test needs. Scenario fixtures set up specific test cases. Factories generate data programmatically when you need flexibility or volume. -```bash -fixtures/ -├── base/ # Minimal data for any test run -│ ├── users.json -│ └── products.json -├── scenarios/ # Specific test case setups -│ ├── empty-cart/ -│ ├── checkout-flow/ -│ └── edge-cases/ -├── factories/ # Programmatic generation -│ ├── user_factory.py -│ └── order_factory.py -└── seeds/ # Environment-specific seeding - ├── development.py - ├── staging.py - └── e2e.py -``` - -Figure: Fixture directory structure separating concerns + Base fixtures are checked into version control and rarely change. They represent the "happy path" data that most tests assume exists — a default user, a few products, standard configuration. Keep them minimal; tests that need specific setups should use scenario fixtures or factories. @@ -563,7 +593,11 @@ Synthetic data isn't just a technical convenience — it's often a legal require Different regulations have different specifics, but the common thread is clear: personal data in test environments is either prohibited outright or requires controls most teams don't have.
    Auditors look for evidence of process, not just policy. A documented policy saying "we use synthetic data" isn't sufficient. You need: Synthetic data with this documentation passes audits easily. - ## Conclusion diff --git a/src/pages/testing/comps/scratchpad.astro b/src/pages/testing/comps/scratchpad.astro index 836aaf63..1ce1cf19 100644 --- a/src/pages/testing/comps/scratchpad.astro +++ b/src/pages/testing/comps/scratchpad.astro @@ -134,12 +134,12 @@ const variants = [ height: 2.125rem !important; padding: 0.4rem !important; border-radius: 9999px !important; + background-color: var(--color-page-offset) !important; transition: background-color 0.2s ease, transform 0.1s ease !important; } - /* Added slight grey background on hover and upwards nudge */ + /* Keep hover focused on motion only */ .variant-4 .share-button:hover { - background-color: var(--color-page-offset) !important; transform: translateY(-1px) !important; } From 3ad3c2ce49c60d983ee6f0db2fd7d685f8dc1757 Mon Sep 17 00:00:00 2001 From: Kevin Brown Date: Fri, 20 Mar 2026 02:45:27 +0300 Subject: [PATCH 17/20] Styling fixes to table and list components in terraform-module-design-defaults-versioning-interfaces article --- .../client/__tests__/index.spec.ts | 1 + .../client/__tests__/selectors.spec.ts | 4 + .../Social/Highlighter/client/index.ts | 8 +- src/components/Social/Highlighter/index.css | 39 +++-- .../index.mdx | 12 +- .../pdf.mdx | 142 ++++++++++------- src/pages/testing/comps/scratchpad.astro | 144 ------------------ 7 files changed, 133 insertions(+), 217 deletions(-) diff --git a/src/components/Social/Highlighter/client/__tests__/index.spec.ts b/src/components/Social/Highlighter/client/__tests__/index.spec.ts index d2a09d96..edec5916 100644 --- a/src/components/Social/Highlighter/client/__tests__/index.spec.ts +++ b/src/components/Social/Highlighter/client/__tests__/index.spec.ts @@ -167,6 +167,7 @@ describe('HighlighterElement', () => { const dialog = element.querySelector('.share-dialog') as HTMLElement | null expect(dialog?.getAttribute('role')).toBe('toolbar') + expect(dialog?.querySelector('.share-dialog__text')?.textContent).toBe('Share Selection') const describedBy = trigger?.getAttribute('aria-describedby') expect(describedBy).toBeTruthy() diff --git a/src/components/Social/Highlighter/client/__tests__/selectors.spec.ts b/src/components/Social/Highlighter/client/__tests__/selectors.spec.ts index 74be4e66..1b9461b3 100644 --- a/src/components/Social/Highlighter/client/__tests__/selectors.spec.ts +++ b/src/components/Social/Highlighter/client/__tests__/selectors.spec.ts @@ -59,6 +59,10 @@ describe('HighlighterElement selectors', () => { dialog.getAttribute('role'), 'HighlighterElement dialog should be role="toolbar"' ).toBe('toolbar') + expect( + dialog.querySelector('.share-dialog__text')?.textContent, + 'HighlighterElement should render a visible share label inside the dialog' + ).toBe('Share Selection') expect( shareButtons.length, 'HighlighterElement should render at least one share button' diff --git a/src/components/Social/Highlighter/client/index.ts b/src/components/Social/Highlighter/client/index.ts index 455b373f..82895ab4 100644 --- a/src/components/Social/Highlighter/client/index.ts +++ b/src/components/Social/Highlighter/client/index.ts @@ -32,6 +32,7 @@ const COMPONENT_TAG_NAME = 'highlighter-element' const ICON_BANK_ID = 'highlighter-icon-bank' let highlighterInstanceCounter = 0 +const VISIBLE_SHARE_LABEL = 'Share Selection' /** * Highlighter element that creates a shareable text highlight @@ -144,6 +145,7 @@ export class HighlighterElement extends LitElement { aria-label="${this.label}" aria-hidden="true" > + , they\'re depending on that output existing, being a list, and having at least one element. Change the output name or type and their code breaks.', }, { lead: 'Resource behavior', @@ -53,13 +53,15 @@ Think of a module interface in five layers: }, { lead: 'Implicit contracts', - text: 'The undocumented assumptions. Maybe your module names resources with a specific pattern (`{environment}-{name}-{resource_type}`), applies a standard set of tags, or assumes the VPC has DNS hostnames enabled. Perhaps it expects certain IAM permissions to exist, or assumes subnets have internet access through a NAT gateway. These are the trickiest because they\'re easy to break without realizing it — you rename your tagging convention and suddenly a downstream cost allocation dashboard stops working.', + text: 'The undocumented assumptions. Maybe your module names resources with a specific pattern ({environment}-{name}-{resource_type}), applies a standard set of tags, or assumes the VPC has DNS hostnames enabled. Perhaps it expects certain IAM permissions to exist, or assumes subnets have internet access through a NAT gateway. These are the trickiest because they\'re easy to break without realizing it — you rename your tagging convention and suddenly a downstream cost allocation dashboard stops working.', }, ]} /> @@ -69,7 +71,7 @@ Think of a module interface in five layers: Not all changes are equal. Some are safe to make in a minor version; others require a major version bump and migration guidance.
    -_Minor version bumps_ are for backward-compatible additions: +__Minor version bumps__ are for backward-compatible additions: @@ -532,19 +545,22 @@ _Minor version bumps_ are for backward-compatible additions: __Patch version bumps__ are for fixes that don't touch the interface: @@ -570,7 +586,8 @@ Code: Pessimistic constraint for most modules, exact pinning for critical infras The pessimistic constraint (`~>`) is the sweet spot for most use cases. It accepts patch and minor updates but stops at the next major version, giving you bug fixes without breaking changes. For critical infrastructure like databases, exact pinning provides maximum stability at the cost of manual updates.
    @@ -946,26 +963,45 @@ Run upgrade tests as part of your release process. If you support multiple major A well-organized module repository makes tests discoverable and examples easy to find. This structure works for most modules: -```text -├── main.tf # Primary resource logic -├── variables.tf # Input variable definitions -├── outputs.tf # Output declarations -├── versions.tf # Terraform and provider version constraints -├── README.md # Usage documentation -├── CHANGELOG.md # Version history and migration guides -├── examples/ # Working usage examples -│ ├── simple/ # Minimal configuration -│ └── complete/ # All features demonstrated -├── modules/ # Internal sub-modules (if needed) -│ └── networking/ # Encapsulated sub-component -└── tests/ # Test files - ├── contract.tftest.hcl # Input/output contract tests - ├── validation.tftest.hcl # Validation rule tests - ├── defaults.tftest.hcl # Default value tests - └── upgrade_v1_to_v2.tftest.hcl # Version migration tests -``` - -Code: Recommended module repository structure. + The `examples/` directory serves double duty: it provides documentation for consumers and test fixtures for integration tests. Point your `README.md` at these examples so consumers can see working configurations, not just variable descriptions. diff --git a/src/pages/testing/comps/scratchpad.astro b/src/pages/testing/comps/scratchpad.astro index 1ce1cf19..bca17e91 100644 --- a/src/pages/testing/comps/scratchpad.astro +++ b/src/pages/testing/comps/scratchpad.astro @@ -5,154 +5,10 @@ import BaseLayout from '@layouts/BaseLayout.astro' const pageTitle = 'Scratchpad' const pageDescription = 'Troubleshooting Component Variants' const path = '/testing/comps/scratchpad' - -const icons = { - x: ``, - linkedin: ``, - bluesky: ``, - reddit: ``, - mastodon: `` -} - -const variants = [ - { id: 'variant-4', title: 'Variant 4: Divided Context Card', text: 'Share Selection' }, -]; ---
    -
    -

    Highlighter Share Dialog Styling

    -

    Review the variants below to decide which setup looks best.

    -
    - {variants.map(variant => ( -
    -

    {variant.title}

    - - -
    - - - - - {variant.text && } -
    -
    - ))}
    - - From 0e8dcfb2d736ef20ad5aad74f1d52075dc122667 Mon Sep 17 00:00:00 2001 From: Kevin Brown Date: Fri, 20 Mar 2026 03:08:23 +0300 Subject: [PATCH 18/20] Styling fixes to table and list components in terraform-state-locking-corruption-recovery-backend article --- .../index.mdx | 21 ++++--- .../pdf.mdx | 60 ++++++++++++------- 2 files changed, 52 insertions(+), 29 deletions(-) diff --git a/src/content/articles/terraform-state-locking-corruption-recovery-backend/index.mdx b/src/content/articles/terraform-state-locking-corruption-recovery-backend/index.mdx index 7511f1f3..9f77498f 100644 --- a/src/content/articles/terraform-state-locking-corruption-recovery-backend/index.mdx +++ b/src/content/articles/terraform-state-locking-corruption-recovery-backend/index.mdx @@ -18,7 +18,7 @@ It's 2 AM and you're staring at a `terraform plan` that wants to destroy half yo State corruption is a when, not an if. Every Terraform practitioner eventually experiences that moment of panic when state diverges from reality. The state file is Terraform's memory — it maps your HCL configuration to actual cloud resources. When that mapping breaks, Terraform loses its ability to reason about your infrastructure. The results range from orphaned resources you have to clean up manually to unintended destruction of production systems. -The good news: state problems fall into predictable patterns, and each pattern has a specific recovery procedure. The question isn't whether you'll face state corruption, but whether you'll recover in minutes with practiced procedures or spend days reconstructing through imports and manual investigation. +The good news: state problems fall into predictable patterns, and each pattern has a specific recovery procedure. The question isn't whether you'll face state corruption, but whether you'll recover in minutes with practiced procedures or spend days reconstructing through imports and manual investigation. ## Recognizing What's Wrong @@ -27,7 +27,11 @@ State corruption isn't always obvious. Sometimes Terraform tells you directly wi The most common causes, roughly in order of frequency: cancel-in-progress: false in GitHub Actions — never cancel a running apply.', }, { lead: 'Pin provider and Terraform versions.', @@ -227,11 +234,11 @@ Most state problems are preventable. These practices eliminate the patterns that }, { lead: 'Save plans to files.', - text: 'Run `terraform plan -out=tfplan`, then `terraform apply tfplan`. This prevents drift between plan and apply.', + text: 'Run terraform plan -out=tfplan, then terraform apply tfplan. This prevents drift between plan and apply.', }, { lead: 'Never edit state manually.', - text: 'Use `terraform state` commands instead. Manual JSON edits bypass validation and corrupt checksums.', + text: 'Use terraform state commands instead. Manual JSON edits bypass validation and corrupt checksums.', }, { lead: 'Practice recovery quarterly.', diff --git a/src/content/articles/terraform-state-locking-corruption-recovery-backend/pdf.mdx b/src/content/articles/terraform-state-locking-corruption-recovery-backend/pdf.mdx index 42d2ee33..575e5ad2 100644 --- a/src/content/articles/terraform-state-locking-corruption-recovery-backend/pdf.mdx +++ b/src/content/articles/terraform-state-locking-corruption-recovery-backend/pdf.mdx @@ -77,23 +77,28 @@ Code: Terraform state file structure showing the key fields. The critical fields are: version', text: 'The state format version (currently 4). Terraform uses this to handle state migrations between versions.', }, { - lead: 'serial', + lead: 'serial', text: 'Incremented on every state change. This is how Terraform detects concurrent modifications — if you try to write serial 43 but the backend has serial 44, someone else modified state while you were working.', }, { - lead: 'lineage', + lead: 'lineage', text: "A unique identifier for this state's history. If lineage doesn't match, you're trying to push state from an entirely different Terraform configuration.", }, { - lead: 'resources', - text: 'The mapping between your config (`aws_vpc.main`) and real infrastructure (`vpc-0123456789abcdef0`), including all the attributes Terraform knows about.', + lead: 'resources', + text: 'The mapping between your config (aws_vpc.main) and real infrastructure (vpc-0123456789abcdef0), including all the attributes Terraform knows about.', }, ]} /> @@ -105,8 +110,9 @@ When you run `terraform plan`, Terraform reads this state, queries the cloud pro State solves essential problems, but each solution introduces its own failure modes.
    @@ -203,8 +208,10 @@ Point-in-time recovery on the lock table might seem excessive, but it's saved me Different backends implement locking differently, with varying reliability and recovery procedures.
    terraform force-unlock LOCK_ID'], }, { th: 'Azure Blob', @@ -232,7 +239,6 @@ Different backends implement locking differently, with varying reliability and r }, ], }, - figure: 'Backend locking mechanisms and recovery methods.', }} /> @@ -249,7 +255,10 @@ State corruption isn't always obvious. Sometimes Terraform tells you directly wi The most frequent causes of state problems, roughly in order of how often I've encountered them: @@ -612,11 +624,11 @@ The lifecycle configuration keeps 90 days of old versions, transitioning them to How you organize state files across environments and components affects both security and operational complexity. There are three common patterns: @@ -636,15 +648,16 @@ terraform { Code: Path-based state separation by environment and component. @@ -754,19 +767,22 @@ Having backups is not enough — you must verify you can restore from them. Sche Beyond CI/CD and backups, a few operational practices significantly reduce state problems: -out for plans.', text: 'Always save plans to a file and apply that file. This prevents drift between plan and apply, especially in CI where infrastructure might change between the two steps.', }, { lead: 'Never edit state manually.', - text: 'If you find yourself opening a state file in an editor, stop. Use `terraform state mv`, `terraform state rm`, or `terraform import` instead. Manual edits bypass validation and corrupt checksums.', + text: 'If you find yourself opening a state file in an editor, stop. Use terraform state mv, terraform state rm, or terraform import instead. Manual edits bypass validation and corrupt checksums.', }, { lead: 'Review plans carefully.', From f3a3eac5482f65339951ef67032d300ef2eec29a Mon Sep 17 00:00:00 2001 From: Kevin Brown Date: Fri, 20 Mar 2026 03:41:39 +0300 Subject: [PATCH 19/20] Styling fixes to table and list components in workload-identity-federation-keyless-cloud-authentication article --- .../index.mdx | 24 ++-- .../pdf.mdx | 124 ++++++++++++------ 2 files changed, 99 insertions(+), 49 deletions(-) diff --git a/src/content/articles/workload-identity-federation-keyless-cloud-authentication/index.mdx b/src/content/articles/workload-identity-federation-keyless-cloud-authentication/index.mdx index dd4240be..cf860b39 100644 --- a/src/content/articles/workload-identity-federation-keyless-cloud-authentication/index.mdx +++ b/src/content/articles/workload-identity-federation-keyless-cloud-authentication/index.mdx @@ -20,7 +20,7 @@ import oidcTokenDiagram from "./diagrams/oidc-token-exchange-flow-for-workload-i It starts with a Slack message at 2 AM: "Why is there a $47,000 charge on our Azure bill?" You dig through the activity logs and find a cryptomining operation running in your subscription. The trail leads back to a service principal secret created eighteen months ago during a "quick proof of concept." Nobody remembers who created it. Nobody knows where else it might have been copied. And now you're explaining to leadership how a credential that was supposed to be temporary gave an attacker free rein over your infrastructure. -This scenario plays out constantly. Service account keys are the most common entry point for cloud breaches, and the attack vector is almost always the same: a static credential that was created, copied somewhere, and forgotten. +This scenario plays out constantly. Service account keys are the most common entry point for cloud breaches, and the attack vector is almost always the same: a static credential that was created, copied somewhere, and forgotten. ## The Hidden Risk of Long-Lived Keys @@ -29,7 +29,10 @@ Service account keys are static credentials that live forever until explicitly r The security model is fundamentally broken: you're distributing secrets that grant access, then hoping nobody loses track of them. But secrets spread. They end up in git history, in Slack messages, in screenshots of terminal sessions. Each copy is a potential breach waiting to happen.
    iss (issuer)', + text: 'Where the token came from — https://token.actions.githubusercontent.com', }, { - lead: 'sub (subject)', - text: 'The specific workload identity — `repo:myorg/myrepo:ref:refs/heads/main`', + lead: 'sub (subject)', + text: 'The specific workload identity — repo:myorg/myrepo:ref:refs/heads/main', }, { - lead: 'aud (audience)', + lead: 'aud (audience)', text: "Who the token is intended for — your cloud provider's token exchange endpoint", }, ]} diff --git a/src/content/articles/workload-identity-federation-keyless-cloud-authentication/pdf.mdx b/src/content/articles/workload-identity-federation-keyless-cloud-authentication/pdf.mdx index 137d3f69..c8386505 100644 --- a/src/content/articles/workload-identity-federation-keyless-cloud-authentication/pdf.mdx +++ b/src/content/articles/workload-identity-federation-keyless-cloud-authentication/pdf.mdx @@ -26,7 +26,7 @@ import workloadIdentityDiagram from "./diagrams/workload-identity-federation-aut *[STS]: Security Token Service *[SPIFFE]: Secure Production Identity Framework for Everyone -If you've ever inherited a CI/CD pipeline and found service account keys created by someone who left three years ago, you know the feeling. Nobody knows where the copies are. Nobody's rotated them. You're stuck wondering whether to touch them or leave them alone because __something__ might break. +If you've ever inherited a CI/CD pipeline and found service account keys created by someone who left three years ago, you know the feeling. Nobody knows where the copies are. Nobody's rotated them. You're stuck wondering whether to touch them or leave them alone because _something_ might break. Workload identity federation offers a way out: instead of managing secrets that can be stolen, your workloads prove who they are and receive short-lived credentials that expire before anyone could misuse them. @@ -41,23 +41,26 @@ I've seen the same pattern play out at multiple organizations. A key gets create The typical lifecycle looks something like this: The really insidious part is that most of this happens without anyone noticing. Keys don't send notifications when they're copied. Git doesn't warn you when you're about to expose credentials in a repository you're making public.
    @@ -114,6 +118,7 @@ With federation, your CI runner requests a token from its platform, then exchang @@ -154,30 +159,35 @@ Code: Standard and GitHub-specific claims in an OIDC token. The key claims for access control are: iss (issuer)', + text: 'Where the token came from — GitHub\'s OIDC endpoint in this case (https://token.actions.githubusercontent.com)', }, { - lead: "sub (subject):", - text: "The unique identifier for the workload, which varies based on context (branch, environment, pull request)", + lead: 'sub (subject)', + text: 'The unique identifier for the workload, which varies based on context (branch, environment, pull request) — repo:myorg/myrepo:ref:refs/heads/main', }, { - lead: "aud (audience):", + lead: 'aud (audience)', text: "Who the token is intended for — your cloud provider's token exchange endpoint", }, { - lead: "repository and repository_owner:", + lead: 'repository and repository_owner:', text: "Which repo triggered the workflow", }, { - lead: "ref:", + lead: 'ref:', text: "The git reference (branch or tag) being built", }, { - lead: "environment:", + lead: 'environment:', text: "The GitHub Environment, if you're using environment protection rules", }, ]} @@ -199,14 +209,31 @@ Here's what's happening at each step: @@ -787,6 +814,7 @@ The flow inside the cluster looks like this: @@ -799,12 +827,12 @@ You're not going to migrate everything to workload identity in a weekend. In any ### Migration Planning -Start by figuring out what you have. Most cloud providers offer ways to list service account keys and their last-used timestamps. AWS has IAM credential reports; GCP has the Policy Analyzer; Azure has sign-in logs. But those only tell you __that__ a key was used, not __where__ it's being used from. +Start by figuring out what you have. Most cloud providers offer ways to list service account keys and their last-used timestamps. AWS has IAM credential reports; GCP has the Policy Analyzer; Azure has sign-in logs. But those only tell you _that_ a key was used, not _where_ it's being used from. For each key, you need to answer:
    main', + }, + { + lead: 'Repository renamed', + text: 'Someone renamed the repository' }, + { + lead: 'Pull request context', + text: 'You\'re running in a pull request context but the policy expects a branch ref', + }, + { + lead: 'Environment name typo', + text: 'The GitHub Environment name has a typo', + }, { lead: 'Audience mismatch', text: 'Means the aud claim in your token doesn\'t match the client_id_list in your OIDC provider configuration. This typically means the workflow is requesting a token for a different audience than what you configured. Check that your workflow\'s audience parameter matches what\'s in your cloud provider\'s OIDC provider setup.', @@ -1015,7 +1057,7 @@ Once you can see what's in the token, most federation failures fall into a few c The error usually looks like `AccessDenied: Not authorized to perform sts:AssumeRoleWithWebIdentity`. Compare the decoded `sub` claim against your trust policy condition — the mismatch is usually obvious.
    InvalidIdentityToken', td: ['Token expired or malformed', 'Retry the workflow; check for clock skew'], }, { - th: '`AccessDenied: Not authorized`', + th: 'AccessDenied: Not authorized', td: ['Trust policy condition mismatch', 'Decode token, compare `sub` to policy'], }, { - th: '`Audience does not match`', + th: 'Audience does not match', td: ['Wrong audience in token request', 'Check workflow audience vs provider config'], }, { - th: '`WebIdentityErr: failed to retrieve`', + th: 'WebIdentityErr: failed to retrieve', td: ['OIDC endpoint unreachable', 'Check GitHub status; retry later'], }, { - th: '`Service account does not exist`', + th: 'Service account does not exist', td: ['Deleted or wrong SA binding', 'Verify SA exists and binding is correct'], }, ], From 3cc5015109ca1e8513191b0d8c5b921d5332fd5f Mon Sep 17 00:00:00 2001 From: Kevin Brown Date: Fri, 20 Mar 2026 04:09:15 +0300 Subject: [PATCH 20/20] Fix lint errors --- package-lock.json | 2503 +++++------------ package.json | 16 +- src/components/List/server/selectors.ts | 7 + src/components/List/server/slotItems.ts | 27 +- src/components/Social/Highlighter/index.css | 2 +- .../Troubleshooter/__tests__/index.spec.ts | 2 +- src/components/Troubleshooter/index.astro | 1 - 7 files changed, 731 insertions(+), 1827 deletions(-) create mode 100644 src/components/List/server/selectors.ts diff --git a/package-lock.json b/package-lock.json index 2d7f06bd..d81bc79f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,12 +14,12 @@ "dependencies": { "@adobe/remark-gridtables": "^3.0.18", "@astrojs/check": "0.9.8", - "@astrojs/db": "^0.20.0", - "@astrojs/mdx": "5.0.1", - "@astrojs/preact": "5.0.1", + "@astrojs/db": "^0.20.1", + "@astrojs/mdx": "5.0.2", + "@astrojs/preact": "5.0.2", "@astrojs/rss": "4.0.17", "@astrojs/sitemap": "^3.7.1", - "@astrojs/vercel": "^10.0.1", + "@astrojs/vercel": "^10.0.2", "@axe-core/playwright": "^4.11.1", "@eslint-community/eslint-plugin-eslint-comments": "^4.7.1", "@eslint/js": "10.0.1", @@ -33,12 +33,12 @@ "@playwright/browser-chromium": "^1.58.2", "@playwright/test": "1.58.2", "@semantic-ui/astro-lit": "^5.3.0", - "@sentry/astro": "^10.43.0", - "@sentry/browser": "^10.43.0", + "@sentry/astro": "^10.45.0", + "@sentry/browser": "^10.45.0", "@shikijs/transformers": "^4.0.2", "@tailwindcss/forms": "0.5.11", "@tailwindcss/typography": "0.5.19", - "@tailwindcss/vite": "^4.2.1", + "@tailwindcss/vite": "^4.2.2", "@testing-library/dom": "10.4.1", "@testing-library/preact": "3.2.4", "@testing-library/user-event": "14.6.1", @@ -51,7 +51,7 @@ "@types/glidejs__glide": "^3.6.6", "@types/hast": "^3.0.4", "@types/js-cookie": "^3.0.6", - "@types/jsdom": "^28.0.0", + "@types/jsdom": "^28.0.1", "@types/node": "^25.5.0", "@types/nodemailer": "^7.0.11", "@types/pubsub-js": "^1.8.6", @@ -75,11 +75,11 @@ "@vitest/coverage-v8": "^4.1.0", "@webcomponents/template-shadowroot": "^0.2.1", "alex": "^11.0.1", - "astro": "6.0.5", + "astro": "6.0.7", "astro-link-validator": "github:rodgtr1/astro-link-validator", "astro-og-canvas": "^0.10.1", "astro-vtbot": "^2.1.12", - "baseline-browser-mapping": "^2.10.8", + "baseline-browser-mapping": "^2.10.9", "canvas-confetti": "^1.9.4", "confusing-browser-globals": "1.0.11", "cross-env": "^10.1.0", @@ -117,7 +117,7 @@ "md-attr-parser": "^1.3.0", "mermaid": "^11.13.0", "nanostores": "^1.2.0", - "nodemailer": "^8.0.2", + "nodemailer": "^8.0.3", "npm": "^11.11.1", "playwright-lighthouse": "^4.0.0", "postcss": "8.5.8", @@ -153,16 +153,16 @@ "retext": "^9.0.0", "retext-smartypants": "^6.2.0", "rimraf": "6.1.3", - "sanitize-html": "^2.17.1", + "sanitize-html": "^2.17.2", "schema-dts": "^1.1.5", "sharp": "^0.34.5", "shiki": "^4.0.2", "space-separated-tokens": "^2.0.2", - "stylelint": "^17.4.0", + "stylelint": "^17.5.0", "stylelint-config-standard": "^40.0.0", "stylelint-declaration-block-no-ignored-properties": "3.0.0", "stylelint-order": "8.1.1", - "tailwindcss": "^4.2.1", + "tailwindcss": "^4.2.2", "temp-dir": "3.0.0", "timezones-ical-library": "^2.1.3", "title-case": "4.3.2", @@ -176,8 +176,8 @@ "unist-util-is": "^6.0.1", "unist-util-visit": "^5.1.0", "uuid": "^13.0.0", - "vercel": "^50.32.5", - "vite": "^8.0.0", + "vercel": "^50.34.2", + "vite": "^8.0.1", "vitest": "4.1.0", "vitest-axe": "0.1.0", "workbox-build": "7.4.0", @@ -319,15 +319,15 @@ "license": "MIT" }, "node_modules/@astrojs/db": { - "version": "0.20.0", - "resolved": "https://registry.npmjs.org/@astrojs/db/-/db-0.20.0.tgz", - "integrity": "sha512-NnVkcdPg6E4G4MyOT6qJKChRjKSB5lQWEkw/0PYP21Oxqd5Tk4/kbd/CtO6QziSkalMyiKd808yHHck2Lt0lkw==", + "version": "0.20.1", + "resolved": "https://registry.npmjs.org/@astrojs/db/-/db-0.20.1.tgz", + "integrity": "sha512-WmV1nKMdATOP3DDphRiD4cAFeL0K/gaaoPcHUYpODS8Id/DoiTkLzcr1rjWBAbBEVa1XTBAPTMdA1H0PdUgiHA==", "license": "MIT", "dependencies": { "@clack/prompts": "^1.0.1", "@libsql/client": "^0.17.0", - "deep-diff": "^1.0.2", "drizzle-orm": "^0.42.0", + "microdiff": "^1.5.0", "nanoid": "^5.1.6", "piccolore": "^0.1.3", "yargs-parser": "^22.0.0", @@ -424,13 +424,13 @@ } }, "node_modules/@astrojs/markdown-remark": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/@astrojs/markdown-remark/-/markdown-remark-7.0.0.tgz", - "integrity": "sha512-jTAXHPy45L7o1ljH4jYV+ShtOHtyQUa1mGp3a5fJp1soX8lInuTJQ6ihmldHzVM4Q7QptU4SzIDIcKbBJO7sXQ==", + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/@astrojs/markdown-remark/-/markdown-remark-7.0.1.tgz", + "integrity": "sha512-zAfLJmn07u9SlDNNHTpjv0RT4F8D4k54NR7ReRas8CO4OeGoqSvOuKwqCFg2/cqN3wHwdWlK/7Yv/lMXlhVIaw==", "license": "MIT", "dependencies": { "@astrojs/internal-helpers": "0.8.0", - "@astrojs/prism": "4.0.0", + "@astrojs/prism": "4.0.1", "github-slugger": "^2.0.0", "hast-util-from-html": "^2.0.3", "hast-util-to-text": "^4.0.2", @@ -452,12 +452,12 @@ } }, "node_modules/@astrojs/mdx": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/@astrojs/mdx/-/mdx-5.0.1.tgz", - "integrity": "sha512-xfvc9MuV/5Kl6JaiYYEFi7ilbGYyaaOboH+gH8f2jHAZ2pmmHtnSrewS03vNeruBsa8rtS3X8NHJrqeZt+0wLg==", + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/@astrojs/mdx/-/mdx-5.0.2.tgz", + "integrity": "sha512-0as6odPH9ZQhS3pdH9dWmVOwgXuDtytJiE4VvYgR0lSFBvF4PSTyE0HdODHm/d7dBghvWTPc2bQaBm4y4nTBNw==", "license": "MIT", "dependencies": { - "@astrojs/markdown-remark": "7.0.0", + "@astrojs/markdown-remark": "7.0.1", "@mdx-js/mdx": "^3.1.1", "acorn": "^8.16.0", "es-module-lexer": "^2.0.0", @@ -472,16 +472,16 @@ "vfile": "^6.0.3" }, "engines": { - "node": "^20.19.1 || >=22.12.0" + "node": ">=22.12.0" }, "peerDependencies": { "astro": "^6.0.0" } }, "node_modules/@astrojs/preact": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/@astrojs/preact/-/preact-5.0.1.tgz", - "integrity": "sha512-KGX2Eku/6Og21+zoIvIDCGoMbMvX+lX4mCxx2A7FUBg539jkw6czmRpnm0VEIy5RvNOsTh7AAtzgmnB00t2c1A==", + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/@astrojs/preact/-/preact-5.0.2.tgz", + "integrity": "sha512-u3bzJQuTtWgq5hBexo++Djo/SfgB2bsyN6IVzmLcxIy00Ma5DVvHFXHU1kKSUOD8/D/2YgN/5bxdRtKGTDQrGQ==", "license": "MIT", "dependencies": { "@astrojs/internal-helpers": "0.8.0", @@ -492,7 +492,7 @@ "vite": "^7.3.1" }, "engines": { - "node": "^20.19.1 || >=22.12.0" + "node": ">=22.12.0" }, "peerDependencies": { "preact": "^10.6.5" @@ -653,15 +653,15 @@ } }, "node_modules/@astrojs/prism": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@astrojs/prism/-/prism-4.0.0.tgz", - "integrity": "sha512-NndtNPpxaGinRpRytljGBvYHpTOwHycSZ/c+lQi5cHvkqqrHKWdkPEhImlODBNmbuB+vyQUNUDXyjzt66CihJg==", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@astrojs/prism/-/prism-4.0.1.tgz", + "integrity": "sha512-nksZQVjlferuWzhPsBpQ1JE5XuKAf1id1/9Hj4a9KG4+ofrlzxUUwX4YGQF/SuDiuiGKEnzopGOt38F3AnVWsQ==", "license": "MIT", "dependencies": { "prismjs": "^1.30.0" }, "engines": { - "node": "^20.19.1 || >=22.12.0" + "node": ">=22.12.0" } }, "node_modules/@astrojs/rss": { @@ -705,9 +705,9 @@ } }, "node_modules/@astrojs/vercel": { - "version": "10.0.1", - "resolved": "https://registry.npmjs.org/@astrojs/vercel/-/vercel-10.0.1.tgz", - "integrity": "sha512-Ghl8L2ckDuNbfHbYYTRymUn87YrNTWD64Zz0zwYaGlZDkrG2nT3pmAU5BqzfOOV4Yvzgqxea+HEiEn6puefQZQ==", + "version": "10.0.2", + "resolved": "https://registry.npmjs.org/@astrojs/vercel/-/vercel-10.0.2.tgz", + "integrity": "sha512-l3TzsOnlEr9j0lMy/KhXnWaZh199tqIs3dd7FoQIm8HxMbbcmfMd5nUWCSU2+pXdfIUnsCmrlP0txDvlV5Vqxw==", "license": "MIT", "dependencies": { "@astrojs/internal-helpers": "0.8.0", @@ -3359,9 +3359,9 @@ } }, "node_modules/@fastify/otel": { - "version": "0.16.0", - "resolved": "https://registry.npmjs.org/@fastify/otel/-/otel-0.16.0.tgz", - "integrity": "sha512-2304BdM5Q/kUvQC9qJO1KZq3Zn1WWsw+WWkVmFEaj1UE2hEIiuFqrPeglQOwEtw/ftngisqfQ3v70TWMmwhhHA==", + "version": "0.17.1", + "resolved": "https://registry.npmjs.org/@fastify/otel/-/otel-0.17.1.tgz", + "integrity": "sha512-K4wyxfUZx2ux5o+b6BtTqouYFVILohLZmSbA2tKUueJstNcBnoGPVhllCaOvbQ3ZrXdUxUC/fyrSWSCqHhdOPg==", "funding": [ { "type": "github", @@ -3375,18 +3375,18 @@ "license": "MIT", "dependencies": { "@opentelemetry/core": "^2.0.0", - "@opentelemetry/instrumentation": "^0.208.0", + "@opentelemetry/instrumentation": "^0.212.0", "@opentelemetry/semantic-conventions": "^1.28.0", - "minimatch": "^10.0.3" + "minimatch": "^10.2.4" }, "peerDependencies": { "@opentelemetry/api": "^1.9.0" } }, "node_modules/@fastify/otel/node_modules/@opentelemetry/api-logs": { - "version": "0.208.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/api-logs/-/api-logs-0.208.0.tgz", - "integrity": "sha512-CjruKY9V6NMssL/T1kAFgzosF1v9o6oeN+aX5JB/C/xPNtmgIJqcXHG7fA82Ou1zCpWGl4lROQUKwUNE1pMCyg==", + "version": "0.212.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/api-logs/-/api-logs-0.212.0.tgz", + "integrity": "sha512-TEEVrLbNROUkYY51sBJGk7lO/OLjuepch8+hmpM6ffMJQ2z/KVCjdHuCFX6fJj8OkJP2zckPjrJzQtXU3IAsFg==", "license": "Apache-2.0", "dependencies": { "@opentelemetry/api": "^1.3.0" @@ -3396,13 +3396,13 @@ } }, "node_modules/@fastify/otel/node_modules/@opentelemetry/instrumentation": { - "version": "0.208.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation/-/instrumentation-0.208.0.tgz", - "integrity": "sha512-Eju0L4qWcQS+oXxi6pgh7zvE2byogAkcsVv0OjHF/97iOz1N/aKE6etSGowYkie+YA1uo6DNwdSxaaNnLvcRlA==", + "version": "0.212.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation/-/instrumentation-0.212.0.tgz", + "integrity": "sha512-IyXmpNnifNouMOe0I/gX7ENfv2ZCNdYTF0FpCsoBcpbIHzk81Ww9rQTYTnvghszCg7qGrIhNvWC8dhEifgX9Jg==", "license": "Apache-2.0", "dependencies": { - "@opentelemetry/api-logs": "0.208.0", - "import-in-the-middle": "^2.0.0", + "@opentelemetry/api-logs": "0.212.0", + "import-in-the-middle": "^2.0.6", "require-in-the-middle": "^8.0.0" }, "engines": { @@ -3433,6 +3433,24 @@ "node": "18 || 20 || >=22" } }, + "node_modules/@fastify/otel/node_modules/cjs-module-lexer": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-2.2.0.tgz", + "integrity": "sha512-4bHTS2YuzUvtoLjdy+98ykbNB5jS0+07EvFNXerqZQJ89F7DI6ET7OQo/HJuW6K0aVsKA9hj9/RVb2kQVOrPDQ==", + "license": "MIT" + }, + "node_modules/@fastify/otel/node_modules/import-in-the-middle": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/import-in-the-middle/-/import-in-the-middle-2.0.6.tgz", + "integrity": "sha512-3vZV3jX0XRFW3EJDTwzWoZa+RH1b8eTTx6YOCjglrLyPuepwoBti1k3L2dKwdCUrnVEfc5CuRuGstaC/uQJJaw==", + "license": "Apache-2.0", + "dependencies": { + "acorn": "^8.15.0", + "acorn-import-attributes": "^1.9.5", + "cjs-module-lexer": "^2.2.0", + "module-details-from-path": "^1.0.4" + } + }, "node_modules/@fastify/otel/node_modules/minimatch": { "version": "10.2.4", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.4.tgz", @@ -4782,9 +4800,9 @@ } }, "node_modules/@opentelemetry/api-logs": { - "version": "0.211.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/api-logs/-/api-logs-0.211.0.tgz", - "integrity": "sha512-swFdZq8MCdmdR22jTVGQDhwqDzcI4M10nhjXkLr1EsIzXgZBqm4ZlmmcWsg3TSNf+3mzgOiqveXmBLZuDi2Lgg==", + "version": "0.213.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/api-logs/-/api-logs-0.213.0.tgz", + "integrity": "sha512-zRM5/Qj6G84Ej3F1yt33xBVY/3tnMxtL1fiDIxYbDWYaZ/eudVw3/PBiZ8G7JwUxXxjW8gU4g6LnOyfGKYHYgw==", "license": "Apache-2.0", "dependencies": { "@opentelemetry/api": "^1.3.0" @@ -4821,13 +4839,13 @@ } }, "node_modules/@opentelemetry/instrumentation": { - "version": "0.211.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation/-/instrumentation-0.211.0.tgz", - "integrity": "sha512-h0nrZEC/zvI994nhg7EgQ8URIHt0uDTwN90r3qQUdZORS455bbx+YebnGeEuFghUT0HlJSrLF4iHw67f+odY+Q==", + "version": "0.213.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation/-/instrumentation-0.213.0.tgz", + "integrity": "sha512-3i9NdkET/KvQomeh7UaR/F4r9P25Rx6ooALlWXPIjypcEOUxksCmVu0zA70NBJWlrMW1rPr/LRidFAflLI+s/w==", "license": "Apache-2.0", "dependencies": { - "@opentelemetry/api-logs": "0.211.0", - "import-in-the-middle": "^2.0.0", + "@opentelemetry/api-logs": "0.213.0", + "import-in-the-middle": "^3.0.0", "require-in-the-middle": "^8.0.0" }, "engines": { @@ -4838,13 +4856,13 @@ } }, "node_modules/@opentelemetry/instrumentation-amqplib": { - "version": "0.58.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-amqplib/-/instrumentation-amqplib-0.58.0.tgz", - "integrity": "sha512-fjpQtH18J6GxzUZ+cwNhWUpb71u+DzT7rFkg5pLssDGaEber91Y2WNGdpVpwGivfEluMlNMZumzjEqfg8DeKXQ==", + "version": "0.60.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-amqplib/-/instrumentation-amqplib-0.60.0.tgz", + "integrity": "sha512-q/B2IvoVXRm1M00MvhnzpMN6rKYOszPXVsALi6u0ss4AYHe+TidZEtLW9N1ZhrobI1dSriHnBqqtAOZVAv07sg==", "license": "Apache-2.0", "dependencies": { "@opentelemetry/core": "^2.0.0", - "@opentelemetry/instrumentation": "^0.211.0", + "@opentelemetry/instrumentation": "^0.213.0", "@opentelemetry/semantic-conventions": "^1.33.0" }, "engines": { @@ -4855,13 +4873,13 @@ } }, "node_modules/@opentelemetry/instrumentation-connect": { - "version": "0.54.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-connect/-/instrumentation-connect-0.54.0.tgz", - "integrity": "sha512-43RmbhUhqt3uuPnc16cX6NsxEASEtn8z/cYV8Zpt6EP4p2h9s4FNuJ4Q9BbEQ2C0YlCCB/2crO1ruVz/hWt8fA==", + "version": "0.56.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-connect/-/instrumentation-connect-0.56.0.tgz", + "integrity": "sha512-PKp+sSZ7AfzMvGgO3VCyo1inwNu+q7A1k9X88WK4PQ+S6Hp7eFk8pie+sWHDTaARovmqq5V2osav3lQej2B0nw==", "license": "Apache-2.0", "dependencies": { "@opentelemetry/core": "^2.0.0", - "@opentelemetry/instrumentation": "^0.211.0", + "@opentelemetry/instrumentation": "^0.213.0", "@opentelemetry/semantic-conventions": "^1.27.0", "@types/connect": "3.4.38" }, @@ -4873,12 +4891,12 @@ } }, "node_modules/@opentelemetry/instrumentation-dataloader": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-dataloader/-/instrumentation-dataloader-0.28.0.tgz", - "integrity": "sha512-ExXGBp0sUj8yhm6Znhf9jmuOaGDsYfDES3gswZnKr4MCqoBWQdEFn6EoDdt5u+RdbxQER+t43FoUihEfTSqsjA==", + "version": "0.30.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-dataloader/-/instrumentation-dataloader-0.30.0.tgz", + "integrity": "sha512-MXHP2Q38cd2OhzEBKAIXUi9uBlPEYzF6BNJbyjUXBQ6kLaf93kRC41vNMIz0Nl5mnuwK7fDvKT+/lpx7BXRwdg==", "license": "Apache-2.0", "dependencies": { - "@opentelemetry/instrumentation": "^0.211.0" + "@opentelemetry/instrumentation": "^0.213.0" }, "engines": { "node": "^18.19.0 || >=20.6.0" @@ -4888,13 +4906,13 @@ } }, "node_modules/@opentelemetry/instrumentation-express": { - "version": "0.59.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-express/-/instrumentation-express-0.59.0.tgz", - "integrity": "sha512-pMKV/qnHiW/Q6pmbKkxt0eIhuNEtvJ7sUAyee192HErlr+a1Jx+FZ3WjfmzhQL1geewyGEiPGkmjjAgNY8TgDA==", + "version": "0.61.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-express/-/instrumentation-express-0.61.0.tgz", + "integrity": "sha512-Xdmqo9RZuZlL29Flg8QdwrrX7eW1CZ7wFQPKHyXljNymgKhN1MCsYuqQ/7uxavhSKwAl7WxkTzKhnqpUApLMvQ==", "license": "Apache-2.0", "dependencies": { "@opentelemetry/core": "^2.0.0", - "@opentelemetry/instrumentation": "^0.211.0", + "@opentelemetry/instrumentation": "^0.213.0", "@opentelemetry/semantic-conventions": "^1.27.0" }, "engines": { @@ -4905,13 +4923,13 @@ } }, "node_modules/@opentelemetry/instrumentation-fs": { - "version": "0.30.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-fs/-/instrumentation-fs-0.30.0.tgz", - "integrity": "sha512-n3Cf8YhG7reaj5dncGlRIU7iT40bxPOjsBEA5Bc1a1g6e9Qvb+JFJ7SEiMlPbUw4PBmxE3h40ltE8LZ3zVt6OA==", + "version": "0.32.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-fs/-/instrumentation-fs-0.32.0.tgz", + "integrity": "sha512-koR6apx0g0wX6RRiPpjA4AFQUQUbXrK16kq4/SZjVp7u5cffJhNkY4TnITxcGA4acGSPYAfx3NHRIv4Khn1axQ==", "license": "Apache-2.0", "dependencies": { "@opentelemetry/core": "^2.0.0", - "@opentelemetry/instrumentation": "^0.211.0" + "@opentelemetry/instrumentation": "^0.213.0" }, "engines": { "node": "^18.19.0 || >=20.6.0" @@ -4921,12 +4939,12 @@ } }, "node_modules/@opentelemetry/instrumentation-generic-pool": { - "version": "0.54.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-generic-pool/-/instrumentation-generic-pool-0.54.0.tgz", - "integrity": "sha512-8dXMBzzmEdXfH/wjuRvcJnUFeWzZHUnExkmFJ2uPfa31wmpyBCMxO59yr8f/OXXgSogNgi/uPo9KW9H7LMIZ+g==", + "version": "0.56.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-generic-pool/-/instrumentation-generic-pool-0.56.0.tgz", + "integrity": "sha512-fg+Jffs6fqrf0uQS0hom7qBFKsbtpBiBl8+Vkc63Gx8xh6pVh+FhagmiO6oM0m3vyb683t1lP7yGYq22SiDnqg==", "license": "Apache-2.0", "dependencies": { - "@opentelemetry/instrumentation": "^0.211.0" + "@opentelemetry/instrumentation": "^0.213.0" }, "engines": { "node": "^18.19.0 || >=20.6.0" @@ -4936,12 +4954,12 @@ } }, "node_modules/@opentelemetry/instrumentation-graphql": { - "version": "0.58.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-graphql/-/instrumentation-graphql-0.58.0.tgz", - "integrity": "sha512-+yWVVY7fxOs3j2RixCbvue8vUuJ1inHxN2q1sduqDB0Wnkr4vOzVKRYl/Zy7B31/dcPS72D9lo/kltdOTBM3bQ==", + "version": "0.61.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-graphql/-/instrumentation-graphql-0.61.0.tgz", + "integrity": "sha512-pUiVASv6nh2XrerTvlbVHh7vKFzscpgwiQ/xvnZuAIzQ5lRjWVdRPUuXbvZJ/Yq79QsE81TZdJ7z9YsXiss1ew==", "license": "Apache-2.0", "dependencies": { - "@opentelemetry/instrumentation": "^0.211.0" + "@opentelemetry/instrumentation": "^0.213.0" }, "engines": { "node": "^18.19.0 || >=20.6.0" @@ -4951,13 +4969,13 @@ } }, "node_modules/@opentelemetry/instrumentation-hapi": { - "version": "0.57.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-hapi/-/instrumentation-hapi-0.57.0.tgz", - "integrity": "sha512-Os4THbvls8cTQTVA8ApLfZZztuuqGEeqog0XUnyRW7QVF0d/vOVBEcBCk1pazPFmllXGEdNbbat8e2fYIWdFbw==", + "version": "0.59.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-hapi/-/instrumentation-hapi-0.59.0.tgz", + "integrity": "sha512-33wa4mEr+9+ztwdgLor1SeBu4Opz4IsmpcLETXAd3VmBrOjez8uQtrsOhPCa5Vhbm5gzDlMYTgFRLQzf8/YHFA==", "license": "Apache-2.0", "dependencies": { "@opentelemetry/core": "^2.0.0", - "@opentelemetry/instrumentation": "^0.211.0", + "@opentelemetry/instrumentation": "^0.213.0", "@opentelemetry/semantic-conventions": "^1.27.0" }, "engines": { @@ -4968,13 +4986,13 @@ } }, "node_modules/@opentelemetry/instrumentation-http": { - "version": "0.211.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-http/-/instrumentation-http-0.211.0.tgz", - "integrity": "sha512-n0IaQ6oVll9PP84SjbOCwDjaJasWRHi6BLsbMLiT6tNj7QbVOkuA5sk/EfZczwI0j5uTKl1awQPivO/ldVtsqA==", + "version": "0.213.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-http/-/instrumentation-http-0.213.0.tgz", + "integrity": "sha512-B978Xsm5XEPGhm1P07grDoaOFLHapJPkOG9h016cJsyWWxmiLnPu2M/4Nrm7UCkHSiLnkXgC+zVGUAIahy8EEA==", "license": "Apache-2.0", "dependencies": { - "@opentelemetry/core": "2.5.0", - "@opentelemetry/instrumentation": "0.211.0", + "@opentelemetry/core": "2.6.0", + "@opentelemetry/instrumentation": "0.213.0", "@opentelemetry/semantic-conventions": "^1.29.0", "forwarded-parse": "2.1.2" }, @@ -4985,28 +5003,13 @@ "@opentelemetry/api": "^1.3.0" } }, - "node_modules/@opentelemetry/instrumentation-http/node_modules/@opentelemetry/core": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.5.0.tgz", - "integrity": "sha512-ka4H8OM6+DlUhSAZpONu0cPBtPPTQKxbxVzC4CzVx5+K4JnroJVBtDzLAMx4/3CDTJXRvVFhpFjtl4SaiTNoyQ==", - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/semantic-conventions": "^1.29.0" - }, - "engines": { - "node": "^18.19.0 || >=20.6.0" - }, - "peerDependencies": { - "@opentelemetry/api": ">=1.0.0 <1.10.0" - } - }, "node_modules/@opentelemetry/instrumentation-ioredis": { - "version": "0.59.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-ioredis/-/instrumentation-ioredis-0.59.0.tgz", - "integrity": "sha512-875UxzBHWkW+P4Y45SoFM2AR8f8TzBMD8eO7QXGCyFSCUMP5s9vtt/BS8b/r2kqLyaRPK6mLbdnZznK3XzQWvw==", + "version": "0.61.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-ioredis/-/instrumentation-ioredis-0.61.0.tgz", + "integrity": "sha512-hsHDadUtAFbws1YSDc1XW0svGFKiUbqv2td1Cby+UAiwvojm1NyBo/taifH0t8CuFZ0x/2SDm0iuTwrM5pnVOg==", "license": "Apache-2.0", "dependencies": { - "@opentelemetry/instrumentation": "^0.211.0", + "@opentelemetry/instrumentation": "^0.213.0", "@opentelemetry/redis-common": "^0.38.2", "@opentelemetry/semantic-conventions": "^1.33.0" }, @@ -5018,12 +5021,12 @@ } }, "node_modules/@opentelemetry/instrumentation-kafkajs": { - "version": "0.20.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-kafkajs/-/instrumentation-kafkajs-0.20.0.tgz", - "integrity": "sha512-yJXOuWZROzj7WmYCUiyT27tIfqBrVtl1/TwVbQyWPz7rL0r1Lu7kWjD0PiVeTCIL6CrIZ7M2s8eBxsTAOxbNvw==", + "version": "0.22.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-kafkajs/-/instrumentation-kafkajs-0.22.0.tgz", + "integrity": "sha512-wJU4IBQMUikdJAcTChLFqK5lo+flo7pahqd8DSLv7uMxsdOdAHj6RzKYAm8pPfUS6ItKYutYyuicwKaFwQKsoA==", "license": "Apache-2.0", "dependencies": { - "@opentelemetry/instrumentation": "^0.211.0", + "@opentelemetry/instrumentation": "^0.213.0", "@opentelemetry/semantic-conventions": "^1.30.0" }, "engines": { @@ -5034,12 +5037,12 @@ } }, "node_modules/@opentelemetry/instrumentation-knex": { - "version": "0.55.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-knex/-/instrumentation-knex-0.55.0.tgz", - "integrity": "sha512-FtTL5DUx5Ka/8VK6P1VwnlUXPa3nrb7REvm5ddLUIeXXq4tb9pKd+/ThB1xM/IjefkRSN3z8a5t7epYw1JLBJQ==", + "version": "0.57.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-knex/-/instrumentation-knex-0.57.0.tgz", + "integrity": "sha512-vMCSh8kolEm5rRsc+FZeTZymWmIJwc40hjIKnXH4O0Dv/gAkJJIRXCsPX5cPbe0c0j/34+PsENd0HqKruwhVYw==", "license": "Apache-2.0", "dependencies": { - "@opentelemetry/instrumentation": "^0.211.0", + "@opentelemetry/instrumentation": "^0.213.0", "@opentelemetry/semantic-conventions": "^1.33.1" }, "engines": { @@ -5050,13 +5053,13 @@ } }, "node_modules/@opentelemetry/instrumentation-koa": { - "version": "0.59.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-koa/-/instrumentation-koa-0.59.0.tgz", - "integrity": "sha512-K9o2skADV20Skdu5tG2bogPKiSpXh4KxfLjz6FuqIVvDJNibwSdu5UvyyBzRVp1rQMV6UmoIk6d3PyPtJbaGSg==", + "version": "0.61.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-koa/-/instrumentation-koa-0.61.0.tgz", + "integrity": "sha512-lvrfWe9ShK/D2X4brmx8ZqqeWPfRl8xekU0FCn7C1dHm5k6+rTOOi36+4fnaHAP8lig9Ux6XQ1D4RNIpPCt1WQ==", "license": "Apache-2.0", "dependencies": { "@opentelemetry/core": "^2.0.0", - "@opentelemetry/instrumentation": "^0.211.0", + "@opentelemetry/instrumentation": "^0.213.0", "@opentelemetry/semantic-conventions": "^1.36.0" }, "engines": { @@ -5067,12 +5070,12 @@ } }, "node_modules/@opentelemetry/instrumentation-lru-memoizer": { - "version": "0.55.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-lru-memoizer/-/instrumentation-lru-memoizer-0.55.0.tgz", - "integrity": "sha512-FDBfT7yDGcspN0Cxbu/k8A0Pp1Jhv/m7BMTzXGpcb8ENl3tDj/51U65R5lWzUH15GaZA15HQ5A5wtafklxYj7g==", + "version": "0.57.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-lru-memoizer/-/instrumentation-lru-memoizer-0.57.0.tgz", + "integrity": "sha512-cEqpUocSKJfwDtLYTTJehRLWzkZ2eoePCxfVIgGkGkb83fMB71O+y4MvRHJPbeV2bdoWdOVrl8uO0+EynWhTEA==", "license": "Apache-2.0", "dependencies": { - "@opentelemetry/instrumentation": "^0.211.0" + "@opentelemetry/instrumentation": "^0.213.0" }, "engines": { "node": "^18.19.0 || >=20.6.0" @@ -5082,12 +5085,12 @@ } }, "node_modules/@opentelemetry/instrumentation-mongodb": { - "version": "0.64.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-mongodb/-/instrumentation-mongodb-0.64.0.tgz", - "integrity": "sha512-pFlCJjweTqVp7B220mCvCld1c1eYKZfQt1p3bxSbcReypKLJTwat+wbL2YZoX9jPi5X2O8tTKFEOahO5ehQGsA==", + "version": "0.66.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-mongodb/-/instrumentation-mongodb-0.66.0.tgz", + "integrity": "sha512-d7m9QnAY+4TCWI4q1QRkfrc6fo/92VwssaB1DzQfXNRvu51b78P+HJlWP7Qg6N6nkwdb9faMZNBCZJfftmszkw==", "license": "Apache-2.0", "dependencies": { - "@opentelemetry/instrumentation": "^0.211.0", + "@opentelemetry/instrumentation": "^0.213.0", "@opentelemetry/semantic-conventions": "^1.33.0" }, "engines": { @@ -5098,13 +5101,13 @@ } }, "node_modules/@opentelemetry/instrumentation-mongoose": { - "version": "0.57.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-mongoose/-/instrumentation-mongoose-0.57.0.tgz", - "integrity": "sha512-MthiekrU/BAJc5JZoZeJmo0OTX6ycJMiP6sMOSRTkvz5BrPMYDqaJos0OgsLPL/HpcgHP7eo5pduETuLguOqcg==", + "version": "0.59.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-mongoose/-/instrumentation-mongoose-0.59.0.tgz", + "integrity": "sha512-6/jWU+c1NgznkVLDU/2y0bXV2nJo3o9FWZ9mZ9nN6T/JBNRoMnVXZl2FdBmgH+a5MwaWLs5kmRJTP5oUVGIkPw==", "license": "Apache-2.0", "dependencies": { "@opentelemetry/core": "^2.0.0", - "@opentelemetry/instrumentation": "^0.211.0", + "@opentelemetry/instrumentation": "^0.213.0", "@opentelemetry/semantic-conventions": "^1.33.0" }, "engines": { @@ -5115,12 +5118,12 @@ } }, "node_modules/@opentelemetry/instrumentation-mysql": { - "version": "0.57.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-mysql/-/instrumentation-mysql-0.57.0.tgz", - "integrity": "sha512-HFS/+FcZ6Q7piM7Il7CzQ4VHhJvGMJWjx7EgCkP5AnTntSN5rb5Xi3TkYJHBKeR27A0QqPlGaCITi93fUDs++Q==", + "version": "0.59.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-mysql/-/instrumentation-mysql-0.59.0.tgz", + "integrity": "sha512-r+V/Fh0sm7Ga8/zk/TI5H5FQRAjwr0RrpfPf8kNIehlsKf12XnvIaZi8ViZkpX0gyPEpLXqzqWD6QHlgObgzZw==", "license": "Apache-2.0", "dependencies": { - "@opentelemetry/instrumentation": "^0.211.0", + "@opentelemetry/instrumentation": "^0.213.0", "@opentelemetry/semantic-conventions": "^1.33.0", "@types/mysql": "2.15.27" }, @@ -5132,12 +5135,12 @@ } }, "node_modules/@opentelemetry/instrumentation-mysql2": { - "version": "0.57.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-mysql2/-/instrumentation-mysql2-0.57.0.tgz", - "integrity": "sha512-nHSrYAwF7+aV1E1V9yOOP9TchOodb6fjn4gFvdrdQXiRE7cMuffyLLbCZlZd4wsspBzVwOXX8mpURdRserAhNA==", + "version": "0.59.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-mysql2/-/instrumentation-mysql2-0.59.0.tgz", + "integrity": "sha512-n9/xrVCRBfG9egVbffnlU1uhr+HX0vF4GgtAB/Bvm48wpFgRidqD8msBMiym1kRYzmpWvJqTxNT47u1MkgBEdw==", "license": "Apache-2.0", "dependencies": { - "@opentelemetry/instrumentation": "^0.211.0", + "@opentelemetry/instrumentation": "^0.213.0", "@opentelemetry/semantic-conventions": "^1.33.0", "@opentelemetry/sql-common": "^0.41.2" }, @@ -5149,13 +5152,13 @@ } }, "node_modules/@opentelemetry/instrumentation-pg": { - "version": "0.63.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-pg/-/instrumentation-pg-0.63.0.tgz", - "integrity": "sha512-dKm/ODNN3GgIQVlbD6ZPxwRc3kleLf95hrRWXM+l8wYo+vSeXtEpQPT53afEf6VFWDVzJK55VGn8KMLtSve/cg==", + "version": "0.65.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-pg/-/instrumentation-pg-0.65.0.tgz", + "integrity": "sha512-W0zpHEIEuyZ8zvb3njaX9AAbHgPYOsSWVOoWmv1sjVRSF6ZpBqtlxBWbU+6hhq1TFWBeWJOXZ8nZS/PUFpLJYQ==", "license": "Apache-2.0", "dependencies": { "@opentelemetry/core": "^2.0.0", - "@opentelemetry/instrumentation": "^0.211.0", + "@opentelemetry/instrumentation": "^0.213.0", "@opentelemetry/semantic-conventions": "^1.34.0", "@opentelemetry/sql-common": "^0.41.2", "@types/pg": "8.15.6", @@ -5178,12 +5181,12 @@ } }, "node_modules/@opentelemetry/instrumentation-redis": { - "version": "0.59.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-redis/-/instrumentation-redis-0.59.0.tgz", - "integrity": "sha512-JKv1KDDYA2chJ1PC3pLP+Q9ISMQk6h5ey+99mB57/ARk0vQPGZTTEb4h4/JlcEpy7AYT8HIGv7X6l+br03Neeg==", + "version": "0.61.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-redis/-/instrumentation-redis-0.61.0.tgz", + "integrity": "sha512-JnPexA034/0UJRsvH96B0erQoNOqKJZjE2ZRSw9hiTSC23LzE0nJE/u6D+xqOhgUhRnhhcPHq4MdYtmUdYTF+Q==", "license": "Apache-2.0", "dependencies": { - "@opentelemetry/instrumentation": "^0.211.0", + "@opentelemetry/instrumentation": "^0.213.0", "@opentelemetry/redis-common": "^0.38.2", "@opentelemetry/semantic-conventions": "^1.27.0" }, @@ -5291,12 +5294,12 @@ } }, "node_modules/@opentelemetry/instrumentation-tedious": { - "version": "0.30.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-tedious/-/instrumentation-tedious-0.30.0.tgz", - "integrity": "sha512-bZy9Q8jFdycKQ2pAsyuHYUHNmCxCOGdG6eg1Mn75RvQDccq832sU5OWOBnc12EFUELI6icJkhR7+EQKMBam2GA==", + "version": "0.32.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-tedious/-/instrumentation-tedious-0.32.0.tgz", + "integrity": "sha512-BQS6gG8RJ1foEqfEZ+wxoqlwfCAzb1ZVG0ad8Gfe4x8T658HJCLGLd4E4NaoQd8EvPfLqOXgzGaE/2U4ytDSWA==", "license": "Apache-2.0", "dependencies": { - "@opentelemetry/instrumentation": "^0.211.0", + "@opentelemetry/instrumentation": "^0.213.0", "@opentelemetry/semantic-conventions": "^1.33.0", "@types/tedious": "^4.0.14" }, @@ -5308,13 +5311,13 @@ } }, "node_modules/@opentelemetry/instrumentation-undici": { - "version": "0.21.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-undici/-/instrumentation-undici-0.21.0.tgz", - "integrity": "sha512-gok0LPUOTz2FQ1YJMZzaHcOzDFyT64XJ8M9rNkugk923/p6lDGms/cRW1cqgqp6N6qcd6K6YdVHwPEhnx9BWbw==", + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-undici/-/instrumentation-undici-0.23.0.tgz", + "integrity": "sha512-LL0VySzKVR2cJSFVZaTYpZl1XTpBGnfzoQPe2W7McS2267ldsaEIqtQY6VXs2KCXN0poFjze5110PIpxHDaDGg==", "license": "Apache-2.0", "dependencies": { "@opentelemetry/core": "^2.0.0", - "@opentelemetry/instrumentation": "^0.211.0", + "@opentelemetry/instrumentation": "^0.213.0", "@opentelemetry/semantic-conventions": "^1.24.0" }, "engines": { @@ -5367,9 +5370,9 @@ } }, "node_modules/@opentelemetry/semantic-conventions": { - "version": "1.39.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-1.39.0.tgz", - "integrity": "sha512-R5R9tb2AXs2IRLNKLBJDynhkfmx7mX0vi8NkhZb3gUkPWHn6HXk5J8iQ/dql0U3ApfWym4kXXmBDRGO+oeOfjg==", + "version": "1.40.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-1.40.0.tgz", + "integrity": "sha512-cifvXDhcqMwwTlTK04GBNeIe7yyo28Mfby85QXFe1Yk8nmi36Ab/5UQwptOx84SsoGNRg+EVSjwzfSZMy6pmlw==", "license": "Apache-2.0", "engines": { "node": ">=14" @@ -5408,15 +5411,6 @@ "url": "https://github.com/sponsors/ota-meshi" } }, - "node_modules/@oxc-project/runtime": { - "version": "0.115.0", - "resolved": "https://registry.npmjs.org/@oxc-project/runtime/-/runtime-0.115.0.tgz", - "integrity": "sha512-Rg8Wlt5dCbXhQnsXPrkOjL1DTSvXLgb2R/KYfnf1/K+R0k6UMLEmbQXPM+kwrWqSmWA2t0B1EtHy2/3zikQpvQ==", - "license": "MIT", - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, "node_modules/@oxc-project/types": { "version": "0.110.0", "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.110.0.tgz", @@ -5956,9 +5950,9 @@ "license": "MIT" }, "node_modules/@prisma/instrumentation": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/@prisma/instrumentation/-/instrumentation-7.2.0.tgz", - "integrity": "sha512-Rh9Z4x5kEj1OdARd7U18AtVrnL6rmLSI0qYShaB4W7Wx5BKbgzndWF+QnuzMb7GLfVdlT5aYCXoPQVYuYtVu0g==", + "version": "7.4.2", + "resolved": "https://registry.npmjs.org/@prisma/instrumentation/-/instrumentation-7.4.2.tgz", + "integrity": "sha512-r9JfchJF1Ae6yAxcaLu/V1TGqBhAuSDe3mRNOssBfx1rMzfZ4fdNvrgUBwyb/TNTGXFxlH9AZix5P257x07nrg==", "license": "Apache-2.0", "dependencies": { "@opentelemetry/instrumentation": "^0.207.0" @@ -5996,6 +5990,24 @@ "@opentelemetry/api": "^1.3.0" } }, + "node_modules/@prisma/instrumentation/node_modules/cjs-module-lexer": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-2.2.0.tgz", + "integrity": "sha512-4bHTS2YuzUvtoLjdy+98ykbNB5jS0+07EvFNXerqZQJ89F7DI6ET7OQo/HJuW6K0aVsKA9hj9/RVb2kQVOrPDQ==", + "license": "MIT" + }, + "node_modules/@prisma/instrumentation/node_modules/import-in-the-middle": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/import-in-the-middle/-/import-in-the-middle-2.0.6.tgz", + "integrity": "sha512-3vZV3jX0XRFW3EJDTwzWoZa+RH1b8eTTx6YOCjglrLyPuepwoBti1k3L2dKwdCUrnVEfc5CuRuGstaC/uQJJaw==", + "license": "Apache-2.0", + "dependencies": { + "acorn": "^8.15.0", + "acorn-import-attributes": "^1.9.5", + "cjs-module-lexer": "^2.2.0", + "module-details-from-path": "^1.0.4" + } + }, "node_modules/@puppeteer/browsers": { "version": "2.11.0", "resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-2.11.0.tgz", @@ -6152,9 +6164,9 @@ } }, "node_modules/@rolldown/binding-linux-ppc64-gnu": { - "version": "1.0.0-rc.9", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.0.0-rc.9.tgz", - "integrity": "sha512-aOKU9dJheda8Kj8Y3w9gnt9QFOO+qKPAl8SWd7JPHP+Cu0EuDAE5wokQubLzIDQWg2myXq2XhTpOVS07qqvT+w==", + "version": "1.0.0-rc.10", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.0.0-rc.10.tgz", + "integrity": "sha512-ZqrufYTgzxbHwpqOjzSsb0UV/aV2TFIY5rP8HdsiPTv/CuAgCRjM6s9cYFwQ4CNH+hf9Y4erHW1GjZuZ7WoI7w==", "cpu": [ "ppc64" ], @@ -6168,9 +6180,9 @@ } }, "node_modules/@rolldown/binding-linux-s390x-gnu": { - "version": "1.0.0-rc.9", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.0.0-rc.9.tgz", - "integrity": "sha512-OalO94fqj7IWRn3VdXWty75jC5dk4C197AWEuMhIpvVv2lw9fiPhud0+bW2ctCxb3YoBZor71QHbY+9/WToadA==", + "version": "1.0.0-rc.10", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.0.0-rc.10.tgz", + "integrity": "sha512-gSlmVS1FZJSRicA6IyjoRoKAFK7IIHBs7xJuHRSmjImqk3mPPWbR7RhbnfH2G6bcmMEllCt2vQ/7u9e6bBnByg==", "cpu": [ "s390x" ], @@ -6696,71 +6708,71 @@ } }, "node_modules/@sentry-internal/browser-utils": { - "version": "10.43.0", - "resolved": "https://registry.npmjs.org/@sentry-internal/browser-utils/-/browser-utils-10.43.0.tgz", - "integrity": "sha512-8zYTnzhAPvNkVH1Irs62wl0J/c+0QcJ62TonKnzpSFUUD3V5qz8YDZbjIDGfxy+1EB9fO0sxtddKCzwTHF/MbQ==", + "version": "10.45.0", + "resolved": "https://registry.npmjs.org/@sentry-internal/browser-utils/-/browser-utils-10.45.0.tgz", + "integrity": "sha512-ZPZpeIarXKScvquGx2AfNKcYiVNDA4wegMmjyGVsTA2JPmP0TrJoO3UybJS6KGDeee8V3I3EfD/ruauMm7jOFQ==", "license": "MIT", "dependencies": { - "@sentry/core": "10.43.0" + "@sentry/core": "10.45.0" }, "engines": { "node": ">=18" } }, "node_modules/@sentry-internal/feedback": { - "version": "10.43.0", - "resolved": "https://registry.npmjs.org/@sentry-internal/feedback/-/feedback-10.43.0.tgz", - "integrity": "sha512-YoXuwluP6eOcQxTeTtaWb090++MrLyWOVsUTejzUQQ6LFL13Jwt+bDPF1kvBugMq4a7OHw/UNKQfd6//rZMn2g==", + "version": "10.45.0", + "resolved": "https://registry.npmjs.org/@sentry-internal/feedback/-/feedback-10.45.0.tgz", + "integrity": "sha512-vCSurazFVq7RUeYiM5X326jA5gOVrWYD6lYX2fbjBOMcyCEhDnveNxMT62zKkZDyNT/jyD194nz/cjntBUkyWA==", "license": "MIT", "dependencies": { - "@sentry/core": "10.43.0" + "@sentry/core": "10.45.0" }, "engines": { "node": ">=18" } }, "node_modules/@sentry-internal/replay": { - "version": "10.43.0", - "resolved": "https://registry.npmjs.org/@sentry-internal/replay/-/replay-10.43.0.tgz", - "integrity": "sha512-khCXlGrlH1IU7P5zCEAJFestMeH97zDVCekj8OsNNDtN/1BmCJ46k6Xi0EqAUzdJgrOLJeLdoYdgtiIjovZ8Sg==", + "version": "10.45.0", + "resolved": "https://registry.npmjs.org/@sentry-internal/replay/-/replay-10.45.0.tgz", + "integrity": "sha512-vjosRoGA1bzhVAEO1oce+CsRdd70quzBeo7WvYqpcUnoLe/Rv8qpOMqWX3j26z7XfFHMExWQNQeLxmtYOArvlw==", "license": "MIT", "dependencies": { - "@sentry-internal/browser-utils": "10.43.0", - "@sentry/core": "10.43.0" + "@sentry-internal/browser-utils": "10.45.0", + "@sentry/core": "10.45.0" }, "engines": { "node": ">=18" } }, "node_modules/@sentry-internal/replay-canvas": { - "version": "10.43.0", - "resolved": "https://registry.npmjs.org/@sentry-internal/replay-canvas/-/replay-canvas-10.43.0.tgz", - "integrity": "sha512-ZIw1UNKOFXo1LbPCJPMAx9xv7D8TMZQusLDUgb6BsPQJj0igAuwd7KRGTkjjgnrwBp2O/sxcQFRhQhknWk7QPg==", + "version": "10.45.0", + "resolved": "https://registry.npmjs.org/@sentry-internal/replay-canvas/-/replay-canvas-10.45.0.tgz", + "integrity": "sha512-nvq/AocdZTuD7y0KSiWi3gVaY0s5HOFy86mC/v1kDZmT/jsBAzN5LDkk/f1FvsWma1peqQmpUqxvhC+YIW294Q==", "license": "MIT", "dependencies": { - "@sentry-internal/replay": "10.43.0", - "@sentry/core": "10.43.0" + "@sentry-internal/replay": "10.45.0", + "@sentry/core": "10.45.0" }, "engines": { "node": ">=18" } }, "node_modules/@sentry/astro": { - "version": "10.43.0", - "resolved": "https://registry.npmjs.org/@sentry/astro/-/astro-10.43.0.tgz", - "integrity": "sha512-9xN86+dDTa4yv0zRlp+761s2H6jq0Q4GSRDniMC7cJ+o0aDN4C3YRvg43HKUYjzvR8cty/EFSHG5goIxeFP6Vg==", + "version": "10.45.0", + "resolved": "https://registry.npmjs.org/@sentry/astro/-/astro-10.45.0.tgz", + "integrity": "sha512-gSR03t20TgFCnHOied6IQUl2M82B/z9xx2D7iOYxxIKaDDQg9kRZm/+E+zol66P5aiVaN1tuWaNS9JTF6M2ohw==", "license": "MIT", "dependencies": { - "@sentry/browser": "10.43.0", - "@sentry/core": "10.43.0", - "@sentry/node": "10.43.0", + "@sentry/browser": "10.45.0", + "@sentry/core": "10.45.0", + "@sentry/node": "10.45.0", "@sentry/vite-plugin": "^5.1.0" }, "engines": { "node": ">=18.19.1" }, "peerDependencies": { - "astro": ">=3.x || >=4.0.0-beta || >=5.x" + "astro": ">=3.x || >=4.0.0-beta" } }, "node_modules/@sentry/babel-plugin-component-annotate": { @@ -6773,16 +6785,16 @@ } }, "node_modules/@sentry/browser": { - "version": "10.43.0", - "resolved": "https://registry.npmjs.org/@sentry/browser/-/browser-10.43.0.tgz", - "integrity": "sha512-2V3I3sXi3SMeiZpKixd9ztokSgK27cmvsD9J5oyOyjhGLTW/6QKCwHbKnluMgQMXq20nixQk5zN4wRjRUma3sg==", + "version": "10.45.0", + "resolved": "https://registry.npmjs.org/@sentry/browser/-/browser-10.45.0.tgz", + "integrity": "sha512-e/a8UMiQhqqv706McSIcG6XK+AoQf9INthi2pD+giZfNRTzXTdqHzUT5OIO5hg8Am6eF63nDJc+vrYNPhzs51Q==", "license": "MIT", "dependencies": { - "@sentry-internal/browser-utils": "10.43.0", - "@sentry-internal/feedback": "10.43.0", - "@sentry-internal/replay": "10.43.0", - "@sentry-internal/replay-canvas": "10.43.0", - "@sentry/core": "10.43.0" + "@sentry-internal/browser-utils": "10.45.0", + "@sentry-internal/feedback": "10.45.0", + "@sentry-internal/replay": "10.45.0", + "@sentry-internal/replay-canvas": "10.45.0", + "@sentry/core": "10.45.0" }, "engines": { "node": ">=18" @@ -7062,69 +7074,69 @@ } }, "node_modules/@sentry/core": { - "version": "10.43.0", - "resolved": "https://registry.npmjs.org/@sentry/core/-/core-10.43.0.tgz", - "integrity": "sha512-l0SszQAPiQGWl/ferw8GP3ALyHXiGiRKJaOvNmhGO+PrTQyZTZ6OYyPnGijAFRg58dE1V3RCH/zw5d2xSUIiNg==", + "version": "10.45.0", + "resolved": "https://registry.npmjs.org/@sentry/core/-/core-10.45.0.tgz", + "integrity": "sha512-s69UXxvefeQxuZ5nY7/THtTrIEvJxNVCp3ns4kwoCw1qMpgpvn/296WCKVmM7MiwnaAdzEKnAvLAwaxZc2nM7Q==", "license": "MIT", "engines": { "node": ">=18" } }, "node_modules/@sentry/node": { - "version": "10.43.0", - "resolved": "https://registry.npmjs.org/@sentry/node/-/node-10.43.0.tgz", - "integrity": "sha512-oNwXcuZUc4uTTr0WbHZBBIKsKwAKvNMTgbXwxfB37CfzV18wbTirbQABZ/Ir3WNxSgi6ZcnC6UE013jF5XWPqw==", + "version": "10.45.0", + "resolved": "https://registry.npmjs.org/@sentry/node/-/node-10.45.0.tgz", + "integrity": "sha512-Kpiq9lRGnJc1ex8SwxOBl+FLQNl4Y137BydVooP7AFiAYZ6ftwHsIEF1bcYXaipHMT1YHS2bdhC2UQaaB2jkuQ==", "license": "MIT", "dependencies": { - "@fastify/otel": "0.16.0", + "@fastify/otel": "0.17.1", "@opentelemetry/api": "^1.9.0", - "@opentelemetry/context-async-hooks": "^2.5.1", - "@opentelemetry/core": "^2.5.1", - "@opentelemetry/instrumentation": "^0.211.0", - "@opentelemetry/instrumentation-amqplib": "0.58.0", - "@opentelemetry/instrumentation-connect": "0.54.0", - "@opentelemetry/instrumentation-dataloader": "0.28.0", - "@opentelemetry/instrumentation-express": "0.59.0", - "@opentelemetry/instrumentation-fs": "0.30.0", - "@opentelemetry/instrumentation-generic-pool": "0.54.0", - "@opentelemetry/instrumentation-graphql": "0.58.0", - "@opentelemetry/instrumentation-hapi": "0.57.0", - "@opentelemetry/instrumentation-http": "0.211.0", - "@opentelemetry/instrumentation-ioredis": "0.59.0", - "@opentelemetry/instrumentation-kafkajs": "0.20.0", - "@opentelemetry/instrumentation-knex": "0.55.0", - "@opentelemetry/instrumentation-koa": "0.59.0", - "@opentelemetry/instrumentation-lru-memoizer": "0.55.0", - "@opentelemetry/instrumentation-mongodb": "0.64.0", - "@opentelemetry/instrumentation-mongoose": "0.57.0", - "@opentelemetry/instrumentation-mysql": "0.57.0", - "@opentelemetry/instrumentation-mysql2": "0.57.0", - "@opentelemetry/instrumentation-pg": "0.63.0", - "@opentelemetry/instrumentation-redis": "0.59.0", - "@opentelemetry/instrumentation-tedious": "0.30.0", - "@opentelemetry/instrumentation-undici": "0.21.0", - "@opentelemetry/resources": "^2.5.1", - "@opentelemetry/sdk-trace-base": "^2.5.1", - "@opentelemetry/semantic-conventions": "^1.39.0", - "@prisma/instrumentation": "7.2.0", - "@sentry/core": "10.43.0", - "@sentry/node-core": "10.43.0", - "@sentry/opentelemetry": "10.43.0", - "import-in-the-middle": "^2.0.6" + "@opentelemetry/context-async-hooks": "^2.6.0", + "@opentelemetry/core": "^2.6.0", + "@opentelemetry/instrumentation": "^0.213.0", + "@opentelemetry/instrumentation-amqplib": "0.60.0", + "@opentelemetry/instrumentation-connect": "0.56.0", + "@opentelemetry/instrumentation-dataloader": "0.30.0", + "@opentelemetry/instrumentation-express": "0.61.0", + "@opentelemetry/instrumentation-fs": "0.32.0", + "@opentelemetry/instrumentation-generic-pool": "0.56.0", + "@opentelemetry/instrumentation-graphql": "0.61.0", + "@opentelemetry/instrumentation-hapi": "0.59.0", + "@opentelemetry/instrumentation-http": "0.213.0", + "@opentelemetry/instrumentation-ioredis": "0.61.0", + "@opentelemetry/instrumentation-kafkajs": "0.22.0", + "@opentelemetry/instrumentation-knex": "0.57.0", + "@opentelemetry/instrumentation-koa": "0.61.0", + "@opentelemetry/instrumentation-lru-memoizer": "0.57.0", + "@opentelemetry/instrumentation-mongodb": "0.66.0", + "@opentelemetry/instrumentation-mongoose": "0.59.0", + "@opentelemetry/instrumentation-mysql": "0.59.0", + "@opentelemetry/instrumentation-mysql2": "0.59.0", + "@opentelemetry/instrumentation-pg": "0.65.0", + "@opentelemetry/instrumentation-redis": "0.61.0", + "@opentelemetry/instrumentation-tedious": "0.32.0", + "@opentelemetry/instrumentation-undici": "0.23.0", + "@opentelemetry/resources": "^2.6.0", + "@opentelemetry/sdk-trace-base": "^2.6.0", + "@opentelemetry/semantic-conventions": "^1.40.0", + "@prisma/instrumentation": "7.4.2", + "@sentry/core": "10.45.0", + "@sentry/node-core": "10.45.0", + "@sentry/opentelemetry": "10.45.0", + "import-in-the-middle": "^3.0.0" }, "engines": { "node": ">=18" } }, "node_modules/@sentry/node-core": { - "version": "10.43.0", - "resolved": "https://registry.npmjs.org/@sentry/node-core/-/node-core-10.43.0.tgz", - "integrity": "sha512-w2H3NSkNMoYOS7o7mR55BM7+xL++dPxMSv1/XDfsra9FYHGppO+Mxk667Ee5k+uDi+wNIioICIh+5XOvZh4+HQ==", + "version": "10.45.0", + "resolved": "https://registry.npmjs.org/@sentry/node-core/-/node-core-10.45.0.tgz", + "integrity": "sha512-KQZEvLKM344+EqXiA9HIzWbW5hzq6/9nnFUQ8niaBPoOgR9AiJhrccfIscfgb8vjkriiEtzE03OW/4h1CTgZ3Q==", "license": "MIT", "dependencies": { - "@sentry/core": "10.43.0", - "@sentry/opentelemetry": "10.43.0", - "import-in-the-middle": "^2.0.6" + "@sentry/core": "10.45.0", + "@sentry/opentelemetry": "10.45.0", + "import-in-the-middle": "^3.0.0" }, "engines": { "node": ">=18" @@ -7163,12 +7175,12 @@ } }, "node_modules/@sentry/opentelemetry": { - "version": "10.43.0", - "resolved": "https://registry.npmjs.org/@sentry/opentelemetry/-/opentelemetry-10.43.0.tgz", - "integrity": "sha512-+fIcnnLdvBHdq4nKq23t9v/B9D4L97fPWEDksXbpGs11o6BsqY4Tlzmce6cP95iiQhPckCEag3FthSND+BYtYQ==", + "version": "10.45.0", + "resolved": "https://registry.npmjs.org/@sentry/opentelemetry/-/opentelemetry-10.45.0.tgz", + "integrity": "sha512-PmuGO+p/gC3ZQ8ddOeJ5P9ApnTTm35i12Bpuyb13AckCbNSJFvG2ggZda35JQOmiFU0kKYiwkoFAa8Mvj9od3Q==", "license": "MIT", "dependencies": { - "@sentry/core": "10.43.0" + "@sentry/core": "10.45.0" }, "engines": { "node": ">=18" @@ -7423,47 +7435,47 @@ } }, "node_modules/@tailwindcss/node": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.2.1.tgz", - "integrity": "sha512-jlx6sLk4EOwO6hHe1oCGm1Q4AN/s0rSrTTPBGPM0/RQ6Uylwq17FuU8IeJJKEjtc6K6O07zsvP+gDO6MMWo7pg==", + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.2.2.tgz", + "integrity": "sha512-pXS+wJ2gZpVXqFaUEjojq7jzMpTGf8rU6ipJz5ovJV6PUGmlJ+jvIwGrzdHdQ80Sg+wmQxUFuoW1UAAwHNEdFA==", "license": "MIT", "dependencies": { "@jridgewell/remapping": "^2.3.5", "enhanced-resolve": "^5.19.0", "jiti": "^2.6.1", - "lightningcss": "1.31.1", + "lightningcss": "1.32.0", "magic-string": "^0.30.21", "source-map-js": "^1.2.1", - "tailwindcss": "4.2.1" + "tailwindcss": "4.2.2" } }, "node_modules/@tailwindcss/oxide": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.2.1.tgz", - "integrity": "sha512-yv9jeEFWnjKCI6/T3Oq50yQEOqmpmpfzG1hcZsAOaXFQPfzWprWrlHSdGPEF3WQTi8zu8ohC9Mh9J470nT5pUw==", + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.2.2.tgz", + "integrity": "sha512-qEUA07+E5kehxYp9BVMpq9E8vnJuBHfJEC0vPC5e7iL/hw7HR61aDKoVoKzrG+QKp56vhNZe4qwkRmMC0zDLvg==", "license": "MIT", "engines": { "node": ">= 20" }, "optionalDependencies": { - "@tailwindcss/oxide-android-arm64": "4.2.1", - "@tailwindcss/oxide-darwin-arm64": "4.2.1", - "@tailwindcss/oxide-darwin-x64": "4.2.1", - "@tailwindcss/oxide-freebsd-x64": "4.2.1", - "@tailwindcss/oxide-linux-arm-gnueabihf": "4.2.1", - "@tailwindcss/oxide-linux-arm64-gnu": "4.2.1", - "@tailwindcss/oxide-linux-arm64-musl": "4.2.1", - "@tailwindcss/oxide-linux-x64-gnu": "4.2.1", - "@tailwindcss/oxide-linux-x64-musl": "4.2.1", - "@tailwindcss/oxide-wasm32-wasi": "4.2.1", - "@tailwindcss/oxide-win32-arm64-msvc": "4.2.1", - "@tailwindcss/oxide-win32-x64-msvc": "4.2.1" + "@tailwindcss/oxide-android-arm64": "4.2.2", + "@tailwindcss/oxide-darwin-arm64": "4.2.2", + "@tailwindcss/oxide-darwin-x64": "4.2.2", + "@tailwindcss/oxide-freebsd-x64": "4.2.2", + "@tailwindcss/oxide-linux-arm-gnueabihf": "4.2.2", + "@tailwindcss/oxide-linux-arm64-gnu": "4.2.2", + "@tailwindcss/oxide-linux-arm64-musl": "4.2.2", + "@tailwindcss/oxide-linux-x64-gnu": "4.2.2", + "@tailwindcss/oxide-linux-x64-musl": "4.2.2", + "@tailwindcss/oxide-wasm32-wasi": "4.2.2", + "@tailwindcss/oxide-win32-arm64-msvc": "4.2.2", + "@tailwindcss/oxide-win32-x64-msvc": "4.2.2" } }, "node_modules/@tailwindcss/oxide-android-arm64": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.2.1.tgz", - "integrity": "sha512-eZ7G1Zm5EC8OOKaesIKuw77jw++QJ2lL9N+dDpdQiAB/c/B2wDh0QPFHbkBVrXnwNugvrbJFk1gK2SsVjwWReg==", + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.2.2.tgz", + "integrity": "sha512-dXGR1n+P3B6748jZO/SvHZq7qBOqqzQ+yFrXpoOWWALWndF9MoSKAT3Q0fYgAzYzGhxNYOoysRvYlpixRBBoDg==", "cpu": [ "arm64" ], @@ -7477,9 +7489,9 @@ } }, "node_modules/@tailwindcss/oxide-darwin-arm64": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.2.1.tgz", - "integrity": "sha512-q/LHkOstoJ7pI1J0q6djesLzRvQSIfEto148ppAd+BVQK0JYjQIFSK3JgYZJa+Yzi0DDa52ZsQx2rqytBnf8Hw==", + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.2.2.tgz", + "integrity": "sha512-iq9Qjr6knfMpZHj55/37ouZeykwbDqF21gPFtfnhCCKGDcPI/21FKC9XdMO/XyBM7qKORx6UIhGgg6jLl7BZlg==", "cpu": [ "arm64" ], @@ -7493,9 +7505,9 @@ } }, "node_modules/@tailwindcss/oxide-darwin-x64": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.2.1.tgz", - "integrity": "sha512-/f/ozlaXGY6QLbpvd/kFTro2l18f7dHKpB+ieXz+Cijl4Mt9AI2rTrpq7V+t04nK+j9XBQHnSMdeQRhbGyt6fw==", + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.2.2.tgz", + "integrity": "sha512-BlR+2c3nzc8f2G639LpL89YY4bdcIdUmiOOkv2GQv4/4M0vJlpXEa0JXNHhCHU7VWOKWT/CjqHdTP8aUuDJkuw==", "cpu": [ "x64" ], @@ -7509,9 +7521,9 @@ } }, "node_modules/@tailwindcss/oxide-freebsd-x64": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.2.1.tgz", - "integrity": "sha512-5e/AkgYJT/cpbkys/OU2Ei2jdETCLlifwm7ogMC7/hksI2fC3iiq6OcXwjibcIjPung0kRtR3TxEITkqgn0TcA==", + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.2.2.tgz", + "integrity": "sha512-YUqUgrGMSu2CDO82hzlQ5qSb5xmx3RUrke/QgnoEx7KvmRJHQuZHZmZTLSuuHwFf0DJPybFMXMYf+WJdxHy/nQ==", "cpu": [ "x64" ], @@ -7525,9 +7537,9 @@ } }, "node_modules/@tailwindcss/oxide-linux-arm-gnueabihf": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.2.1.tgz", - "integrity": "sha512-Uny1EcVTTmerCKt/1ZuKTkb0x8ZaiuYucg2/kImO5A5Y/kBz41/+j0gxUZl+hTF3xkWpDmHX+TaWhOtba2Fyuw==", + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.2.2.tgz", + "integrity": "sha512-FPdhvsW6g06T9BWT0qTwiVZYE2WIFo2dY5aCSpjG/S/u1tby+wXoslXS0kl3/KXnULlLr1E3NPRRw0g7t2kgaQ==", "cpu": [ "arm" ], @@ -7541,9 +7553,9 @@ } }, "node_modules/@tailwindcss/oxide-linux-arm64-gnu": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.2.1.tgz", - "integrity": "sha512-CTrwomI+c7n6aSSQlsPL0roRiNMDQ/YzMD9EjcR+H4f0I1SQ8QqIuPnsVp7QgMkC1Qi8rtkekLkOFjo7OlEFRQ==", + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.2.2.tgz", + "integrity": "sha512-4og1V+ftEPXGttOO7eCmW7VICmzzJWgMx+QXAJRAhjrSjumCwWqMfkDrNu1LXEQzNAwz28NCUpucgQPrR4S2yw==", "cpu": [ "arm64" ], @@ -7557,9 +7569,9 @@ } }, "node_modules/@tailwindcss/oxide-linux-arm64-musl": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.2.1.tgz", - "integrity": "sha512-WZA0CHRL/SP1TRbA5mp9htsppSEkWuQ4KsSUumYQnyl8ZdT39ntwqmz4IUHGN6p4XdSlYfJwM4rRzZLShHsGAQ==", + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.2.2.tgz", + "integrity": "sha512-oCfG/mS+/+XRlwNjnsNLVwnMWYH7tn/kYPsNPh+JSOMlnt93mYNCKHYzylRhI51X+TbR+ufNhhKKzm6QkqX8ag==", "cpu": [ "arm64" ], @@ -7573,9 +7585,9 @@ } }, "node_modules/@tailwindcss/oxide-linux-x64-gnu": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.2.1.tgz", - "integrity": "sha512-qMFzxI2YlBOLW5PhblzuSWlWfwLHaneBE0xHzLrBgNtqN6mWfs+qYbhryGSXQjFYB1Dzf5w+LN5qbUTPhW7Y5g==", + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.2.2.tgz", + "integrity": "sha512-rTAGAkDgqbXHNp/xW0iugLVmX62wOp2PoE39BTCGKjv3Iocf6AFbRP/wZT/kuCxC9QBh9Pu8XPkv/zCZB2mcMg==", "cpu": [ "x64" ], @@ -7589,9 +7601,9 @@ } }, "node_modules/@tailwindcss/oxide-linux-x64-musl": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.2.1.tgz", - "integrity": "sha512-5r1X2FKnCMUPlXTWRYpHdPYUY6a1Ar/t7P24OuiEdEOmms5lyqjDRvVY1yy9Rmioh+AunQ0rWiOTPE8F9A3v5g==", + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.2.2.tgz", + "integrity": "sha512-XW3t3qwbIwiSyRCggeO2zxe3KWaEbM0/kW9e8+0XpBgyKU4ATYzcVSMKteZJ1iukJ3HgHBjbg9P5YPRCVUxlnQ==", "cpu": [ "x64" ], @@ -7605,9 +7617,9 @@ } }, "node_modules/@tailwindcss/oxide-wasm32-wasi": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.2.1.tgz", - "integrity": "sha512-MGFB5cVPvshR85MTJkEvqDUnuNoysrsRxd6vnk1Lf2tbiqNlXpHYZqkqOQalydienEWOHHFyyuTSYRsLfxFJ2Q==", + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.2.2.tgz", + "integrity": "sha512-eKSztKsmEsn1O5lJ4ZAfyn41NfG7vzCg496YiGtMDV86jz1q/irhms5O0VrY6ZwTUkFy/EKG3RfWgxSI3VbZ8Q==", "bundleDependencies": [ "@napi-rs/wasm-runtime", "@emnapi/core", @@ -7633,68 +7645,10 @@ "node": ">=14.0.0" } }, - "node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@emnapi/core": { - "version": "1.8.1", - "inBundle": true, - "license": "MIT", - "optional": true, - "dependencies": { - "@emnapi/wasi-threads": "1.1.0", - "tslib": "^2.4.0" - } - }, - "node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@emnapi/runtime": { - "version": "1.8.1", - "inBundle": true, - "license": "MIT", - "optional": true, - "dependencies": { - "tslib": "^2.4.0" - } - }, - "node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@emnapi/wasi-threads": { - "version": "1.1.0", - "inBundle": true, - "license": "MIT", - "optional": true, - "dependencies": { - "tslib": "^2.4.0" - } - }, - "node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@napi-rs/wasm-runtime": { - "version": "1.1.1", - "inBundle": true, - "license": "MIT", - "optional": true, - "dependencies": { - "@emnapi/core": "^1.7.1", - "@emnapi/runtime": "^1.7.1", - "@tybys/wasm-util": "^0.10.1" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/Brooooooklyn" - } - }, - "node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@tybys/wasm-util": { - "version": "0.10.1", - "inBundle": true, - "license": "MIT", - "optional": true, - "dependencies": { - "tslib": "^2.4.0" - } - }, - "node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/tslib": { - "version": "2.8.1", - "inBundle": true, - "license": "0BSD", - "optional": true - }, "node_modules/@tailwindcss/oxide-win32-arm64-msvc": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.2.1.tgz", - "integrity": "sha512-YlUEHRHBGnCMh4Nj4GnqQyBtsshUPdiNroZj8VPkvTZSoHsilRCwXcVKnG9kyi0ZFAS/3u+qKHBdDc81SADTRA==", + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.2.2.tgz", + "integrity": "sha512-qPmaQM4iKu5mxpsrWZMOZRgZv1tOZpUm+zdhhQP0VhJfyGGO3aUKdbh3gDZc/dPLQwW4eSqWGrrcWNBZWUWaXQ==", "cpu": [ "arm64" ], @@ -7708,9 +7662,9 @@ } }, "node_modules/@tailwindcss/oxide-win32-x64-msvc": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.2.1.tgz", - "integrity": "sha512-rbO34G5sMWWyrN/idLeVxAZgAKWrn5LiR3/I90Q9MkA67s6T1oB0xtTe+0heoBvHSpbU9Mk7i6uwJnpo4u21XQ==", + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.2.2.tgz", + "integrity": "sha512-1T/37VvI7WyH66b+vqHj/cLwnCxt7Qt3WFu5Q8hk65aOvlwAhs7rAp1VkulBJw/N4tMirXjVnylTR72uI0HGcA==", "cpu": [ "x64" ], @@ -7736,17 +7690,17 @@ } }, "node_modules/@tailwindcss/vite": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/@tailwindcss/vite/-/vite-4.2.1.tgz", - "integrity": "sha512-TBf2sJjYeb28jD2U/OhwdW0bbOsxkWPwQ7SrqGf9sVcoYwZj7rkXljroBO9wKBut9XnmQLXanuDUeqQK0lGg/w==", + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@tailwindcss/vite/-/vite-4.2.2.tgz", + "integrity": "sha512-mEiF5HO1QqCLXoNEfXVA1Tzo+cYsrqV7w9Juj2wdUFyW07JRenqMG225MvPwr3ZD9N1bFQj46X7r33iHxLUW0w==", "license": "MIT", "dependencies": { - "@tailwindcss/node": "4.2.1", - "@tailwindcss/oxide": "4.2.1", - "tailwindcss": "4.2.1" + "@tailwindcss/node": "4.2.2", + "@tailwindcss/oxide": "4.2.2", + "tailwindcss": "4.2.2" }, "peerDependencies": { - "vite": "^5.2.0 || ^6 || ^7" + "vite": "^5.2.0 || ^6 || ^7 || ^8" } }, "node_modules/@testing-library/dom": { @@ -8367,9 +8321,9 @@ "license": "MIT" }, "node_modules/@types/jsdom": { - "version": "28.0.0", - "resolved": "https://registry.npmjs.org/@types/jsdom/-/jsdom-28.0.0.tgz", - "integrity": "sha512-A8TBQQC/xAOojy9kM8E46cqT00sF0h7dWjV8t8BJhUi2rG6JRh7XXQo/oLoENuZIQEpXsxLccLCnknyQd7qssQ==", + "version": "28.0.1", + "resolved": "https://registry.npmjs.org/@types/jsdom/-/jsdom-28.0.1.tgz", + "integrity": "sha512-GJq2QE4TAZ5ajSoCasn5DOFm8u1mI3tIFvM5tIq3W5U/RTB6gsHwc6Yhpl91X9VSDOUVblgXmG+2+sSvFQrdlw==", "license": "MIT", "dependencies": { "@types/node": "*", @@ -9486,13 +9440,13 @@ } }, "node_modules/@vercel/backends": { - "version": "0.0.45", - "resolved": "https://registry.npmjs.org/@vercel/backends/-/backends-0.0.45.tgz", - "integrity": "sha512-KIdt/z4LfH7NgFMqgSuKi0H9UIasly7ByzP+/ZXulgNrWyeJKT9KCas3SDT65o5tU6x1D/jBysZA9AnOt8Ivew==", + "version": "0.0.49", + "resolved": "https://registry.npmjs.org/@vercel/backends/-/backends-0.0.49.tgz", + "integrity": "sha512-9N+t8/nKSMMmMgowktRqWTj07DLyXF1TXWAFZa95v6irMkqXLCmhj5tA1QTCuZ5kghe+Xtf+lK8JJMypXbDMkQ==", "license": "Apache-2.0", "dependencies": { - "@vercel/build-utils": "13.8.0", - "@vercel/nft": "1.3.0", + "@vercel/build-utils": "13.8.2", + "@vercel/nft": "1.4.0", "execa": "3.2.0", "fs-extra": "11.1.0", "oxc-transform": "0.111.0", @@ -9507,59 +9461,6 @@ "typescript": "^4.0.0 || ^5.0.0" } }, - "node_modules/@vercel/backends/node_modules/@vercel/nft": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/@vercel/nft/-/nft-1.3.0.tgz", - "integrity": "sha512-i4EYGkCsIjzu4vorDUbqglZc5eFtQI2syHb++9ZUDm6TU4edVywGpVnYDein35x9sevONOn9/UabfQXuNXtuzQ==", - "license": "MIT", - "dependencies": { - "@mapbox/node-pre-gyp": "^2.0.0", - "@rollup/pluginutils": "^5.1.3", - "acorn": "^8.6.0", - "acorn-import-attributes": "^1.9.5", - "async-sema": "^3.1.1", - "bindings": "^1.4.0", - "estree-walker": "2.0.2", - "glob": "^13.0.0", - "graceful-fs": "^4.2.9", - "node-gyp-build": "^4.2.2", - "picomatch": "^4.0.2", - "resolve-from": "^5.0.0" - }, - "bin": { - "nft": "out/cli.js" - }, - "engines": { - "node": ">=20" - } - }, - "node_modules/@vercel/backends/node_modules/balanced-match": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", - "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", - "license": "MIT", - "engines": { - "node": "18 || 20 || >=22" - } - }, - "node_modules/@vercel/backends/node_modules/brace-expansion": { - "version": "5.0.4", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.4.tgz", - "integrity": "sha512-h+DEnpVvxmfVefa4jFbCf5HdH5YMDXRsmKflpf1pILZWRFlTbJpxeU55nJl4Smt5HQaGzg1o6RHFPJaOqnmBDg==", - "license": "MIT", - "dependencies": { - "balanced-match": "^4.0.2" - }, - "engines": { - "node": "18 || 20 || >=22" - } - }, - "node_modules/@vercel/backends/node_modules/estree-walker": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", - "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", - "license": "MIT" - }, "node_modules/@vercel/backends/node_modules/fs-extra": { "version": "11.1.0", "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.1.0.tgz", @@ -9574,63 +9475,6 @@ "node": ">=14.14" } }, - "node_modules/@vercel/backends/node_modules/glob": { - "version": "13.0.6", - "resolved": "https://registry.npmjs.org/glob/-/glob-13.0.6.tgz", - "integrity": "sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw==", - "license": "BlueOak-1.0.0", - "dependencies": { - "minimatch": "^10.2.2", - "minipass": "^7.1.3", - "path-scurry": "^2.0.2" - }, - "engines": { - "node": "18 || 20 || >=22" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/@vercel/backends/node_modules/lru-cache": { - "version": "11.2.6", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.6.tgz", - "integrity": "sha512-ESL2CrkS/2wTPfuend7Zhkzo2u0daGJ/A2VucJOgQ/C48S/zB8MMeMHSGKYpXhIjbPxfuezITkaBH1wqv00DDQ==", - "license": "BlueOak-1.0.0", - "engines": { - "node": "20 || >=22" - } - }, - "node_modules/@vercel/backends/node_modules/minimatch": { - "version": "10.2.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.4.tgz", - "integrity": "sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg==", - "license": "BlueOak-1.0.0", - "dependencies": { - "brace-expansion": "^5.0.2" - }, - "engines": { - "node": "18 || 20 || >=22" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/@vercel/backends/node_modules/path-scurry": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.2.tgz", - "integrity": "sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg==", - "license": "BlueOak-1.0.0", - "dependencies": { - "lru-cache": "^11.0.0", - "minipass": "^7.1.2" - }, - "engines": { - "node": "18 || 20 || >=22" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/@vercel/backends/node_modules/path-to-regexp": { "version": "8.3.0", "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.3.0.tgz", @@ -9641,18 +9485,6 @@ "url": "https://opencollective.com/express" } }, - "node_modules/@vercel/backends/node_modules/picomatch": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", - "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, "node_modules/@vercel/backends/node_modules/zod": { "version": "3.22.4", "resolved": "https://registry.npmjs.org/zod/-/zod-3.22.4.tgz", @@ -9688,21 +9520,21 @@ } }, "node_modules/@vercel/build-utils": { - "version": "13.8.0", - "resolved": "https://registry.npmjs.org/@vercel/build-utils/-/build-utils-13.8.0.tgz", - "integrity": "sha512-moQS4Qd0pvluPd6WRTHxLN3Hh0oSObVNdFv3V0spiEmCk/wm6571up3n1th2PQFqf1a3gheNfxzL7h4I9CWs2A==", + "version": "13.8.2", + "resolved": "https://registry.npmjs.org/@vercel/build-utils/-/build-utils-13.8.2.tgz", + "integrity": "sha512-JSxGntOIq3JJA+w1CZ2w+QDocvW0JPtkkzTkGAa/pxmhE2/QnpCv15StI1Dpb4JJAtrC4zUmwMxKI9BgRdbPJw==", "license": "Apache-2.0", "dependencies": { - "@vercel/python-analysis": "0.9.1" + "@vercel/python-analysis": "0.10.1" } }, "node_modules/@vercel/cervel": { - "version": "0.0.32", - "resolved": "https://registry.npmjs.org/@vercel/cervel/-/cervel-0.0.32.tgz", - "integrity": "sha512-g/LIa97d/m3yIZGyBjwl4FOQK1Lg5HU2+N3uHiGVajLoSgJM2aBiwETDZc8bVCr8IO3IqXZQsT2hGy1Jnyko1g==", + "version": "0.0.36", + "resolved": "https://registry.npmjs.org/@vercel/cervel/-/cervel-0.0.36.tgz", + "integrity": "sha512-Tj/aOWdwGoo4GWhqXPDhlomXfzcU5gOEmaRfyLNzsM9iLBinfCeHH2tr8zZzxNQYr872dAKdyOwoZnStUZIDQw==", "license": "Apache-2.0", "dependencies": { - "@vercel/backends": "0.0.45" + "@vercel/backends": "0.0.49" }, "bin": { "cervel": "bin/cervel.mjs" @@ -9721,12 +9553,12 @@ } }, "node_modules/@vercel/elysia": { - "version": "0.1.48", - "resolved": "https://registry.npmjs.org/@vercel/elysia/-/elysia-0.1.48.tgz", - "integrity": "sha512-QlmOHUSOx/uE67Y6u/o8VbNF7ebZ8SZFbrEoBo7iZAd0MC2Vn4xUmlrHFXNR/FEkHFXWkhFKda5Ade3XMqMY8A==", + "version": "0.1.51", + "resolved": "https://registry.npmjs.org/@vercel/elysia/-/elysia-0.1.51.tgz", + "integrity": "sha512-KuMM9PiuJQmPyg1xsCoFIOHJSZIDLOyGsQ8ZBfQJnwHAK2FaxYnyHO50LnxmjZcLbOstErOM8shWAcVbwoSzIg==", "license": "Apache-2.0", "dependencies": { - "@vercel/node": "5.6.15", + "@vercel/node": "5.6.18", "@vercel/static-config": "3.2.0" } }, @@ -9737,14 +9569,14 @@ "license": "Apache-2.0" }, "node_modules/@vercel/express": { - "version": "0.1.57", - "resolved": "https://registry.npmjs.org/@vercel/express/-/express-0.1.57.tgz", - "integrity": "sha512-/Ih1eiJrBGSW6JPzexB8y8AMX/8MzuDUf0xzmpYbKLCbehp3NNuxde5RxT+6cALglFivDETsJBCFwaFuIh3ZqQ==", + "version": "0.1.61", + "resolved": "https://registry.npmjs.org/@vercel/express/-/express-0.1.61.tgz", + "integrity": "sha512-9DFKkp2wRuCIZ1Q9oRh5J9jxcLdeUIkBrwCv8O2tNgPkgJXoK/HuwVRISvyePeHRwRt71MpbDD2pBCbD4jkGZA==", "license": "Apache-2.0", "dependencies": { - "@vercel/cervel": "0.0.32", - "@vercel/nft": "1.1.1", - "@vercel/node": "5.6.15", + "@vercel/cervel": "0.0.36", + "@vercel/nft": "1.4.0", + "@vercel/node": "5.6.18", "@vercel/static-config": "3.2.0", "fs-extra": "11.1.0", "path-to-regexp": "8.3.0", @@ -9752,59 +9584,6 @@ "zod": "3.22.4" } }, - "node_modules/@vercel/express/node_modules/@vercel/nft": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@vercel/nft/-/nft-1.1.1.tgz", - "integrity": "sha512-mKMGa7CEUcXU75474kOeqHbtvK1kAcu4wiahhmlUenB5JbTQB8wVlDI8CyHR3rpGo0qlzoRWqcDzI41FUoBJCA==", - "license": "MIT", - "dependencies": { - "@mapbox/node-pre-gyp": "^2.0.0", - "@rollup/pluginutils": "^5.1.3", - "acorn": "^8.6.0", - "acorn-import-attributes": "^1.9.5", - "async-sema": "^3.1.1", - "bindings": "^1.4.0", - "estree-walker": "2.0.2", - "glob": "^13.0.0", - "graceful-fs": "^4.2.9", - "node-gyp-build": "^4.2.2", - "picomatch": "^4.0.2", - "resolve-from": "^5.0.0" - }, - "bin": { - "nft": "out/cli.js" - }, - "engines": { - "node": ">=20" - } - }, - "node_modules/@vercel/express/node_modules/balanced-match": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", - "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", - "license": "MIT", - "engines": { - "node": "18 || 20 || >=22" - } - }, - "node_modules/@vercel/express/node_modules/brace-expansion": { - "version": "5.0.4", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.4.tgz", - "integrity": "sha512-h+DEnpVvxmfVefa4jFbCf5HdH5YMDXRsmKflpf1pILZWRFlTbJpxeU55nJl4Smt5HQaGzg1o6RHFPJaOqnmBDg==", - "license": "MIT", - "dependencies": { - "balanced-match": "^4.0.2" - }, - "engines": { - "node": "18 || 20 || >=22" - } - }, - "node_modules/@vercel/express/node_modules/estree-walker": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", - "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", - "license": "MIT" - }, "node_modules/@vercel/express/node_modules/fs-extra": { "version": "11.1.0", "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.1.0.tgz", @@ -9819,63 +9598,6 @@ "node": ">=14.14" } }, - "node_modules/@vercel/express/node_modules/glob": { - "version": "13.0.6", - "resolved": "https://registry.npmjs.org/glob/-/glob-13.0.6.tgz", - "integrity": "sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw==", - "license": "BlueOak-1.0.0", - "dependencies": { - "minimatch": "^10.2.2", - "minipass": "^7.1.3", - "path-scurry": "^2.0.2" - }, - "engines": { - "node": "18 || 20 || >=22" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/@vercel/express/node_modules/lru-cache": { - "version": "11.2.6", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.6.tgz", - "integrity": "sha512-ESL2CrkS/2wTPfuend7Zhkzo2u0daGJ/A2VucJOgQ/C48S/zB8MMeMHSGKYpXhIjbPxfuezITkaBH1wqv00DDQ==", - "license": "BlueOak-1.0.0", - "engines": { - "node": "20 || >=22" - } - }, - "node_modules/@vercel/express/node_modules/minimatch": { - "version": "10.2.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.4.tgz", - "integrity": "sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg==", - "license": "BlueOak-1.0.0", - "dependencies": { - "brace-expansion": "^5.0.2" - }, - "engines": { - "node": "18 || 20 || >=22" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/@vercel/express/node_modules/path-scurry": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.2.tgz", - "integrity": "sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg==", - "license": "BlueOak-1.0.0", - "dependencies": { - "lru-cache": "^11.0.0", - "minipass": "^7.1.2" - }, - "engines": { - "node": "18 || 20 || >=22" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/@vercel/express/node_modules/path-to-regexp": { "version": "8.3.0", "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.3.0.tgz", @@ -9886,18 +9608,6 @@ "url": "https://opencollective.com/express" } }, - "node_modules/@vercel/express/node_modules/picomatch": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", - "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, "node_modules/@vercel/express/node_modules/zod": { "version": "3.22.4", "resolved": "https://registry.npmjs.org/zod/-/zod-3.22.4.tgz", @@ -9908,12 +9618,12 @@ } }, "node_modules/@vercel/fastify": { - "version": "0.1.51", - "resolved": "https://registry.npmjs.org/@vercel/fastify/-/fastify-0.1.51.tgz", - "integrity": "sha512-c9CwFQqmoUm5eEGwAcqb98DcxsRmSZGngCAo1B+nlDhaACKcrJ7Ro0tYdTDhi8Lkt+OcPnRWr4+pepgWwH94GQ==", + "version": "0.1.54", + "resolved": "https://registry.npmjs.org/@vercel/fastify/-/fastify-0.1.54.tgz", + "integrity": "sha512-yCY5h8q1XvmQPppM9EAzsHHDHrrW7cZReqxSf2vLlDVcNDFa2ScvbLiTqhGlzDiUorOmNOPHvSYfvQX95+vEFg==", "license": "Apache-2.0", "dependencies": { - "@vercel/node": "5.6.15", + "@vercel/node": "5.6.18", "@vercel/static-config": "3.2.0" } }, @@ -10073,13 +9783,13 @@ } }, "node_modules/@vercel/gatsby-plugin-vercel-builder": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/@vercel/gatsby-plugin-vercel-builder/-/gatsby-plugin-vercel-builder-2.1.0.tgz", - "integrity": "sha512-avJ5IFev2h2K6E/Pd7qd00cFLALj3OyEmQE3UoGs1dmoncINFqa1RoIZDJ9wIhWm1Euan4wrFMRsXkCwNhEGhw==", + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@vercel/gatsby-plugin-vercel-builder/-/gatsby-plugin-vercel-builder-2.1.2.tgz", + "integrity": "sha512-KRcrjf5Rbkc0fZGC4BvxF2pjgalpQz1oArbjKxc4AFEoxvHUk312z9IkH4CzNhTl/C/5/lJMjUwcI9Z1FLLFPQ==", "license": "Apache-2.0", "dependencies": { "@sinclair/typebox": "0.25.24", - "@vercel/build-utils": "13.8.0", + "@vercel/build-utils": "13.8.2", "esbuild": "0.27.0", "etag": "1.8.1", "fs-extra": "11.1.0" @@ -10563,23 +10273,23 @@ "license": "Apache-2.0" }, "node_modules/@vercel/h3": { - "version": "0.1.57", - "resolved": "https://registry.npmjs.org/@vercel/h3/-/h3-0.1.57.tgz", - "integrity": "sha512-I7Q1ity7xEdIVw4OQuKOAR8i6DrvfjpKy+y2uTwbqBD80Gg0KpsMS/KKA+UjvCuclFmP/EHYS3kpMLc/O1rVqg==", + "version": "0.1.60", + "resolved": "https://registry.npmjs.org/@vercel/h3/-/h3-0.1.60.tgz", + "integrity": "sha512-vkbRCTMWUPuXcIJcvg6NJC7ty7YFcUsweyyBYvR56a27fuxIjvbK2tGCR+9qc3kmBJ9GVzlAvBcJYymN0gTK+A==", "license": "Apache-2.0", "dependencies": { - "@vercel/node": "5.6.15", + "@vercel/node": "5.6.18", "@vercel/static-config": "3.2.0" } }, "node_modules/@vercel/hono": { - "version": "0.2.51", - "resolved": "https://registry.npmjs.org/@vercel/hono/-/hono-0.2.51.tgz", - "integrity": "sha512-iYEjjF4qR3gTZpVoB4sMQNm5OOdKw5/P2bL1CtolDTeGaea9KwqUrLyXmdK4N41HIRZr/wduID31mmHhSdC3sA==", + "version": "0.2.54", + "resolved": "https://registry.npmjs.org/@vercel/hono/-/hono-0.2.54.tgz", + "integrity": "sha512-fz/GxVx7wdrdYxXbEa9L+nPxcj0tBvdKPwinnk81wg2Nrw8CqqdmMsc2AxK6lZMEzK8eaSoUupnujOhdBMm+kg==", "license": "Apache-2.0", "dependencies": { - "@vercel/nft": "1.1.1", - "@vercel/node": "5.6.15", + "@vercel/nft": "1.4.0", + "@vercel/node": "5.6.18", "@vercel/static-config": "3.2.0", "fs-extra": "11.1.0", "path-to-regexp": "8.3.0", @@ -10587,59 +10297,6 @@ "zod": "3.22.4" } }, - "node_modules/@vercel/hono/node_modules/@vercel/nft": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@vercel/nft/-/nft-1.1.1.tgz", - "integrity": "sha512-mKMGa7CEUcXU75474kOeqHbtvK1kAcu4wiahhmlUenB5JbTQB8wVlDI8CyHR3rpGo0qlzoRWqcDzI41FUoBJCA==", - "license": "MIT", - "dependencies": { - "@mapbox/node-pre-gyp": "^2.0.0", - "@rollup/pluginutils": "^5.1.3", - "acorn": "^8.6.0", - "acorn-import-attributes": "^1.9.5", - "async-sema": "^3.1.1", - "bindings": "^1.4.0", - "estree-walker": "2.0.2", - "glob": "^13.0.0", - "graceful-fs": "^4.2.9", - "node-gyp-build": "^4.2.2", - "picomatch": "^4.0.2", - "resolve-from": "^5.0.0" - }, - "bin": { - "nft": "out/cli.js" - }, - "engines": { - "node": ">=20" - } - }, - "node_modules/@vercel/hono/node_modules/balanced-match": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", - "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", - "license": "MIT", - "engines": { - "node": "18 || 20 || >=22" - } - }, - "node_modules/@vercel/hono/node_modules/brace-expansion": { - "version": "5.0.4", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.4.tgz", - "integrity": "sha512-h+DEnpVvxmfVefa4jFbCf5HdH5YMDXRsmKflpf1pILZWRFlTbJpxeU55nJl4Smt5HQaGzg1o6RHFPJaOqnmBDg==", - "license": "MIT", - "dependencies": { - "balanced-match": "^4.0.2" - }, - "engines": { - "node": "18 || 20 || >=22" - } - }, - "node_modules/@vercel/hono/node_modules/estree-walker": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", - "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", - "license": "MIT" - }, "node_modules/@vercel/hono/node_modules/fs-extra": { "version": "11.1.0", "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.1.0.tgz", @@ -10654,63 +10311,6 @@ "node": ">=14.14" } }, - "node_modules/@vercel/hono/node_modules/glob": { - "version": "13.0.6", - "resolved": "https://registry.npmjs.org/glob/-/glob-13.0.6.tgz", - "integrity": "sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw==", - "license": "BlueOak-1.0.0", - "dependencies": { - "minimatch": "^10.2.2", - "minipass": "^7.1.3", - "path-scurry": "^2.0.2" - }, - "engines": { - "node": "18 || 20 || >=22" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/@vercel/hono/node_modules/lru-cache": { - "version": "11.2.6", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.6.tgz", - "integrity": "sha512-ESL2CrkS/2wTPfuend7Zhkzo2u0daGJ/A2VucJOgQ/C48S/zB8MMeMHSGKYpXhIjbPxfuezITkaBH1wqv00DDQ==", - "license": "BlueOak-1.0.0", - "engines": { - "node": "20 || >=22" - } - }, - "node_modules/@vercel/hono/node_modules/minimatch": { - "version": "10.2.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.4.tgz", - "integrity": "sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg==", - "license": "BlueOak-1.0.0", - "dependencies": { - "brace-expansion": "^5.0.2" - }, - "engines": { - "node": "18 || 20 || >=22" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/@vercel/hono/node_modules/path-scurry": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.2.tgz", - "integrity": "sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg==", - "license": "BlueOak-1.0.0", - "dependencies": { - "lru-cache": "^11.0.0", - "minipass": "^7.1.2" - }, - "engines": { - "node": "18 || 20 || >=22" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/@vercel/hono/node_modules/path-to-regexp": { "version": "8.3.0", "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.3.0.tgz", @@ -10721,18 +10321,6 @@ "url": "https://opencollective.com/express" } }, - "node_modules/@vercel/hono/node_modules/picomatch": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", - "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, "node_modules/@vercel/hono/node_modules/zod": { "version": "3.22.4", "resolved": "https://registry.npmjs.org/zod/-/zod-3.22.4.tgz", @@ -10753,160 +10341,38 @@ } }, "node_modules/@vercel/koa": { - "version": "0.1.31", - "resolved": "https://registry.npmjs.org/@vercel/koa/-/koa-0.1.31.tgz", - "integrity": "sha512-Gj4sjqNA80/gnHpC0tRPCTtVEct69tUK21fsjVmawCa+nDNQ4bqXu3dSewna7GtCcp9KnrWgzAIaeVAQUl53mQ==", + "version": "0.1.34", + "resolved": "https://registry.npmjs.org/@vercel/koa/-/koa-0.1.34.tgz", + "integrity": "sha512-OCJKa/QzPKQC9jXjp0MZfdsVj8Ekyqbg2tXsAnY0mKxtffkMunuvy/3LriaMGvkgDURjlxxMhCmDGUGHu4BXlg==", "license": "Apache-2.0", "dependencies": { - "@vercel/node": "5.6.15", + "@vercel/node": "5.6.18", "@vercel/static-config": "3.2.0" } }, "node_modules/@vercel/nestjs": { - "version": "0.2.52", - "resolved": "https://registry.npmjs.org/@vercel/nestjs/-/nestjs-0.2.52.tgz", - "integrity": "sha512-yfy4rpWJ1BRWZ1xBBPew0egVIblFe6G7laCKDzyESnbw19zY0F16g9HCu1H/0IfuKIQvOc95dtbmBsqLm9jyqw==", + "version": "0.2.55", + "resolved": "https://registry.npmjs.org/@vercel/nestjs/-/nestjs-0.2.55.tgz", + "integrity": "sha512-ziO5+6vIiAEpQdoQWnKWQ2JU8pPZxh9suABnBaGkFyMLRmIW1sxLfMsnVqYdvBdYuLIU3G7GecWecUgqgPWKFA==", "license": "Apache-2.0", "dependencies": { - "@vercel/node": "5.6.15", + "@vercel/node": "5.6.18", "@vercel/static-config": "3.2.0" } }, "node_modules/@vercel/next": { - "version": "4.16.1", - "resolved": "https://registry.npmjs.org/@vercel/next/-/next-4.16.1.tgz", - "integrity": "sha512-gwy3XQRZ/f6RdKuC7BZIRMAzUOQf/R5+k9LMf1LcOm1CVZOLpDhDkeZMTG5vb3Lk9LWwBrJJ2ohhZaYuYEOHaw==", + "version": "4.16.2", + "resolved": "https://registry.npmjs.org/@vercel/next/-/next-4.16.2.tgz", + "integrity": "sha512-s7O7opPCETMacdC/N7vDBv8iUPtkkeYLzETQgXTdDsXEI3oHq4zdn/4881yrXAaELZWAeTbgxvWCSj9zqQcOCw==", "license": "Apache-2.0", "dependencies": { - "@vercel/nft": "1.1.1" - } - }, - "node_modules/@vercel/next/node_modules/@vercel/nft": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@vercel/nft/-/nft-1.1.1.tgz", - "integrity": "sha512-mKMGa7CEUcXU75474kOeqHbtvK1kAcu4wiahhmlUenB5JbTQB8wVlDI8CyHR3rpGo0qlzoRWqcDzI41FUoBJCA==", - "license": "MIT", - "dependencies": { - "@mapbox/node-pre-gyp": "^2.0.0", - "@rollup/pluginutils": "^5.1.3", - "acorn": "^8.6.0", - "acorn-import-attributes": "^1.9.5", - "async-sema": "^3.1.1", - "bindings": "^1.4.0", - "estree-walker": "2.0.2", - "glob": "^13.0.0", - "graceful-fs": "^4.2.9", - "node-gyp-build": "^4.2.2", - "picomatch": "^4.0.2", - "resolve-from": "^5.0.0" - }, - "bin": { - "nft": "out/cli.js" - }, - "engines": { - "node": ">=20" - } - }, - "node_modules/@vercel/next/node_modules/balanced-match": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", - "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", - "license": "MIT", - "engines": { - "node": "18 || 20 || >=22" - } - }, - "node_modules/@vercel/next/node_modules/brace-expansion": { - "version": "5.0.4", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.4.tgz", - "integrity": "sha512-h+DEnpVvxmfVefa4jFbCf5HdH5YMDXRsmKflpf1pILZWRFlTbJpxeU55nJl4Smt5HQaGzg1o6RHFPJaOqnmBDg==", - "license": "MIT", - "dependencies": { - "balanced-match": "^4.0.2" - }, - "engines": { - "node": "18 || 20 || >=22" - } - }, - "node_modules/@vercel/next/node_modules/estree-walker": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", - "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", - "license": "MIT" - }, - "node_modules/@vercel/next/node_modules/glob": { - "version": "13.0.6", - "resolved": "https://registry.npmjs.org/glob/-/glob-13.0.6.tgz", - "integrity": "sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw==", - "license": "BlueOak-1.0.0", - "dependencies": { - "minimatch": "^10.2.2", - "minipass": "^7.1.3", - "path-scurry": "^2.0.2" - }, - "engines": { - "node": "18 || 20 || >=22" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/@vercel/next/node_modules/lru-cache": { - "version": "11.2.6", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.6.tgz", - "integrity": "sha512-ESL2CrkS/2wTPfuend7Zhkzo2u0daGJ/A2VucJOgQ/C48S/zB8MMeMHSGKYpXhIjbPxfuezITkaBH1wqv00DDQ==", - "license": "BlueOak-1.0.0", - "engines": { - "node": "20 || >=22" - } - }, - "node_modules/@vercel/next/node_modules/minimatch": { - "version": "10.2.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.4.tgz", - "integrity": "sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg==", - "license": "BlueOak-1.0.0", - "dependencies": { - "brace-expansion": "^5.0.2" - }, - "engines": { - "node": "18 || 20 || >=22" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/@vercel/next/node_modules/path-scurry": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.2.tgz", - "integrity": "sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg==", - "license": "BlueOak-1.0.0", - "dependencies": { - "lru-cache": "^11.0.0", - "minipass": "^7.1.2" - }, - "engines": { - "node": "18 || 20 || >=22" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/@vercel/next/node_modules/picomatch": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", - "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" + "@vercel/nft": "1.4.0" } }, "node_modules/@vercel/nft": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/@vercel/nft/-/nft-1.3.2.tgz", - "integrity": "sha512-HC8venRc4Ya7vNeBsJneKHHMDDWpQie7VaKhAIOst3MKO+DES+Y/SbzSp8mFkD7OzwAE2HhHkeSuSmwS20mz3A==", + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@vercel/nft/-/nft-1.4.0.tgz", + "integrity": "sha512-rr7JVnI7YGjA4lngucrWjZ7eCOJZZQaDHB+5NRGOuNc+k4PU2Lb9PmYm8uBmW8qichF7WkR2RmwmhXHBhx6wzw==", "license": "MIT", "dependencies": { "@mapbox/node-pre-gyp": "^2.0.0", @@ -11026,18 +10492,18 @@ } }, "node_modules/@vercel/node": { - "version": "5.6.15", - "resolved": "https://registry.npmjs.org/@vercel/node/-/node-5.6.15.tgz", - "integrity": "sha512-xc5fxmdk8jtuUY8y9/8W5UhTn8R1Ii1Fb3q+V8Zv+2moU9enrrBADA9ercHgE0/DtoiNDpb9Wmvnrb4bUcFOzA==", + "version": "5.6.18", + "resolved": "https://registry.npmjs.org/@vercel/node/-/node-5.6.18.tgz", + "integrity": "sha512-Hwu7S7JfKFOVVlvQEMMEbsCX4tXLprBemU9q15uN8iKJmuUtZpOZWqgcrxP9naa3cqfk0ZtsT+yik4Hi2Q1Z8Q==", "license": "Apache-2.0", "dependencies": { "@edge-runtime/node-utils": "2.3.0", "@edge-runtime/primitives": "4.1.0", "@edge-runtime/vm": "3.2.0", "@types/node": "20.11.0", - "@vercel/build-utils": "13.8.0", + "@vercel/build-utils": "13.8.2", "@vercel/error-utils": "2.0.3", - "@vercel/nft": "1.1.1", + "@vercel/nft": "1.4.0", "@vercel/static-config": "3.2.0", "async-listen": "3.0.0", "cjs-module-lexer": "1.2.3", @@ -11480,32 +10946,6 @@ "undici-types": "~5.26.4" } }, - "node_modules/@vercel/node/node_modules/@vercel/nft": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@vercel/nft/-/nft-1.1.1.tgz", - "integrity": "sha512-mKMGa7CEUcXU75474kOeqHbtvK1kAcu4wiahhmlUenB5JbTQB8wVlDI8CyHR3rpGo0qlzoRWqcDzI41FUoBJCA==", - "license": "MIT", - "dependencies": { - "@mapbox/node-pre-gyp": "^2.0.0", - "@rollup/pluginutils": "^5.1.3", - "acorn": "^8.6.0", - "acorn-import-attributes": "^1.9.5", - "async-sema": "^3.1.1", - "bindings": "^1.4.0", - "estree-walker": "2.0.2", - "glob": "^13.0.0", - "graceful-fs": "^4.2.9", - "node-gyp-build": "^4.2.2", - "picomatch": "^4.0.2", - "resolve-from": "^5.0.0" - }, - "bin": { - "nft": "out/cli.js" - }, - "engines": { - "node": ">=20" - } - }, "node_modules/@vercel/node/node_modules/async-listen": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/async-listen/-/async-listen-3.0.0.tgz", @@ -11515,27 +10955,6 @@ "node": ">= 14" } }, - "node_modules/@vercel/node/node_modules/balanced-match": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", - "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", - "license": "MIT", - "engines": { - "node": "18 || 20 || >=22" - } - }, - "node_modules/@vercel/node/node_modules/brace-expansion": { - "version": "5.0.4", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.4.tgz", - "integrity": "sha512-h+DEnpVvxmfVefa4jFbCf5HdH5YMDXRsmKflpf1pILZWRFlTbJpxeU55nJl4Smt5HQaGzg1o6RHFPJaOqnmBDg==", - "license": "MIT", - "dependencies": { - "balanced-match": "^4.0.2" - }, - "engines": { - "node": "18 || 20 || >=22" - } - }, "node_modules/@vercel/node/node_modules/cjs-module-lexer": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.2.3.tgz", @@ -11589,53 +11008,6 @@ "@esbuild/win32-x64": "0.27.0" } }, - "node_modules/@vercel/node/node_modules/estree-walker": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", - "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", - "license": "MIT" - }, - "node_modules/@vercel/node/node_modules/glob": { - "version": "13.0.6", - "resolved": "https://registry.npmjs.org/glob/-/glob-13.0.6.tgz", - "integrity": "sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw==", - "license": "BlueOak-1.0.0", - "dependencies": { - "minimatch": "^10.2.2", - "minipass": "^7.1.3", - "path-scurry": "^2.0.2" - }, - "engines": { - "node": "18 || 20 || >=22" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/@vercel/node/node_modules/lru-cache": { - "version": "11.2.6", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.6.tgz", - "integrity": "sha512-ESL2CrkS/2wTPfuend7Zhkzo2u0daGJ/A2VucJOgQ/C48S/zB8MMeMHSGKYpXhIjbPxfuezITkaBH1wqv00DDQ==", - "license": "BlueOak-1.0.0", - "engines": { - "node": "20 || >=22" - } - }, - "node_modules/@vercel/node/node_modules/minimatch": { - "version": "10.2.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.4.tgz", - "integrity": "sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg==", - "license": "BlueOak-1.0.0", - "dependencies": { - "brace-expansion": "^5.0.2" - }, - "engines": { - "node": "18 || 20 || >=22" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/@vercel/node/node_modules/node-fetch": { "version": "2.6.9", "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.9.tgz", @@ -11656,34 +11028,6 @@ } } }, - "node_modules/@vercel/node/node_modules/path-scurry": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.2.tgz", - "integrity": "sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg==", - "license": "BlueOak-1.0.0", - "dependencies": { - "lru-cache": "^11.0.0", - "minipass": "^7.1.2" - }, - "engines": { - "node": "18 || 20 || >=22" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/@vercel/node/node_modules/picomatch": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", - "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, "node_modules/@vercel/node/node_modules/undici": { "version": "5.28.4", "resolved": "https://registry.npmjs.org/undici/-/undici-5.28.4.tgz", @@ -11711,19 +11055,25 @@ "node": ">= 20" } }, + "node_modules/@vercel/prepare-flags-definitions": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/@vercel/prepare-flags-definitions/-/prepare-flags-definitions-0.2.0.tgz", + "integrity": "sha512-2yvulNR7yyv/gbWLPPRvLYBq70X6sBuG5kMQWQMQrIJQZrcrTagy/OCj9gTnhNGod5JmYKXjVBV6GW6DZcNWlQ==", + "license": "MIT" + }, "node_modules/@vercel/python": { - "version": "6.23.0", - "resolved": "https://registry.npmjs.org/@vercel/python/-/python-6.23.0.tgz", - "integrity": "sha512-P4cwbfk1zaVfX6obR3h+tEiuRIMX1cHyqmOlsbi9CBrHsxEbw45rtvXQLe29MErzUKO90kzbnQ4NbVQ8d4jt0g==", + "version": "6.25.0", + "resolved": "https://registry.npmjs.org/@vercel/python/-/python-6.25.0.tgz", + "integrity": "sha512-6XP04p//ZeMzW1C8dmFtNhZmVuWdIMk3p7jD/cdPgN+Wq7xXLpMSae5lDu/WS9qRI442SSMHwGDc2euUAHRULg==", "license": "Apache-2.0", "dependencies": { - "@vercel/python-analysis": "0.9.1" + "@vercel/python-analysis": "0.10.1" } }, "node_modules/@vercel/python-analysis": { - "version": "0.9.1", - "resolved": "https://registry.npmjs.org/@vercel/python-analysis/-/python-analysis-0.9.1.tgz", - "integrity": "sha512-ZwEi/F2DPxFPYmfjHFy7qM3+JTWRxD1EMpbIotNNhUyd/pnIG0wNt7S73RJSx62n1Y7pmFOFowoImnhULQgKvA==", + "version": "0.10.1", + "resolved": "https://registry.npmjs.org/@vercel/python-analysis/-/python-analysis-0.10.1.tgz", + "integrity": "sha512-VH56vAqg97HX2c2IMfpqOaXZAg1YN3N/S1w9rpD1LhKU2ZrgfZ3R9RsAYuOs+GcFYLL8ic5PgxcLpjfogwXjSg==", "license": "Apache-2.0", "dependencies": { "@bytecodealliance/preview2-shim": "0.17.6", @@ -11731,7 +11081,6 @@ "fs-extra": "11.1.1", "js-yaml": "4.1.1", "minimatch": "10.1.1", - "pip-requirements-js": "1.0.3", "smol-toml": "1.5.2", "zod": "3.22.4" } @@ -11787,275 +11136,31 @@ } }, "node_modules/@vercel/redwood": { - "version": "2.4.10", - "resolved": "https://registry.npmjs.org/@vercel/redwood/-/redwood-2.4.10.tgz", - "integrity": "sha512-7C5lUn9g9kLm1KpX55b8iizVPOB6087+kVyQyKyXGk8bbkYySL26yb+LIwyL/7mXwHlq/JTC0AxVdC3nNmPZuw==", + "version": "2.4.11", + "resolved": "https://registry.npmjs.org/@vercel/redwood/-/redwood-2.4.11.tgz", + "integrity": "sha512-fvVfbV4c5sELxO2sQAcLtyWJPKTla5zcuS8PF+QPDRb8Kx3X8YJPySiHW7NUkBBFLMP3S7GDvuAu8d4u+dVCjQ==", "license": "Apache-2.0", "dependencies": { - "@vercel/nft": "1.1.1", + "@vercel/nft": "1.4.0", "@vercel/static-config": "3.2.0", "semver": "6.3.1", "ts-morph": "12.0.0" } }, - "node_modules/@vercel/redwood/node_modules/@vercel/nft": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@vercel/nft/-/nft-1.1.1.tgz", - "integrity": "sha512-mKMGa7CEUcXU75474kOeqHbtvK1kAcu4wiahhmlUenB5JbTQB8wVlDI8CyHR3rpGo0qlzoRWqcDzI41FUoBJCA==", - "license": "MIT", - "dependencies": { - "@mapbox/node-pre-gyp": "^2.0.0", - "@rollup/pluginutils": "^5.1.3", - "acorn": "^8.6.0", - "acorn-import-attributes": "^1.9.5", - "async-sema": "^3.1.1", - "bindings": "^1.4.0", - "estree-walker": "2.0.2", - "glob": "^13.0.0", - "graceful-fs": "^4.2.9", - "node-gyp-build": "^4.2.2", - "picomatch": "^4.0.2", - "resolve-from": "^5.0.0" - }, - "bin": { - "nft": "out/cli.js" - }, - "engines": { - "node": ">=20" - } - }, - "node_modules/@vercel/redwood/node_modules/balanced-match": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", - "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", - "license": "MIT", - "engines": { - "node": "18 || 20 || >=22" - } - }, - "node_modules/@vercel/redwood/node_modules/brace-expansion": { - "version": "5.0.4", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.4.tgz", - "integrity": "sha512-h+DEnpVvxmfVefa4jFbCf5HdH5YMDXRsmKflpf1pILZWRFlTbJpxeU55nJl4Smt5HQaGzg1o6RHFPJaOqnmBDg==", - "license": "MIT", - "dependencies": { - "balanced-match": "^4.0.2" - }, - "engines": { - "node": "18 || 20 || >=22" - } - }, - "node_modules/@vercel/redwood/node_modules/estree-walker": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", - "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", - "license": "MIT" - }, - "node_modules/@vercel/redwood/node_modules/glob": { - "version": "13.0.6", - "resolved": "https://registry.npmjs.org/glob/-/glob-13.0.6.tgz", - "integrity": "sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw==", - "license": "BlueOak-1.0.0", - "dependencies": { - "minimatch": "^10.2.2", - "minipass": "^7.1.3", - "path-scurry": "^2.0.2" - }, - "engines": { - "node": "18 || 20 || >=22" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/@vercel/redwood/node_modules/lru-cache": { - "version": "11.2.6", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.6.tgz", - "integrity": "sha512-ESL2CrkS/2wTPfuend7Zhkzo2u0daGJ/A2VucJOgQ/C48S/zB8MMeMHSGKYpXhIjbPxfuezITkaBH1wqv00DDQ==", - "license": "BlueOak-1.0.0", - "engines": { - "node": "20 || >=22" - } - }, - "node_modules/@vercel/redwood/node_modules/minimatch": { - "version": "10.2.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.4.tgz", - "integrity": "sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg==", - "license": "BlueOak-1.0.0", - "dependencies": { - "brace-expansion": "^5.0.2" - }, - "engines": { - "node": "18 || 20 || >=22" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/@vercel/redwood/node_modules/path-scurry": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.2.tgz", - "integrity": "sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg==", - "license": "BlueOak-1.0.0", - "dependencies": { - "lru-cache": "^11.0.0", - "minipass": "^7.1.2" - }, - "engines": { - "node": "18 || 20 || >=22" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/@vercel/redwood/node_modules/picomatch": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", - "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, "node_modules/@vercel/remix-builder": { - "version": "5.7.0", - "resolved": "https://registry.npmjs.org/@vercel/remix-builder/-/remix-builder-5.7.0.tgz", - "integrity": "sha512-R44EHl+PQjX5PrCmyGust+bk+65eT5omxOLhEIJkSI90Kx/vvuyKnFNic/Zwo//GCMjxt9httvQUr/6vIBbjHg==", + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/@vercel/remix-builder/-/remix-builder-5.7.1.tgz", + "integrity": "sha512-v2jLvhLbh1iYX48vnyNzNgzG1cjA1GN9oFwYjktM548o54jbxXovbiortgrqYcYWwdtXyVsCgpJCUNlM8RcQCw==", "license": "Apache-2.0", "dependencies": { "@vercel/error-utils": "2.0.3", - "@vercel/nft": "1.1.1", + "@vercel/nft": "1.4.0", "@vercel/static-config": "3.2.0", "path-to-regexp": "6.1.0", "path-to-regexp-updated": "npm:path-to-regexp@6.3.0", "ts-morph": "12.0.0" } }, - "node_modules/@vercel/remix-builder/node_modules/@vercel/nft": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@vercel/nft/-/nft-1.1.1.tgz", - "integrity": "sha512-mKMGa7CEUcXU75474kOeqHbtvK1kAcu4wiahhmlUenB5JbTQB8wVlDI8CyHR3rpGo0qlzoRWqcDzI41FUoBJCA==", - "license": "MIT", - "dependencies": { - "@mapbox/node-pre-gyp": "^2.0.0", - "@rollup/pluginutils": "^5.1.3", - "acorn": "^8.6.0", - "acorn-import-attributes": "^1.9.5", - "async-sema": "^3.1.1", - "bindings": "^1.4.0", - "estree-walker": "2.0.2", - "glob": "^13.0.0", - "graceful-fs": "^4.2.9", - "node-gyp-build": "^4.2.2", - "picomatch": "^4.0.2", - "resolve-from": "^5.0.0" - }, - "bin": { - "nft": "out/cli.js" - }, - "engines": { - "node": ">=20" - } - }, - "node_modules/@vercel/remix-builder/node_modules/balanced-match": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", - "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", - "license": "MIT", - "engines": { - "node": "18 || 20 || >=22" - } - }, - "node_modules/@vercel/remix-builder/node_modules/brace-expansion": { - "version": "5.0.4", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.4.tgz", - "integrity": "sha512-h+DEnpVvxmfVefa4jFbCf5HdH5YMDXRsmKflpf1pILZWRFlTbJpxeU55nJl4Smt5HQaGzg1o6RHFPJaOqnmBDg==", - "license": "MIT", - "dependencies": { - "balanced-match": "^4.0.2" - }, - "engines": { - "node": "18 || 20 || >=22" - } - }, - "node_modules/@vercel/remix-builder/node_modules/estree-walker": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", - "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", - "license": "MIT" - }, - "node_modules/@vercel/remix-builder/node_modules/glob": { - "version": "13.0.6", - "resolved": "https://registry.npmjs.org/glob/-/glob-13.0.6.tgz", - "integrity": "sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw==", - "license": "BlueOak-1.0.0", - "dependencies": { - "minimatch": "^10.2.2", - "minipass": "^7.1.3", - "path-scurry": "^2.0.2" - }, - "engines": { - "node": "18 || 20 || >=22" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/@vercel/remix-builder/node_modules/lru-cache": { - "version": "11.2.6", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.6.tgz", - "integrity": "sha512-ESL2CrkS/2wTPfuend7Zhkzo2u0daGJ/A2VucJOgQ/C48S/zB8MMeMHSGKYpXhIjbPxfuezITkaBH1wqv00DDQ==", - "license": "BlueOak-1.0.0", - "engines": { - "node": "20 || >=22" - } - }, - "node_modules/@vercel/remix-builder/node_modules/minimatch": { - "version": "10.2.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.4.tgz", - "integrity": "sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg==", - "license": "BlueOak-1.0.0", - "dependencies": { - "brace-expansion": "^5.0.2" - }, - "engines": { - "node": "18 || 20 || >=22" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/@vercel/remix-builder/node_modules/path-scurry": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.2.tgz", - "integrity": "sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg==", - "license": "BlueOak-1.0.0", - "dependencies": { - "lru-cache": "^11.0.0", - "minipass": "^7.1.2" - }, - "engines": { - "node": "18 || 20 || >=22" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/@vercel/remix-builder/node_modules/picomatch": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", - "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, "node_modules/@vercel/routing-utils": { "version": "5.3.3", "resolved": "https://registry.npmjs.org/@vercel/routing-utils/-/routing-utils-5.3.3.tgz", @@ -12136,13 +11241,13 @@ "license": "ISC" }, "node_modules/@vercel/static-build": { - "version": "2.9.0", - "resolved": "https://registry.npmjs.org/@vercel/static-build/-/static-build-2.9.0.tgz", - "integrity": "sha512-3SHWntz8swxL6ve750dY8kyl4NwVUplYtun/ei7y11q2UnI70WnWmm6L9fi02Wy1o3RGhbvOnlFLcHOUBm3DXQ==", + "version": "2.9.2", + "resolved": "https://registry.npmjs.org/@vercel/static-build/-/static-build-2.9.2.tgz", + "integrity": "sha512-0jJPVPDkUZFcrc/CE5RcKQ55FX+Dh7G+b5luQzR/34Zea6VZoeetrB9c729+s5ZkwN0K+mF1PJUr8c65dlMaQg==", "license": "Apache-2.0", "dependencies": { "@vercel/gatsby-plugin-vercel-analytics": "1.0.11", - "@vercel/gatsby-plugin-vercel-builder": "2.1.0", + "@vercel/gatsby-plugin-vercel-builder": "2.1.2", "@vercel/static-config": "3.2.0", "ts-morph": "12.0.0" } @@ -14491,14 +13596,14 @@ } }, "node_modules/astro": { - "version": "6.0.5", - "resolved": "https://registry.npmjs.org/astro/-/astro-6.0.5.tgz", - "integrity": "sha512-JnLCwaoCaRXIHuIB8yNztJrd7M3hXrHUMAoQmeXtEBKxRu/738REhaCZ1lapjrS9HlpHsWTu3JUXTERB/0PA7g==", + "version": "6.0.7", + "resolved": "https://registry.npmjs.org/astro/-/astro-6.0.7.tgz", + "integrity": "sha512-tCUrtQI+2Dk13xTM07JYrvk16T4ekWqSXh0/dVCunne816ZV+RCs1tomSoTHZi3DJdoaTnLJmkH+uxCC3b1KWw==", "license": "MIT", "dependencies": { "@astrojs/compiler": "^3.0.0", "@astrojs/internal-helpers": "0.8.0", - "@astrojs/markdown-remark": "7.0.0", + "@astrojs/markdown-remark": "7.0.1", "@astrojs/telemetry": "3.3.0", "@capsizecss/unpack": "^4.0.0", "@clack/prompts": "^1.0.1", @@ -14556,7 +13661,7 @@ "astro": "bin/astro.mjs" }, "engines": { - "node": "^20.19.1 || >=22.12.0", + "node": ">=22.12.0", "npm": ">=9.6.5", "pnpm": ">=7.1.0" }, @@ -15271,9 +14376,9 @@ "license": "MIT" }, "node_modules/baseline-browser-mapping": { - "version": "2.10.8", - "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.8.tgz", - "integrity": "sha512-PCLz/LXGBsNTErbtB6i5u4eLpHeMfi93aUv5duMmj6caNu6IphS4q6UevDnL36sZQv9lrP11dbPKGMaXPwMKfQ==", + "version": "2.10.9", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.9.tgz", + "integrity": "sha512-OZd0e2mU11ClX8+IdXe3r0dbqMEznRiT4TfbhYIbcRPZkqJ7Qwer8ij3GZAmLsRKa+II9V1v5czCkvmHH3XZBg==", "license": "Apache-2.0", "bin": { "baseline-browser-mapping": "dist/cli.cjs" @@ -16417,9 +15522,9 @@ } }, "node_modules/cosmiconfig": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-9.0.0.tgz", - "integrity": "sha512-itvL5h8RETACmOTFc4UfIyB2RfEHi71Ax6E/PivVxq9NseKbOWpeyHEOIbmAw1rs8Ak0VursQNww7lf7YtUwzg==", + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-9.0.1.tgz", + "integrity": "sha512-hr4ihw+DBqcvrsEDioRO31Z17x71pUYoNe/4h6Z0wB72p7MU7/9gH8Q3s12NFhHPfYBBOV3qyfUxmr/Yn3shnQ==", "license": "MIT", "dependencies": { "env-paths": "^2.2.1", @@ -17347,12 +16452,6 @@ } } }, - "node_modules/deep-diff": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/deep-diff/-/deep-diff-1.0.2.tgz", - "integrity": "sha512-aWS3UIVH+NPGCD1kki+DCU9Dua032iSsO43LqQpcs4R3+dVv7tX0qBGjiVHJHjplsoUM2XRO/KB92glqc68awg==", - "license": "MIT" - }, "node_modules/deep-equal": { "version": "2.2.3", "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-2.2.3.tgz", @@ -19927,9 +19026,9 @@ } }, "node_modules/get-east-asian-width": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.4.0.tgz", - "integrity": "sha512-QZjmEOC+IT1uk6Rx0sX22V6uHWVwbdbxf1faPqJ1QhLdGgsRGCZoyaQBm/piRdJy/D2um6hM1UP7ZEeQ4EkP+Q==", + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.5.0.tgz", + "integrity": "sha512-CQ+bEO+Tva/qlmw24dCejulK5pMzVnUOFOijVogd3KQs07HnRIgp8TGipvCCRT06xeYEbpbgwaCxglFyiuIcmA==", "license": "MIT", "engines": { "node": ">=18" @@ -20246,18 +19345,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/globby/node_modules/unicorn-magic": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/unicorn-magic/-/unicorn-magic-0.4.0.tgz", - "integrity": "sha512-wH590V9VNgYH9g3lH9wWjTrUoKsjLF6sGLjhR4sH1LWpLmCOH0Zf7PukhDA8BiS7KHe4oPNkcTHqYkj7SOGUOw==", - "license": "MIT", - "engines": { - "node": ">=20" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/globjoin": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/globjoin/-/globjoin-0.1.4.tgz", @@ -21495,15 +20582,18 @@ } }, "node_modules/import-in-the-middle": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/import-in-the-middle/-/import-in-the-middle-2.0.6.tgz", - "integrity": "sha512-3vZV3jX0XRFW3EJDTwzWoZa+RH1b8eTTx6YOCjglrLyPuepwoBti1k3L2dKwdCUrnVEfc5CuRuGstaC/uQJJaw==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/import-in-the-middle/-/import-in-the-middle-3.0.0.tgz", + "integrity": "sha512-OnGy+eYT7wVejH2XWgLRgbmzujhhVIATQH0ztIeRilwHBjTeG3pD+XnH3PKX0r9gJ0BuJmJ68q/oh9qgXnNDQg==", "license": "Apache-2.0", "dependencies": { "acorn": "^8.15.0", "acorn-import-attributes": "^1.9.5", "cjs-module-lexer": "^2.2.0", "module-details-from-path": "^1.0.4" + }, + "engines": { + "node": ">=18" } }, "node_modules/import-in-the-middle/node_modules/cjs-module-lexer": { @@ -23875,9 +22965,9 @@ } }, "node_modules/lightningcss": { - "version": "1.31.1", - "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.31.1.tgz", - "integrity": "sha512-l51N2r93WmGUye3WuFoN5k10zyvrVs0qfKBhyC5ogUQ6Ew6JUSswh78mbSO+IU3nTWsyOArqPCcShdQSadghBQ==", + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.32.0.tgz", + "integrity": "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==", "license": "MPL-2.0", "dependencies": { "detect-libc": "^2.0.3" @@ -23890,23 +22980,23 @@ "url": "https://opencollective.com/parcel" }, "optionalDependencies": { - "lightningcss-android-arm64": "1.31.1", - "lightningcss-darwin-arm64": "1.31.1", - "lightningcss-darwin-x64": "1.31.1", - "lightningcss-freebsd-x64": "1.31.1", - "lightningcss-linux-arm-gnueabihf": "1.31.1", - "lightningcss-linux-arm64-gnu": "1.31.1", - "lightningcss-linux-arm64-musl": "1.31.1", - "lightningcss-linux-x64-gnu": "1.31.1", - "lightningcss-linux-x64-musl": "1.31.1", - "lightningcss-win32-arm64-msvc": "1.31.1", - "lightningcss-win32-x64-msvc": "1.31.1" + "lightningcss-android-arm64": "1.32.0", + "lightningcss-darwin-arm64": "1.32.0", + "lightningcss-darwin-x64": "1.32.0", + "lightningcss-freebsd-x64": "1.32.0", + "lightningcss-linux-arm-gnueabihf": "1.32.0", + "lightningcss-linux-arm64-gnu": "1.32.0", + "lightningcss-linux-arm64-musl": "1.32.0", + "lightningcss-linux-x64-gnu": "1.32.0", + "lightningcss-linux-x64-musl": "1.32.0", + "lightningcss-win32-arm64-msvc": "1.32.0", + "lightningcss-win32-x64-msvc": "1.32.0" } }, "node_modules/lightningcss-android-arm64": { - "version": "1.31.1", - "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.31.1.tgz", - "integrity": "sha512-HXJF3x8w9nQ4jbXRiNppBCqeZPIAfUo8zE/kOEGbW5NZvGc/K7nMxbhIr+YlFlHW5mpbg/YFPdbnCh1wAXCKFg==", + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.32.0.tgz", + "integrity": "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==", "cpu": [ "arm64" ], @@ -23924,9 +23014,9 @@ } }, "node_modules/lightningcss-darwin-arm64": { - "version": "1.31.1", - "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.31.1.tgz", - "integrity": "sha512-02uTEqf3vIfNMq3h/z2cJfcOXnQ0GRwQrkmPafhueLb2h7mqEidiCzkE4gBMEH65abHRiQvhdcQ+aP0D0g67sg==", + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.32.0.tgz", + "integrity": "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==", "cpu": [ "arm64" ], @@ -23944,9 +23034,9 @@ } }, "node_modules/lightningcss-darwin-x64": { - "version": "1.31.1", - "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.31.1.tgz", - "integrity": "sha512-1ObhyoCY+tGxtsz1lSx5NXCj3nirk0Y0kB/g8B8DT+sSx4G9djitg9ejFnjb3gJNWo7qXH4DIy2SUHvpoFwfTA==", + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.32.0.tgz", + "integrity": "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==", "cpu": [ "x64" ], @@ -23964,9 +23054,9 @@ } }, "node_modules/lightningcss-freebsd-x64": { - "version": "1.31.1", - "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.31.1.tgz", - "integrity": "sha512-1RINmQKAItO6ISxYgPwszQE1BrsVU5aB45ho6O42mu96UiZBxEXsuQ7cJW4zs4CEodPUioj/QrXW1r9pLUM74A==", + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.32.0.tgz", + "integrity": "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==", "cpu": [ "x64" ], @@ -23984,9 +23074,9 @@ } }, "node_modules/lightningcss-linux-arm-gnueabihf": { - "version": "1.31.1", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.31.1.tgz", - "integrity": "sha512-OOCm2//MZJ87CdDK62rZIu+aw9gBv4azMJuA8/KB74wmfS3lnC4yoPHm0uXZ/dvNNHmnZnB8XLAZzObeG0nS1g==", + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.32.0.tgz", + "integrity": "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==", "cpu": [ "arm" ], @@ -24004,9 +23094,9 @@ } }, "node_modules/lightningcss-linux-arm64-gnu": { - "version": "1.31.1", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.31.1.tgz", - "integrity": "sha512-WKyLWztD71rTnou4xAD5kQT+982wvca7E6QoLpoawZ1gP9JM0GJj4Tp5jMUh9B3AitHbRZ2/H3W5xQmdEOUlLg==", + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.32.0.tgz", + "integrity": "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==", "cpu": [ "arm64" ], @@ -24024,9 +23114,9 @@ } }, "node_modules/lightningcss-linux-arm64-musl": { - "version": "1.31.1", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.31.1.tgz", - "integrity": "sha512-mVZ7Pg2zIbe3XlNbZJdjs86YViQFoJSpc41CbVmKBPiGmC4YrfeOyz65ms2qpAobVd7WQsbW4PdsSJEMymyIMg==", + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.32.0.tgz", + "integrity": "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==", "cpu": [ "arm64" ], @@ -24044,9 +23134,9 @@ } }, "node_modules/lightningcss-linux-x64-gnu": { - "version": "1.31.1", - "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.31.1.tgz", - "integrity": "sha512-xGlFWRMl+0KvUhgySdIaReQdB4FNudfUTARn7q0hh/V67PVGCs3ADFjw+6++kG1RNd0zdGRlEKa+T13/tQjPMA==", + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.32.0.tgz", + "integrity": "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==", "cpu": [ "x64" ], @@ -24064,9 +23154,9 @@ } }, "node_modules/lightningcss-linux-x64-musl": { - "version": "1.31.1", - "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.31.1.tgz", - "integrity": "sha512-eowF8PrKHw9LpoZii5tdZwnBcYDxRw2rRCyvAXLi34iyeYfqCQNA9rmUM0ce62NlPhCvof1+9ivRaTY6pSKDaA==", + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.32.0.tgz", + "integrity": "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==", "cpu": [ "x64" ], @@ -24084,9 +23174,9 @@ } }, "node_modules/lightningcss-win32-arm64-msvc": { - "version": "1.31.1", - "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.31.1.tgz", - "integrity": "sha512-aJReEbSEQzx1uBlQizAOBSjcmr9dCdL3XuC/6HLXAxmtErsj2ICo5yYggg1qOODQMtnjNQv2UHb9NpOuFtYe4w==", + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.32.0.tgz", + "integrity": "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==", "cpu": [ "arm64" ], @@ -24104,9 +23194,9 @@ } }, "node_modules/lightningcss-win32-x64-msvc": { - "version": "1.31.1", - "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.31.1.tgz", - "integrity": "sha512-I9aiFrbd7oYHwlnQDqr1Roz+fTz61oDDJX7n9tYF9FJymH1cIN1DtKw3iYt6b8WZgEjoNwVSncwF4wx/ZedMhw==", + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.32.0.tgz", + "integrity": "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==", "cpu": [ "x64" ], @@ -25885,9 +24975,9 @@ "license": "MIT" }, "node_modules/meow": { - "version": "14.0.0", - "resolved": "https://registry.npmjs.org/meow/-/meow-14.0.0.tgz", - "integrity": "sha512-JhC3R1f6dbspVtmF3vKjAWz1EVIvwFrGGPLSdU6rK79xBwHWTuHoLnRX/t1/zHS1Ch1Y2UtIrih7DAHuH9JFJA==", + "version": "14.1.0", + "resolved": "https://registry.npmjs.org/meow/-/meow-14.1.0.tgz", + "integrity": "sha512-EDYo6VlmtnumlcBCbh1gLJ//9jvM/ndXHfVXIFrZVr6fGcwTUyCTFNTLCKuY3ffbK8L/+3Mzqnd58RojiZqHVw==", "license": "MIT", "engines": { "node": ">=20" @@ -26014,6 +25104,12 @@ "integrity": "sha512-ZWc51jO3qegGkVh8Hwpv636EkbesNV5ZNQPCtRa+0qytRYPEs9IYT9qITY9buezqUH5uqyzlWLcufrzU2rffdg==", "license": "MIT" }, + "node_modules/microdiff": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/microdiff/-/microdiff-1.5.0.tgz", + "integrity": "sha512-Drq+/THMvDdzRYrK0oxJmOKiC24ayUV8ahrt8l3oRK51PWt6gdtrIGrlIH3pT/lFh1z93FbAcidtsHcWbnRz8Q==", + "license": "MIT" + }, "node_modules/micromark": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/micromark/-/micromark-4.0.2.tgz", @@ -27441,9 +26537,9 @@ "license": "MIT" }, "node_modules/nodemailer": { - "version": "8.0.2", - "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-8.0.2.tgz", - "integrity": "sha512-zbj002pZAIkWQFxyAaqoxvn+zoIwRnS40hgjqTXudKOOJkiFFgBeNqjgD3/YCR12sZnrghWYBY+yP1ZucdDRpw==", + "version": "8.0.3", + "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-8.0.3.tgz", + "integrity": "sha512-JQNBqvK+bj3NMhUFR3wmCl3SYcOeMotDiwDBvIoCuQdF0PvlIY0BH+FJ2CG7u4cXKPChplE78oowlH/Otsc4ZQ==", "license": "MIT-0", "engines": { "node": ">=6.0.0" @@ -29540,15 +28636,6 @@ "integrity": "sha512-RdR9FQrFwNBNXAr4GixM8YaRZRJ5PUWbKYbE5eOsrwAjJW0q2REGcf79oYPsLyskQCZG1PLN+S/K1V00joZAoQ==", "license": "MIT" }, - "node_modules/ohm-js": { - "version": "17.5.0", - "resolved": "https://registry.npmjs.org/ohm-js/-/ohm-js-17.5.0.tgz", - "integrity": "sha512-l4Sa7026+6jsvYbt0PXKmL+f+ML32fD++IznLgxDhx2t9Cx6NC7zwRqblCujPHGGmkQerHoeBzRutdxaw/S72g==", - "license": "MIT", - "engines": { - "node": ">=0.12.1" - } - }, "node_modules/once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", @@ -30462,15 +29549,6 @@ "node": ">=0.10.0" } }, - "node_modules/pip-requirements-js": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/pip-requirements-js/-/pip-requirements-js-1.0.3.tgz", - "integrity": "sha512-1O9Bx0mPOZht3tW4LuxOA46qkD8A1AGymWXz3UwIMqGQgiTiOaFptsCf+9IE67qcbBrg8KHG6l8ePF7CoFRW/A==", - "license": "MPL-2.0", - "dependencies": { - "ohm-js": "^17.1.0" - } - }, "node_modules/pixelmatch": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/pixelmatch/-/pixelmatch-4.0.2.tgz", @@ -34298,23 +33376,35 @@ "license": "MIT" }, "node_modules/sanitize-html": { - "version": "2.17.1", - "resolved": "https://registry.npmjs.org/sanitize-html/-/sanitize-html-2.17.1.tgz", - "integrity": "sha512-ehFCW+q1a4CSOWRAdX97BX/6/PDEkCqw7/0JXZAGQV57FQB3YOkTa/rrzHPeJ+Aghy4vZAFfWMYyfxIiB7F/gw==", + "version": "2.17.2", + "resolved": "https://registry.npmjs.org/sanitize-html/-/sanitize-html-2.17.2.tgz", + "integrity": "sha512-EnffJUl46VE9uvZ0XeWzObHLurClLlT12gsOk1cHyP2Ol1P0BnBnsXmShlBmWVJM+dKieQI68R0tsPY5m/B+Jg==", "license": "MIT", "dependencies": { "deepmerge": "^4.2.2", "escape-string-regexp": "^4.0.0", - "htmlparser2": "^8.0.0", + "htmlparser2": "^10.1.0", "is-plain-object": "^5.0.0", "parse-srcset": "^1.0.2", "postcss": "^8.3.11" } }, + "node_modules/sanitize-html/node_modules/entities": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-7.0.1.tgz", + "integrity": "sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, "node_modules/sanitize-html/node_modules/htmlparser2": { - "version": "8.0.2", - "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-8.0.2.tgz", - "integrity": "sha512-GYdjWKDkbRLkZ5geuHs5NY1puJ+PXwP7+fHPRz06Eirsb9ugf6d8kkXav6ADhcODhFFPMIXyxkxSuMf3D6NCFA==", + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-10.1.0.tgz", + "integrity": "sha512-VTZkM9GWRAtEpveh7MSF6SjjrpNVNNVJfFup7xTY3UpFtm67foy9HDVXneLtFVt4pMz5kZtgNcvCniNFb1hlEQ==", "funding": [ "https://github.com/fb55/htmlparser2?sponsor=1", { @@ -34326,8 +33416,8 @@ "dependencies": { "domelementtype": "^2.3.0", "domhandler": "^5.0.3", - "domutils": "^3.0.1", - "entities": "^4.4.0" + "domutils": "^3.2.2", + "entities": "^7.0.1" } }, "node_modules/sass-formatter": { @@ -35392,6 +34482,22 @@ "safe-buffer": "~5.2.0" } }, + "node_modules/string-width": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-8.2.0.tgz", + "integrity": "sha512-6hJPQ8N0V0P3SNmP6h2J99RLuzrWz2gvT7VnK5tKvrNqJoyS9W4/Fb8mo31UiPvy00z7DQXkP2hnKBVav76thw==", + "license": "MIT", + "dependencies": { + "get-east-asian-width": "^1.5.0", + "strip-ansi": "^7.1.2" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/string-width-cjs": { "name": "string-width", "version": "4.2.3", @@ -35675,9 +34781,9 @@ } }, "node_modules/stylelint": { - "version": "17.4.0", - "resolved": "https://registry.npmjs.org/stylelint/-/stylelint-17.4.0.tgz", - "integrity": "sha512-3kQ2/cHv3Zt8OBg+h2B8XCx9evEABQIrv4hh3uXahGz/ZEHrTR80zxBiK2NfXNaSoyBzxO1pjsz1Vhdzwn5XSw==", + "version": "17.5.0", + "resolved": "https://registry.npmjs.org/stylelint/-/stylelint-17.5.0.tgz", + "integrity": "sha512-o/NS6zhsPZFmgUm5tXX4pVNg1XDOZSlucLdf2qow/lVn4JIyzZIQ5b3kad1ugqUj3GSIgr2u5lQw7X8rjqw33g==", "funding": [ { "type": "opencollective", @@ -35692,21 +34798,21 @@ "dependencies": { "@csstools/css-calc": "^3.1.1", "@csstools/css-parser-algorithms": "^4.0.0", - "@csstools/css-syntax-patches-for-csstree": "^1.0.27", + "@csstools/css-syntax-patches-for-csstree": "^1.0.29", "@csstools/css-tokenizer": "^4.0.0", "@csstools/media-query-list-parser": "^5.0.0", "@csstools/selector-resolve-nested": "^4.0.0", "@csstools/selector-specificity": "^6.0.0", "colord": "^2.9.3", - "cosmiconfig": "^9.0.0", + "cosmiconfig": "^9.0.1", "css-functions-list": "^3.3.3", - "css-tree": "^3.1.0", + "css-tree": "^3.2.1", "debug": "^4.4.3", "fast-glob": "^3.3.3", "fastest-levenshtein": "^1.0.16", "file-entry-cache": "^11.1.2", "global-modules": "^2.0.0", - "globby": "^16.1.0", + "globby": "^16.1.1", "globjoin": "^0.1.4", "html-tags": "^5.1.0", "ignore": "^7.0.5", @@ -35714,15 +34820,15 @@ "imurmurhash": "^0.1.4", "is-plain-object": "^5.0.0", "mathml-tag-names": "^4.0.0", - "meow": "^14.0.0", + "meow": "^14.1.0", "micromatch": "^4.0.8", "normalize-path": "^3.0.0", "picocolors": "^1.1.1", - "postcss": "^8.5.6", + "postcss": "^8.5.8", "postcss-safe-parser": "^7.0.1", "postcss-selector-parser": "^7.1.1", "postcss-value-parser": "^4.2.0", - "string-width": "^8.1.1", + "string-width": "^8.2.0", "supports-hyperlinks": "^4.4.0", "svg-tags": "^1.0.0", "table": "^6.9.0", @@ -35874,6 +34980,26 @@ "hookified": "^1.15.0" } }, + "node_modules/stylelint/node_modules/globby": { + "version": "16.1.1", + "resolved": "https://registry.npmjs.org/globby/-/globby-16.1.1.tgz", + "integrity": "sha512-dW7vl+yiAJSp6aCekaVnVJxurRv7DCOLyXqEG3RYMYUg7AuJ2jCqPkZTA8ooqC2vtnkaMcV5WfFBMuEnTu1OQg==", + "license": "MIT", + "dependencies": { + "@sindresorhus/merge-streams": "^4.0.0", + "fast-glob": "^3.3.3", + "ignore": "^7.0.5", + "is-path-inside": "^4.0.0", + "slash": "^5.1.0", + "unicorn-magic": "^0.4.0" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/stylelint/node_modules/ignore": { "version": "7.0.5", "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz", @@ -35883,6 +35009,18 @@ "node": ">= 4" } }, + "node_modules/stylelint/node_modules/is-path-inside": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-4.0.0.tgz", + "integrity": "sha512-lJJV/5dYS+RcL8uQdBDW9c9uWFLLBNRyFhnAKXw5tVqLlKZ4RMGZKv+YQ/IA3OhD+RpbJa1LLFM1FQPGyIXvOA==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/stylelint/node_modules/postcss-safe-parser": { "version": "7.0.1", "resolved": "https://registry.npmjs.org/postcss-safe-parser/-/postcss-safe-parser-7.0.1.tgz", @@ -35922,22 +35060,6 @@ "node": ">=4" } }, - "node_modules/stylelint/node_modules/string-width": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-8.1.1.tgz", - "integrity": "sha512-KpqHIdDL9KwYk22wEOg/VIqYbrnLeSApsKT/bSj6Ez7pn3CftUiLAv2Lccpq1ALcpLV9UX1Ppn92npZWu2w/aw==", - "license": "MIT", - "dependencies": { - "get-east-asian-width": "^1.3.0", - "strip-ansi": "^7.1.0" - }, - "engines": { - "node": ">=20" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/stylis": { "version": "4.3.6", "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.3.6.tgz", @@ -36143,9 +35265,9 @@ } }, "node_modules/tailwindcss": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.2.1.tgz", - "integrity": "sha512-/tBrSQ36vCleJkAOsy9kbNTgaxvGbyOamC30PRePTQe/o1MFwEKHQk4Cn7BNGaPtjp+PuUrByJehM1hgxfq4sw==", + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.2.2.tgz", + "integrity": "sha512-KWBIxs1Xb6NoLdMVqhbhgwZf2PGBpPEiwOqgI4pFIYbNTfBXiKYyWoTsXgBQ9WFg/OlhnvHaY+AEpW7wSmFo2Q==", "license": "MIT" }, "node_modules/tapable": { @@ -37176,6 +36298,18 @@ "node": ">=12" } }, + "node_modules/unicorn-magic": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/unicorn-magic/-/unicorn-magic-0.4.0.tgz", + "integrity": "sha512-wH590V9VNgYH9g3lH9wWjTrUoKsjLF6sGLjhR4sH1LWpLmCOH0Zf7PukhDA8BiS7KHe4oPNkcTHqYkj7SOGUOw==", + "license": "MIT", + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/unified": { "version": "11.0.5", "resolved": "https://registry.npmjs.org/unified/-/unified-11.0.5.tgz", @@ -38121,33 +37255,34 @@ } }, "node_modules/vercel": { - "version": "50.32.5", - "resolved": "https://registry.npmjs.org/vercel/-/vercel-50.32.5.tgz", - "integrity": "sha512-gwxkUgVLQGSUV3EgsJa3rbBXDgoKIjp73bxEgZm+BgSRDSG8sMJD1shozgu5NI+Od8HkzvsZBIX5c2XOsLH8+w==", + "version": "50.34.2", + "resolved": "https://registry.npmjs.org/vercel/-/vercel-50.34.2.tgz", + "integrity": "sha512-FWWgUntVniaopvUNljIU5c9ahXXMfsx4BvNTdCdUhZBcGifyJoZ6mfy9XEEeYGllRdIn6iIoBHQsqXtEjn8Fgg==", "license": "Apache-2.0", "dependencies": { - "@vercel/backends": "0.0.45", + "@vercel/backends": "0.0.49", "@vercel/blob": "2.3.0", - "@vercel/build-utils": "13.8.0", + "@vercel/build-utils": "13.8.2", "@vercel/detect-agent": "1.2.1", - "@vercel/elysia": "0.1.48", - "@vercel/express": "0.1.57", - "@vercel/fastify": "0.1.51", + "@vercel/elysia": "0.1.51", + "@vercel/express": "0.1.61", + "@vercel/fastify": "0.1.54", "@vercel/fun": "1.3.0", "@vercel/go": "3.4.5", - "@vercel/h3": "0.1.57", - "@vercel/hono": "0.2.51", + "@vercel/h3": "0.1.60", + "@vercel/hono": "0.2.54", "@vercel/hydrogen": "1.3.6", - "@vercel/koa": "0.1.31", - "@vercel/nestjs": "0.2.52", - "@vercel/next": "4.16.1", - "@vercel/node": "5.6.15", - "@vercel/python": "6.23.0", - "@vercel/redwood": "2.4.10", - "@vercel/remix-builder": "5.7.0", + "@vercel/koa": "0.1.34", + "@vercel/nestjs": "0.2.55", + "@vercel/next": "4.16.2", + "@vercel/node": "5.6.18", + "@vercel/prepare-flags-definitions": "0.2.0", + "@vercel/python": "6.25.0", + "@vercel/redwood": "2.4.11", + "@vercel/remix-builder": "5.7.1", "@vercel/ruby": "2.3.2", "@vercel/rust": "1.0.5", - "@vercel/static-build": "2.9.0", + "@vercel/static-build": "2.9.2", "chokidar": "4.0.0", "esbuild": "0.27.0", "form-data": "^4.0.0", @@ -39029,16 +38164,15 @@ } }, "node_modules/vite": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/vite/-/vite-8.0.0.tgz", - "integrity": "sha512-fPGaRNj9Zytaf8LEiBhY7Z6ijnFKdzU/+mL8EFBaKr7Vw1/FWcTBAMW0wLPJAGMPX38ZPVCVgLceWiEqeoqL2Q==", + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/vite/-/vite-8.0.1.tgz", + "integrity": "sha512-wt+Z2qIhfFt85uiyRt5LPU4oVEJBXj8hZNWKeqFG4gRG/0RaRGJ7njQCwzFVjO+v4+Ipmf5CY7VdmZRAYYBPHw==", "license": "MIT", "dependencies": { - "@oxc-project/runtime": "0.115.0", "lightningcss": "^1.32.0", "picomatch": "^4.0.3", "postcss": "^8.5.8", - "rolldown": "1.0.0-rc.9", + "rolldown": "1.0.0-rc.10", "tinyglobby": "^0.2.15" }, "bin": { @@ -39055,7 +38189,7 @@ }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", - "@vitejs/devtools": "^0.0.0-alpha.31", + "@vitejs/devtools": "^0.1.0", "esbuild": "^0.27.0", "jiti": ">=1.21.0", "less": "^4.0.0", @@ -39123,18 +38257,18 @@ } }, "node_modules/vite/node_modules/@oxc-project/types": { - "version": "0.115.0", - "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.115.0.tgz", - "integrity": "sha512-4n91DKnebUS4yjUHl2g3/b2T+IUdCfmoZGhmwsovZCDaJSs+QkVAM+0AqqTxHSsHfeiMuueT75cZaZcT/m0pSw==", + "version": "0.120.0", + "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.120.0.tgz", + "integrity": "sha512-k1YNu55DuvAip/MGE1FTsIuU3FUCn6v/ujG9V7Nq5Df/kX2CWb13hhwD0lmJGMGqE+bE1MXvv9SZVnMzEXlWcg==", "license": "MIT", "funding": { "url": "https://github.com/sponsors/Boshen" } }, "node_modules/vite/node_modules/@rolldown/binding-android-arm64": { - "version": "1.0.0-rc.9", - "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.0-rc.9.tgz", - "integrity": "sha512-lcJL0bN5hpgJfSIz/8PIf02irmyL43P+j1pTCfbD1DbLkmGRuFIA4DD3B3ZOvGqG0XiVvRznbKtN0COQVaKUTg==", + "version": "1.0.0-rc.10", + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.0-rc.10.tgz", + "integrity": "sha512-jOHxwXhxmFKuXztiu1ORieJeTbx5vrTkcOkkkn2d35726+iwhrY1w/+nYY/AGgF12thg33qC3R1LMBF5tHTZHg==", "cpu": [ "arm64" ], @@ -39148,9 +38282,9 @@ } }, "node_modules/vite/node_modules/@rolldown/binding-darwin-arm64": { - "version": "1.0.0-rc.9", - "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.0.0-rc.9.tgz", - "integrity": "sha512-J7Zk3kLYFsLtuH6U+F4pS2sYVzac0qkjcO5QxHS7OS7yZu2LRs+IXo+uvJ/mvpyUljDJ3LROZPoQfgBIpCMhdQ==", + "version": "1.0.0-rc.10", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.0.0-rc.10.tgz", + "integrity": "sha512-gED05Teg/vtTZbIJBc4VNMAxAFDUPkuO/rAIyyxZjTj1a1/s6z5TII/5yMGZ0uLRCifEtwUQn8OlYzuYc0m70w==", "cpu": [ "arm64" ], @@ -39164,9 +38298,9 @@ } }, "node_modules/vite/node_modules/@rolldown/binding-darwin-x64": { - "version": "1.0.0-rc.9", - "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.0.0-rc.9.tgz", - "integrity": "sha512-iwtmmghy8nhfRGeNAIltcNXzD0QMNaaA5U/NyZc1Ia4bxrzFByNMDoppoC+hl7cDiUq5/1CnFthpT9n+UtfFyg==", + "version": "1.0.0-rc.10", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.0.0-rc.10.tgz", + "integrity": "sha512-rI15NcM1mA48lqrIxVkHfAqcyFLcQwyXWThy+BQ5+mkKKPvSO26ir+ZDp36AgYoYVkqvMcdS8zOE6SeBsR9e8A==", "cpu": [ "x64" ], @@ -39180,9 +38314,9 @@ } }, "node_modules/vite/node_modules/@rolldown/binding-freebsd-x64": { - "version": "1.0.0-rc.9", - "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.0.0-rc.9.tgz", - "integrity": "sha512-DLFYI78SCiZr5VvdEplsVC2Vx53lnA4/Ga5C65iyldMVaErr86aiqCoNBLl92PXPfDtUYjUh+xFFor40ueNs4Q==", + "version": "1.0.0-rc.10", + "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.0.0-rc.10.tgz", + "integrity": "sha512-XZRXHdTa+4ME1MuDVp021+doQ+z6Ei4CCFmNc5/sKbqb8YmkiJdj8QKlV3rCI0AJtAeSB5n0WGPuJWNL9p/L2w==", "cpu": [ "x64" ], @@ -39196,9 +38330,9 @@ } }, "node_modules/vite/node_modules/@rolldown/binding-linux-arm-gnueabihf": { - "version": "1.0.0-rc.9", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.0.0-rc.9.tgz", - "integrity": "sha512-CsjTmTwd0Hri6iTw/DRMK7kOZ7FwAkrO4h8YWKoX/kcj833e4coqo2wzIFywtch/8Eb5enQ/lwLM7w6JX1W5RQ==", + "version": "1.0.0-rc.10", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.0.0-rc.10.tgz", + "integrity": "sha512-R0SQMRluISSLzFE20sPWYHVmJdDQnRyc/FzSCN72BqQmh2SOZUFG+N3/vBZpR4C6WpEUVYJLrYUXaj43sJsNLA==", "cpu": [ "arm" ], @@ -39212,9 +38346,9 @@ } }, "node_modules/vite/node_modules/@rolldown/binding-linux-arm64-gnu": { - "version": "1.0.0-rc.9", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.0.0-rc.9.tgz", - "integrity": "sha512-2x9O2JbSPxpxMDhP9Z74mahAStibTlrBMW0520+epJH5sac7/LwZW5Bmg/E6CXuEF53JJFW509uP+lSedaUNxg==", + "version": "1.0.0-rc.10", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.0.0-rc.10.tgz", + "integrity": "sha512-Y1reMrV/o+cwpduYhJuOE3OMKx32RMYCidf14y+HssARRmhDuWXJ4yVguDg2R/8SyyGNo+auzz64LnPK9Hq6jg==", "cpu": [ "arm64" ], @@ -39228,9 +38362,9 @@ } }, "node_modules/vite/node_modules/@rolldown/binding-linux-arm64-musl": { - "version": "1.0.0-rc.9", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.0.0-rc.9.tgz", - "integrity": "sha512-JA1QRW31ogheAIRhIg9tjMfsYbglXXYGNPLdPEYrwFxdbkQCAzvpSCSHCDWNl4hTtrol8WeboCSEpjdZK8qrCg==", + "version": "1.0.0-rc.10", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.0.0-rc.10.tgz", + "integrity": "sha512-vELN+HNb2IzuzSBUOD4NHmP9yrGwl1DVM29wlQvx1OLSclL0NgVWnVDKl/8tEks79EFek/kebQKnNJkIAA4W2g==", "cpu": [ "arm64" ], @@ -39244,9 +38378,9 @@ } }, "node_modules/vite/node_modules/@rolldown/binding-linux-x64-gnu": { - "version": "1.0.0-rc.9", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.0.0-rc.9.tgz", - "integrity": "sha512-cVEl1vZtBsBZna3YMjGXNvnYYrOJ7RzuWvZU0ffvJUexWkukMaDuGhUXn0rjnV0ptzGVkvc+vW9Yqy6h8YX4pg==", + "version": "1.0.0-rc.10", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.0.0-rc.10.tgz", + "integrity": "sha512-eOCKUpluKgfObT2pHjztnaWEIbUabWzk3qPZ5PuacuPmr4+JtQG4k2vGTY0H15edaTnicgU428XW/IH6AimcQw==", "cpu": [ "x64" ], @@ -39260,9 +38394,9 @@ } }, "node_modules/vite/node_modules/@rolldown/binding-linux-x64-musl": { - "version": "1.0.0-rc.9", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.0.0-rc.9.tgz", - "integrity": "sha512-UzYnKCIIc4heAKgI4PZ3dfBGUZefGCJ1TPDuLHoCzgrMYPb5Rv6TLFuYtyM4rWyHM7hymNdsg5ik2C+UD9VDbA==", + "version": "1.0.0-rc.10", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.0.0-rc.10.tgz", + "integrity": "sha512-Xdf2jQbfQowJnLcgYfD/m0Uu0Qj5OdxKallD78/IPPfzaiaI4KRAwZzHcKQ4ig1gtg1SuzC7jovNiM2TzQsBXA==", "cpu": [ "x64" ], @@ -39276,9 +38410,9 @@ } }, "node_modules/vite/node_modules/@rolldown/binding-openharmony-arm64": { - "version": "1.0.0-rc.9", - "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.0.0-rc.9.tgz", - "integrity": "sha512-+6zoiF+RRyf5cdlFQP7nm58mq7+/2PFaY2DNQeD4B87N36JzfF/l9mdBkkmTvSYcYPE8tMh/o3cRlsx1ldLfog==", + "version": "1.0.0-rc.10", + "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.0.0-rc.10.tgz", + "integrity": "sha512-o1hYe8hLi1EY6jgPFyxQgQ1wcycX+qz8eEbVmot2hFkgUzPxy9+kF0u0NIQBeDq+Mko47AkaFFaChcvZa9UX9Q==", "cpu": [ "arm64" ], @@ -39292,9 +38426,9 @@ } }, "node_modules/vite/node_modules/@rolldown/binding-wasm32-wasi": { - "version": "1.0.0-rc.9", - "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.0.0-rc.9.tgz", - "integrity": "sha512-rgFN6sA/dyebil3YTlL2evvi/M+ivhfnyxec7AccTpRPccno/rPoNlqybEZQBkcbZu8Hy+eqNJCqfBR8P7Pg8g==", + "version": "1.0.0-rc.10", + "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.0.0-rc.10.tgz", + "integrity": "sha512-Ugv9o7qYJudqQO5Y5y2N2SOo6S4WiqiNOpuQyoPInnhVzCY+wi/GHltcLHypG9DEUYMB0iTB/huJrpadiAcNcA==", "cpu": [ "wasm32" ], @@ -39308,9 +38442,9 @@ } }, "node_modules/vite/node_modules/@rolldown/binding-win32-arm64-msvc": { - "version": "1.0.0-rc.9", - "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.0.0-rc.9.tgz", - "integrity": "sha512-lHVNUG/8nlF1IQk1C0Ci574qKYyty2goMiPlRqkC5R+3LkXDkL5Dhx8ytbxq35m+pkHVIvIxviD+TWLdfeuadA==", + "version": "1.0.0-rc.10", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.0.0-rc.10.tgz", + "integrity": "sha512-7UODQb4fQUNT/vmgDZBl3XOBAIOutP5R3O/rkxg0aLfEGQ4opbCgU5vOw/scPe4xOqBwL9fw7/RP1vAMZ6QlAQ==", "cpu": [ "arm64" ], @@ -39324,9 +38458,9 @@ } }, "node_modules/vite/node_modules/@rolldown/binding-win32-x64-msvc": { - "version": "1.0.0-rc.9", - "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.0.0-rc.9.tgz", - "integrity": "sha512-G0oA4+w1iY5AGi5HcDTxWsoxF509hrFIPB2rduV5aDqS9FtDg1CAfa7V34qImbjfhIcA8C+RekocJZA96EarwQ==", + "version": "1.0.0-rc.10", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.0.0-rc.10.tgz", + "integrity": "sha512-PYxKHMVHOb5NJuDL53vBUl1VwUjymDcYI6rzpIni0C9+9mTiJedvUxSk7/RPp7OOAm3v+EjgMu9bIy3N6b408w==", "cpu": [ "x64" ], @@ -39340,9 +38474,9 @@ } }, "node_modules/vite/node_modules/@rolldown/pluginutils": { - "version": "1.0.0-rc.9", - "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-rc.9.tgz", - "integrity": "sha512-w6oiRWgEBl04QkFZgmW+jnU1EC9b57Oihi2ot3HNWIQRqgHp5PnYDia5iZ5FF7rpa4EQdiqMDXjlqKGXBhsoXw==", + "version": "1.0.0-rc.10", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-rc.10.tgz", + "integrity": "sha512-UkVDEFk1w3mveXeKgaTuYfKWtPbvgck1dT8TUG3bnccrH0XtLTuAyfCoks4Q/M5ZGToSVJTIQYCzy2g/atAOeg==", "license": "MIT" }, "node_modules/vite/node_modules/fsevents": { @@ -39359,255 +38493,6 @@ "node": "^8.16.0 || ^10.6.0 || >=11.0.0" } }, - "node_modules/vite/node_modules/lightningcss": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.32.0.tgz", - "integrity": "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==", - "license": "MPL-2.0", - "dependencies": { - "detect-libc": "^2.0.3" - }, - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - }, - "optionalDependencies": { - "lightningcss-android-arm64": "1.32.0", - "lightningcss-darwin-arm64": "1.32.0", - "lightningcss-darwin-x64": "1.32.0", - "lightningcss-freebsd-x64": "1.32.0", - "lightningcss-linux-arm-gnueabihf": "1.32.0", - "lightningcss-linux-arm64-gnu": "1.32.0", - "lightningcss-linux-arm64-musl": "1.32.0", - "lightningcss-linux-x64-gnu": "1.32.0", - "lightningcss-linux-x64-musl": "1.32.0", - "lightningcss-win32-arm64-msvc": "1.32.0", - "lightningcss-win32-x64-msvc": "1.32.0" - } - }, - "node_modules/vite/node_modules/lightningcss-android-arm64": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.32.0.tgz", - "integrity": "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==", - "cpu": [ - "arm64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/vite/node_modules/lightningcss-darwin-arm64": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.32.0.tgz", - "integrity": "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==", - "cpu": [ - "arm64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/vite/node_modules/lightningcss-darwin-x64": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.32.0.tgz", - "integrity": "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==", - "cpu": [ - "x64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/vite/node_modules/lightningcss-freebsd-x64": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.32.0.tgz", - "integrity": "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==", - "cpu": [ - "x64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/vite/node_modules/lightningcss-linux-arm-gnueabihf": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.32.0.tgz", - "integrity": "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==", - "cpu": [ - "arm" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/vite/node_modules/lightningcss-linux-arm64-gnu": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.32.0.tgz", - "integrity": "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==", - "cpu": [ - "arm64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/vite/node_modules/lightningcss-linux-arm64-musl": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.32.0.tgz", - "integrity": "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==", - "cpu": [ - "arm64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/vite/node_modules/lightningcss-linux-x64-gnu": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.32.0.tgz", - "integrity": "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==", - "cpu": [ - "x64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/vite/node_modules/lightningcss-linux-x64-musl": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.32.0.tgz", - "integrity": "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==", - "cpu": [ - "x64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/vite/node_modules/lightningcss-win32-arm64-msvc": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.32.0.tgz", - "integrity": "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==", - "cpu": [ - "arm64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/vite/node_modules/lightningcss-win32-x64-msvc": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.32.0.tgz", - "integrity": "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==", - "cpu": [ - "x64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, "node_modules/vite/node_modules/picomatch": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", @@ -39621,13 +38506,13 @@ } }, "node_modules/vite/node_modules/rolldown": { - "version": "1.0.0-rc.9", - "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.0.0-rc.9.tgz", - "integrity": "sha512-9EbgWge7ZH+yqb4d2EnELAntgPTWbfL8ajiTW+SyhJEC4qhBbkCKbqFV4Ge4zmu5ziQuVbWxb/XwLZ+RIO7E8Q==", + "version": "1.0.0-rc.10", + "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.0.0-rc.10.tgz", + "integrity": "sha512-q7j6vvarRFmKpgJUT8HCAUljkgzEp4LAhPlJUvQhA5LA1SUL36s5QCysMutErzL3EbNOZOkoziSx9iZC4FddKA==", "license": "MIT", "dependencies": { - "@oxc-project/types": "=0.115.0", - "@rolldown/pluginutils": "1.0.0-rc.9" + "@oxc-project/types": "=0.120.0", + "@rolldown/pluginutils": "1.0.0-rc.10" }, "bin": { "rolldown": "bin/cli.mjs" @@ -39636,21 +38521,21 @@ "node": "^20.19.0 || >=22.12.0" }, "optionalDependencies": { - "@rolldown/binding-android-arm64": "1.0.0-rc.9", - "@rolldown/binding-darwin-arm64": "1.0.0-rc.9", - "@rolldown/binding-darwin-x64": "1.0.0-rc.9", - "@rolldown/binding-freebsd-x64": "1.0.0-rc.9", - "@rolldown/binding-linux-arm-gnueabihf": "1.0.0-rc.9", - "@rolldown/binding-linux-arm64-gnu": "1.0.0-rc.9", - "@rolldown/binding-linux-arm64-musl": "1.0.0-rc.9", - "@rolldown/binding-linux-ppc64-gnu": "1.0.0-rc.9", - "@rolldown/binding-linux-s390x-gnu": "1.0.0-rc.9", - "@rolldown/binding-linux-x64-gnu": "1.0.0-rc.9", - "@rolldown/binding-linux-x64-musl": "1.0.0-rc.9", - "@rolldown/binding-openharmony-arm64": "1.0.0-rc.9", - "@rolldown/binding-wasm32-wasi": "1.0.0-rc.9", - "@rolldown/binding-win32-arm64-msvc": "1.0.0-rc.9", - "@rolldown/binding-win32-x64-msvc": "1.0.0-rc.9" + "@rolldown/binding-android-arm64": "1.0.0-rc.10", + "@rolldown/binding-darwin-arm64": "1.0.0-rc.10", + "@rolldown/binding-darwin-x64": "1.0.0-rc.10", + "@rolldown/binding-freebsd-x64": "1.0.0-rc.10", + "@rolldown/binding-linux-arm-gnueabihf": "1.0.0-rc.10", + "@rolldown/binding-linux-arm64-gnu": "1.0.0-rc.10", + "@rolldown/binding-linux-arm64-musl": "1.0.0-rc.10", + "@rolldown/binding-linux-ppc64-gnu": "1.0.0-rc.10", + "@rolldown/binding-linux-s390x-gnu": "1.0.0-rc.10", + "@rolldown/binding-linux-x64-gnu": "1.0.0-rc.10", + "@rolldown/binding-linux-x64-musl": "1.0.0-rc.10", + "@rolldown/binding-openharmony-arm64": "1.0.0-rc.10", + "@rolldown/binding-wasm32-wasi": "1.0.0-rc.10", + "@rolldown/binding-win32-arm64-msvc": "1.0.0-rc.10", + "@rolldown/binding-win32-x64-msvc": "1.0.0-rc.10" } }, "node_modules/vitefu": { diff --git a/package.json b/package.json index b17f6b44..4681de38 100644 --- a/package.json +++ b/package.json @@ -73,7 +73,7 @@ "@astrojs/preact": "5.0.2", "@astrojs/rss": "4.0.17", "@astrojs/sitemap": "^3.7.1", - "@astrojs/vercel": "^10.0.1", + "@astrojs/vercel": "^10.0.2", "@axe-core/playwright": "^4.11.1", "@eslint-community/eslint-plugin-eslint-comments": "^4.7.1", "@eslint/js": "10.0.1", @@ -87,8 +87,8 @@ "@playwright/browser-chromium": "^1.58.2", "@playwright/test": "1.58.2", "@semantic-ui/astro-lit": "^5.3.0", - "@sentry/astro": "^10.44.0", - "@sentry/browser": "^10.44.0", + "@sentry/astro": "^10.45.0", + "@sentry/browser": "^10.45.0", "@shikijs/transformers": "^4.0.2", "@tailwindcss/forms": "0.5.11", "@tailwindcss/typography": "0.5.19", @@ -105,7 +105,7 @@ "@types/glidejs__glide": "^3.6.6", "@types/hast": "^3.0.4", "@types/js-cookie": "^3.0.6", - "@types/jsdom": "^28.0.0", + "@types/jsdom": "^28.0.1", "@types/node": "^25.5.0", "@types/nodemailer": "^7.0.11", "@types/pubsub-js": "^1.8.6", @@ -129,11 +129,11 @@ "@vitest/coverage-v8": "^4.1.0", "@webcomponents/template-shadowroot": "^0.2.1", "alex": "^11.0.1", - "astro": "6.0.6", + "astro": "6.0.7", "astro-link-validator": "github:rodgtr1/astro-link-validator", "astro-og-canvas": "^0.10.1", "astro-vtbot": "^2.1.12", - "baseline-browser-mapping": "^2.10.8", + "baseline-browser-mapping": "^2.10.9", "canvas-confetti": "^1.9.4", "confusing-browser-globals": "1.0.11", "cross-env": "^10.1.0", @@ -212,7 +212,7 @@ "sharp": "^0.34.5", "shiki": "^4.0.2", "space-separated-tokens": "^2.0.2", - "stylelint": "^17.4.0", + "stylelint": "^17.5.0", "stylelint-config-standard": "^40.0.0", "stylelint-declaration-block-no-ignored-properties": "3.0.0", "stylelint-order": "8.1.1", @@ -230,7 +230,7 @@ "unist-util-is": "^6.0.1", "unist-util-visit": "^5.1.0", "uuid": "^13.0.0", - "vercel": "^50.33.1", + "vercel": "^50.34.2", "vite": "^8.0.1", "vitest": "4.1.0", "vitest-axe": "0.1.0", diff --git a/src/components/List/server/selectors.ts b/src/components/List/server/selectors.ts new file mode 100644 index 00000000..4f1fb830 --- /dev/null +++ b/src/components/List/server/selectors.ts @@ -0,0 +1,7 @@ +import { isType1Element } from '@components/scripts/assertions/elements' + +export const queryListItemElements = (context: ParentNode): Element[] => { + return Array.from(context.querySelectorAll('wsb-list-item')).filter((element): element is Element => { + return isType1Element(element) && element.tagName.toLowerCase() === 'wsb-list-item' + }) +} \ No newline at end of file diff --git a/src/components/List/server/slotItems.ts b/src/components/List/server/slotItems.ts index 0757e543..e2ae66cf 100644 --- a/src/components/List/server/slotItems.ts +++ b/src/components/List/server/slotItems.ts @@ -1,8 +1,17 @@ import { JSDOM } from 'jsdom' import { BuildError } from '@lib/errors/BuildError' -import type { Props as ListProps } from '@components/List/index.astro' +import { queryListItemElements } from '@components/List/server/selectors' -type ListItemShape = NonNullable[number] +type ListItemShape = { + title?: string + lead?: string + text: string + link?: string + icon?: string + color?: string + inverseColor?: string + bgColor?: string +} const unsupportedRichSlotVariants = new Set([ 'plain-icon-list', @@ -28,7 +37,7 @@ export function getListItemsFromSlotMarkup(markup: string, variant: string): Lis } const document = new JSDOM(`${markup}`).window.document - const listItemElements = Array.from(document.body.querySelectorAll('wsb-list-item')) + const listItemElements = queryListItemElements(document.body) if (listItemElements.length === 0) { throw new BuildError( @@ -37,8 +46,12 @@ export function getListItemsFromSlotMarkup(markup: string, variant: string): Lis ) } - return listItemElements.map((element) => ({ - lead: element.getAttribute('data-lead') ?? undefined, - text: element.innerHTML.trim(), - })) + return listItemElements.map((element) => { + const lead = element.getAttribute('data-lead') + + return { + ...(lead ? { lead } : {}), + text: element.innerHTML.trim(), + } + }) } \ No newline at end of file diff --git a/src/components/Social/Highlighter/index.css b/src/components/Social/Highlighter/index.css index b5b85e38..f1282672 100644 --- a/src/components/Social/Highlighter/index.css +++ b/src/components/Social/Highlighter/index.css @@ -128,8 +128,8 @@ highlighter-element .share-dialog__arrow { bottom: -0.375rem; height: 0.75rem; left: 50%; - position: absolute; pointer-events: none; + position: absolute; transform: translateX(-50%) rotate(45deg); width: 0.75rem; } diff --git a/src/components/Troubleshooter/__tests__/index.spec.ts b/src/components/Troubleshooter/__tests__/index.spec.ts index 3db695c1..72734405 100644 --- a/src/components/Troubleshooter/__tests__/index.spec.ts +++ b/src/components/Troubleshooter/__tests__/index.spec.ts @@ -17,7 +17,7 @@ describe('Troubleshooter (Astro)', () => { await withJsdomEnvironment(async ({ window }) => { window.document.body.innerHTML = renderedHtml - const region = window.document.querySelector('section[role="region"][aria-label="Troubleshooting"]') + const region = window.document.querySelector('section[aria-label="Troubleshooting"]') expect(region).toBeTruthy() const visibleHeading = region?.querySelector('h3') diff --git a/src/components/Troubleshooter/index.astro b/src/components/Troubleshooter/index.astro index cdd42fb3..3ac806c3 100644 --- a/src/components/Troubleshooter/index.astro +++ b/src/components/Troubleshooter/index.astro @@ -50,7 +50,6 @@ function getSectionClassNames(color: string) {