From 302c7bedf08a1b221ed5a1f98e4a5a9f4b8cb248 Mon Sep 17 00:00:00 2001 From: Rene Cannao Date: Tue, 24 Mar 2026 15:35:15 +0000 Subject: [PATCH 01/15] docs: add Phase 3 PostgreSQL provider design spec Covers provider interface changes, deb extraction, single sandbox, streaming replication, ProxySQL+PostgreSQL wiring, cross-database topology constraints, and testing strategy. --- ...03-24-phase3-postgresql-provider-design.md | 346 ++++++++++++++++++ 1 file changed, 346 insertions(+) create mode 100644 docs/superpowers/specs/2026-03-24-phase3-postgresql-provider-design.md diff --git a/docs/superpowers/specs/2026-03-24-phase3-postgresql-provider-design.md b/docs/superpowers/specs/2026-03-24-phase3-postgresql-provider-design.md new file mode 100644 index 0000000..cb562b8 --- /dev/null +++ b/docs/superpowers/specs/2026-03-24-phase3-postgresql-provider-design.md @@ -0,0 +1,346 @@ +# Phase 3 — PostgreSQL Provider Design + +**Date:** 2026-03-24 +**Author:** Rene (ProxySQL) +**Status:** Draft +**Prerequisite:** Phase 2b complete (provider interface, MySQL/ProxySQL providers) + +## Context + +dbdeployer's provider architecture (Phase 2) introduced a `Provider` interface with MySQL and ProxySQL implementations. Phase 3 validates that this architecture scales to a fundamentally different database system — PostgreSQL — where initialization, configuration, replication, and binary management all differ significantly from MySQL. + +**Primary motivation:** Enable ProxySQL protocol compatibility testing against PostgreSQL backends, and prove the provider model generalizes beyond MySQL-family databases. + +## Scope + +- PostgreSQL provider: binary management (deb extraction), single sandbox, lifecycle +- Streaming replication topology +- Cross-database topology constraints and validation +- ProxySQL + PostgreSQL backend wiring +- Unit tests from day one; integration tests written but CI-gated as manual + +## Provider Interface Changes + +Two methods added to the `Provider` interface: + +```go +type Provider interface { + // ... existing methods ... + + // SupportedTopologies returns which topology types this provider can deploy. + // The cmd layer validates against this before attempting deployment. + SupportedTopologies() []string + + // CreateReplica creates a replica from a running primary instance. + // Returns ErrNotSupported if the provider doesn't support replication. + // Called by the topology layer after the primary is started. + CreateReplica(primary SandboxInfo, config SandboxConfig) (*SandboxInfo, error) +} +``` + +**Per-provider topology support:** + +| Provider | Supported Topologies | +|------------|---------------------------------------------------------------------------| +| mysql | single, multiple, replication, group, fan-in, all-masters, ndb, pxc | +| proxysql | single | +| postgresql | single, multiple, replication | + +**MySQL provider** returns the full topology list from `SupportedTopologies()` — these topologies are served by the legacy `sandbox` package, not through the provider interface's `CreateSandbox`/`CreateReplica` methods. The topology list is accurate to what dbdeployer can deploy; it just flows through the old code path. `CreateReplica` returns `ErrNotSupported`. + +**ProxySQL provider** returns `["single"]` and `ErrNotSupported` from `CreateReplica`. + +**Binary resolution in `CreateReplica`:** The replica's `config.Version` is used to resolve binaries internally via `FindBinary(config.Version)`. This avoids needing to pass basedir through `SandboxInfo`. + +### Cleanup on Failure + +If `CreateSandbox` or `CreateReplica` fails partway through (e.g., initdb succeeds but config generation fails), the method cleans up its own sandbox directory before returning the error. The caller is not responsible for partial cleanup within a single sandbox. + +For multi-node replication topologies, if replica N fails, the topology layer is responsible for stopping and destroying the primary and any previously created replicas. This matches the existing MySQL behavior where partial topology failures trigger full cleanup. + +## Binary Management — Deb Extraction + +PostgreSQL does not distribute pre-compiled tarballs. Binaries are extracted from `.deb` packages. + +### Usage + +```bash +# User downloads debs (familiar apt workflow) +apt-get download postgresql-16 postgresql-client-16 + +# dbdeployer extracts and lays out binaries +dbdeployer unpack --provider=postgresql postgresql-16_16.13.deb postgresql-client-16_16.13.deb +``` + +### Extraction Flow + +1. Validate both debs are provided (server + client) +2. Extract each via `dpkg-deb -x` to a temp directory +3. Copy `usr/lib/postgresql/16/bin/` → `~/opt/postgresql/16.13/bin/` +4. Copy `usr/lib/postgresql/16/lib/` → `~/opt/postgresql/16.13/lib/` +5. Copy `usr/share/postgresql/16/` → `~/opt/postgresql/16.13/share/` +6. Validate required binaries exist: `postgres`, `initdb`, `pg_ctl`, `psql`, `pg_basebackup` +7. Clean up temp directory + +### Version Detection + +Extracted from deb filename pattern `postgresql-NN_X.Y-*`. Overridable via `--version=16.13`. + +### Target Layout + +``` +~/opt/postgresql/16.13/ + bin/ (postgres, initdb, pg_ctl, psql, pg_basebackup, pg_dump, ...) + lib/ (shared libraries) + share/ (timezone data, extension SQL — required by initdb) +``` + +**Implementation:** `providers/postgresql/unpack.go`, called from `cmd/unpack.go` when `--provider=postgresql`. + +## PostgreSQL Provider — Single Sandbox + +### Registration + +Same pattern as ProxySQL: `Register()` called from `cmd/root.go` init. + +### Port Allocation + +`DefaultPorts()` returns `{BasePort: 15000, PortsPerInstance: 1}`. + +Version-to-port formula: `BasePort + major * 100 + minor`. Examples: +- `16.13` → `15000 + 1600 + 13` = `16613` +- `16.3` → `15000 + 1600 + 3` = `16603` +- `17.1` → `15000 + 1700 + 1` = `16701` +- `17.10` → `15000 + 1700 + 10` = `16710` + +Single port per instance (PostgreSQL uses one port for all connections). + +### Version Validation + +`ValidateVersion()` accepts exactly `major.minor` format where both parts are integers. Major must be >= 12 (oldest supported PostgreSQL with streaming replication via `pg_basebackup -R`). Three-part versions like `16.13.1` are rejected (PostgreSQL does not use them). + +### FindBinary + +Looks in `~/opt/postgresql//bin/postgres`. Provider determines base path (`~/opt/postgresql/`); `--basedir` overrides for custom locations. + +### CreateSandbox Flow + +1. **Create log directory:** `mkdir -p /data/log` + +2. **Init database:** + ```bash + initdb -D /data --auth=trust --username=postgres + ``` + Note: `initdb` locates `share/` data relative to its own binary path (`../share/`). Since the extraction layout places `share/` as a sibling of `bin/`, no `-L` flag is needed. If the layout ever changes, `-L /share` can be added as a fallback. + +3. **Generate `postgresql.conf`:** + ``` + port = + listen_addresses = '127.0.0.1' + unix_socket_directories = '/data' + logging_collector = on + log_directory = '/data/log' + ``` + +4. **Generate `pg_hba.conf`** (overwrite initdb default): + ``` + local all all trust + host all all 127.0.0.1/32 trust + host all all ::1/128 trust + ``` + +5. **Write lifecycle scripts** (inline generation, like ProxySQL): + - `start` — `pg_ctl -D -l /postgresql.log start` + - `stop` — `pg_ctl -D stop -m fast` + - `status` — `pg_ctl -D status` + - `restart` — `pg_ctl -D -l /postgresql.log restart` + - `use` — `psql -h 127.0.0.1 -p -U postgres` + - `clear` — stop + remove data directory + re-init + +6. **Set environment in all scripts:** + - `LD_LIBRARY_PATH=/lib/` (extracted debs need this for shared libraries) + - Unset `PGDATA`, `PGPORT`, `PGHOST`, `PGUSER`, `PGDATABASE` to prevent environment contamination from the user's shell + +7. **Return `SandboxInfo`** with dir, port. `Socket` field is left empty (lifecycle scripts use TCP via `127.0.0.1`, matching the ProxySQL provider pattern). The unix socket exists at `/data/.s.PGSQL.` but is not the primary connection method. + +### Multiple Topology + +`dbdeployer deploy multiple 16.13 --provider=postgresql` creates N independent PostgreSQL instances using `CreateSandbox` with sequential port allocation. No additional configuration beyond what single provides — each instance is standalone with no replication relationship. + +## PostgreSQL Replication + +### CreateReplica Flow + +1. **No `initdb`** — replica data comes from the running primary via `pg_basebackup`: + ```bash + pg_basebackup -h 127.0.0.1 -p -U postgres -D /data -Fp -Xs -R + ``` + - `-Fp` = plain format + - `-Xs` = stream WAL during backup + - `-R` = auto-create `standby.signal` + write `primary_conninfo` to `postgresql.auto.conf` + +2. **Modify replica's `postgresql.conf`:** + - Change `port` to replica's assigned port + - Change `unix_socket_directories` to replica's sandbox dir + +3. **Write lifecycle scripts** — same as single sandbox with replica's port + +4. **Start replica** — `pg_ctl -D -l /postgresql.log start` + +### Primary-Side Configuration + +When replication is intended (`config.Options["replication"] = "true"`), `CreateSandbox` adds: + +**postgresql.conf:** +``` +wal_level = replica +max_wal_senders = 10 +hot_standby = on +``` + +**pg_hba.conf:** +``` +host replication all 127.0.0.1/32 trust +``` + +### Topology Layer Flow + +For `dbdeployer deploy replication 16.13 --provider=postgresql`: + +1. `CreateSandbox()` for primary with replication options +2. `StartSandbox()` for primary — **must be running before replicas** +3. For each replica: `CreateReplica(primaryInfo, replicaConfig)` — **sequential, not concurrent** +4. Each replica starts automatically as part of `CreateReplica` + +### Monitoring Scripts + +Generated in the topology directory: + +**`check_replication`** — connects to primary, shows connected replicas: +```bash +psql -h 127.0.0.1 -p -U postgres -c \ + "SELECT client_addr, state, sent_lsn, write_lsn, flush_lsn, replay_lsn FROM pg_stat_replication;" +``` + +**`check_recovery`** — connects to each replica, verifies standby status: +```bash +# For each replica: +psql -h 127.0.0.1 -p -U postgres -c "SELECT pg_is_in_recovery();" +``` + +## ProxySQL + PostgreSQL Wiring + +### Triggering + +```bash +dbdeployer deploy replication 16.13 --provider=postgresql --with-proxysql +``` + +### Config Generation + +ProxySQL supports PostgreSQL backends natively. The backend provider type is passed via: +```go +config.Options["backend_provider"] = "postgresql" +``` + +The ProxySQL config generator (`providers/proxysql/config.go`) branches: + +| Backend Provider | Config Blocks | +|------------------|-------------------------------------------------------| +| mysql (default) | `mysql_servers`, `mysql_users`, `mysql_variables` | +| postgresql | `pgsql_servers`, `pgsql_users`, `pgsql_variables` | + +### End-to-End Flow + +1. Deploy PostgreSQL primary + replicas (streaming replication) +2. Deploy ProxySQL with `pgsql_servers` pointing to primary (HG 0) + replicas (HG 1) +3. Generate `use_proxy` script: `psql -h 127.0.0.1 -p -U postgres` + +### Port Allocation + +ProxySQL admin port stays on its usual range (6032+). The frontend port uses the next consecutive port, same as today. The ProxySQL `ProxySQLConfig` struct's `MySQLPort` field is reused for the frontend listener port regardless of backend type — the field name is a misnomer but changing it would break the MySQL path. The `use_proxy` script uses `psql` instead of `mysql` when `backend_provider` is `postgresql`. + +## Cross-Database Topology Constraints + +### Topology Validation + +Cmd layer validates provider supports the requested topology before any sandbox creation: + +``` +$ dbdeployer deploy group 16.13 --provider=postgresql +Error: provider "postgresql" does not support topology "group" +Supported topologies: single, multiple, replication +``` + +### Flavor Validation + +`--flavor` is MySQL-specific. Rejected when `--provider` is not `mysql`: + +``` +$ dbdeployer deploy single 16.13 --provider=postgresql --flavor=ndb +Error: --flavor is only valid with --provider=mysql +``` + +### Cross-Provider Wiring Validation + +Compatibility map determines which addons work with which providers: + +```go +var compatibleAddons = map[string][]string{ + "proxysql": {"mysql", "postgresql"}, + // future: "orchestrator": {"mysql"}, +} +``` + +``` +$ dbdeployer deploy single 16.13 --provider=postgresql --with-orchestrator +Error: --with-orchestrator is not compatible with provider "postgresql" +``` + +## Testing Strategy + +### Unit Tests (no binaries needed) + +- `providers/postgresql/postgresql_test.go` — `ValidateVersion()`, `DefaultPorts()`, `SupportedTopologies()`, port calculation, config generation (postgresql.conf, pg_hba.conf), script generation +- `providers/postgresql/unpack_test.go` — deb filename parsing, version extraction, required binary validation +- `providers/proxysql/config_test.go` — extend for PostgreSQL backend config (`pgsql_servers`/`pgsql_users`) +- `providers/provider_test.go` — extend for topology validation, flavor rejection, cross-provider compatibility +- Cmd-level tests — `--provider=postgresql --flavor=ndb` errors, unsupported topologies error + +### Integration Tests (`//go:build integration`) + +`providers/postgresql/integration_test.go`: +- Single sandbox: initdb → start → connect via psql → stop → destroy +- Replication: primary + 2 replicas → verify `pg_stat_replication` shows 2 senders → verify `pg_is_in_recovery() = true` +- With ProxySQL: replication + proxysql → connect through ProxySQL → verify routing +- Deb extraction: unpack real .deb files → verify binary layout + +### CI Follow-Up (tracked as GitHub issues) + +1. Add PostgreSQL deb caching to CI pipeline +2. Add PostgreSQL integration tests to CI matrix +3. Nightly topology tests for PostgreSQL replication + +Integration tests run locally until CI is set up. + +## File Structure + +``` +providers/postgresql/ + postgresql.go # Provider implementation (CreateSandbox, CreateReplica, lifecycle) + unpack.go # Deb extraction logic + config.go # postgresql.conf and pg_hba.conf generation + postgresql_test.go # Unit tests + unpack_test.go # Deb extraction unit tests + integration_test.go # Integration tests (build-tagged) +``` + +Modifications to existing files: +- `providers/provider.go` — add `SupportedTopologies()`, `CreateReplica()` to interface +- `providers/mysql/mysql.go` — implement new interface methods (return full topology list, ErrNotSupported for CreateReplica) +- `providers/proxysql/proxysql.go` — implement new interface methods +- `providers/proxysql/config.go` — PostgreSQL backend config generation +- `cmd/root.go` — register PostgreSQL provider +- `cmd/single.go`, `cmd/multiple.go`, `cmd/replication.go` — `--provider` flag, topology validation +- `cmd/unpack.go` — `--provider` flag for deb extraction +- `globals/globals.go` — PostgreSQL constants, flag labels From 6f20b954bf3289f474d0fe73f7d0bf8790998810 Mon Sep 17 00:00:00 2001 From: Rene Cannao Date: Tue, 24 Mar 2026 15:53:19 +0000 Subject: [PATCH 02/15] docs: add Phase 3 PostgreSQL provider implementation plan 13 tasks covering: provider interface changes, PostgreSQL core, config generation, CreateSandbox, deb extraction, replication, cmd layer routing, ProxySQL wiring, topology constraints, standalone command, integration tests, and CI issue tracking. --- .../2026-03-24-phase3-postgresql-provider.md | 2389 +++++++++++++++++ 1 file changed, 2389 insertions(+) create mode 100644 docs/superpowers/plans/2026-03-24-phase3-postgresql-provider.md diff --git a/docs/superpowers/plans/2026-03-24-phase3-postgresql-provider.md b/docs/superpowers/plans/2026-03-24-phase3-postgresql-provider.md new file mode 100644 index 0000000..bc4996a --- /dev/null +++ b/docs/superpowers/plans/2026-03-24-phase3-postgresql-provider.md @@ -0,0 +1,2389 @@ +# Phase 3 — PostgreSQL Provider Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** Add a PostgreSQL provider to dbdeployer supporting single sandbox, streaming replication, cross-database topology constraints, and ProxySQL+PostgreSQL backend wiring. + +**Architecture:** Extend the Provider interface with `SupportedTopologies()` and `CreateReplica()`. Implement a PostgreSQL provider that uses `initdb`/`pg_ctl`/`pg_basebackup` for sandbox lifecycle. Add deb extraction for binary management. Wire into existing cmd layer via `--provider` flag. Extend ProxySQL config generator for PostgreSQL backends. + +**Tech Stack:** Go, PostgreSQL CLI tools (initdb, pg_ctl, pg_basebackup, psql), dpkg-deb + +**Spec:** `docs/superpowers/specs/2026-03-24-phase3-postgresql-provider-design.md` + +--- + +## File Structure + +### New Files +- `providers/postgresql/postgresql.go` — Provider struct, registration, Name/ValidateVersion/DefaultPorts/FindBinary/StartSandbox/StopSandbox/SupportedTopologies/CreateReplica +- `providers/postgresql/sandbox.go` — CreateSandbox implementation (initdb, config gen, script gen) +- `providers/postgresql/config.go` — postgresql.conf and pg_hba.conf generation functions +- `providers/postgresql/scripts.go` — lifecycle script generation (start, stop, status, restart, use, clear) +- `providers/postgresql/unpack.go` — deb extraction logic +- `providers/postgresql/postgresql_test.go` — unit tests for provider methods +- `providers/postgresql/config_test.go` — unit tests for config generation +- `providers/postgresql/unpack_test.go` — unit tests for deb extraction +- `providers/postgresql/integration_test.go` — integration tests (build-tagged) +- `cmd/deploy_postgresql.go` — `dbdeployer deploy postgresql ` standalone command + +### Modified Files +- `providers/provider.go` — add `SupportedTopologies()`, `CreateReplica()`, `ErrNotSupported` +- `providers/provider_test.go` — update mock, add topology/validation tests +- `providers/mysql/mysql.go` — implement new interface methods +- `providers/proxysql/proxysql.go` — implement new interface methods +- `providers/proxysql/config.go` — add PostgreSQL backend config generation +- `providers/proxysql/proxysql_test.go` — update for new interface methods +- `providers/proxysql/config_test.go` — test PostgreSQL backend config +- `sandbox/proxysql_topology.go` — accept `backendProvider` parameter +- `cmd/root.go` — register PostgreSQL provider +- `cmd/single.go` — add `--provider` flag, route to provider +- `cmd/multiple.go` — add `--provider` flag, route to provider +- `cmd/replication.go` — add `--provider` flag, PostgreSQL replication flow +- `cmd/unpack.go` — add `--provider` flag for deb extraction +- `globals/globals.go` — PostgreSQL constants + +--- + +## Task 1: Extend Provider Interface + +**Files:** +- Modify: `providers/provider.go` +- Modify: `providers/provider_test.go` +- Modify: `providers/mysql/mysql.go` +- Modify: `providers/proxysql/proxysql.go` +- Modify: `providers/proxysql/proxysql_test.go` + +- [ ] **Step 1: Write failing test for SupportedTopologies on mock provider** + +In `providers/provider_test.go`, add `SupportedTopologies` and `CreateReplica` to `mockProvider`, then write a test: + +```go +func (m *mockProvider) SupportedTopologies() []string { + return []string{"single", "multiple"} +} +func (m *mockProvider) CreateReplica(primary SandboxInfo, config SandboxConfig) (*SandboxInfo, error) { + return nil, ErrNotSupported +} + +func TestErrNotSupported(t *testing.T) { + mock := &mockProvider{name: "test"} + _, err := mock.CreateReplica(SandboxInfo{}, SandboxConfig{}) + if err != ErrNotSupported { + t.Errorf("expected ErrNotSupported, got %v", err) + } +} + +func TestSupportedTopologies(t *testing.T) { + mock := &mockProvider{name: "test"} + topos := mock.SupportedTopologies() + if len(topos) != 2 || topos[0] != "single" { + t.Errorf("unexpected topologies: %v", topos) + } +} +``` + +- [ ] **Step 2: Run test to verify it fails** + +Run: `cd /data/rene/dbdeployer && go test ./providers/ -run TestErrNotSupported -v` +Expected: Compilation error — `ErrNotSupported` and `SupportedTopologies` not defined on interface. + +- [ ] **Step 3: Add interface methods and ErrNotSupported to provider.go** + +In `providers/provider.go`, add: + +```go +import ( + "errors" + "fmt" + "sort" +) + +var ErrNotSupported = errors.New("operation not supported by this provider") + +type Provider interface { + Name() string + ValidateVersion(version string) error + DefaultPorts() PortRange + FindBinary(version string) (string, error) + CreateSandbox(config SandboxConfig) (*SandboxInfo, error) + StartSandbox(dir string) error + StopSandbox(dir string) error + SupportedTopologies() []string + CreateReplica(primary SandboxInfo, config SandboxConfig) (*SandboxInfo, error) +} +``` + +- [ ] **Step 4: Update MySQLProvider to implement new methods** + +In `providers/mysql/mysql.go`, add: + +```go +func (p *MySQLProvider) SupportedTopologies() []string { + return []string{"single", "multiple", "replication", "group", "fan-in", "all-masters", "ndb", "pxc"} +} + +func (p *MySQLProvider) CreateReplica(primary providers.SandboxInfo, config providers.SandboxConfig) (*providers.SandboxInfo, error) { + return nil, providers.ErrNotSupported +} +``` + +- [ ] **Step 5: Update ProxySQLProvider to implement new methods** + +In `providers/proxysql/proxysql.go`, add: + +```go +func (p *ProxySQLProvider) SupportedTopologies() []string { + return []string{"single"} +} + +func (p *ProxySQLProvider) CreateReplica(primary providers.SandboxInfo, config providers.SandboxConfig) (*providers.SandboxInfo, error) { + return nil, providers.ErrNotSupported +} +``` + +- [ ] **Step 6: Run all provider tests to verify they pass** + +Run: `cd /data/rene/dbdeployer && go test ./providers/... -v` +Expected: All tests pass, including the new ones and existing ProxySQL tests. + +- [ ] **Step 7: Commit** + +```bash +git add providers/provider.go providers/provider_test.go providers/mysql/mysql.go providers/proxysql/proxysql.go +git commit -m "feat: extend Provider interface with SupportedTopologies and CreateReplica" +``` + +--- + +## Task 2: PostgreSQL Provider — Core Structure and Version Validation + +**Files:** +- Create: `providers/postgresql/postgresql.go` +- Create: `providers/postgresql/postgresql_test.go` + +- [ ] **Step 1: Write failing tests for PostgreSQL provider basics** + +Create `providers/postgresql/postgresql_test.go`: + +```go +package postgresql + +import ( + "testing" + + "github.com/ProxySQL/dbdeployer/providers" +) + +func TestPostgreSQLProviderName(t *testing.T) { + p := NewPostgreSQLProvider() + if p.Name() != "postgresql" { + t.Errorf("expected 'postgresql', got %q", p.Name()) + } +} + +func TestPostgreSQLProviderValidateVersion(t *testing.T) { + p := NewPostgreSQLProvider() + tests := []struct { + version string + wantErr bool + }{ + {"16.13", false}, + {"17.1", false}, + {"12.0", false}, + {"11.5", true}, // major < 12 + {"16", true}, // missing minor + {"16.13.1", true}, // three parts + {"abc", true}, + {"", true}, + } + for _, tt := range tests { + err := p.ValidateVersion(tt.version) + if (err != nil) != tt.wantErr { + t.Errorf("ValidateVersion(%q) error = %v, wantErr %v", tt.version, err, tt.wantErr) + } + } +} + +func TestPostgreSQLProviderDefaultPorts(t *testing.T) { + p := NewPostgreSQLProvider() + ports := p.DefaultPorts() + if ports.BasePort != 15000 { + t.Errorf("expected BasePort 15000, got %d", ports.BasePort) + } + if ports.PortsPerInstance != 1 { + t.Errorf("expected PortsPerInstance 1, got %d", ports.PortsPerInstance) + } +} + +func TestPostgreSQLProviderSupportedTopologies(t *testing.T) { + p := NewPostgreSQLProvider() + topos := p.SupportedTopologies() + expected := map[string]bool{"single": true, "multiple": true, "replication": true} + if len(topos) != len(expected) { + t.Fatalf("expected %d topologies, got %d: %v", len(expected), len(topos), topos) + } + for _, topo := range topos { + if !expected[topo] { + t.Errorf("unexpected topology %q", topo) + } + } +} + +func TestPostgreSQLVersionToPort(t *testing.T) { + tests := []struct { + version string + expected int + }{ + {"16.13", 16613}, + {"16.3", 16603}, + {"17.1", 16701}, + {"17.10", 16710}, + {"12.0", 16200}, + } + for _, tt := range tests { + port, err := VersionToPort(tt.version) + if err != nil { + t.Errorf("VersionToPort(%q) unexpected error: %v", tt.version, err) + continue + } + if port != tt.expected { + t.Errorf("VersionToPort(%q) = %d, want %d", tt.version, port, tt.expected) + } + } +} + +func TestPostgreSQLProviderRegister(t *testing.T) { + reg := providers.NewRegistry() + if err := Register(reg); err != nil { + t.Fatalf("Register failed: %v", err) + } + p, err := reg.Get("postgresql") + if err != nil { + t.Fatalf("Get failed: %v", err) + } + if p.Name() != "postgresql" { + t.Errorf("expected 'postgresql', got %q", p.Name()) + } +} +``` + +- [ ] **Step 2: Run tests to verify they fail** + +Run: `cd /data/rene/dbdeployer && go test ./providers/postgresql/ -v` +Expected: Compilation error — package doesn't exist. + +- [ ] **Step 3: Implement PostgreSQL provider core** + +Create `providers/postgresql/postgresql.go`: + +```go +package postgresql + +import ( + "fmt" + "os" + "os/exec" + "path/filepath" + "strconv" + "strings" + + "github.com/ProxySQL/dbdeployer/providers" +) + +const ProviderName = "postgresql" + +type PostgreSQLProvider struct{} + +func NewPostgreSQLProvider() *PostgreSQLProvider { return &PostgreSQLProvider{} } + +func (p *PostgreSQLProvider) Name() string { return ProviderName } + +func (p *PostgreSQLProvider) ValidateVersion(version string) error { + parts := strings.Split(version, ".") + if len(parts) != 2 { + return fmt.Errorf("invalid PostgreSQL version format: %q (expected major.minor, e.g. 16.13)", version) + } + major, err := strconv.Atoi(parts[0]) + if err != nil { + return fmt.Errorf("invalid PostgreSQL major version %q: %w", parts[0], err) + } + if major < 12 { + return fmt.Errorf("PostgreSQL major version must be >= 12, got %d", major) + } + if _, err := strconv.Atoi(parts[1]); err != nil { + return fmt.Errorf("invalid PostgreSQL minor version %q: %w", parts[1], err) + } + return nil +} + +func (p *PostgreSQLProvider) DefaultPorts() providers.PortRange { + return providers.PortRange{BasePort: 15000, PortsPerInstance: 1} +} + +func (p *PostgreSQLProvider) SupportedTopologies() []string { + return []string{"single", "multiple", "replication"} +} + +// VersionToPort converts a PostgreSQL version to a port number. +// Formula: BasePort + major*100 + minor +// Example: 16.13 -> 15000 + 1600 + 13 = 16613 +func VersionToPort(version string) (int, error) { + parts := strings.Split(version, ".") + if len(parts) != 2 { + return 0, fmt.Errorf("invalid version format: %q", version) + } + major, err := strconv.Atoi(parts[0]) + if err != nil { + return 0, err + } + minor, err := strconv.Atoi(parts[1]) + if err != nil { + return 0, err + } + return 15000 + major*100 + minor, nil +} + +// FindBinary returns the path to the postgres binary for the given version. +// Looks in ~/opt/postgresql//bin/postgres by default. +func (p *PostgreSQLProvider) FindBinary(version string) (string, error) { + home, err := os.UserHomeDir() + if err != nil { + return "", fmt.Errorf("cannot determine home directory: %w", err) + } + binPath := filepath.Join(home, "opt", "postgresql", version, "bin", "postgres") + if _, err := os.Stat(binPath); err != nil { + return "", fmt.Errorf("PostgreSQL binary not found at %s: %w", binPath, err) + } + return binPath, nil +} + +// basedirFromVersion returns the base directory for a PostgreSQL version. +func basedirFromVersion(version string) (string, error) { + home, err := os.UserHomeDir() + if err != nil { + return "", fmt.Errorf("cannot determine home directory: %w", err) + } + return filepath.Join(home, "opt", "postgresql", version), nil +} + +func (p *PostgreSQLProvider) StartSandbox(dir string) error { + cmd := exec.Command("bash", filepath.Join(dir, "start")) + output, err := cmd.CombinedOutput() + if err != nil { + return fmt.Errorf("start failed: %s: %w", string(output), err) + } + return nil +} + +func (p *PostgreSQLProvider) StopSandbox(dir string) error { + cmd := exec.Command("bash", filepath.Join(dir, "stop")) + output, err := cmd.CombinedOutput() + if err != nil { + return fmt.Errorf("stop failed: %s: %w", string(output), err) + } + return nil +} + +func Register(reg *providers.Registry) error { + return reg.Register(NewPostgreSQLProvider()) +} +``` + +Note: `CreateSandbox` and `CreateReplica` are implemented in Task 4 and Task 6 respectively, in separate files. Add stubs for now: + +```go +func (p *PostgreSQLProvider) CreateSandbox(config providers.SandboxConfig) (*providers.SandboxInfo, error) { + return nil, fmt.Errorf("PostgreSQLProvider.CreateSandbox: not yet implemented") +} + +func (p *PostgreSQLProvider) CreateReplica(primary providers.SandboxInfo, config providers.SandboxConfig) (*providers.SandboxInfo, error) { + return nil, fmt.Errorf("PostgreSQLProvider.CreateReplica: not yet implemented") +} +``` + +- [ ] **Step 4: Run tests to verify they pass** + +Run: `cd /data/rene/dbdeployer && go test ./providers/postgresql/ -v` +Expected: All tests pass. + +- [ ] **Step 5: Commit** + +```bash +git add providers/postgresql/postgresql.go providers/postgresql/postgresql_test.go +git commit -m "feat: add PostgreSQL provider core structure and version validation" +``` + +--- + +## Task 3: PostgreSQL Config Generation + +**Files:** +- Create: `providers/postgresql/config.go` +- Create: `providers/postgresql/config_test.go` + +- [ ] **Step 1: Write failing tests for config generation** + +Create `providers/postgresql/config_test.go`: + +```go +package postgresql + +import ( + "strings" + "testing" +) + +func TestGeneratePostgresqlConf(t *testing.T) { + conf := GeneratePostgresqlConf(PostgresqlConfOptions{ + Port: 5433, + ListenAddresses: "127.0.0.1", + UnixSocketDir: "/tmp/sandbox/data", + LogDir: "/tmp/sandbox/data/log", + Replication: false, + }) + if !strings.Contains(conf, "port = 5433") { + t.Error("missing port setting") + } + if !strings.Contains(conf, "listen_addresses = '127.0.0.1'") { + t.Error("missing listen_addresses") + } + if !strings.Contains(conf, "unix_socket_directories = '/tmp/sandbox/data'") { + t.Error("missing unix_socket_directories") + } + if !strings.Contains(conf, "logging_collector = on") { + t.Error("missing logging_collector") + } + if strings.Contains(conf, "wal_level") { + t.Error("should not contain wal_level when replication is false") + } +} + +func TestGeneratePostgresqlConfWithReplication(t *testing.T) { + conf := GeneratePostgresqlConf(PostgresqlConfOptions{ + Port: 5433, + ListenAddresses: "127.0.0.1", + UnixSocketDir: "/tmp/sandbox/data", + LogDir: "/tmp/sandbox/data/log", + Replication: true, + }) + if !strings.Contains(conf, "wal_level = replica") { + t.Error("missing wal_level = replica") + } + if !strings.Contains(conf, "max_wal_senders = 10") { + t.Error("missing max_wal_senders") + } + if !strings.Contains(conf, "hot_standby = on") { + t.Error("missing hot_standby") + } +} + +func TestGeneratePgHbaConf(t *testing.T) { + conf := GeneratePgHbaConf(false) + if !strings.Contains(conf, "local all") { + t.Error("missing local all entry") + } + if !strings.Contains(conf, "host all") { + t.Error("missing host all entry") + } + if strings.Contains(conf, "replication") { + t.Error("should not contain replication when replication is false") + } +} + +func TestGeneratePgHbaConfWithReplication(t *testing.T) { + conf := GeneratePgHbaConf(true) + if !strings.Contains(conf, "host replication") { + t.Error("missing replication entry") + } +} +``` + +- [ ] **Step 2: Run tests to verify they fail** + +Run: `cd /data/rene/dbdeployer && go test ./providers/postgresql/ -run TestGenerate -v` +Expected: Compilation error — functions not defined. + +- [ ] **Step 3: Implement config generation** + +Create `providers/postgresql/config.go`: + +```go +package postgresql + +import ( + "fmt" + "strings" +) + +type PostgresqlConfOptions struct { + Port int + ListenAddresses string + UnixSocketDir string + LogDir string + Replication bool +} + +func GeneratePostgresqlConf(opts PostgresqlConfOptions) string { + var b strings.Builder + b.WriteString(fmt.Sprintf("port = %d\n", opts.Port)) + b.WriteString(fmt.Sprintf("listen_addresses = '%s'\n", opts.ListenAddresses)) + b.WriteString(fmt.Sprintf("unix_socket_directories = '%s'\n", opts.UnixSocketDir)) + b.WriteString("logging_collector = on\n") + b.WriteString(fmt.Sprintf("log_directory = '%s'\n", opts.LogDir)) + + if opts.Replication { + b.WriteString("\n# Replication settings\n") + b.WriteString("wal_level = replica\n") + b.WriteString("max_wal_senders = 10\n") + b.WriteString("hot_standby = on\n") + } + + return b.String() +} + +func GeneratePgHbaConf(replication bool) string { + var b strings.Builder + b.WriteString("# TYPE DATABASE USER ADDRESS METHOD\n") + b.WriteString("local all all trust\n") + b.WriteString("host all all 127.0.0.1/32 trust\n") + b.WriteString("host all all ::1/128 trust\n") + + if replication { + b.WriteString("host replication all 127.0.0.1/32 trust\n") + } + + return b.String() +} +``` + +- [ ] **Step 4: Run tests to verify they pass** + +Run: `cd /data/rene/dbdeployer && go test ./providers/postgresql/ -run TestGenerate -v` +Expected: All pass. + +- [ ] **Step 5: Commit** + +```bash +git add providers/postgresql/config.go providers/postgresql/config_test.go +git commit -m "feat: add PostgreSQL config generation (postgresql.conf, pg_hba.conf)" +``` + +--- + +## Task 4: PostgreSQL Script Generation and CreateSandbox + +**Files:** +- Create: `providers/postgresql/scripts.go` +- Create: `providers/postgresql/sandbox.go` +- Modify: `providers/postgresql/postgresql.go` (replace CreateSandbox stub) +- Modify: `providers/postgresql/postgresql_test.go` (add script tests) + +- [ ] **Step 1: Write failing tests for script generation** + +Add to `providers/postgresql/postgresql_test.go`: + +```go +func TestGenerateScripts(t *testing.T) { + opts := ScriptOptions{ + SandboxDir: "/tmp/pg_sandbox", + DataDir: "/tmp/pg_sandbox/data", + BinDir: "/opt/postgresql/16.13/bin", + LibDir: "/opt/postgresql/16.13/lib", + Port: 16613, + LogFile: "/tmp/pg_sandbox/postgresql.log", + } + scripts := GenerateScripts(opts) + + // Verify all expected scripts exist + expectedScripts := []string{"start", "stop", "status", "restart", "use", "clear"} + for _, name := range expectedScripts { + if _, ok := scripts[name]; !ok { + t.Errorf("missing script %q", name) + } + } + + // Verify start script contents + start := scripts["start"] + if !strings.Contains(start, "pg_ctl") { + t.Error("start script missing pg_ctl") + } + if !strings.Contains(start, "LD_LIBRARY_PATH") { + t.Error("start script missing LD_LIBRARY_PATH") + } + if !strings.Contains(start, "unset PGDATA") { + t.Error("start script missing PGDATA unset") + } + + // Verify use script + use := scripts["use"] + if !strings.Contains(use, "psql") { + t.Error("use script missing psql") + } + if !strings.Contains(use, "16613") { + t.Error("use script missing port") + } +} +``` + +- [ ] **Step 2: Run test to verify it fails** + +Run: `cd /data/rene/dbdeployer && go test ./providers/postgresql/ -run TestGenerateScripts -v` +Expected: Compilation error — `ScriptOptions` and `GenerateScripts` not defined. + +- [ ] **Step 3: Implement script generation** + +Create `providers/postgresql/scripts.go`: + +```go +package postgresql + +import "fmt" + +type ScriptOptions struct { + SandboxDir string + DataDir string + BinDir string + LibDir string + Port int + LogFile string +} + +const envPreamble = `#!/bin/bash +export LD_LIBRARY_PATH="%s" +unset PGDATA PGPORT PGHOST PGUSER PGDATABASE +` + +func GenerateScripts(opts ScriptOptions) map[string]string { + preamble := fmt.Sprintf(envPreamble, opts.LibDir) + + return map[string]string{ + "start": fmt.Sprintf("%s%s/pg_ctl -D %s -l %s start\n", + preamble, opts.BinDir, opts.DataDir, opts.LogFile), + + "stop": fmt.Sprintf("%s%s/pg_ctl -D %s stop -m fast\n", + preamble, opts.BinDir, opts.DataDir), + + "status": fmt.Sprintf("%s%s/pg_ctl -D %s status\n", + preamble, opts.BinDir, opts.DataDir), + + "restart": fmt.Sprintf("%s%s/pg_ctl -D %s -l %s restart\n", + preamble, opts.BinDir, opts.DataDir, opts.LogFile), + + "use": fmt.Sprintf("%s%s/psql -h 127.0.0.1 -p %d -U postgres \"$@\"\n", + preamble, opts.BinDir, opts.Port), + + "clear": fmt.Sprintf("%s%s/pg_ctl -D %s stop -m fast 2>/dev/null\nrm -rf %s\n%s/initdb -D %s --auth=trust --username=postgres\necho \"Sandbox cleared.\"\n", + preamble, opts.BinDir, opts.DataDir, opts.DataDir, opts.BinDir, opts.DataDir), + } +} +``` + +- [ ] **Step 4: Run script generation tests to verify they pass** + +Run: `cd /data/rene/dbdeployer && go test ./providers/postgresql/ -run TestGenerateScripts -v` +Expected: PASS. + +- [ ] **Step 5: Implement CreateSandbox** + +Create `providers/postgresql/sandbox.go`: + +```go +package postgresql + +import ( + "fmt" + "os" + "os/exec" + "path/filepath" + + "github.com/ProxySQL/dbdeployer/providers" +) + +func (p *PostgreSQLProvider) CreateSandbox(config providers.SandboxConfig) (*providers.SandboxInfo, error) { + basedir, err := p.resolveBasedir(config) + if err != nil { + return nil, err + } + binDir := filepath.Join(basedir, "bin") + libDir := filepath.Join(basedir, "lib") + dataDir := filepath.Join(config.Dir, "data") + logDir := filepath.Join(dataDir, "log") + logFile := filepath.Join(config.Dir, "postgresql.log") + + replication := config.Options["replication"] == "true" + + // Create log directory + if err := os.MkdirAll(logDir, 0755); err != nil { + return nil, fmt.Errorf("creating log directory: %w", err) + } + + // Run initdb + initdbPath := filepath.Join(binDir, "initdb") + initCmd := exec.Command(initdbPath, "-D", dataDir, "--auth=trust", "--username=postgres") + initCmd.Env = append(os.Environ(), fmt.Sprintf("LD_LIBRARY_PATH=%s", libDir)) + if output, err := initCmd.CombinedOutput(); err != nil { + os.RemoveAll(config.Dir) // cleanup on failure + return nil, fmt.Errorf("initdb failed: %s: %w", string(output), err) + } + + // Generate and write postgresql.conf + pgConf := GeneratePostgresqlConf(PostgresqlConfOptions{ + Port: config.Port, + ListenAddresses: "127.0.0.1", + UnixSocketDir: dataDir, + LogDir: logDir, + Replication: replication, + }) + confPath := filepath.Join(dataDir, "postgresql.conf") + if err := os.WriteFile(confPath, []byte(pgConf), 0644); err != nil { + os.RemoveAll(config.Dir) + return nil, fmt.Errorf("writing postgresql.conf: %w", err) + } + + // Generate and write pg_hba.conf + hbaConf := GeneratePgHbaConf(replication) + hbaPath := filepath.Join(dataDir, "pg_hba.conf") + if err := os.WriteFile(hbaPath, []byte(hbaConf), 0644); err != nil { + os.RemoveAll(config.Dir) + return nil, fmt.Errorf("writing pg_hba.conf: %w", err) + } + + // Generate and write lifecycle scripts + scripts := GenerateScripts(ScriptOptions{ + SandboxDir: config.Dir, + DataDir: dataDir, + BinDir: binDir, + LibDir: libDir, + Port: config.Port, + LogFile: logFile, + }) + for name, content := range scripts { + scriptPath := filepath.Join(config.Dir, name) + if err := os.WriteFile(scriptPath, []byte(content), 0755); err != nil { + os.RemoveAll(config.Dir) + return nil, fmt.Errorf("writing script %s: %w", name, err) + } + } + + return &providers.SandboxInfo{ + Dir: config.Dir, + Port: config.Port, + Status: "stopped", + }, nil +} + +// resolveBasedir determines the PostgreSQL base directory. +// Uses config.Options["basedir"] if set, otherwise ~/opt/postgresql/. +func (p *PostgreSQLProvider) resolveBasedir(config providers.SandboxConfig) (string, error) { + if bd, ok := config.Options["basedir"]; ok && bd != "" { + return bd, nil + } + return basedirFromVersion(config.Version) +} +``` + +- [ ] **Step 6: Remove the CreateSandbox stub from postgresql.go** + +In `providers/postgresql/postgresql.go`, remove the stub `CreateSandbox` method (now implemented in sandbox.go). + +- [ ] **Step 7: Run all PostgreSQL provider tests** + +Run: `cd /data/rene/dbdeployer && go test ./providers/postgresql/ -v` +Expected: All pass. + +- [ ] **Step 8: Commit** + +```bash +git add providers/postgresql/scripts.go providers/postgresql/sandbox.go providers/postgresql/postgresql.go providers/postgresql/postgresql_test.go +git commit -m "feat: implement PostgreSQL CreateSandbox with initdb, config gen, and lifecycle scripts" +``` + +--- + +## Task 5: Deb Extraction for PostgreSQL Binaries + +**Files:** +- Create: `providers/postgresql/unpack.go` +- Create: `providers/postgresql/unpack_test.go` + +- [ ] **Step 1: Write failing tests for deb filename parsing and validation** + +Create `providers/postgresql/unpack_test.go`: + +```go +package postgresql + +import "testing" + +func TestParseDebVersion(t *testing.T) { + tests := []struct { + filename string + wantVer string + wantErr bool + }{ + {"postgresql-16_16.13-0ubuntu0.24.04.1_amd64.deb", "16.13", false}, + {"postgresql-17_17.2-1_amd64.deb", "17.2", false}, + {"postgresql-client-16_16.13-0ubuntu0.24.04.1_amd64.deb", "16.13", false}, + {"random-file.tar.gz", "", true}, + {"postgresql-16_bad-version.deb", "", true}, + } + for _, tt := range tests { + ver, err := ParseDebVersion(tt.filename) + if (err != nil) != tt.wantErr { + t.Errorf("ParseDebVersion(%q) error = %v, wantErr %v", tt.filename, err, tt.wantErr) + continue + } + if ver != tt.wantVer { + t.Errorf("ParseDebVersion(%q) = %q, want %q", tt.filename, ver, tt.wantVer) + } + } +} + +func TestClassifyDebs(t *testing.T) { + files := []string{ + "postgresql-16_16.13-0ubuntu0.24.04.1_amd64.deb", + "postgresql-client-16_16.13-0ubuntu0.24.04.1_amd64.deb", + } + server, client, err := ClassifyDebs(files) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if server != files[0] { + t.Errorf("server = %q, want %q", server, files[0]) + } + if client != files[1] { + t.Errorf("client = %q, want %q", client, files[1]) + } +} + +func TestClassifyDebsMissingClient(t *testing.T) { + files := []string{"postgresql-16_16.13-0ubuntu0.24.04.1_amd64.deb"} + _, _, err := ClassifyDebs(files) + if err == nil { + t.Error("expected error for missing client deb") + } +} + +func TestRequiredBinaries(t *testing.T) { + expected := []string{"postgres", "initdb", "pg_ctl", "psql", "pg_basebackup"} + got := RequiredBinaries() + if len(got) != len(expected) { + t.Fatalf("expected %d binaries, got %d", len(expected), len(got)) + } + for i, name := range expected { + if got[i] != name { + t.Errorf("binary[%d] = %q, want %q", i, got[i], name) + } + } +} +``` + +- [ ] **Step 2: Run tests to verify they fail** + +Run: `cd /data/rene/dbdeployer && go test ./providers/postgresql/ -run "TestParseDeb|TestClassify|TestRequired" -v` +Expected: Compilation error — functions not defined. + +- [ ] **Step 3: Implement deb extraction logic** + +Create `providers/postgresql/unpack.go`: + +```go +package postgresql + +import ( + "fmt" + "os" + "os/exec" + "path/filepath" + "regexp" + "strings" +) + +var debVersionRegex = regexp.MustCompile(`^postgresql(?:-client)?-(\d+)_(\d+\.\d+)`) + +// ParseDebVersion extracts the PostgreSQL version from a deb filename. +func ParseDebVersion(filename string) (string, error) { + base := filepath.Base(filename) + matches := debVersionRegex.FindStringSubmatch(base) + if matches == nil { + return "", fmt.Errorf("cannot parse PostgreSQL version from %q (expected postgresql[-client]-NN_X.Y-*)", base) + } + return matches[2], nil +} + +// ClassifyDebs identifies server and client debs from a list of filenames. +func ClassifyDebs(files []string) (server, client string, err error) { + for _, f := range files { + base := filepath.Base(f) + if strings.HasPrefix(base, "postgresql-client-") { + client = f + } else if strings.HasPrefix(base, "postgresql-") && strings.HasSuffix(base, ".deb") { + server = f + } + } + if server == "" { + return "", "", fmt.Errorf("no server deb found (expected postgresql-NN_*.deb)") + } + if client == "" { + return "", "", fmt.Errorf("no client deb found (expected postgresql-client-NN_*.deb)") + } + return server, client, nil +} + +// RequiredBinaries returns the binaries that must exist after extraction. +func RequiredBinaries() []string { + return []string{"postgres", "initdb", "pg_ctl", "psql", "pg_basebackup"} +} + +// UnpackDebs extracts PostgreSQL server and client debs into the target directory. +// targetDir is the final layout dir, e.g. ~/opt/postgresql/16.13/ +func UnpackDebs(serverDeb, clientDeb, targetDir string) error { + tmpDir, err := os.MkdirTemp("", "dbdeployer-pg-unpack-*") + if err != nil { + return fmt.Errorf("creating temp directory: %w", err) + } + defer os.RemoveAll(tmpDir) + + // Extract both debs + for _, deb := range []string{serverDeb, clientDeb} { + cmd := exec.Command("dpkg-deb", "-x", deb, tmpDir) + if output, err := cmd.CombinedOutput(); err != nil { + return fmt.Errorf("extracting %s: %s: %w", filepath.Base(deb), string(output), err) + } + } + + // Determine the major version directory inside the extracted tree + version, err := ParseDebVersion(serverDeb) + if err != nil { + return err + } + major := strings.Split(version, ".")[0] + + // Source paths within extracted debs + srcBin := filepath.Join(tmpDir, "usr", "lib", "postgresql", major, "bin") + srcLib := filepath.Join(tmpDir, "usr", "lib", "postgresql", major, "lib") + srcShare := filepath.Join(tmpDir, "usr", "share", "postgresql", major) + + // Create target directories + dstBin := filepath.Join(targetDir, "bin") + dstLib := filepath.Join(targetDir, "lib") + dstShare := filepath.Join(targetDir, "share") + + for _, dir := range []string{dstBin, dstLib, dstShare} { + if err := os.MkdirAll(dir, 0755); err != nil { + return fmt.Errorf("creating directory %s: %w", dir, err) + } + } + + // Copy files using cp -a to preserve permissions and symlinks + copies := []struct{ src, dst string }{ + {srcBin, dstBin}, + {srcLib, dstLib}, + {srcShare, dstShare}, + } + for _, c := range copies { + if _, err := os.Stat(c.src); os.IsNotExist(err) { + continue // some dirs may not exist in the client deb + } + cmd := exec.Command("cp", "-a", c.src+"/.", c.dst+"/") + if output, err := cmd.CombinedOutput(); err != nil { + return fmt.Errorf("copying %s to %s: %s: %w", c.src, c.dst, string(output), err) + } + } + + // Validate required binaries + for _, bin := range RequiredBinaries() { + binPath := filepath.Join(dstBin, bin) + if _, err := os.Stat(binPath); err != nil { + return fmt.Errorf("required binary %q not found at %s after extraction", bin, binPath) + } + } + + return nil +} +``` + +- [ ] **Step 4: Run tests to verify they pass** + +Run: `cd /data/rene/dbdeployer && go test ./providers/postgresql/ -run "TestParseDeb|TestClassify|TestRequired" -v` +Expected: All pass. + +- [ ] **Step 5: Commit** + +```bash +git add providers/postgresql/unpack.go providers/postgresql/unpack_test.go +git commit -m "feat: add PostgreSQL deb extraction for binary management" +``` + +--- + +## Task 6: PostgreSQL Replication (CreateReplica) + +**Files:** +- Modify: `providers/postgresql/postgresql.go` (replace CreateReplica stub) +- Create: `providers/postgresql/replication.go` +- Modify: `providers/postgresql/postgresql_test.go` (add replication config tests) + +- [ ] **Step 1: Write failing tests for replication monitoring script generation** + +Add to `providers/postgresql/postgresql_test.go`: + +```go +func TestGenerateCheckReplicationScript(t *testing.T) { + script := GenerateCheckReplicationScript(ScriptOptions{ + BinDir: "/opt/postgresql/16.13/bin", + LibDir: "/opt/postgresql/16.13/lib", + Port: 16613, + }) + if !strings.Contains(script, "pg_stat_replication") { + t.Error("missing pg_stat_replication query") + } + if !strings.Contains(script, "16613") { + t.Error("missing primary port") + } +} + +func TestGenerateCheckRecoveryScript(t *testing.T) { + ports := []int{16614, 16615} + script := GenerateCheckRecoveryScript(ScriptOptions{ + BinDir: "/opt/postgresql/16.13/bin", + LibDir: "/opt/postgresql/16.13/lib", + }, ports) + if !strings.Contains(script, "pg_is_in_recovery") { + t.Error("missing pg_is_in_recovery query") + } + if !strings.Contains(script, "16614") || !strings.Contains(script, "16615") { + t.Error("missing replica ports") + } +} +``` + +- [ ] **Step 2: Run tests to verify they fail** + +Run: `cd /data/rene/dbdeployer && go test ./providers/postgresql/ -run "TestGenerateCheck" -v` +Expected: Compilation error — functions not defined. + +- [ ] **Step 3: Add monitoring script generators to scripts.go** + +Add to `providers/postgresql/scripts.go`: + +```go +func GenerateCheckReplicationScript(opts ScriptOptions) string { + preamble := fmt.Sprintf(envPreamble, opts.LibDir) + return fmt.Sprintf(`%s%s/psql -h 127.0.0.1 -p %d -U postgres -c \ + "SELECT client_addr, state, sent_lsn, write_lsn, flush_lsn, replay_lsn FROM pg_stat_replication;" +`, preamble, opts.BinDir, opts.Port) +} + +func GenerateCheckRecoveryScript(opts ScriptOptions, replicaPorts []int) string { + preamble := fmt.Sprintf(envPreamble, opts.LibDir) + var b strings.Builder + b.WriteString(preamble) + for _, port := range replicaPorts { + b.WriteString(fmt.Sprintf("echo \"=== Replica port %d ===\"\n", port)) + b.WriteString(fmt.Sprintf("%s/psql -h 127.0.0.1 -p %d -U postgres -c \"SELECT pg_is_in_recovery();\"\n", opts.BinDir, port)) + } + return b.String() +} +``` + +Add `"strings"` to imports in `scripts.go`. + +- [ ] **Step 4: Implement CreateReplica** + +Create `providers/postgresql/replication.go`: + +```go +package postgresql + +import ( + "fmt" + "os" + "os/exec" + "path/filepath" + "strings" + + "github.com/ProxySQL/dbdeployer/providers" +) + +func (p *PostgreSQLProvider) CreateReplica(primary providers.SandboxInfo, config providers.SandboxConfig) (*providers.SandboxInfo, error) { + basedir, err := p.resolveBasedir(config) + if err != nil { + return nil, err + } + binDir := filepath.Join(basedir, "bin") + libDir := filepath.Join(basedir, "lib") + dataDir := filepath.Join(config.Dir, "data") + logFile := filepath.Join(config.Dir, "postgresql.log") + + // pg_basebackup from the running primary + pgBasebackup := filepath.Join(binDir, "pg_basebackup") + bbCmd := exec.Command(pgBasebackup, + "-h", "127.0.0.1", + "-p", fmt.Sprintf("%d", primary.Port), + "-U", "postgres", + "-D", dataDir, + "-Fp", "-Xs", "-R", + ) + bbCmd.Env = append(os.Environ(), fmt.Sprintf("LD_LIBRARY_PATH=%s", libDir)) + if output, err := bbCmd.CombinedOutput(); err != nil { + os.RemoveAll(config.Dir) // cleanup on failure + return nil, fmt.Errorf("pg_basebackup failed: %s: %w", string(output), err) + } + + // Modify replica's postgresql.conf: update port and unix_socket_directories + confPath := filepath.Join(dataDir, "postgresql.conf") + confBytes, err := os.ReadFile(confPath) + if err != nil { + os.RemoveAll(config.Dir) + return nil, fmt.Errorf("reading postgresql.conf: %w", err) + } + + conf := string(confBytes) + // Replace port line + lines := strings.Split(conf, "\n") + var newLines []string + for _, line := range lines { + trimmed := strings.TrimSpace(line) + if strings.HasPrefix(trimmed, "port =") || strings.HasPrefix(trimmed, "port=") { + newLines = append(newLines, fmt.Sprintf("port = %d", config.Port)) + } else if strings.HasPrefix(trimmed, "unix_socket_directories =") || strings.HasPrefix(trimmed, "unix_socket_directories=") { + newLines = append(newLines, fmt.Sprintf("unix_socket_directories = '%s'", dataDir)) + } else { + newLines = append(newLines, line) + } + } + + if err := os.WriteFile(confPath, []byte(strings.Join(newLines, "\n")), 0644); err != nil { + os.RemoveAll(config.Dir) + return nil, fmt.Errorf("writing modified postgresql.conf: %w", err) + } + + // Write lifecycle scripts + scripts := GenerateScripts(ScriptOptions{ + SandboxDir: config.Dir, + DataDir: dataDir, + BinDir: binDir, + LibDir: libDir, + Port: config.Port, + LogFile: logFile, + }) + for name, content := range scripts { + scriptPath := filepath.Join(config.Dir, name) + if err := os.WriteFile(scriptPath, []byte(content), 0755); err != nil { + os.RemoveAll(config.Dir) + return nil, fmt.Errorf("writing script %s: %w", name, err) + } + } + + // Start the replica + if err := p.StartSandbox(config.Dir); err != nil { + os.RemoveAll(config.Dir) + return nil, fmt.Errorf("starting replica: %w", err) + } + + return &providers.SandboxInfo{ + Dir: config.Dir, + Port: config.Port, + Status: "running", + }, nil +} +``` + +- [ ] **Step 5: Remove CreateReplica stub from postgresql.go** + +In `providers/postgresql/postgresql.go`, remove the stub `CreateReplica` method. + +- [ ] **Step 6: Run tests to verify they pass** + +Run: `cd /data/rene/dbdeployer && go test ./providers/postgresql/ -v` +Expected: All tests pass (unit tests; replication flow is integration-tested). + +- [ ] **Step 7: Commit** + +```bash +git add providers/postgresql/replication.go providers/postgresql/scripts.go providers/postgresql/postgresql.go providers/postgresql/postgresql_test.go +git commit -m "feat: implement PostgreSQL CreateReplica with pg_basebackup and monitoring scripts" +``` + +--- + +## Task 7: Register Provider and Add --provider Flag to Commands + +**Files:** +- Modify: `cmd/root.go` +- Modify: `cmd/single.go` +- Modify: `cmd/multiple.go` +- Modify: `cmd/replication.go` +- Modify: `globals/globals.go` +- Modify: `providers/provider.go` (add `ContainsString` helper) +- Modify: `sandbox/proxysql_topology.go` (add `backendProvider` parameter) + +**Note:** This task introduces `cmd/deploy_postgresql.go` (Task 11) and splits files not in the original spec (`sandbox.go`, `scripts.go`). These are intentional improvements for code organization and UX. + +- [ ] **Step 1: Add PostgreSQL constants and ContainsString helper to providers** + +In `globals/globals.go`, add near the existing constant blocks: + +```go +const ( + ProviderLabel = "provider" + ProviderValue = "mysql" // default provider +) +``` + +In `providers/provider.go`, add an exported helper: + +```go +// ContainsString checks if a string slice contains a given value. +func ContainsString(slice []string, s string) bool { + for _, item := range slice { + if item == s { + return true + } + } + return false +} +``` + +- [ ] **Step 2: Register PostgreSQL provider in cmd/root.go** + +In `cmd/root.go`, add import for PostgreSQL provider and register it in `init()`: + +```go +import ( + // existing imports... + postgresqlprovider "github.com/ProxySQL/dbdeployer/providers/postgresql" +) + +// In init(), after proxysql registration: +_ = postgresqlprovider.Register(providers.DefaultRegistry) +``` + +- [ ] **Step 3: Update DeployProxySQLForTopology signature** + +In `sandbox/proxysql_topology.go`, add a `backendProvider` parameter. All callers must be updated: + +```go +func DeployProxySQLForTopology(sandboxDir string, masterPort int, slavePorts []int, proxysqlPort int, host string, backendProvider string) error { + // ... existing code unchanged until config building ... + config := providers.SandboxConfig{ + // ... existing fields ... + Options: map[string]string{ + "monitor_user": "msandbox", + "monitor_password": "msandbox", + "backends": strings.Join(backendParts, ","), + "backend_provider": backendProvider, // NEW: "" for mysql, "postgresql" for pg + }, + } + // ... rest unchanged ... +} +``` + +**Callers to update** (pass `""` to preserve existing MySQL behavior): +- `cmd/single.go:485` — `sandbox.DeployProxySQLForTopology(sandboxDir, masterPort, nil, 0, "127.0.0.1", "")` +- `cmd/replication.go:135` — `sandbox.DeployProxySQLForTopology(sandboxDir, masterPort, slavePorts, 0, "127.0.0.1", "")` + +- [ ] **Step 4: Update cmd/single.go — add --provider flag and routing** + +The key design decision: for non-MySQL providers, we **skip `fillSandboxDefinition` entirely** because it is deeply MySQL-specific (checks for MySQL directories, runs `common.CheckLibraries`, calls `getFlavor`, etc.). Instead, non-MySQL providers build a `providers.SandboxConfig` directly from CLI flags. + +Replace `singleSandbox()` with this structure: + +```go +func singleSandbox(cmd *cobra.Command, args []string) { + flags := cmd.Flags() + providerName, _ := flags.GetString(globals.ProviderLabel) + + // Non-MySQL providers: bypass fillSandboxDefinition entirely + if providerName != "mysql" { + deploySingleNonMySQL(cmd, args, providerName) + return + } + + // Existing MySQL path — completely unchanged + var sd sandbox.SandboxDef + var err error + common.CheckOrigin(args) + sd, err = fillSandboxDefinition(cmd, args, false) + // ... rest of existing code unchanged, BUT update DeployProxySQLForTopology call: + // sandbox.DeployProxySQLForTopology(sandboxDir, masterPort, nil, 0, "127.0.0.1", "") +} + +func deploySingleNonMySQL(cmd *cobra.Command, args []string, providerName string) { + flags := cmd.Flags() + version := args[0] + + p, err := providers.DefaultRegistry.Get(providerName) + if err != nil { + common.Exitf(1, "provider error: %s", err) + } + + // Flavor validation: --flavor is MySQL-only + flavor, _ := flags.GetString(globals.FlavorLabel) + if flavor != "" { + common.Exitf(1, "--flavor is only valid with --provider=mysql") + } + + // Topology validation + if !providers.ContainsString(p.SupportedTopologies(), "single") { + common.Exitf(1, "provider %q does not support topology \"single\"\nSupported topologies: %s", + providerName, strings.Join(p.SupportedTopologies(), ", ")) + } + + if err := p.ValidateVersion(version); err != nil { + common.Exitf(1, "version validation failed: %s", err) + } + + if _, err := p.FindBinary(version); err != nil { + common.Exitf(1, "binaries not found: %s", err) + } + + // Compute port from provider's default port range + portRange := p.DefaultPorts() + port := portRange.BasePort + // For PostgreSQL, use VersionToPort + if providerName == "postgresql" { + port, _ = postgresql.VersionToPort(version) + } + freePort, portErr := common.FindFreePort(port, []int{}, portRange.PortsPerInstance) + if portErr == nil { + port = freePort + } + + sandboxHome := defaults.Defaults().SandboxHome + sandboxDir := path.Join(sandboxHome, fmt.Sprintf("%s_sandbox_%d", providerName, port)) + if common.DirExists(sandboxDir) { + common.Exitf(1, "sandbox directory %s already exists", sandboxDir) + } + + skipStart, _ := flags.GetBool(globals.SkipStartLabel) + config := providers.SandboxConfig{ + Version: version, + Dir: sandboxDir, + Port: port, + Host: "127.0.0.1", + DbUser: "postgres", + Options: map[string]string{}, + } + + if _, err := p.CreateSandbox(config); err != nil { + common.Exitf(1, "error creating sandbox: %s", err) + } + + if !skipStart { + if err := p.StartSandbox(sandboxDir); err != nil { + common.Exitf(1, "error starting sandbox: %s", err) + } + } + + // Handle --with-proxysql + withProxySQL, _ := flags.GetBool("with-proxysql") + if withProxySQL { + if !providers.ContainsString(providers.CompatibleAddons["proxysql"], providerName) { + common.Exitf(1, "--with-proxysql is not compatible with provider %q", providerName) + } + err := sandbox.DeployProxySQLForTopology(sandboxDir, port, nil, 0, "127.0.0.1", providerName) + if err != nil { + common.Exitf(1, "ProxySQL deployment failed: %s", err) + } + } + + fmt.Printf("%s %s sandbox deployed in %s (port: %d)\n", providerName, version, sandboxDir, port) +} +``` + +Add flag in `init()`: + +```go +singleCmd.PersistentFlags().String(globals.ProviderLabel, globals.ProviderValue, "Database provider (mysql, postgresql)") +``` + +Add imports for `postgresql` and `providers` packages. + +- [ ] **Step 5: Update cmd/multiple.go — add --provider flag and routing** + +Same bypass pattern. For non-MySQL providers, create N instances with sequential ports: + +```go +func multipleSandbox(cmd *cobra.Command, args []string) { + flags := cmd.Flags() + providerName, _ := flags.GetString(globals.ProviderLabel) + + if providerName != "mysql" { + deployMultipleNonMySQL(cmd, args, providerName) + return + } + + // Existing MySQL path unchanged, no modification needed + // ... +} + +func deployMultipleNonMySQL(cmd *cobra.Command, args []string, providerName string) { + flags := cmd.Flags() + version := args[0] + nodes, _ := flags.GetInt(globals.NodesLabel) + + p, err := providers.DefaultRegistry.Get(providerName) + if err != nil { + common.Exitf(1, "provider error: %s", err) + } + + flavor, _ := flags.GetString(globals.FlavorLabel) + if flavor != "" { + common.Exitf(1, "--flavor is only valid with --provider=mysql") + } + + if !providers.ContainsString(p.SupportedTopologies(), "multiple") { + common.Exitf(1, "provider %q does not support topology \"multiple\"\nSupported topologies: %s", + providerName, strings.Join(p.SupportedTopologies(), ", ")) + } + + if err := p.ValidateVersion(version); err != nil { + common.Exitf(1, "version validation failed: %s", err) + } + + if _, err := p.FindBinary(version); err != nil { + common.Exitf(1, "binaries not found: %s", err) + } + + // Compute base port + basePort := p.DefaultPorts().BasePort + if providerName == "postgresql" { + basePort, _ = postgresql.VersionToPort(version) + } + + sandboxHome := defaults.Defaults().SandboxHome + topologyDir := path.Join(sandboxHome, fmt.Sprintf("%s_multi_%d", providerName, basePort)) + if common.DirExists(topologyDir) { + common.Exitf(1, "sandbox directory %s already exists", topologyDir) + } + os.MkdirAll(topologyDir, 0755) + + skipStart, _ := flags.GetBool(globals.SkipStartLabel) + + for i := 1; i <= nodes; i++ { + port := basePort + i + freePort, err := common.FindFreePort(port, []int{}, 1) + if err == nil { + port = freePort + } + + nodeDir := path.Join(topologyDir, fmt.Sprintf("node%d", i)) + config := providers.SandboxConfig{ + Version: version, + Dir: nodeDir, + Port: port, + Host: "127.0.0.1", + DbUser: "postgres", + Options: map[string]string{}, + } + + if _, err := p.CreateSandbox(config); err != nil { + common.Exitf(1, "error creating node %d: %s", i, err) + } + + if !skipStart { + if err := p.StartSandbox(nodeDir); err != nil { + common.Exitf(1, "error starting node %d: %s", i, err) + } + } + + fmt.Printf(" Node %d deployed in %s (port: %d)\n", i, nodeDir, port) + } + + fmt.Printf("%s multiple sandbox (%d nodes) deployed in %s\n", providerName, nodes, topologyDir) +} +``` + +Add flag in `init()`: + +```go +multipleCmd.PersistentFlags().String(globals.ProviderLabel, globals.ProviderValue, "Database provider (mysql, postgresql)") +``` + +- [ ] **Step 6: Update cmd/replication.go — add --provider flag and PostgreSQL replication flow** + +Same bypass pattern. For PostgreSQL: create primary with replication options, start it, then CreateReplica for each replica sequentially: + +```go +func replicationSandbox(cmd *cobra.Command, args []string) { + flags := cmd.Flags() + providerName, _ := flags.GetString(globals.ProviderLabel) + + if providerName != "mysql" { + deployReplicationNonMySQL(cmd, args, providerName) + return + } + + // Existing MySQL path unchanged, BUT update DeployProxySQLForTopology call: + // sandbox.DeployProxySQLForTopology(sandboxDir, masterPort, slavePorts, 0, "127.0.0.1", "") + // ... +} + +func deployReplicationNonMySQL(cmd *cobra.Command, args []string, providerName string) { + flags := cmd.Flags() + version := args[0] + nodes, _ := flags.GetInt(globals.NodesLabel) + + p, err := providers.DefaultRegistry.Get(providerName) + if err != nil { + common.Exitf(1, "provider error: %s", err) + } + + flavor, _ := flags.GetString(globals.FlavorLabel) + if flavor != "" { + common.Exitf(1, "--flavor is only valid with --provider=mysql") + } + + if !providers.ContainsString(p.SupportedTopologies(), "replication") { + common.Exitf(1, "provider %q does not support topology \"replication\"\nSupported topologies: %s", + providerName, strings.Join(p.SupportedTopologies(), ", ")) + } + + if err := p.ValidateVersion(version); err != nil { + common.Exitf(1, "version validation failed: %s", err) + } + + if _, err := p.FindBinary(version); err != nil { + common.Exitf(1, "binaries not found: %s", err) + } + + // Compute base port + basePort := p.DefaultPorts().BasePort + if providerName == "postgresql" { + basePort, _ = postgresql.VersionToPort(version) + } + + sandboxHome := defaults.Defaults().SandboxHome + topologyDir := path.Join(sandboxHome, fmt.Sprintf("%s_repl_%d", providerName, basePort)) + if common.DirExists(topologyDir) { + common.Exitf(1, "sandbox directory %s already exists", topologyDir) + } + os.MkdirAll(topologyDir, 0755) + + skipStart, _ := flags.GetBool(globals.SkipStartLabel) + primaryPort := basePort + + // 1. Create and start primary with replication options + primaryDir := path.Join(topologyDir, "primary") + primaryConfig := providers.SandboxConfig{ + Version: version, + Dir: primaryDir, + Port: primaryPort, + Host: "127.0.0.1", + DbUser: "postgres", + Options: map[string]string{"replication": "true"}, + } + + if _, err := p.CreateSandbox(primaryConfig); err != nil { + common.Exitf(1, "error creating primary: %s", err) + } + + if !skipStart { + if err := p.StartSandbox(primaryDir); err != nil { + common.Exitf(1, "error starting primary: %s", err) + } + } + + fmt.Printf(" Primary deployed in %s (port: %d)\n", primaryDir, primaryPort) + + primaryInfo := providers.SandboxInfo{Dir: primaryDir, Port: primaryPort, Status: "running"} + + // 2. Create replicas sequentially (pg_basebackup requires running primary) + var replicaPorts []int + for i := 1; i <= nodes-1; i++ { + replicaPort := primaryPort + i + freePort, err := common.FindFreePort(replicaPort, []int{}, 1) + if err == nil { + replicaPort = freePort + } + + replicaDir := path.Join(topologyDir, fmt.Sprintf("replica%d", i)) + replicaConfig := providers.SandboxConfig{ + Version: version, + Dir: replicaDir, + Port: replicaPort, + Host: "127.0.0.1", + DbUser: "postgres", + Options: map[string]string{}, + } + + if _, err := p.CreateReplica(primaryInfo, replicaConfig); err != nil { + // Cleanup: stop primary and any already-running replicas + p.StopSandbox(primaryDir) + for j := 1; j < i; j++ { + p.StopSandbox(path.Join(topologyDir, fmt.Sprintf("replica%d", j))) + } + common.Exitf(1, "error creating replica %d: %s", i, err) + } + + replicaPorts = append(replicaPorts, replicaPort) + fmt.Printf(" Replica %d deployed in %s (port: %d)\n", i, replicaDir, replicaPort) + } + + // 3. Generate topology-level monitoring scripts + home, _ := os.UserHomeDir() + basedir := path.Join(home, "opt", "postgresql", version) + binDir := path.Join(basedir, "bin") + libDir := path.Join(basedir, "lib") + + scriptOpts := postgresql.ScriptOptions{ + BinDir: binDir, + LibDir: libDir, + Port: primaryPort, + } + + checkReplScript := postgresql.GenerateCheckReplicationScript(scriptOpts) + os.WriteFile(path.Join(topologyDir, "check_replication"), []byte(checkReplScript), 0755) + + checkRecovScript := postgresql.GenerateCheckRecoveryScript(scriptOpts, replicaPorts) + os.WriteFile(path.Join(topologyDir, "check_recovery"), []byte(checkRecovScript), 0755) + + // 4. Handle --with-proxysql + withProxySQL, _ := flags.GetBool("with-proxysql") + if withProxySQL { + if !providers.ContainsString(providers.CompatibleAddons["proxysql"], providerName) { + common.Exitf(1, "--with-proxysql is not compatible with provider %q", providerName) + } + err := sandbox.DeployProxySQLForTopology(topologyDir, primaryPort, replicaPorts, 0, "127.0.0.1", providerName) + if err != nil { + common.Exitf(1, "ProxySQL deployment failed: %s", err) + } + } + + fmt.Printf("%s replication sandbox (1 primary + %d replicas) deployed in %s\n", + providerName, nodes-1, topologyDir) +} +``` + +Add flag in `init()`: + +```go +replicationCmd.PersistentFlags().String(globals.ProviderLabel, globals.ProviderValue, "Database provider (mysql, postgresql)") +``` + +- [ ] **Step 7: Run full test suite to verify nothing is broken** + +Run: `cd /data/rene/dbdeployer && go test ./... -v -timeout 5m` +Expected: All existing tests pass. No regressions. + +- [ ] **Step 8: Commit** + +```bash +git add globals/globals.go providers/provider.go cmd/root.go cmd/single.go cmd/multiple.go cmd/replication.go sandbox/proxysql_topology.go +git commit -m "feat: add --provider flag and PostgreSQL routing to deploy commands" +``` + +--- + +## Task 8: Unpack Command for PostgreSQL Debs + +**Files:** +- Modify: `cmd/unpack.go` + +- [ ] **Step 1: Add --provider flag to unpack command** + +In `cmd/unpack.go`, modify `unpackTarball()` to check `--provider` flag. When `--provider=postgresql`, route to PostgreSQL deb extraction instead of MySQL tarball extraction: + +```go +providerName, _ := flags.GetString(globals.ProviderLabel) +if providerName == "postgresql" { + // PostgreSQL deb extraction + if len(args) < 2 { + common.Exitf(1, "PostgreSQL unpack requires both server and client .deb files\n"+ + "Usage: dbdeployer unpack --provider=postgresql postgresql-16_*.deb postgresql-client-16_*.deb") + } + server, client, err := postgresql.ClassifyDebs(args) + if err != nil { + common.Exitf(1, "error classifying deb files: %s", err) + } + version := Version // from --unpack-version flag + if version == "" { + version, err = postgresql.ParseDebVersion(server) + if err != nil { + common.Exitf(1, "cannot detect version from filename: %s\nUse --unpack-version to specify", err) + } + } + targetDir := filepath.Join(home, "opt", "postgresql", version) + if err := postgresql.UnpackDebs(server, client, targetDir); err != nil { + common.Exitf(1, "error unpacking PostgreSQL debs: %s", err) + } + fmt.Printf("PostgreSQL %s unpacked to %s\n", version, targetDir) + return +} +// ... existing MySQL tarball path unchanged +``` + +Add the `--provider` flag in `init()`: + +```go +unpackCmd.PersistentFlags().String(globals.ProviderLabel, globals.ProviderValue, "Database provider (mysql, postgresql)") +``` + +Update `unpackCmd` to accept variadic args for PostgreSQL (currently `Args: cobra.ExactArgs(1)`): + +```go +Args: cobra.MinimumNArgs(1), +``` + +- [ ] **Step 2: Run tests to verify no regressions** + +Run: `cd /data/rene/dbdeployer && go test ./... -timeout 5m` +Expected: All pass. + +- [ ] **Step 3: Commit** + +```bash +git add cmd/unpack.go +git commit -m "feat: add --provider=postgresql support to dbdeployer unpack for deb extraction" +``` + +--- + +## Task 9: ProxySQL + PostgreSQL Backend Wiring + +**Files:** +- Modify: `providers/proxysql/config.go` +- Modify: `providers/proxysql/config_test.go` (or create if absent) +- Modify: `providers/proxysql/proxysql.go` +- Modify: `sandbox/proxysql_topology.go` + +- [ ] **Step 1: Write failing test for PostgreSQL backend config generation** + +Add to `providers/proxysql/config_test.go` (create if needed): + +```go +package proxysql + +import ( + "strings" + "testing" +) + +func TestGenerateConfigMySQL(t *testing.T) { + cfg := ProxySQLConfig{ + AdminHost: "127.0.0.1", + AdminPort: 6032, + AdminUser: "admin", + AdminPassword: "admin", + MySQLPort: 6033, + DataDir: "/tmp/proxysql/data", + MonitorUser: "msandbox", + MonitorPass: "msandbox", + Backends: []BackendServer{ + {Host: "127.0.0.1", Port: 3306, Hostgroup: 0, MaxConns: 200}, + }, + } + config := GenerateConfig(cfg) + if !strings.Contains(config, "mysql_servers") { + t.Error("expected mysql_servers block") + } + if !strings.Contains(config, "mysql_variables") { + t.Error("expected mysql_variables block") + } +} + +func TestGenerateConfigPostgreSQL(t *testing.T) { + cfg := ProxySQLConfig{ + AdminHost: "127.0.0.1", + AdminPort: 6032, + AdminUser: "admin", + AdminPassword: "admin", + MySQLPort: 6033, + DataDir: "/tmp/proxysql/data", + MonitorUser: "postgres", + MonitorPass: "postgres", + BackendProvider: "postgresql", + Backends: []BackendServer{ + {Host: "127.0.0.1", Port: 16613, Hostgroup: 0, MaxConns: 200}, + {Host: "127.0.0.1", Port: 16614, Hostgroup: 1, MaxConns: 200}, + }, + } + config := GenerateConfig(cfg) + if !strings.Contains(config, "pgsql_servers") { + t.Error("expected pgsql_servers block") + } + if !strings.Contains(config, "pgsql_users") { + t.Error("expected pgsql_users block") + } + if !strings.Contains(config, "pgsql_variables") { + t.Error("expected pgsql_variables block") + } + if strings.Contains(config, "mysql_servers") { + t.Error("should not contain mysql_servers for postgresql backend") + } +} +``` + +- [ ] **Step 2: Run tests to verify they fail** + +Run: `cd /data/rene/dbdeployer && go test ./providers/proxysql/ -run TestGenerateConfig -v` +Expected: Fail — `BackendProvider` field doesn't exist yet. + +- [ ] **Step 3: Add BackendProvider field to ProxySQLConfig and update GenerateConfig** + +In `providers/proxysql/config.go`: + +Add `BackendProvider string` field to `ProxySQLConfig`. + +Update `GenerateConfig` to branch on `BackendProvider`: + +```go +func GenerateConfig(cfg ProxySQLConfig) string { + var b strings.Builder + b.WriteString(fmt.Sprintf("datadir=\"%s\"\n\n", cfg.DataDir)) + + b.WriteString("admin_variables=\n{\n") + b.WriteString(fmt.Sprintf(" admin_credentials=\"%s:%s\"\n", cfg.AdminUser, cfg.AdminPassword)) + b.WriteString(fmt.Sprintf(" mysql_ifaces=\"%s:%d\"\n", cfg.AdminHost, cfg.AdminPort)) + b.WriteString("}\n\n") + + isPgsql := cfg.BackendProvider == "postgresql" + + if isPgsql { + b.WriteString("pgsql_variables=\n{\n") + b.WriteString(fmt.Sprintf(" interfaces=\"%s:%d\"\n", cfg.AdminHost, cfg.MySQLPort)) + b.WriteString(fmt.Sprintf(" monitor_username=\"%s\"\n", cfg.MonitorUser)) + b.WriteString(fmt.Sprintf(" monitor_password=\"%s\"\n", cfg.MonitorPass)) + b.WriteString("}\n\n") + } else { + b.WriteString("mysql_variables=\n{\n") + b.WriteString(fmt.Sprintf(" interfaces=\"%s:%d\"\n", cfg.AdminHost, cfg.MySQLPort)) + b.WriteString(fmt.Sprintf(" monitor_username=\"%s\"\n", cfg.MonitorUser)) + b.WriteString(fmt.Sprintf(" monitor_password=\"%s\"\n", cfg.MonitorPass)) + b.WriteString(" monitor_connect_interval=2000\n") + b.WriteString(" monitor_ping_interval=2000\n") + b.WriteString("}\n\n") + } + + serversKey := "mysql_servers" + usersKey := "mysql_users" + if isPgsql { + serversKey = "pgsql_servers" + usersKey = "pgsql_users" + } + + if len(cfg.Backends) > 0 { + b.WriteString(fmt.Sprintf("%s=\n(\n", serversKey)) + for i, srv := range cfg.Backends { + b.WriteString(" {\n") + b.WriteString(fmt.Sprintf(" address=\"%s\"\n", srv.Host)) + b.WriteString(fmt.Sprintf(" port=%d\n", srv.Port)) + b.WriteString(fmt.Sprintf(" hostgroup=%d\n", srv.Hostgroup)) + maxConns := srv.MaxConns + if maxConns == 0 { + maxConns = 200 + } + b.WriteString(fmt.Sprintf(" max_connections=%d\n", maxConns)) + b.WriteString(" }") + if i < len(cfg.Backends)-1 { + b.WriteString(",") + } + b.WriteString("\n") + } + b.WriteString(")\n\n") + } + + b.WriteString(fmt.Sprintf("%s=\n(\n", usersKey)) + b.WriteString(" {\n") + b.WriteString(fmt.Sprintf(" username=\"%s\"\n", cfg.MonitorUser)) + b.WriteString(fmt.Sprintf(" password=\"%s\"\n", cfg.MonitorPass)) + b.WriteString(" default_hostgroup=0\n") + b.WriteString(" }\n") + b.WriteString(")\n") + + return b.String() +} +``` + +- [ ] **Step 4: Update ProxySQLProvider.CreateSandbox to pass BackendProvider** + +In `providers/proxysql/proxysql.go`, set `BackendProvider` from `config.Options["backend_provider"]`: + +```go +proxyCfg := ProxySQLConfig{ + // ... existing fields ... + BackendProvider: config.Options["backend_provider"], +} +``` + +Also update the `use_proxy` script generation to use `psql` when backend is PostgreSQL: + +```go +if config.Options["backend_provider"] == "postgresql" { + scripts["use_proxy"] = fmt.Sprintf("#!/bin/bash\npsql -h %s -p %d -U %s \"$@\"\n", + host, mysqlPort, monitorUser) +} else { + scripts["use_proxy"] = fmt.Sprintf("#!/bin/bash\nmysql -h %s -P %d -u %s -p%s --prompt 'ProxySQL> ' \"$@\"\n", + host, mysqlPort, monitorUser, monitorPass) +} +``` + +- [ ] **Step 5: Update DeployProxySQLForTopology to accept backend provider** + +In `sandbox/proxysql_topology.go`, add a `backendProvider` parameter: + +```go +func DeployProxySQLForTopology(sandboxDir string, masterPort int, slavePorts []int, proxysqlPort int, host string, backendProvider string) error { + // ... existing code ... + config.Options["backend_provider"] = backendProvider + // ... +} +``` + +Update all callers in `cmd/single.go` and `cmd/replication.go` to pass `""` (empty string = mysql default) or `"postgresql"` when appropriate. + +- [ ] **Step 6: Run all tests** + +Run: `cd /data/rene/dbdeployer && go test ./... -timeout 5m` +Expected: All pass. + +- [ ] **Step 7: Commit** + +```bash +git add providers/proxysql/config.go providers/proxysql/config_test.go providers/proxysql/proxysql.go sandbox/proxysql_topology.go cmd/single.go cmd/replication.go +git commit -m "feat: add ProxySQL PostgreSQL backend wiring (pgsql_servers/pgsql_users config)" +``` + +--- + +## Task 10: Cross-Database Topology Constraints + +**Files:** +- Modify: `cmd/single.go` +- Modify: `cmd/multiple.go` +- Modify: `cmd/replication.go` +- Modify: `providers/provider_test.go` + +This task ensures the validation logic added in Task 7 is properly tested. + +- [ ] **Step 1: Write tests for topology and cross-provider validation** + +Add to `providers/provider_test.go`: + +```go +func TestTopologyValidation(t *testing.T) { + mock := &mockProvider{name: "test"} + topos := mock.SupportedTopologies() + if !containsString(topos, "single") { + t.Error("expected single in supported topologies") + } + if containsString(topos, "group") { + t.Error("did not expect group in supported topologies") + } +} + +func containsString(slice []string, s string) bool { + for _, item := range slice { + if item == s { + return true + } + } + return false +} +``` + +Also add a test for the addon compatibility map (define it in `providers/provider.go` or `cmd/` depending on where it lives): + +```go +var CompatibleAddons = map[string][]string{ + "proxysql": {"mysql", "postgresql"}, +} + +func TestAddonCompatibility(t *testing.T) { + if !containsString(CompatibleAddons["proxysql"], "postgresql") { + t.Error("proxysql should be compatible with postgresql") + } + if containsString(CompatibleAddons["proxysql"], "fake") { + t.Error("proxysql should not be compatible with fake") + } +} +``` + +- [ ] **Step 2: Add CompatibleAddons map to providers/provider.go** + +```go +// CompatibleAddons maps addon names to the list of providers they work with. +var CompatibleAddons = map[string][]string{ + "proxysql": {"mysql", "postgresql"}, +} +``` + +- [ ] **Step 3: Run tests** + +Run: `cd /data/rene/dbdeployer && go test ./providers/ -v` +Expected: All pass. + +- [ ] **Step 4: Commit** + +```bash +git add providers/provider.go providers/provider_test.go +git commit -m "feat: add cross-database topology constraint validation" +``` + +--- + +## Task 11: Standalone PostgreSQL Deploy Command + +**Files:** +- Create: `cmd/deploy_postgresql.go` + +- [ ] **Step 1: Create deploy postgresql subcommand** + +Create `cmd/deploy_postgresql.go` following the pattern from `cmd/deploy_proxysql.go`: + +```go +package cmd + +import ( + "fmt" + "path" + + "github.com/ProxySQL/dbdeployer/common" + "github.com/ProxySQL/dbdeployer/defaults" + "github.com/ProxySQL/dbdeployer/providers" + "github.com/ProxySQL/dbdeployer/providers/postgresql" + "github.com/spf13/cobra" +) + +func deploySandboxPostgreSQL(cmd *cobra.Command, args []string) { + version := args[0] + flags := cmd.Flags() + skipStart, _ := flags.GetBool("skip-start") + + p, err := providers.DefaultRegistry.Get("postgresql") + if err != nil { + common.Exitf(1, "PostgreSQL provider not available: %s", err) + } + + if err := p.ValidateVersion(version); err != nil { + common.Exitf(1, "invalid version: %s", err) + } + + if _, err := p.FindBinary(version); err != nil { + common.Exitf(1, "PostgreSQL binaries not found: %s\nRun: dbdeployer unpack --provider=postgresql ", err) + } + + port, err := postgresql.VersionToPort(version) + if err != nil { + common.Exitf(1, "error computing port: %s", err) + } + freePort, portErr := common.FindFreePort(port, []int{}, 1) + if portErr == nil { + port = freePort + } + + sandboxHome := defaults.Defaults().SandboxHome + sandboxDir := path.Join(sandboxHome, fmt.Sprintf("pg_sandbox_%d", port)) + + if common.DirExists(sandboxDir) { + common.Exitf(1, "sandbox directory %s already exists", sandboxDir) + } + + config := providers.SandboxConfig{ + Version: version, + Dir: sandboxDir, + Port: port, + Host: "127.0.0.1", + DbUser: "postgres", + DbPassword: "", + Options: map[string]string{}, + } + + if _, err := p.CreateSandbox(config); err != nil { + common.Exitf(1, "error creating PostgreSQL sandbox: %s", err) + } + + if !skipStart { + if err := p.StartSandbox(sandboxDir); err != nil { + common.Exitf(1, "error starting PostgreSQL: %s", err) + } + } + + fmt.Printf("PostgreSQL %s sandbox deployed in %s (port: %d)\n", version, sandboxDir, port) +} + +var deployPostgreSQLCmd = &cobra.Command{ + Use: "postgresql version", + Short: "deploys a PostgreSQL sandbox", + Long: `postgresql deploys a standalone PostgreSQL instance as a sandbox. +It creates a sandbox directory with data, configuration, start/stop scripts, and a +psql client script. + +Requires PostgreSQL binaries to be extracted first: + dbdeployer unpack --provider=postgresql postgresql-16_*.deb postgresql-client-16_*.deb + +Example: + dbdeployer deploy postgresql 16.13 + dbdeployer deploy postgresql 17.1 --skip-start +`, + Args: cobra.ExactArgs(1), + Run: deploySandboxPostgreSQL, +} + +func init() { + deployCmd.AddCommand(deployPostgreSQLCmd) + deployPostgreSQLCmd.Flags().Bool("skip-start", false, "Do not start PostgreSQL after deployment") +} +``` + +- [ ] **Step 2: Run build to verify compilation** + +Run: `cd /data/rene/dbdeployer && go build -o /dev/null .` +Expected: Build succeeds. + +- [ ] **Step 3: Commit** + +```bash +git add cmd/deploy_postgresql.go +git commit -m "feat: add 'dbdeployer deploy postgresql' standalone command" +``` + +--- + +## Task 12: Integration Tests + +**Files:** +- Create: `providers/postgresql/integration_test.go` + +- [ ] **Step 1: Write integration tests (build-tagged)** + +Create `providers/postgresql/integration_test.go`: + +```go +//go:build integration + +package postgresql + +import ( + "fmt" + "os" + "os/exec" + "path/filepath" + "testing" + "time" + + "github.com/ProxySQL/dbdeployer/common" + "github.com/ProxySQL/dbdeployer/providers" +) + +func findPostgresVersion(t *testing.T) string { + t.Helper() + home, _ := os.UserHomeDir() + entries, err := os.ReadDir(filepath.Join(home, "opt", "postgresql")) + if err != nil { + t.Skipf("no PostgreSQL installations found: %v", err) + } + for _, e := range entries { + if e.IsDir() { + return e.Name() + } + } + t.Skip("no PostgreSQL version directories found") + return "" +} + +func TestIntegrationSingleSandbox(t *testing.T) { + version := findPostgresVersion(t) + p := NewPostgreSQLProvider() + + tmpDir := t.TempDir() + sandboxDir := filepath.Join(tmpDir, "pg_test") + + config := providers.SandboxConfig{ + Version: version, + Dir: sandboxDir, + Port: 15432, + Host: "127.0.0.1", + DbUser: "postgres", + Options: map[string]string{}, + } + + // Create + info, err := p.CreateSandbox(config) + if err != nil { + t.Fatalf("CreateSandbox failed: %v", err) + } + if info.Port != 15432 { + t.Errorf("expected port 15432, got %d", info.Port) + } + + // Start + if err := p.StartSandbox(sandboxDir); err != nil { + t.Fatalf("StartSandbox failed: %v", err) + } + stopped := false + defer func() { + if !stopped { + p.StopSandbox(sandboxDir) + } + }() + time.Sleep(2 * time.Second) + + // Connect via psql + home, _ := os.UserHomeDir() + psql := filepath.Join(home, "opt", "postgresql", version, "bin", "psql") + cmd := exec.Command(psql, "-h", "127.0.0.1", "-p", "15432", "-U", "postgres", "-c", "SELECT 1;") + cmd.Env = append(os.Environ(), fmt.Sprintf("LD_LIBRARY_PATH=%s", + filepath.Join(home, "opt", "postgresql", version, "lib"))) + output, err := cmd.CombinedOutput() + if err != nil { + t.Fatalf("psql connection failed: %s: %v", string(output), err) + } + + // Stop + if err := p.StopSandbox(sandboxDir); err != nil { + t.Fatalf("StopSandbox failed: %v", err) + } + stopped = true +} + +func TestIntegrationReplication(t *testing.T) { + version := findPostgresVersion(t) + p := NewPostgreSQLProvider() + + tmpDir := t.TempDir() + primaryDir := filepath.Join(tmpDir, "primary") + replica1Dir := filepath.Join(tmpDir, "replica1") + replica2Dir := filepath.Join(tmpDir, "replica2") + + // Create and start primary with replication + primaryConfig := providers.SandboxConfig{ + Version: version, + Dir: primaryDir, + Port: 15500, + Host: "127.0.0.1", + DbUser: "postgres", + Options: map[string]string{"replication": "true"}, + } + + _, err := p.CreateSandbox(primaryConfig) + if err != nil { + t.Fatalf("CreateSandbox (primary) failed: %v", err) + } + if err := p.StartSandbox(primaryDir); err != nil { + t.Fatalf("StartSandbox (primary) failed: %v", err) + } + defer p.StopSandbox(primaryDir) + time.Sleep(2 * time.Second) + + primaryInfo := providers.SandboxInfo{Dir: primaryDir, Port: 15500} + + // Create replicas + for i, rDir := range []string{replica1Dir, replica2Dir} { + rConfig := providers.SandboxConfig{ + Version: version, + Dir: rDir, + Port: 15501 + i, + Host: "127.0.0.1", + DbUser: "postgres", + Options: map[string]string{}, + } + _, err := p.CreateReplica(primaryInfo, rConfig) + if err != nil { + t.Fatalf("CreateReplica %d failed: %v", i+1, err) + } + defer p.StopSandbox(rDir) + } + + time.Sleep(2 * time.Second) + + // Verify pg_stat_replication on primary shows 2 replicas + home, _ := os.UserHomeDir() + psql := filepath.Join(home, "opt", "postgresql", version, "bin", "psql") + libDir := filepath.Join(home, "opt", "postgresql", version, "lib") + + cmd := exec.Command(psql, "-h", "127.0.0.1", "-p", "15500", "-U", "postgres", "-t", "-c", + "SELECT count(*) FROM pg_stat_replication;") + cmd.Env = append(os.Environ(), fmt.Sprintf("LD_LIBRARY_PATH=%s", libDir)) + output, err := cmd.CombinedOutput() + if err != nil { + t.Fatalf("replication check failed: %s: %v", string(output), err) + } + + // Verify replicas are in recovery + for _, port := range []int{15501, 15502} { + cmd := exec.Command(psql, "-h", "127.0.0.1", "-p", fmt.Sprintf("%d", port), "-U", "postgres", "-t", "-c", + "SELECT pg_is_in_recovery();") + cmd.Env = append(os.Environ(), fmt.Sprintf("LD_LIBRARY_PATH=%s", libDir)) + output, err := cmd.CombinedOutput() + if err != nil { + t.Fatalf("recovery check on port %d failed: %s: %v", port, string(output), err) + } + } +} +``` + +- [ ] **Step 2: Verify unit tests still pass (integration tests skipped by default)** + +Run: `cd /data/rene/dbdeployer && go test ./providers/postgresql/ -v` +Expected: All unit tests pass. Integration tests are not compiled without the build tag. + +- [ ] **Step 3: Commit** + +```bash +git add providers/postgresql/integration_test.go +git commit -m "test: add PostgreSQL integration tests (build-tagged)" +``` + +--- + +## Task 13: Create GitHub Issues for CI Follow-Up + +**Files:** None (GitHub issues only) + +- [ ] **Step 1: Create GitHub issue for PostgreSQL deb caching in CI** + +```bash +gh issue create --title "CI: Add PostgreSQL deb caching to CI pipeline" \ + --body "Add caching of PostgreSQL server and client .deb packages to CI, similar to MySQL tarball caching. This enables running PostgreSQL integration tests in CI." \ + --label "enhancement,ci" +``` + +- [ ] **Step 2: Create GitHub issue for PostgreSQL integration tests in CI matrix** + +```bash +gh issue create --title "CI: Add PostgreSQL integration tests to CI matrix" \ + --body "Add PostgreSQL integration tests (providers/postgresql/integration_test.go) to the CI test matrix. Requires PostgreSQL deb caching (#) to be in place." \ + --label "enhancement,ci" +``` + +- [ ] **Step 3: Create GitHub issue for nightly PostgreSQL topology tests** + +```bash +gh issue create --title "CI: Add nightly PostgreSQL replication topology tests" \ + --body "Add nightly CI job that runs full PostgreSQL replication topology tests (primary + replicas, ProxySQL wiring)." \ + --label "enhancement,ci" +``` + +- [ ] **Step 4: Commit (no code changes, just documenting)** + +No commit needed — issues are tracked in GitHub. + +--- + +## Execution Notes + +### Dependencies between tasks +- Task 1 (interface changes) must complete before all other tasks +- Tasks 2-5 can run in parallel after Task 1 +- Task 6 (replication) depends on Tasks 2-4 +- Task 7 (cmd layer) depends on Tasks 2-6 +- Task 8 (unpack cmd) depends on Task 5 +- Task 9 (ProxySQL wiring) depends on Tasks 6-7 +- Task 10 (constraints) depends on Task 7 +- Task 11 (deploy command) depends on Tasks 2-4, 7 +- Task 12 (integration tests) depends on all implementation tasks +- Task 13 (GitHub issues) is independent + +### Running integration tests locally + +```bash +# Extract PostgreSQL binaries first +apt-get download postgresql-16 postgresql-client-16 +./dbdeployer unpack --provider=postgresql postgresql-16_*.deb postgresql-client-16_*.deb + +# Run integration tests +cd /data/rene/dbdeployer && go test ./providers/postgresql/ -tags integration -v -timeout 10m +``` From 9389a06453a6e2e3acad6428314fe9ee9bb56e31 Mon Sep 17 00:00:00 2001 From: Rene Cannao Date: Tue, 24 Mar 2026 15:59:04 +0000 Subject: [PATCH 03/15] chore: add .worktrees/ to gitignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 87142c6..f73d566 100644 --- a/.gitignore +++ b/.gitignore @@ -10,3 +10,4 @@ test/sort_versions.linux dbdeployer-*.osx* dbdeployer-*.linux* ts/testdata/ +.worktrees/ From 6e85e1557198790592af8f5eb133905afa876a3e Mon Sep 17 00:00:00 2001 From: Rene Cannao Date: Tue, 24 Mar 2026 17:23:16 +0000 Subject: [PATCH 04/15] docs: add website design spec (Astro + Starlight on GitHub Pages) --- .../specs/2026-03-24-website-design.md | 204 ++++++++++++++++++ 1 file changed, 204 insertions(+) create mode 100644 docs/superpowers/specs/2026-03-24-website-design.md diff --git a/docs/superpowers/specs/2026-03-24-website-design.md b/docs/superpowers/specs/2026-03-24-website-design.md new file mode 100644 index 0000000..31985b4 --- /dev/null +++ b/docs/superpowers/specs/2026-03-24-website-design.md @@ -0,0 +1,204 @@ +# dbdeployer Website Design + +**Date:** 2026-03-24 +**Author:** Rene (ProxySQL) +**Status:** Draft + +## Context + +dbdeployer has rich documentation (44 wiki pages, 54 API versions, ProxySQL guide, PostgreSQL provider docs) but no proper website. The current setup is a default Jekyll theme on GitHub Pages rendering the README. The project is evolving from a MySQL-only sandbox tool into a multi-database infrastructure tool under ProxySQL's maintainership, and needs a web presence that reflects this. + +## Goals + +- **Primary audience:** MySQL/PostgreSQL developers searching for local sandbox/testing tools (SEO-first) +- **Secondary goal:** Introduce ProxySQL integration as a natural next step +- **Tone:** Documentation-focused with a commercial/marketing polish — not a corporate site, but professional enough to build confidence + +## Tech Stack + +- **Framework:** Astro with Starlight integration (Astro's official docs theme) +- **Why Starlight:** sidebar navigation, Pagefind search, dark/light mode, content collections, i18n-ready — all out of the box. Custom pages (landing, providers, blog) use standard Astro layouts outside Starlight. +- **Hosting:** GitHub Pages +- **Deployment:** GitHub Actions → builds Astro → pushes to `gh-pages` branch + +## Project Structure + +``` +website/ + astro.config.mjs + package.json + src/ + content/ + docs/ # Starlight docs (migrated wiki pages) + blog/ # Blog posts as .md files + pages/ + index.astro # Landing page (custom, not Starlight) + providers.astro # Providers comparison page + components/ # Reusable Astro components (Hero, FeatureGrid, etc.) + layouts/ # Custom layouts for landing/blog + styles/ # Global CSS + public/ + images/ # Screenshots, diagrams +``` + +Source lives in `website/` at the repo root. The `gh-pages` branch contains only the built output. + +## Site Sections + +### Home (Landing Page) + +Custom `index.astro` — not a Starlight page. Marketing-oriented. + +**Structure (top to bottom):** + +1. **Nav bar** — logo/name, links: Getting Started, Docs, Providers, Blog, GitHub +2. **Hero section:** + - Tagline: *"Deploy MySQL & PostgreSQL sandboxes in seconds"* + - Subtitle: *"Create single instances, replication topologies, and full testing stacks — locally, without root, without Docker"* + - CTAs: "Get Started" → quickstart guide, "View on GitHub" → repo +3. **Quick install snippet** — one-liner in a code block with copy button +4. **Feature grid** — 3-4 cards: + - "Any Topology" — single, replication, group replication, fan-in, all-masters + - "Multiple Databases" — MySQL, PostgreSQL, Percona, MariaDB + - "ProxySQL Integration" — deploy read/write split stacks in one command + - "No Root, No Docker" — runs entirely in userspace +5. **Terminal demo** — animated or static code block showing a deploy + connect flow +6. **Providers section** — brief cards for MySQL, PostgreSQL, ProxySQL linking to Providers page +7. **"What's New" strip** — latest 1-2 blog posts +8. **Footer** — links, GitHub, license + +### Getting Started + +Four polished, tutorial-style guides — **new content**, written fresh: + +1. **Quick Start: MySQL Single** — install, deploy, connect, destroy +2. **Quick Start: MySQL Replication** — deploy replication, check status, test failover +3. **Quick Start: PostgreSQL** — unpack debs, deploy, connect via psql +4. **Quick Start: ProxySQL Integration** — deploy replication with `--with-proxysql`, connect through proxy + +These are the hook — short, copy-pasteable, satisfying in under 2 minutes. + +### Docs + +The 44 existing wiki pages reorganized into a Starlight sidebar: + +``` +Getting Started + ├── Installation + ├── Quick Start: MySQL Single + ├── Quick Start: MySQL Replication + ├── Quick Start: PostgreSQL + └── Quick Start: ProxySQL Integration + +Core Concepts + ├── Sandboxes + ├── Versions & Flavors + ├── Ports & Networking + └── Environment Variables + +Deploying + ├── Single Sandbox + ├── Multiple Sandboxes + ├── Replication + ├── Group Replication + ├── Fan-In & All-Masters + └── NDB Cluster + +Providers + ├── MySQL + ├── PostgreSQL + ├── ProxySQL + └── Percona XtraDB Cluster + +Managing Sandboxes + ├── Starting & Stopping + ├── Using Sandboxes + ├── Customization + ├── Database Users + ├── Logs + └── Deletion & Cleanup + +Advanced + ├── Concurrent Deployment + ├── Importing Databases + ├── Inter-Sandbox Replication + ├── Cloning + ├── Using as a Go Library + └── Compiling from Source + +Reference + ├── CLI Commands + ├── Configuration + └── API Changelog +``` + +**Content strategy:** existing wiki markdown is kept mostly as-is. Navigation is restructured. Pages that don't fit are merged or dropped. Frontmatter is added/adjusted during the build copy step. + +### Providers Page + +Custom layout at `/providers` — the marketing angle for the provider architecture. + +**Structure:** + +1. **Intro** — dbdeployer's provider architecture, one CLI for multiple databases +2. **Comparison matrix:** + +| | MySQL | PostgreSQL | ProxySQL | +|---|---|---|---| +| Single sandbox | ✓ | ✓ | ✓ | +| Multiple sandboxes | ✓ | ✓ | — | +| Replication | ✓ | ✓ (streaming) | — | +| Group replication | ✓ | — | — | +| ProxySQL wiring | ✓ | ✓ | — | +| Binary source | Tarballs | .deb extraction | System binary | + +3. **Per-provider cards** — description, example command, link to docs +4. **"Coming Soon" teaser** — Orchestrator integration (from roadmap) + +This is where ProxySQL gets introduced naturally — users browsing providers see the integration story. + +### Blog + +Content collection in `src/content/blog/`. Each post is a `.md` with frontmatter (title, date, author, tags, description). + +**Blog index** at `/blog` — reverse-chronological, custom layout. + +**Launch posts:** +1. "dbdeployer Under New Maintainership" — ProxySQL team story, what changed, roadmap +2. "PostgreSQL Support is Here" — Phase 3 announcement, examples + +**Home integration:** latest 1-2 posts shown in "What's New" strip above footer. + +## Docs Content Pipeline + +Wiki pages are authored in `docs/wiki/` (close to the Go code). A build step copies them into Starlight's content collection: + +1. GitHub Actions triggers on push to `master` when `website/**` or `docs/wiki/**` change +2. A script copies `docs/wiki/*.md` into `website/src/content/docs/`, mapping filenames to the sidebar structure and adding/adjusting Starlight frontmatter +3. `npm run build` generates the static site +4. Built output is deployed to `gh-pages` branch + +This means: +- Docs live near the code (developers edit `docs/wiki/`) +- The website automatically picks up changes +- No manual sync between repo and site + +## Deployment + +**Workflow:** `.github/workflows/deploy-website.yml` + +Triggers: +- Push to `master` when `website/**` or `docs/wiki/**` change +- Manual `workflow_dispatch` + +Steps: +1. Checkout repo +2. Setup Node.js (LTS) +3. `npm ci` in `website/` +4. Run copy script: `docs/wiki/*.md` → `website/src/content/docs/` with frontmatter mapping +5. `npm run build` +6. Deploy `dist/` to `gh-pages` branch via `actions/deploy-pages` + +**Site URL:** `proxysql.github.io/dbdeployer` (GitHub Pages default for org repos). Custom domain can be configured later. + +**GitHub Pages config:** Settings → Pages → Source: GitHub Actions. From a4045a7cc2d989fc5860764393306cc7a16dedb2 Mon Sep 17 00:00:00 2001 From: Rene Cannao Date: Tue, 24 Mar 2026 17:26:54 +0000 Subject: [PATCH 05/15] docs: update website spec with mapping table, link rewriting, API strategy, assets --- .../specs/2026-03-24-website-design.md | 130 ++++++++++++++++-- 1 file changed, 121 insertions(+), 9 deletions(-) diff --git a/docs/superpowers/specs/2026-03-24-website-design.md b/docs/superpowers/specs/2026-03-24-website-design.md index 31985b4..645d103 100644 --- a/docs/superpowers/specs/2026-03-24-website-design.md +++ b/docs/superpowers/specs/2026-03-24-website-design.md @@ -18,8 +18,10 @@ dbdeployer has rich documentation (44 wiki pages, 54 API versions, ProxySQL guid - **Framework:** Astro with Starlight integration (Astro's official docs theme) - **Why Starlight:** sidebar navigation, Pagefind search, dark/light mode, content collections, i18n-ready — all out of the box. Custom pages (landing, providers, blog) use standard Astro layouts outside Starlight. +- **Node.js:** 20 LTS (Astro 4.x requires Node 18.17+) - **Hosting:** GitHub Pages - **Deployment:** GitHub Actions → builds Astro → pushes to `gh-pages` branch +- **Base path:** `astro.config.mjs` must set `base: '/dbdeployer'` and `site: 'https://proxysql.github.io'` since this is a project repo (not org root) ## Project Structure @@ -29,16 +31,25 @@ website/ package.json src/ content/ + config.ts # Content collection schemas (docs + blog) docs/ # Starlight docs (migrated wiki pages) blog/ # Blog posts as .md files pages/ index.astro # Landing page (custom, not Starlight) providers.astro # Providers comparison page + 404.astro # Custom 404 page (links back to home/docs) + blog/ + index.astro # Blog index (reverse-chronological list) + [...slug].astro # Individual blog post pages components/ # Reusable Astro components (Hero, FeatureGrid, etc.) layouts/ # Custom layouts for landing/blog styles/ # Global CSS public/ + favicon.svg # Site favicon + og-image.png # Default Open Graph image for social sharing images/ # Screenshots, diagrams + scripts/ + copy-wiki.sh # Build step: copies docs/wiki/ into src/content/docs/ ``` Source lives in `website/` at the repo root. The `gh-pages` branch contains only the built output. @@ -152,6 +163,8 @@ Custom layout at `/providers` — the marketing angle for the provider architect | ProxySQL wiring | ✓ | ✓ | — | | Binary source | Tarballs | .deb extraction | System binary | +Note: MariaDB and Percona Server are MySQL-compatible flavors (same binary format, same provider) and are not listed as separate columns. The docs explain this under Providers > MySQL. + 3. **Per-provider cards** — description, example command, link to docs 4. **"Coming Soon" teaser** — Orchestrator integration (from roadmap) @@ -171,18 +184,117 @@ Content collection in `src/content/blog/`. Each post is a `.md` with frontmatter ## Docs Content Pipeline -Wiki pages are authored in `docs/wiki/` (close to the Go code). A build step copies them into Starlight's content collection: - -1. GitHub Actions triggers on push to `master` when `website/**` or `docs/wiki/**` change -2. A script copies `docs/wiki/*.md` into `website/src/content/docs/`, mapping filenames to the sidebar structure and adding/adjusting Starlight frontmatter -3. `npm run build` generates the static site -4. Built output is deployed to `gh-pages` branch +Wiki pages are authored in `docs/wiki/` (close to the Go code). A build script (`website/scripts/copy-wiki.sh`) copies them into Starlight's content collection with transformations. + +### Copy Script Responsibilities + +The script (`copy-wiki.sh`) runs before `npm run build` and does: + +1. **Copy files** from `docs/wiki/*.md` into `website/src/content/docs/
/` per the mapping table below +2. **Normalize filenames** — remove commas, double dots, convert to lowercase kebab-case +3. **Add Starlight frontmatter** — inject `title:` and `sidebar:` fields based on the mapping +4. **Rewrite links** — convert wiki-style links (`[text](other-page.md)`) to Starlight paths (`[text](/docs/
/other-page/)`) +5. **Strip wiki navigation** — remove `[[HOME]]`-style nav links (Starlight sidebar replaces these) +6. **Copy ProxySQL guide** — `docs/proxysql-guide.md` → `website/src/content/docs/providers/proxysql.md` + +### Wiki Page Mapping + +| Wiki File | Target Path | Sidebar Label | +|---|---|---| +| `installation.md` | `getting-started/installation` | Installation | +| *(new content)* | `getting-started/quickstart-mysql-single` | Quick Start: MySQL Single | +| *(new content)* | `getting-started/quickstart-mysql-replication` | Quick Start: MySQL Replication | +| *(new content)* | `getting-started/quickstart-postgresql` | Quick Start: PostgreSQL | +| *(new content)* | `getting-started/quickstart-proxysql` | Quick Start: ProxySQL Integration | +| `default-sandbox.md` | `concepts/sandboxes` | Sandboxes | +| `database-server-flavors.md` | `concepts/flavors` | Versions & Flavors | +| `ports-management.md` | `concepts/ports` | Ports & Networking | +| `../env_variables.md` | `concepts/environment-variables` | Environment Variables | +| `main-operations.md` | `deploying/single` | Single Sandbox | +| `multiple-sandboxes,-same-version-and-type.md` | `deploying/multiple` | Multiple Sandboxes | +| `replication-topologies.md` | `deploying/replication` | Replication | +| *(extract from replication-topologies.md)* | `deploying/group-replication` | Group Replication | +| *(extract from replication-topologies.md)* | `deploying/fan-in-all-masters` | Fan-In & All-Masters | +| *(extract from replication-topologies.md)* | `deploying/ndb-cluster` | NDB Cluster | +| `standard-and-non-standard-basedir-names.md` | `providers/mysql` | MySQL | +| *(new content)* | `providers/postgresql` | PostgreSQL | +| `../proxysql-guide.md` | `providers/proxysql` | ProxySQL | +| *(extract from replication-topologies.md)* | `providers/pxc` | Percona XtraDB Cluster | +| `skip-server-start.md` + `sandbox-management.md` | `managing/starting-stopping` | Starting & Stopping | +| `using-the-latest-sandbox.md` | `managing/using` | Using Sandboxes | +| `sandbox-customization.md` | `managing/customization` | Customization | +| `database-users.md` | `managing/users` | Database Users | +| `database-logs-management..md` | `managing/logs` | Logs | +| `sandbox-deletion.md` | `managing/deletion` | Deletion & Cleanup | +| `concurrent-deployment-and-deletion.md` | `advanced/concurrent` | Concurrent Deployment | +| `importing-databases-into-sandboxes.md` | `advanced/importing` | Importing Databases | +| `replication-between-sandboxes.md` | `advanced/inter-sandbox-replication` | Inter-Sandbox Replication | +| `cloning-databases.md` | `advanced/cloning` | Cloning | +| `using-dbdeployer-source-for-other-projects.md` | `advanced/go-library` | Using as a Go Library | +| `compiling-dbdeployer.md` | `advanced/compiling` | Compiling from Source | +| `command-line-completion.md` | `reference/cli-commands` | CLI Commands | +| `initializing-the-environment.md` | `reference/configuration` | Configuration | +| *(consolidated)* | `reference/api-changelog` | API Changelog | + +### Dropped/Merged Pages + +These wiki pages are NOT mapped to the sidebar (content merged into other pages or no longer relevant): + +| Wiki File | Disposition | +|---|---| +| `Home.md` | Replaced by landing page | +| `do-not-edit.md` | Internal tooling note, drop | +| `generating-additional-documentation.md` | Internal tooling, drop | +| `semantic-versioning.md` | Merge into Reference > Configuration | +| `practical-examples.md` | Content absorbed into quickstart guides | +| `sandbox-macro-operations.md` | Merge into Managing > Using Sandboxes | +| `sandbox-upgrade.md` | Merge into Managing > Using Sandboxes | +| `dedicated-admin-address.md` | Merge into Deploying > Single Sandbox | +| `running-sysbench.md` | Merge into Advanced > Importing (or drop) | +| `mysql-document-store,-mysqlsh,-and-defaults..md` | Merge into Providers > MySQL | +| `installing-mysql-shell.md` | Merge into Providers > MySQL | +| `loading-sample-data-into-sandboxes.md` | Merge into Advanced > Importing | +| `using-dbdeployer-in-scripts.md` | Merge into Advanced > Go Library | +| `using-short-version-numbers.md` | Merge into Concepts > Versions & Flavors | +| `using-the-direct-path-to-the-expanded-tarball.md` | Merge into Concepts > Versions & Flavors | +| `getting-remote-tarballs.md` | Merge into Getting Started > Installation | +| `updating-dbdeployer.md` | Merge into Getting Started > Installation | +| `obtaining-sandbox-metadata.md` | Merge into Managing > Using Sandboxes | +| `exporting-dbdeployer-structure.md` | Merge into Reference > CLI Commands | +| `dbdeployer-operations-logging.md` | Merge into Managing > Logs | + +### API Changelog Strategy + +The 54 API version files (`docs/API/API-1.0.md` through `docs/API/1.68.md`) are **not** published individually. Instead: + +- A single `reference/api-changelog.md` page is generated that consolidates the last 5 versions with full content +- Older versions link to the GitHub directory: "See [full API history on GitHub](https://github.com/ProxySQL/dbdeployer/tree/master/docs/API)" + +This keeps the sidebar clean and avoids 54 pages of version diffs. + +### Pipeline Summary -This means: - Docs live near the code (developers edit `docs/wiki/`) - The website automatically picks up changes - No manual sync between repo and site +## Assets & Metadata + +### SEO & Social + +- **Favicon:** `public/favicon.svg` — simple dbdeployer logo/icon +- **OG image:** `public/og-image.png` — branded card (1200x630) with tagline, used as default `og:image` +- **Meta tags:** Starlight handles `` and `<meta description>` from frontmatter for docs pages. Custom pages (landing, providers, blog) set their own `<meta>` tags in `<head>` +- **Sitemap:** Astro's `@astrojs/sitemap` integration generates `sitemap.xml` automatically + +### Wiki Deprecation + +After the website launches, add a notice to the top of the GitHub wiki `Home.md` (if the wiki is still accessible): + +> "This wiki has moved to [proxysql.github.io/dbdeployer](https://proxysql.github.io/dbdeployer/docs/). These pages are no longer maintained." + +The wiki pages in `docs/wiki/` remain in the repo as the source of truth — they're just served through the website now. + ## Deployment **Workflow:** `.github/workflows/deploy-website.yml` @@ -193,9 +305,9 @@ Triggers: Steps: 1. Checkout repo -2. Setup Node.js (LTS) +2. Setup Node.js 20 LTS (`actions/setup-node` with `node-version: '20'`) 3. `npm ci` in `website/` -4. Run copy script: `docs/wiki/*.md` → `website/src/content/docs/` with frontmatter mapping +4. Run copy script: `bash website/scripts/copy-wiki.sh` — transforms and copies `docs/wiki/*.md` into `website/src/content/docs/` 5. `npm run build` 6. Deploy `dist/` to `gh-pages` branch via `actions/deploy-pages` From 57ae0658b10435b9291b0356b67db1e430ded29c Mon Sep 17 00:00:00 2001 From: Rene Cannao <rene@proxysql.com> Date: Tue, 24 Mar 2026 17:33:56 +0000 Subject: [PATCH 06/15] docs: add website implementation plan 10 tasks: scaffold, wiki pipeline, stub docs, quickstart guides, landing page, providers page, blog, 404/OG, GitHub Actions, verification. --- docs/superpowers/plans/2026-03-24-website.md | 927 +++++++++++++++++++ 1 file changed, 927 insertions(+) create mode 100644 docs/superpowers/plans/2026-03-24-website.md diff --git a/docs/superpowers/plans/2026-03-24-website.md b/docs/superpowers/plans/2026-03-24-website.md new file mode 100644 index 0000000..165209c --- /dev/null +++ b/docs/superpowers/plans/2026-03-24-website.md @@ -0,0 +1,927 @@ +# dbdeployer Website Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** Build a documentation website for dbdeployer with Astro + Starlight, deployed to GitHub Pages, featuring a marketing landing page, migrated wiki docs, quickstart guides, providers page, and blog. + +**Architecture:** Astro project in `website/` at the repo root. Starlight handles the docs section (sidebar, search, dark mode). Custom Astro pages for landing, providers, and blog. A shell script copies `docs/wiki/*.md` into Starlight's content collection with frontmatter injection and link rewriting. GitHub Actions builds and deploys to `gh-pages` on push. + +**Tech Stack:** Astro 4.x, Starlight, Node.js 20 LTS, GitHub Actions, GitHub Pages + +**Spec:** `docs/superpowers/specs/2026-03-24-website-design.md` + +--- + +## File Structure + +### New Files (in `website/`) +- `package.json` — Astro project dependencies +- `astro.config.mjs` — Astro + Starlight config with sidebar, base path, sitemap +- `tsconfig.json` — TypeScript config (Astro default) +- `src/content/config.ts` — Content collection schemas (docs via Starlight, blog custom) +- `src/pages/index.astro` — Landing page +- `src/pages/providers.astro` — Providers comparison page +- `src/pages/404.astro` — Custom 404 +- `src/pages/blog/index.astro` — Blog index +- `src/pages/blog/[...slug].astro` — Blog post pages +- `src/components/Hero.astro` — Hero section component +- `src/components/FeatureGrid.astro` — Feature cards grid +- `src/components/ProviderCard.astro` — Provider card component +- `src/components/Terminal.astro` — Terminal demo component +- `src/components/BlogPostCard.astro` — Blog post preview card +- `src/layouts/Landing.astro` — Layout for marketing pages +- `src/layouts/BlogPost.astro` — Layout for blog posts +- `src/styles/global.css` — Global styles +- `src/content/blog/2026-03-24-new-maintainership.md` — Launch blog post 1 +- `src/content/blog/2026-03-24-postgresql-support.md` — Launch blog post 2 +- `src/content/docs/getting-started/quickstart-mysql-single.md` — New quickstart guide +- `src/content/docs/getting-started/quickstart-mysql-replication.md` — New quickstart guide +- `src/content/docs/getting-started/quickstart-postgresql.md` — New quickstart guide +- `src/content/docs/getting-started/quickstart-proxysql.md` — New quickstart guide +- `src/content/docs/providers/postgresql.md` — New provider docs +- `public/favicon.svg` — Favicon +- `public/og-image.png` — OG social image (placeholder) +- `scripts/copy-wiki.sh` — Wiki content pipeline script + +### New Files (repo root) +- `.github/workflows/deploy-website.yml` — GitHub Actions deployment workflow + +--- + +## Task 1: Scaffold Astro + Starlight Project + +**Files:** +- Create: `website/package.json` +- Create: `website/astro.config.mjs` +- Create: `website/tsconfig.json` +- Create: `website/src/content/config.ts` +- Create: `website/src/content/docs/index.mdx` (Starlight requires at least one doc) +- Create: `website/public/favicon.svg` + +- [ ] **Step 1: Initialize Astro project** + +```bash +cd /data/rene/dbdeployer +mkdir -p website +cd website +npm create astro@latest -- --template starlight --no-git --no-install -y . +``` + +If the template prompt is interactive, manually create the files instead: + +```bash +npm init -y +npm install astro @astrojs/starlight @astrojs/sitemap +``` + +- [ ] **Step 2: Configure astro.config.mjs** + +```js +import { defineConfig } from 'astro/config'; +import starlight from '@astrojs/starlight'; +import sitemap from '@astrojs/sitemap'; + +export default defineConfig({ + site: 'https://proxysql.github.io', + base: '/dbdeployer', + integrations: [ + starlight({ + title: 'dbdeployer', + description: 'Deploy MySQL & PostgreSQL sandboxes in seconds', + social: { + github: 'https://github.com/ProxySQL/dbdeployer', + }, + sidebar: [ + { + label: 'Getting Started', + items: [ + { label: 'Installation', slug: 'getting-started/installation' }, + { label: 'Quick Start: MySQL Single', slug: 'getting-started/quickstart-mysql-single' }, + { label: 'Quick Start: MySQL Replication', slug: 'getting-started/quickstart-mysql-replication' }, + { label: 'Quick Start: PostgreSQL', slug: 'getting-started/quickstart-postgresql' }, + { label: 'Quick Start: ProxySQL Integration', slug: 'getting-started/quickstart-proxysql' }, + ], + }, + { + label: 'Core Concepts', + items: [ + { label: 'Sandboxes', slug: 'concepts/sandboxes' }, + { label: 'Versions & Flavors', slug: 'concepts/flavors' }, + { label: 'Ports & Networking', slug: 'concepts/ports' }, + { label: 'Environment Variables', slug: 'concepts/environment-variables' }, + ], + }, + { + label: 'Deploying', + items: [ + { label: 'Single Sandbox', slug: 'deploying/single' }, + { label: 'Multiple Sandboxes', slug: 'deploying/multiple' }, + { label: 'Replication', slug: 'deploying/replication' }, + { label: 'Group Replication', slug: 'deploying/group-replication' }, + { label: 'Fan-In & All-Masters', slug: 'deploying/fan-in-all-masters' }, + { label: 'NDB Cluster', slug: 'deploying/ndb-cluster' }, + ], + }, + { + label: 'Providers', + items: [ + { label: 'MySQL', slug: 'providers/mysql' }, + { label: 'PostgreSQL', slug: 'providers/postgresql' }, + { label: 'ProxySQL', slug: 'providers/proxysql' }, + { label: 'Percona XtraDB Cluster', slug: 'providers/pxc' }, + ], + }, + { + label: 'Managing Sandboxes', + items: [ + { label: 'Starting & Stopping', slug: 'managing/starting-stopping' }, + { label: 'Using Sandboxes', slug: 'managing/using' }, + { label: 'Customization', slug: 'managing/customization' }, + { label: 'Database Users', slug: 'managing/users' }, + { label: 'Logs', slug: 'managing/logs' }, + { label: 'Deletion & Cleanup', slug: 'managing/deletion' }, + ], + }, + { + label: 'Advanced', + items: [ + { label: 'Concurrent Deployment', slug: 'advanced/concurrent' }, + { label: 'Importing Databases', slug: 'advanced/importing' }, + { label: 'Inter-Sandbox Replication', slug: 'advanced/inter-sandbox-replication' }, + { label: 'Cloning', slug: 'advanced/cloning' }, + { label: 'Using as a Go Library', slug: 'advanced/go-library' }, + { label: 'Compiling from Source', slug: 'advanced/compiling' }, + ], + }, + { + label: 'Reference', + items: [ + { label: 'CLI Commands', slug: 'reference/cli-commands' }, + { label: 'Configuration', slug: 'reference/configuration' }, + { label: 'API Changelog', slug: 'reference/api-changelog' }, + ], + }, + ], + }), + sitemap(), + ], +}); +``` + +- [ ] **Step 3: Create minimal tsconfig.json** + +```json +{ + "extends": "astro/tsconfigs/strict" +} +``` + +- [ ] **Step 4: Create content collection config** + +Create `src/content/config.ts`: + +```ts +import { defineCollection, z } from 'astro:content'; +import { docsSchema } from '@astrojs/starlight/schema'; + +const blog = defineCollection({ + type: 'content', + schema: z.object({ + title: z.string(), + date: z.date(), + author: z.string(), + description: z.string(), + tags: z.array(z.string()).optional(), + }), +}); + +export const collections = { + docs: defineCollection({ schema: docsSchema() }), + blog, +}; +``` + +- [ ] **Step 5: Create placeholder doc page** + +Create `src/content/docs/index.mdx`: + +```mdx +--- +title: dbdeployer Documentation +description: Deploy MySQL & PostgreSQL sandboxes in seconds +--- + +Welcome to dbdeployer documentation. Use the sidebar to navigate. +``` + +- [ ] **Step 6: Create favicon** + +Create `public/favicon.svg` — a simple database icon: + +```svg +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32" fill="none"> + <ellipse cx="16" cy="8" rx="12" ry="5" fill="#3b82f6" opacity="0.9"/> + <path d="M4 8v8c0 2.76 5.37 5 12 5s12-2.24 12-5V8" stroke="#3b82f6" stroke-width="2" fill="none"/> + <path d="M4 16v8c0 2.76 5.37 5 12 5s12-2.24 12-5v-8" stroke="#3b82f6" stroke-width="2" fill="none"/> +</svg> +``` + +- [ ] **Step 7: Install dependencies and verify build** + +```bash +cd website +npm install +npm run build +``` + +Expected: Build succeeds, `dist/` directory created. + +- [ ] **Step 8: Verify dev server** + +```bash +npm run dev +``` + +Expected: Starlight docs site loads at `http://localhost:4321/dbdeployer/` with the placeholder doc page. + +- [ ] **Step 9: Commit** + +```bash +cd /data/rene/dbdeployer +git add website/ +git commit -m "feat: scaffold Astro + Starlight website project" +``` + +--- + +## Task 2: Wiki Content Pipeline (copy-wiki.sh) + +**Files:** +- Create: `website/scripts/copy-wiki.sh` + +- [ ] **Step 1: Create the copy script** + +Create `website/scripts/copy-wiki.sh`: + +```bash +#!/bin/bash +set -euo pipefail + +REPO_ROOT="$(cd "$(dirname "$0")/../.." && pwd)" +WIKI_DIR="$REPO_ROOT/docs/wiki" +DOCS_DIR="$(cd "$(dirname "$0")/.." && pwd)/src/content/docs" + +# Clean previously copied docs (preserve new content written directly) +# Only clean directories that map from wiki +for dir in concepts deploying providers managing advanced reference; do + rm -rf "$DOCS_DIR/$dir" +done + +# Create target directories +for dir in getting-started concepts deploying providers managing advanced reference; do + mkdir -p "$DOCS_DIR/$dir" +done + +# Function to copy a wiki file with frontmatter and link rewriting +copy_wiki() { + local src="$1" + local dst="$2" + local title="$3" + + if [ ! -f "$src" ]; then + echo "WARNING: Source file not found: $src" + return + fi + + # Create frontmatter + content, strip wiki nav links, rewrite .md links + { + echo "---" + echo "title: \"$title\"" + echo "---" + echo "" + cat "$src" + } | sed '/\[\[HOME\]\]/d' \ + | sed 's/\[([^]]*)\](\([^)]*\)\.md)/[\1](\/dbdeployer\/docs\/\2\/)/g' \ + > "$dst" + + echo " Copied: $(basename "$src") -> $dst" +} + +echo "=== Copying wiki pages ===" + +# Getting Started +copy_wiki "$WIKI_DIR/installation.md" "$DOCS_DIR/getting-started/installation.md" "Installation" + +# Core Concepts +copy_wiki "$WIKI_DIR/default-sandbox.md" "$DOCS_DIR/concepts/sandboxes.md" "Sandboxes" +copy_wiki "$WIKI_DIR/database-server-flavors.md" "$DOCS_DIR/concepts/flavors.md" "Versions & Flavors" +copy_wiki "$WIKI_DIR/ports-management.md" "$DOCS_DIR/concepts/ports.md" "Ports & Networking" +copy_wiki "$REPO_ROOT/docs/env_variables.md" "$DOCS_DIR/concepts/environment-variables.md" "Environment Variables" + +# Deploying +copy_wiki "$WIKI_DIR/main-operations.md" "$DOCS_DIR/deploying/single.md" "Single Sandbox" +copy_wiki "$WIKI_DIR/multiple-sandboxes,-same-version-and-type.md" "$DOCS_DIR/deploying/multiple.md" "Multiple Sandboxes" +copy_wiki "$WIKI_DIR/replication-topologies.md" "$DOCS_DIR/deploying/replication.md" "Replication" + +# Providers +copy_wiki "$WIKI_DIR/standard-and-non-standard-basedir-names.md" "$DOCS_DIR/providers/mysql.md" "MySQL" +copy_wiki "$REPO_ROOT/docs/proxysql-guide.md" "$DOCS_DIR/providers/proxysql.md" "ProxySQL" + +# Managing Sandboxes +copy_wiki "$WIKI_DIR/sandbox-management.md" "$DOCS_DIR/managing/starting-stopping.md" "Starting & Stopping" +copy_wiki "$WIKI_DIR/using-the-latest-sandbox.md" "$DOCS_DIR/managing/using.md" "Using Sandboxes" +copy_wiki "$WIKI_DIR/sandbox-customization.md" "$DOCS_DIR/managing/customization.md" "Customization" +copy_wiki "$WIKI_DIR/database-users.md" "$DOCS_DIR/managing/users.md" "Database Users" +copy_wiki "$WIKI_DIR/database-logs-management..md" "$DOCS_DIR/managing/logs.md" "Logs" +copy_wiki "$WIKI_DIR/sandbox-deletion.md" "$DOCS_DIR/managing/deletion.md" "Deletion & Cleanup" + +# Advanced +copy_wiki "$WIKI_DIR/concurrent-deployment-and-deletion.md" "$DOCS_DIR/advanced/concurrent.md" "Concurrent Deployment" +copy_wiki "$WIKI_DIR/importing-databases-into-sandboxes.md" "$DOCS_DIR/advanced/importing.md" "Importing Databases" +copy_wiki "$WIKI_DIR/replication-between-sandboxes.md" "$DOCS_DIR/advanced/inter-sandbox-replication.md" "Inter-Sandbox Replication" +copy_wiki "$WIKI_DIR/cloning-databases.md" "$DOCS_DIR/advanced/cloning.md" "Cloning" +copy_wiki "$WIKI_DIR/using-dbdeployer-source-for-other-projects.md" "$DOCS_DIR/advanced/go-library.md" "Using as a Go Library" +copy_wiki "$WIKI_DIR/compiling-dbdeployer.md" "$DOCS_DIR/advanced/compiling.md" "Compiling from Source" + +# Reference +copy_wiki "$WIKI_DIR/command-line-completion.md" "$DOCS_DIR/reference/cli-commands.md" "CLI Commands" +copy_wiki "$WIKI_DIR/initializing-the-environment.md" "$DOCS_DIR/reference/configuration.md" "Configuration" + +echo "=== Done ===" +``` + +- [ ] **Step 2: Make executable and test** + +```bash +chmod +x website/scripts/copy-wiki.sh +bash website/scripts/copy-wiki.sh +``` + +Expected: Files copied with frontmatter into `website/src/content/docs/` subdirectories. + +- [ ] **Step 3: Verify build with copied docs** + +```bash +cd website && npm run build +``` + +Expected: Build succeeds. Some wiki pages may have broken internal links (acceptable for now — link rewriting is best-effort via sed). + +- [ ] **Step 4: Commit** + +```bash +git add website/scripts/copy-wiki.sh +git commit -m "feat: add wiki content pipeline script (copy-wiki.sh)" +``` + +--- + +## Task 3: Stub Docs for Sidebar Completeness + +Several sidebar entries need placeholder pages that don't come from the wiki (new content, extracted sections, or consolidated pages). Create stubs so the build doesn't break on missing slugs. + +**Files:** +- Create: Multiple stub .md files in `website/src/content/docs/` + +- [ ] **Step 1: Create stub pages** + +For each of these, create a minimal markdown file with frontmatter: + +```bash +# Deploying section (extracted from replication-topologies.md — write stubs for now) +cat > website/src/content/docs/deploying/group-replication.md << 'EOF' +--- +title: "Group Replication" +--- + +Content to be extracted from the replication topologies page. See [Replication](/dbdeployer/docs/deploying/replication/) for the full reference. +EOF + +cat > website/src/content/docs/deploying/fan-in-all-masters.md << 'EOF' +--- +title: "Fan-In & All-Masters" +--- + +Content to be extracted from the replication topologies page. See [Replication](/dbdeployer/docs/deploying/replication/) for the full reference. +EOF + +cat > website/src/content/docs/deploying/ndb-cluster.md << 'EOF' +--- +title: "NDB Cluster" +--- + +Content to be extracted from the replication topologies page. See [Replication](/dbdeployer/docs/deploying/replication/) for the full reference. +EOF + +# Providers section +cat > website/src/content/docs/providers/postgresql.md << 'EOF' +--- +title: "PostgreSQL" +--- + +PostgreSQL support is available starting with dbdeployer v1.75.0. + +## Binary Management + +PostgreSQL does not distribute pre-compiled tarballs. Use `.deb` packages: + +```bash +apt-get download postgresql-16 postgresql-client-16 +dbdeployer unpack --provider=postgresql postgresql-16_*.deb postgresql-client-16_*.deb +``` + +## Deploy a Single Sandbox + +```bash +dbdeployer deploy postgresql 16.13 +``` + +## Streaming Replication + +```bash +dbdeployer deploy replication 16.13 --provider=postgresql +``` + +## With ProxySQL + +```bash +dbdeployer deploy replication 16.13 --provider=postgresql --with-proxysql +``` +EOF + +cat > website/src/content/docs/providers/pxc.md << 'EOF' +--- +title: "Percona XtraDB Cluster" +--- + +Percona XtraDB Cluster (PXC) is deployed using the `pxc` topology within the MySQL provider. + +```bash +dbdeployer deploy replication 8.0.35 --topology=pxc +``` + +See [Replication](/dbdeployer/docs/deploying/replication/) for topology details. +EOF + +# Reference +cat > website/src/content/docs/reference/api-changelog.md << 'EOF' +--- +title: "API Changelog" +--- + +See the [full API history on GitHub](https://github.com/ProxySQL/dbdeployer/tree/master/docs/API) for all versions. +EOF +``` + +- [ ] **Step 2: Run copy script + build** + +```bash +bash website/scripts/copy-wiki.sh +cd website && npm run build +``` + +Expected: Build succeeds with all sidebar entries resolving. + +- [ ] **Step 3: Commit** + +```bash +git add website/src/content/docs/ +git commit -m "feat: add stub docs for sidebar completeness" +``` + +--- + +## Task 4: Getting Started Quickstart Guides + +**Files:** +- Create: `website/src/content/docs/getting-started/quickstart-mysql-single.md` +- Create: `website/src/content/docs/getting-started/quickstart-mysql-replication.md` +- Create: `website/src/content/docs/getting-started/quickstart-postgresql.md` +- Create: `website/src/content/docs/getting-started/quickstart-proxysql.md` + +These are **new content** — polished, tutorial-style, written fresh. Each should be short (under 50 lines), copy-pasteable, and satisfying in under 2 minutes. + +- [ ] **Step 1: Write MySQL Single quickstart** + +Create `website/src/content/docs/getting-started/quickstart-mysql-single.md`: + +```markdown +--- +title: "Quick Start: MySQL Single" +description: "Deploy a MySQL sandbox in 30 seconds" +--- + +## 1. Install dbdeployer + +```bash +# Download the latest release +curl -L https://github.com/ProxySQL/dbdeployer/releases/latest/download/dbdeployer-linux-amd64 -o dbdeployer +chmod +x dbdeployer +sudo mv dbdeployer /usr/local/bin/ +``` + +## 2. Get MySQL binaries + +```bash +dbdeployer downloads get-by-version 8.4 +``` + +## 3. Deploy + +```bash +dbdeployer deploy single 8.4.4 +``` + +## 4. Connect + +```bash +~/sandboxes/msb_8_4_4/use +``` + +You're now in a MySQL shell. Try: + +```sql +SELECT @@version; +SHOW DATABASES; +``` + +## 5. Clean up + +```bash +dbdeployer delete msb_8_4_4 +``` +``` + +- [ ] **Step 2: Write MySQL Replication quickstart** + +Create `website/src/content/docs/getting-started/quickstart-mysql-replication.md` — similar structure: deploy replication, show `./check_slaves`, connect to master/slave. + +- [ ] **Step 3: Write PostgreSQL quickstart** + +Create `website/src/content/docs/getting-started/quickstart-postgresql.md` — unpack debs, deploy, connect via psql, clean up. + +- [ ] **Step 4: Write ProxySQL Integration quickstart** + +Create `website/src/content/docs/getting-started/quickstart-proxysql.md` — deploy replication with `--with-proxysql`, connect through proxy, show hostgroup routing. + +- [ ] **Step 5: Verify build** + +```bash +bash website/scripts/copy-wiki.sh +cd website && npm run build +``` + +- [ ] **Step 6: Commit** + +```bash +git add website/src/content/docs/getting-started/ +git commit -m "feat: add getting started quickstart guides" +``` + +--- + +## Task 5: Landing Page + +**Files:** +- Create: `website/src/layouts/Landing.astro` +- Create: `website/src/components/Hero.astro` +- Create: `website/src/components/FeatureGrid.astro` +- Create: `website/src/components/Terminal.astro` +- Create: `website/src/styles/global.css` +- Create: `website/src/pages/index.astro` + +- [ ] **Step 1: Create Landing layout** + +`src/layouts/Landing.astro` — full HTML layout with nav, main slot, footer. Includes global CSS. Sets OG meta tags. + +- [ ] **Step 2: Create Hero component** + +`src/components/Hero.astro` — tagline, subtitle, two CTA buttons (Get Started, View on GitHub). + +- [ ] **Step 3: Create FeatureGrid component** + +`src/components/FeatureGrid.astro` — 4 feature cards: Any Topology, Multiple Databases, ProxySQL Integration, No Root/No Docker. + +- [ ] **Step 4: Create Terminal component** + +`src/components/Terminal.astro` — styled code block showing a deploy + connect flow. Static (not animated — keep it simple for v1). + +- [ ] **Step 5: Create global CSS** + +`src/styles/global.css` — CSS custom properties for colors, basic typography, responsive utilities. Keep minimal — Starlight handles docs styling. + +- [ ] **Step 6: Assemble landing page** + +`src/pages/index.astro` — imports Landing layout and all components. Includes: +1. Nav bar +2. Hero +3. Quick install snippet (`<code>` block with copy button) +4. Feature grid +5. Terminal demo +6. Provider cards linking to `/providers` +7. Footer + +- [ ] **Step 7: Verify locally** + +```bash +cd website && npm run dev +``` + +Open `http://localhost:4321/dbdeployer/` — landing page should render with all sections. + +- [ ] **Step 8: Commit** + +```bash +git add website/src/layouts/ website/src/components/ website/src/styles/ website/src/pages/index.astro +git commit -m "feat: add marketing landing page with hero, features, and terminal demo" +``` + +--- + +## Task 6: Providers Page + +**Files:** +- Create: `website/src/components/ProviderCard.astro` +- Create: `website/src/pages/providers.astro` + +- [ ] **Step 1: Create ProviderCard component** + +`src/components/ProviderCard.astro` — accepts name, description, example command, docs link. Renders a card with code snippet. + +- [ ] **Step 2: Create providers page** + +`src/pages/providers.astro` — uses Landing layout. Contains: +1. Intro paragraph about provider architecture +2. HTML comparison matrix table (from spec) +3. Three ProviderCards (MySQL, PostgreSQL, ProxySQL) +4. "Coming Soon" teaser for Orchestrator + +- [ ] **Step 3: Verify locally** + +Visit `http://localhost:4321/dbdeployer/providers` — comparison matrix and cards render correctly. + +- [ ] **Step 4: Commit** + +```bash +git add website/src/components/ProviderCard.astro website/src/pages/providers.astro +git commit -m "feat: add providers comparison page" +``` + +--- + +## Task 7: Blog + +**Files:** +- Create: `website/src/layouts/BlogPost.astro` +- Create: `website/src/components/BlogPostCard.astro` +- Create: `website/src/pages/blog/index.astro` +- Create: `website/src/pages/blog/[...slug].astro` +- Create: `website/src/content/blog/2026-03-24-new-maintainership.md` +- Create: `website/src/content/blog/2026-03-24-postgresql-support.md` + +- [ ] **Step 1: Create BlogPost layout** + +`src/layouts/BlogPost.astro` — extends Landing layout. Adds article header (title, date, author), content slot, back-to-blog link. + +- [ ] **Step 2: Create BlogPostCard component** + +`src/components/BlogPostCard.astro` — title, date, description excerpt. Links to full post. + +- [ ] **Step 3: Create blog index page** + +`src/pages/blog/index.astro` — queries blog collection, renders BlogPostCards in reverse chronological order. + +- [ ] **Step 4: Create blog post dynamic route** + +`src/pages/blog/[...slug].astro` — renders individual blog posts using BlogPost layout. + +- [ ] **Step 5: Write launch blog posts** + +Create `src/content/blog/2026-03-24-new-maintainership.md`: + +```markdown +--- +title: "dbdeployer Under New Maintainership" +date: 2026-03-24 +author: "Rene Cannao" +description: "The ProxySQL team takes over dbdeployer with modern MySQL support, a provider architecture, and PostgreSQL on the horizon." +tags: ["announcement", "roadmap"] +--- + +dbdeployer is now maintained by the ProxySQL team... +``` + +Create `src/content/blog/2026-03-24-postgresql-support.md`: + +```markdown +--- +title: "PostgreSQL Support is Here" +date: 2026-03-24 +author: "Rene Cannao" +description: "dbdeployer now supports PostgreSQL sandboxes with streaming replication and ProxySQL integration." +tags: ["release", "postgresql"] +--- + +We're excited to announce PostgreSQL support in dbdeployer... +``` + +- [ ] **Step 6: Add "What's New" strip to landing page** + +Update `src/pages/index.astro` — query latest 2 blog posts, render BlogPostCards above the footer. + +- [ ] **Step 7: Verify locally** + +- `http://localhost:4321/dbdeployer/blog/` — index with 2 posts +- `http://localhost:4321/dbdeployer/blog/2026-03-24-new-maintainership/` — full post +- Landing page shows blog strip + +- [ ] **Step 8: Commit** + +```bash +git add website/src/layouts/BlogPost.astro website/src/components/BlogPostCard.astro website/src/pages/blog/ website/src/content/blog/ website/src/pages/index.astro +git commit -m "feat: add blog with launch posts and landing page integration" +``` + +--- + +## Task 8: 404 Page and OG Image + +**Files:** +- Create: `website/src/pages/404.astro` +- Create: `website/public/og-image.png` + +- [ ] **Step 1: Create 404 page** + +`src/pages/404.astro` — uses Landing layout. Simple message: "Page not found" with links to Home and Docs. + +- [ ] **Step 2: Create placeholder OG image** + +Create a simple 1200x630 PNG or use a placeholder. Can be generated with any tool or be a solid color with text overlay. This can be improved later. + +- [ ] **Step 3: Commit** + +```bash +git add website/src/pages/404.astro website/public/og-image.png +git commit -m "feat: add 404 page and OG social image" +``` + +--- + +## Task 9: GitHub Actions Deployment Workflow + +**Files:** +- Create: `.github/workflows/deploy-website.yml` + +- [ ] **Step 1: Create the workflow** + +```yaml +name: Deploy Website + +on: + push: + branches: [master] + paths: + - 'website/**' + - 'docs/wiki/**' + - 'docs/proxysql-guide.md' + - 'docs/env_variables.md' + workflow_dispatch: + +permissions: + contents: read + pages: write + id-token: write + +concurrency: + group: "pages" + cancel-in-progress: false + +jobs: + build: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: '20' + cache: 'npm' + cache-dependency-path: website/package-lock.json + + - name: Install dependencies + working-directory: website + run: npm ci + + - name: Copy wiki content + run: bash website/scripts/copy-wiki.sh + + - name: Build website + working-directory: website + run: npm run build + + - name: Upload artifact + uses: actions/upload-pages-artifact@v3 + with: + path: website/dist + + deploy: + needs: build + runs-on: ubuntu-latest + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + steps: + - name: Deploy to GitHub Pages + id: deployment + uses: actions/deploy-pages@v4 +``` + +- [ ] **Step 2: Verify workflow syntax** + +```bash +# Check YAML is valid +python3 -c "import yaml; yaml.safe_load(open('.github/workflows/deploy-website.yml'))" +``` + +- [ ] **Step 3: Commit** + +```bash +git add .github/workflows/deploy-website.yml +git commit -m "ci: add GitHub Actions workflow for website deployment" +``` + +--- + +## Task 10: Final Build Verification and Cleanup + +- [ ] **Step 1: Full build from scratch** + +```bash +cd /data/rene/dbdeployer +bash website/scripts/copy-wiki.sh +cd website +rm -rf node_modules dist +npm install +npm run build +``` + +Expected: Clean build succeeds. + +- [ ] **Step 2: Verify all pages render** + +```bash +npm run preview +``` + +Spot-check: +- `/dbdeployer/` — landing page +- `/dbdeployer/docs/` — docs home +- `/dbdeployer/docs/getting-started/installation/` — wiki-migrated page +- `/dbdeployer/docs/getting-started/quickstart-mysql-single/` — new quickstart +- `/dbdeployer/providers` — providers comparison +- `/dbdeployer/blog/` — blog index + +- [ ] **Step 3: Add website/ to .gitignore entries** + +Add to `.gitignore`: +``` +website/node_modules/ +website/dist/ +website/.astro/ +``` + +- [ ] **Step 4: Final commit** + +```bash +git add .gitignore +git commit -m "chore: add website build artifacts to gitignore" +``` + +--- + +## Execution Notes + +### Task dependencies +- Task 1 (scaffold) must complete before all others +- Task 2 (copy script) must complete before Task 3 (stubs) +- Tasks 4-8 can run in parallel after Tasks 1-3 +- Task 9 (deployment) is independent of content tasks +- Task 10 (verification) runs last + +### Local development during implementation + +```bash +# Terminal 1: watch for changes +cd website && npm run dev + +# Terminal 2: re-run copy script after wiki edits +bash website/scripts/copy-wiki.sh +``` + +### After merging + +1. Enable GitHub Pages in repo settings: Settings → Pages → Source: GitHub Actions +2. First push to master with `website/**` changes triggers the deployment +3. Site available at `https://proxysql.github.io/dbdeployer/` From 242905076a03722719a0082563fb40103784f720 Mon Sep 17 00:00:00 2001 From: Rene Cannao <rene@proxysql.com> Date: Tue, 24 Mar 2026 17:38:23 +0000 Subject: [PATCH 07/15] feat: scaffold Astro + Starlight website project Initializes the website/ directory with Astro 6 + Starlight 0.38, including full sidebar config, stub docs for all 34 sidebar pages, content collection config using the Astro v6 loader API, a database- themed SVG favicon, sitemap integration, and a verified production build. --- website/.gitignore | 3 + website/astro.config.mjs | 89 + website/package-lock.json | 6196 +++++++++++++++++ website/package.json | 20 + website/public/favicon.svg | 5 + website/src/content.config.ts | 20 + website/src/content/docs/advanced/cloning.md | 6 + .../src/content/docs/advanced/compiling.md | 6 + .../src/content/docs/advanced/concurrent.md | 6 + .../src/content/docs/advanced/go-library.md | 6 + .../src/content/docs/advanced/importing.md | 6 + .../advanced/inter-sandbox-replication.md | 6 + .../docs/concepts/environment-variables.md | 6 + website/src/content/docs/concepts/flavors.md | 6 + website/src/content/docs/concepts/ports.md | 6 + .../src/content/docs/concepts/sandboxes.md | 6 + .../docs/deploying/fan-in-all-masters.md | 6 + .../docs/deploying/group-replication.md | 6 + .../src/content/docs/deploying/multiple.md | 6 + .../src/content/docs/deploying/ndb-cluster.md | 6 + .../src/content/docs/deploying/replication.md | 6 + website/src/content/docs/deploying/single.md | 6 + .../docs/getting-started/installation.md | 6 + .../quickstart-mysql-replication.md | 6 + .../quickstart-mysql-single.md | 6 + .../getting-started/quickstart-postgresql.md | 6 + .../getting-started/quickstart-proxysql.md | 6 + website/src/content/docs/index.mdx | 6 + .../content/docs/managing/customization.md | 6 + website/src/content/docs/managing/deletion.md | 6 + website/src/content/docs/managing/logs.md | 6 + .../docs/managing/starting-stopping.md | 6 + website/src/content/docs/managing/users.md | 6 + website/src/content/docs/managing/using.md | 6 + website/src/content/docs/providers/mysql.md | 6 + .../src/content/docs/providers/postgresql.md | 6 + .../src/content/docs/providers/proxysql.md | 6 + website/src/content/docs/providers/pxc.md | 6 + .../content/docs/reference/api-changelog.md | 6 + .../content/docs/reference/cli-commands.md | 6 + .../content/docs/reference/configuration.md | 6 + website/tsconfig.json | 3 + 42 files changed, 6546 insertions(+) create mode 100644 website/.gitignore create mode 100644 website/astro.config.mjs create mode 100644 website/package-lock.json create mode 100644 website/package.json create mode 100644 website/public/favicon.svg create mode 100644 website/src/content.config.ts create mode 100644 website/src/content/docs/advanced/cloning.md create mode 100644 website/src/content/docs/advanced/compiling.md create mode 100644 website/src/content/docs/advanced/concurrent.md create mode 100644 website/src/content/docs/advanced/go-library.md create mode 100644 website/src/content/docs/advanced/importing.md create mode 100644 website/src/content/docs/advanced/inter-sandbox-replication.md create mode 100644 website/src/content/docs/concepts/environment-variables.md create mode 100644 website/src/content/docs/concepts/flavors.md create mode 100644 website/src/content/docs/concepts/ports.md create mode 100644 website/src/content/docs/concepts/sandboxes.md create mode 100644 website/src/content/docs/deploying/fan-in-all-masters.md create mode 100644 website/src/content/docs/deploying/group-replication.md create mode 100644 website/src/content/docs/deploying/multiple.md create mode 100644 website/src/content/docs/deploying/ndb-cluster.md create mode 100644 website/src/content/docs/deploying/replication.md create mode 100644 website/src/content/docs/deploying/single.md create mode 100644 website/src/content/docs/getting-started/installation.md create mode 100644 website/src/content/docs/getting-started/quickstart-mysql-replication.md create mode 100644 website/src/content/docs/getting-started/quickstart-mysql-single.md create mode 100644 website/src/content/docs/getting-started/quickstart-postgresql.md create mode 100644 website/src/content/docs/getting-started/quickstart-proxysql.md create mode 100644 website/src/content/docs/index.mdx create mode 100644 website/src/content/docs/managing/customization.md create mode 100644 website/src/content/docs/managing/deletion.md create mode 100644 website/src/content/docs/managing/logs.md create mode 100644 website/src/content/docs/managing/starting-stopping.md create mode 100644 website/src/content/docs/managing/users.md create mode 100644 website/src/content/docs/managing/using.md create mode 100644 website/src/content/docs/providers/mysql.md create mode 100644 website/src/content/docs/providers/postgresql.md create mode 100644 website/src/content/docs/providers/proxysql.md create mode 100644 website/src/content/docs/providers/pxc.md create mode 100644 website/src/content/docs/reference/api-changelog.md create mode 100644 website/src/content/docs/reference/cli-commands.md create mode 100644 website/src/content/docs/reference/configuration.md create mode 100644 website/tsconfig.json diff --git a/website/.gitignore b/website/.gitignore new file mode 100644 index 0000000..ddce69b --- /dev/null +++ b/website/.gitignore @@ -0,0 +1,3 @@ +node_modules/ +dist/ +.astro/ diff --git a/website/astro.config.mjs b/website/astro.config.mjs new file mode 100644 index 0000000..c203cad --- /dev/null +++ b/website/astro.config.mjs @@ -0,0 +1,89 @@ +import { defineConfig } from 'astro/config'; +import starlight from '@astrojs/starlight'; +import sitemap from '@astrojs/sitemap'; + +export default defineConfig({ + site: 'https://proxysql.github.io', + base: '/dbdeployer', + integrations: [ + starlight({ + title: 'dbdeployer', + description: 'Deploy MySQL & PostgreSQL sandboxes in seconds', + social: [ + { icon: 'github', label: 'GitHub', href: 'https://github.com/ProxySQL/dbdeployer' }, + ], + sidebar: [ + { + label: 'Getting Started', + items: [ + { label: 'Installation', slug: 'getting-started/installation' }, + { label: 'Quick Start: MySQL Single', slug: 'getting-started/quickstart-mysql-single' }, + { label: 'Quick Start: MySQL Replication', slug: 'getting-started/quickstart-mysql-replication' }, + { label: 'Quick Start: PostgreSQL', slug: 'getting-started/quickstart-postgresql' }, + { label: 'Quick Start: ProxySQL Integration', slug: 'getting-started/quickstart-proxysql' }, + ], + }, + { + label: 'Core Concepts', + items: [ + { label: 'Sandboxes', slug: 'concepts/sandboxes' }, + { label: 'Versions & Flavors', slug: 'concepts/flavors' }, + { label: 'Ports & Networking', slug: 'concepts/ports' }, + { label: 'Environment Variables', slug: 'concepts/environment-variables' }, + ], + }, + { + label: 'Deploying', + items: [ + { label: 'Single Sandbox', slug: 'deploying/single' }, + { label: 'Multiple Sandboxes', slug: 'deploying/multiple' }, + { label: 'Replication', slug: 'deploying/replication' }, + { label: 'Group Replication', slug: 'deploying/group-replication' }, + { label: 'Fan-In & All-Masters', slug: 'deploying/fan-in-all-masters' }, + { label: 'NDB Cluster', slug: 'deploying/ndb-cluster' }, + ], + }, + { + label: 'Providers', + items: [ + { label: 'MySQL', slug: 'providers/mysql' }, + { label: 'PostgreSQL', slug: 'providers/postgresql' }, + { label: 'ProxySQL', slug: 'providers/proxysql' }, + { label: 'Percona XtraDB Cluster', slug: 'providers/pxc' }, + ], + }, + { + label: 'Managing Sandboxes', + items: [ + { label: 'Starting & Stopping', slug: 'managing/starting-stopping' }, + { label: 'Using Sandboxes', slug: 'managing/using' }, + { label: 'Customization', slug: 'managing/customization' }, + { label: 'Database Users', slug: 'managing/users' }, + { label: 'Logs', slug: 'managing/logs' }, + { label: 'Deletion & Cleanup', slug: 'managing/deletion' }, + ], + }, + { + label: 'Advanced', + items: [ + { label: 'Concurrent Deployment', slug: 'advanced/concurrent' }, + { label: 'Importing Databases', slug: 'advanced/importing' }, + { label: 'Inter-Sandbox Replication', slug: 'advanced/inter-sandbox-replication' }, + { label: 'Cloning', slug: 'advanced/cloning' }, + { label: 'Using as a Go Library', slug: 'advanced/go-library' }, + { label: 'Compiling from Source', slug: 'advanced/compiling' }, + ], + }, + { + label: 'Reference', + items: [ + { label: 'CLI Commands', slug: 'reference/cli-commands' }, + { label: 'Configuration', slug: 'reference/configuration' }, + { label: 'API Changelog', slug: 'reference/api-changelog' }, + ], + }, + ], + }), + sitemap(), + ], +}); diff --git a/website/package-lock.json b/website/package-lock.json new file mode 100644 index 0000000..6543f98 --- /dev/null +++ b/website/package-lock.json @@ -0,0 +1,6196 @@ +{ + "name": "website", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "website", + "version": "1.0.0", + "license": "ISC", + "dependencies": { + "@astrojs/sitemap": "^3.7.1", + "@astrojs/starlight": "^0.38.2", + "astro": "^6.0.8" + } + }, + "node_modules/@astrojs/compiler": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@astrojs/compiler/-/compiler-3.0.1.tgz", + "integrity": "sha512-z97oYbdebO5aoWzuJ/8q5hLK232+17KcLZ7cJ8BCWk6+qNzVxn/gftC0KzMBUTD8WAaBkPpNSQK6PXLnNrZ0CA==", + "license": "MIT" + }, + "node_modules/@astrojs/internal-helpers": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/@astrojs/internal-helpers/-/internal-helpers-0.8.0.tgz", + "integrity": "sha512-J56GrhEiV+4dmrGLPNOl2pZjpHXAndWVyiVDYGDuw6MWKpBSEMLdFxHzeM/6sqaknw9M+HFfHZAcvi3OfT3D/w==", + "license": "MIT", + "dependencies": { + "picomatch": "^4.0.3" + } + }, + "node_modules/@astrojs/markdown-remark": { + "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.1", + "github-slugger": "^2.0.0", + "hast-util-from-html": "^2.0.3", + "hast-util-to-text": "^4.0.2", + "js-yaml": "^4.1.1", + "mdast-util-definitions": "^6.0.0", + "rehype-raw": "^7.0.0", + "rehype-stringify": "^10.0.1", + "remark-gfm": "^4.0.1", + "remark-parse": "^11.0.0", + "remark-rehype": "^11.1.2", + "remark-smartypants": "^3.0.2", + "shiki": "^4.0.0", + "smol-toml": "^1.6.0", + "unified": "^11.0.5", + "unist-util-remove-position": "^5.0.0", + "unist-util-visit": "^5.1.0", + "unist-util-visit-parents": "^6.0.2", + "vfile": "^6.0.3" + } + }, + "node_modules/@astrojs/mdx": { + "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.1", + "@mdx-js/mdx": "^3.1.1", + "acorn": "^8.16.0", + "es-module-lexer": "^2.0.0", + "estree-util-visit": "^2.0.0", + "hast-util-to-html": "^9.0.5", + "piccolore": "^0.1.3", + "rehype-raw": "^7.0.0", + "remark-gfm": "^4.0.1", + "remark-smartypants": "^3.0.2", + "source-map": "^0.7.6", + "unist-util-visit": "^5.1.0", + "vfile": "^6.0.3" + }, + "engines": { + "node": ">=22.12.0" + }, + "peerDependencies": { + "astro": "^6.0.0" + } + }, + "node_modules/@astrojs/prism": { + "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": ">=22.12.0" + } + }, + "node_modules/@astrojs/sitemap": { + "version": "3.7.1", + "resolved": "https://registry.npmjs.org/@astrojs/sitemap/-/sitemap-3.7.1.tgz", + "integrity": "sha512-IzQqdTeskaMX+QDZCzMuJIp8A8C1vgzMBp/NmHNnadepHYNHcxQdGLQZYfkbd2EbRXUfOS+UDIKx8sKg0oWVdw==", + "license": "MIT", + "dependencies": { + "sitemap": "^9.0.0", + "stream-replace-string": "^2.0.0", + "zod": "^4.3.6" + } + }, + "node_modules/@astrojs/starlight": { + "version": "0.38.2", + "resolved": "https://registry.npmjs.org/@astrojs/starlight/-/starlight-0.38.2.tgz", + "integrity": "sha512-7AsrvG4EsXUmJT5uqiXJN4oZqKaY0wc/Ip7C6/zGnShHRVoTAA4jxeYIZ3wqbqA6zv4cnp9qk31vB2m2dUcmfg==", + "license": "MIT", + "dependencies": { + "@astrojs/markdown-remark": "^7.0.0", + "@astrojs/mdx": "^5.0.0", + "@astrojs/sitemap": "^3.7.1", + "@pagefind/default-ui": "^1.3.0", + "@types/hast": "^3.0.4", + "@types/js-yaml": "^4.0.9", + "@types/mdast": "^4.0.4", + "astro-expressive-code": "^0.41.6", + "bcp-47": "^2.1.0", + "hast-util-from-html": "^2.0.1", + "hast-util-select": "^6.0.2", + "hast-util-to-string": "^3.0.0", + "hastscript": "^9.0.0", + "i18next": "^23.11.5", + "js-yaml": "^4.1.0", + "klona": "^2.0.6", + "magic-string": "^0.30.17", + "mdast-util-directive": "^3.0.0", + "mdast-util-to-markdown": "^2.1.0", + "mdast-util-to-string": "^4.0.0", + "pagefind": "^1.3.0", + "rehype": "^13.0.1", + "rehype-format": "^5.0.0", + "remark-directive": "^3.0.0", + "ultrahtml": "^1.6.0", + "unified": "^11.0.5", + "unist-util-visit": "^5.0.0", + "vfile": "^6.0.2" + }, + "peerDependencies": { + "astro": "^6.0.0" + } + }, + "node_modules/@astrojs/telemetry": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/@astrojs/telemetry/-/telemetry-3.3.0.tgz", + "integrity": "sha512-UFBgfeldP06qu6khs/yY+q1cDAaArM2/7AEIqQ9Cuvf7B1hNLq0xDrZkct+QoIGyjq56y8IaE2I3CTvG99mlhQ==", + "license": "MIT", + "dependencies": { + "ci-info": "^4.2.0", + "debug": "^4.4.0", + "dlv": "^1.1.3", + "dset": "^3.1.4", + "is-docker": "^3.0.0", + "is-wsl": "^3.1.0", + "which-pm-runs": "^1.1.0" + }, + "engines": { + "node": "18.20.8 || ^20.3.0 || >=22.0.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", + "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.29.2", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.2.tgz", + "integrity": "sha512-4GgRzy/+fsBa72/RZVJmGKPmZu9Byn8o4MoLpmNe1m8ZfYnz5emHLQz3U4gLud6Zwl0RZIcgiLD7Uq7ySFuDLA==", + "license": "MIT", + "dependencies": { + "@babel/types": "^7.29.0" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/runtime": { + "version": "7.29.2", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.29.2.tgz", + "integrity": "sha512-JiDShH45zKHWyGe4ZNVRrCjBz8Nh9TMmZG1kh4QTK8hCBTWBi8Da+i7s1fJw7/lYpM4ccepSNfqzZ/QvABBi5g==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz", + "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==", + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.28.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@capsizecss/unpack": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@capsizecss/unpack/-/unpack-4.0.0.tgz", + "integrity": "sha512-VERIM64vtTP1C4mxQ5thVT9fK0apjPFobqybMtA1UdUujWka24ERHbRHFGmpbbhp73MhV+KSsHQH9C6uOTdEQA==", + "license": "MIT", + "dependencies": { + "fontkitten": "^1.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@clack/core": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@clack/core/-/core-1.1.0.tgz", + "integrity": "sha512-SVcm4Dqm2ukn64/8Gub2wnlA5nS2iWJyCkdNHcvNHPIeBTGojpdJ+9cZKwLfmqy7irD4N5qLteSilJlE0WLAtA==", + "license": "MIT", + "dependencies": { + "sisteransi": "^1.0.5" + } + }, + "node_modules/@clack/prompts": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@clack/prompts/-/prompts-1.1.0.tgz", + "integrity": "sha512-pkqbPGtohJAvm4Dphs2M8xE29ggupihHdy1x84HNojZuMtFsHiUlRvqD24tM2+XmI+61LlfNceM3Wr7U5QES5g==", + "license": "MIT", + "dependencies": { + "@clack/core": "1.1.0", + "sisteransi": "^1.0.5" + } + }, + "node_modules/@ctrl/tinycolor": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@ctrl/tinycolor/-/tinycolor-4.2.0.tgz", + "integrity": "sha512-kzyuwOAQnXJNLS9PSyrk0CWk35nWJW/zl/6KvnTBMFK65gm7U1/Z5BqjxeapjZCIhQcM/DsrEmcbRwDyXyXK4A==", + "license": "MIT", + "engines": { + "node": ">=14" + } + }, + "node_modules/@emnapi/runtime": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.9.1.tgz", + "integrity": "sha512-VYi5+ZVLhpgK4hQ0TAjiQiZ6ol0oe4mBx7mVv7IflsiEp0OWoVsp/+f9Vc1hOhE0TtkORVrI1GvzyreqpgWtkA==", + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.4.tgz", + "integrity": "sha512-cQPwL2mp2nSmHHJlCyoXgHGhbEPMrEEU5xhkcy3Hs/O7nGZqEpZ2sUtLaL9MORLtDfRvVl2/3PAuEkYZH0Ty8Q==", + "cpu": [ + "ppc64" + ], + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.4.tgz", + "integrity": "sha512-X9bUgvxiC8CHAGKYufLIHGXPJWnr0OCdR0anD2e21vdvgCI8lIfqFbnoeOz7lBjdrAGUhqLZLcQo6MLhTO2DKQ==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.4.tgz", + "integrity": "sha512-gdLscB7v75wRfu7QSm/zg6Rx29VLdy9eTr2t44sfTW7CxwAtQghZ4ZnqHk3/ogz7xao0QAgrkradbBzcqFPasw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.4.tgz", + "integrity": "sha512-PzPFnBNVF292sfpfhiyiXCGSn9HZg5BcAz+ivBuSsl6Rk4ga1oEXAamhOXRFyMcjwr2DVtm40G65N3GLeH1Lvw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.4.tgz", + "integrity": "sha512-b7xaGIwdJlht8ZFCvMkpDN6uiSmnxxK56N2GDTMYPr2/gzvfdQN8rTfBsvVKmIVY/X7EM+/hJKEIbbHs9oA4tQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.4.tgz", + "integrity": "sha512-sR+OiKLwd15nmCdqpXMnuJ9W2kpy0KigzqScqHI3Hqwr7IXxBp3Yva+yJwoqh7rE8V77tdoheRYataNKL4QrPw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.4.tgz", + "integrity": "sha512-jnfpKe+p79tCnm4GVav68A7tUFeKQwQyLgESwEAUzyxk/TJr4QdGog9sqWNcUbr/bZt/O/HXouspuQDd9JxFSw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.4.tgz", + "integrity": "sha512-2kb4ceA/CpfUrIcTUl1wrP/9ad9Atrp5J94Lq69w7UwOMolPIGrfLSvAKJp0RTvkPPyn6CIWrNy13kyLikZRZQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.4.tgz", + "integrity": "sha512-aBYgcIxX/wd5n2ys0yESGeYMGF+pv6g0DhZr3G1ZG4jMfruU9Tl1i2Z+Wnj9/KjGz1lTLCcorqE2viePZqj4Eg==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.4.tgz", + "integrity": "sha512-7nQOttdzVGth1iz57kxg9uCz57dxQLHWxopL6mYuYthohPKEK0vU0C3O21CcBK6KDlkYVcnDXY099HcCDXd9dA==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.4.tgz", + "integrity": "sha512-oPtixtAIzgvzYcKBQM/qZ3R+9TEUd1aNJQu0HhGyqtx6oS7qTpvjheIWBbes4+qu1bNlo2V4cbkISr8q6gRBFA==", + "cpu": [ + "ia32" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.4.tgz", + "integrity": "sha512-8mL/vh8qeCoRcFH2nM8wm5uJP+ZcVYGGayMavi8GmRJjuI3g1v6Z7Ni0JJKAJW+m0EtUuARb6Lmp4hMjzCBWzA==", + "cpu": [ + "loong64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.4.tgz", + "integrity": "sha512-1RdrWFFiiLIW7LQq9Q2NES+HiD4NyT8Itj9AUeCl0IVCA459WnPhREKgwrpaIfTOe+/2rdntisegiPWn/r/aAw==", + "cpu": [ + "mips64el" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.4.tgz", + "integrity": "sha512-tLCwNG47l3sd9lpfyx9LAGEGItCUeRCWeAx6x2Jmbav65nAwoPXfewtAdtbtit/pJFLUWOhpv0FpS6GQAmPrHA==", + "cpu": [ + "ppc64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.4.tgz", + "integrity": "sha512-BnASypppbUWyqjd1KIpU4AUBiIhVr6YlHx/cnPgqEkNoVOhHg+YiSVxM1RLfiy4t9cAulbRGTNCKOcqHrEQLIw==", + "cpu": [ + "riscv64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.4.tgz", + "integrity": "sha512-+eUqgb/Z7vxVLezG8bVB9SfBie89gMueS+I0xYh2tJdw3vqA/0ImZJ2ROeWwVJN59ihBeZ7Tu92dF/5dy5FttA==", + "cpu": [ + "s390x" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.4.tgz", + "integrity": "sha512-S5qOXrKV8BQEzJPVxAwnryi2+Iq5pB40gTEIT69BQONqR7JH1EPIcQ/Uiv9mCnn05jff9umq/5nqzxlqTOg9NA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.4.tgz", + "integrity": "sha512-xHT8X4sb0GS8qTqiwzHqpY00C95DPAq7nAwX35Ie/s+LO9830hrMd3oX0ZMKLvy7vsonee73x0lmcdOVXFzd6Q==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.4.tgz", + "integrity": "sha512-RugOvOdXfdyi5Tyv40kgQnI0byv66BFgAqjdgtAKqHoZTbTF2QqfQrFwa7cHEORJf6X2ht+l9ABLMP0dnKYsgg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.4.tgz", + "integrity": "sha512-2MyL3IAaTX+1/qP0O1SwskwcwCoOI4kV2IBX1xYnDDqthmq5ArrW94qSIKCAuRraMgPOmG0RDTA74mzYNQA9ow==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.4.tgz", + "integrity": "sha512-u8fg/jQ5aQDfsnIV6+KwLOf1CmJnfu1ShpwqdwC0uA7ZPwFws55Ngc12vBdeUdnuWoQYx/SOQLGDcdlfXhYmXQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.4.tgz", + "integrity": "sha512-JkTZrl6VbyO8lDQO3yv26nNr2RM2yZzNrNHEsj9bm6dOwwu9OYN28CjzZkH57bh4w0I2F7IodpQvUAEd1mbWXg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.4.tgz", + "integrity": "sha512-/gOzgaewZJfeJTlsWhvUEmUG4tWEY2Spp5M20INYRg2ZKl9QPO3QEEgPeRtLjEWSW8FilRNacPOg8R1uaYkA6g==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.4.tgz", + "integrity": "sha512-Z9SExBg2y32smoDQdf1HRwHRt6vAHLXcxD2uGgO/v2jK7Y718Ix4ndsbNMU/+1Qiem9OiOdaqitioZwxivhXYg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.4.tgz", + "integrity": "sha512-DAyGLS0Jz5G5iixEbMHi5KdiApqHBWMGzTtMiJ72ZOLhbu/bzxgAe8Ue8CTS3n3HbIUHQz/L51yMdGMeoxXNJw==", + "cpu": [ + "ia32" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.4.tgz", + "integrity": "sha512-+knoa0BDoeXgkNvvV1vvbZX4+hizelrkwmGJBdT17t8FNPwG2lKemmuMZlmaNQ3ws3DKKCxpb4zRZEIp3UxFCg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@expressive-code/core": { + "version": "0.41.7", + "resolved": "https://registry.npmjs.org/@expressive-code/core/-/core-0.41.7.tgz", + "integrity": "sha512-ck92uZYZ9Wba2zxkiZLsZGi9N54pMSAVdrI9uW3Oo9AtLglD5RmrdTwbYPCT2S/jC36JGB2i+pnQtBm/Ib2+dg==", + "license": "MIT", + "dependencies": { + "@ctrl/tinycolor": "^4.0.4", + "hast-util-select": "^6.0.2", + "hast-util-to-html": "^9.0.1", + "hast-util-to-text": "^4.0.1", + "hastscript": "^9.0.0", + "postcss": "^8.4.38", + "postcss-nested": "^6.0.1", + "unist-util-visit": "^5.0.0", + "unist-util-visit-parents": "^6.0.1" + } + }, + "node_modules/@expressive-code/plugin-frames": { + "version": "0.41.7", + "resolved": "https://registry.npmjs.org/@expressive-code/plugin-frames/-/plugin-frames-0.41.7.tgz", + "integrity": "sha512-diKtxjQw/979cTglRFaMCY/sR6hWF0kSMg8jsKLXaZBSfGS0I/Hoe7Qds3vVEgeoW+GHHQzMcwvgx/MOIXhrTA==", + "license": "MIT", + "dependencies": { + "@expressive-code/core": "^0.41.7" + } + }, + "node_modules/@expressive-code/plugin-shiki": { + "version": "0.41.7", + "resolved": "https://registry.npmjs.org/@expressive-code/plugin-shiki/-/plugin-shiki-0.41.7.tgz", + "integrity": "sha512-DL605bLrUOgqTdZ0Ot5MlTaWzppRkzzqzeGEu7ODnHF39IkEBbFdsC7pbl3LbUQ1DFtnfx6rD54k/cdofbW6KQ==", + "license": "MIT", + "dependencies": { + "@expressive-code/core": "^0.41.7", + "shiki": "^3.2.2" + } + }, + "node_modules/@expressive-code/plugin-shiki/node_modules/@shikijs/core": { + "version": "3.23.0", + "resolved": "https://registry.npmjs.org/@shikijs/core/-/core-3.23.0.tgz", + "integrity": "sha512-NSWQz0riNb67xthdm5br6lAkvpDJRTgB36fxlo37ZzM2yq0PQFFzbd8psqC2XMPgCzo1fW6cVi18+ArJ44wqgA==", + "license": "MIT", + "dependencies": { + "@shikijs/types": "3.23.0", + "@shikijs/vscode-textmate": "^10.0.2", + "@types/hast": "^3.0.4", + "hast-util-to-html": "^9.0.5" + } + }, + "node_modules/@expressive-code/plugin-shiki/node_modules/@shikijs/engine-javascript": { + "version": "3.23.0", + "resolved": "https://registry.npmjs.org/@shikijs/engine-javascript/-/engine-javascript-3.23.0.tgz", + "integrity": "sha512-aHt9eiGFobmWR5uqJUViySI1bHMqrAgamWE1TYSUoftkAeCCAiGawPMwM+VCadylQtF4V3VNOZ5LmfItH5f3yA==", + "license": "MIT", + "dependencies": { + "@shikijs/types": "3.23.0", + "@shikijs/vscode-textmate": "^10.0.2", + "oniguruma-to-es": "^4.3.4" + } + }, + "node_modules/@expressive-code/plugin-shiki/node_modules/@shikijs/engine-oniguruma": { + "version": "3.23.0", + "resolved": "https://registry.npmjs.org/@shikijs/engine-oniguruma/-/engine-oniguruma-3.23.0.tgz", + "integrity": "sha512-1nWINwKXxKKLqPibT5f4pAFLej9oZzQTsby8942OTlsJzOBZ0MWKiwzMsd+jhzu8YPCHAswGnnN1YtQfirL35g==", + "license": "MIT", + "dependencies": { + "@shikijs/types": "3.23.0", + "@shikijs/vscode-textmate": "^10.0.2" + } + }, + "node_modules/@expressive-code/plugin-shiki/node_modules/@shikijs/langs": { + "version": "3.23.0", + "resolved": "https://registry.npmjs.org/@shikijs/langs/-/langs-3.23.0.tgz", + "integrity": "sha512-2Ep4W3Re5aB1/62RSYQInK9mM3HsLeB91cHqznAJMuylqjzNVAVCMnNWRHFtcNHXsoNRayP9z1qj4Sq3nMqYXg==", + "license": "MIT", + "dependencies": { + "@shikijs/types": "3.23.0" + } + }, + "node_modules/@expressive-code/plugin-shiki/node_modules/@shikijs/themes": { + "version": "3.23.0", + "resolved": "https://registry.npmjs.org/@shikijs/themes/-/themes-3.23.0.tgz", + "integrity": "sha512-5qySYa1ZgAT18HR/ypENL9cUSGOeI2x+4IvYJu4JgVJdizn6kG4ia5Q1jDEOi7gTbN4RbuYtmHh0W3eccOrjMA==", + "license": "MIT", + "dependencies": { + "@shikijs/types": "3.23.0" + } + }, + "node_modules/@expressive-code/plugin-shiki/node_modules/@shikijs/types": { + "version": "3.23.0", + "resolved": "https://registry.npmjs.org/@shikijs/types/-/types-3.23.0.tgz", + "integrity": "sha512-3JZ5HXOZfYjsYSk0yPwBrkupyYSLpAE26Qc0HLghhZNGTZg/SKxXIIgoxOpmmeQP0RRSDJTk1/vPfw9tbw+jSQ==", + "license": "MIT", + "dependencies": { + "@shikijs/vscode-textmate": "^10.0.2", + "@types/hast": "^3.0.4" + } + }, + "node_modules/@expressive-code/plugin-shiki/node_modules/shiki": { + "version": "3.23.0", + "resolved": "https://registry.npmjs.org/shiki/-/shiki-3.23.0.tgz", + "integrity": "sha512-55Dj73uq9ZXL5zyeRPzHQsK7Nbyt6Y10k5s7OjuFZGMhpp4r/rsLBH0o/0fstIzX1Lep9VxefWljK/SKCzygIA==", + "license": "MIT", + "dependencies": { + "@shikijs/core": "3.23.0", + "@shikijs/engine-javascript": "3.23.0", + "@shikijs/engine-oniguruma": "3.23.0", + "@shikijs/langs": "3.23.0", + "@shikijs/themes": "3.23.0", + "@shikijs/types": "3.23.0", + "@shikijs/vscode-textmate": "^10.0.2", + "@types/hast": "^3.0.4" + } + }, + "node_modules/@expressive-code/plugin-text-markers": { + "version": "0.41.7", + "resolved": "https://registry.npmjs.org/@expressive-code/plugin-text-markers/-/plugin-text-markers-0.41.7.tgz", + "integrity": "sha512-Ewpwuc5t6eFdZmWlFyeuy3e1PTQC0jFvw2Q+2bpcWXbOZhPLsT7+h8lsSIJxb5mS7wZko7cKyQ2RLYDyK6Fpmw==", + "license": "MIT", + "dependencies": { + "@expressive-code/core": "^0.41.7" + } + }, + "node_modules/@img/colour": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@img/colour/-/colour-1.1.0.tgz", + "integrity": "sha512-Td76q7j57o/tLVdgS746cYARfSyxk8iEfRxewL9h4OMzYhbW4TAcppl0mT4eyqXddh6L/jwoM75mo7ixa/pCeQ==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@img/sharp-darwin-arm64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.34.5.tgz", + "integrity": "sha512-imtQ3WMJXbMY4fxb/Ndp6HBTNVtWCUI0WdobyheGf5+ad6xX8VIDO8u2xE4qc/fr08CKG/7dDseFtn6M6g/r3w==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-darwin-arm64": "1.2.4" + } + }, + "node_modules/@img/sharp-darwin-x64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.34.5.tgz", + "integrity": "sha512-YNEFAF/4KQ/PeW0N+r+aVVsoIY0/qxxikF2SWdp+NRkmMB7y9LBZAVqQ4yhGCm/H3H270OSykqmQMKLBhBJDEw==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-darwin-x64": "1.2.4" + } + }, + "node_modules/@img/sharp-libvips-darwin-arm64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.2.4.tgz", + "integrity": "sha512-zqjjo7RatFfFoP0MkQ51jfuFZBnVE2pRiaydKJ1G/rHZvnsrHAOcQALIi9sA5co5xenQdTugCvtb1cuf78Vf4g==", + "cpu": [ + "arm64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "darwin" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-darwin-x64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.2.4.tgz", + "integrity": "sha512-1IOd5xfVhlGwX+zXv2N93k0yMONvUlANylbJw1eTah8K/Jtpi15KC+WSiaX/nBmbm2HxRM1gZ0nSdjSsrZbGKg==", + "cpu": [ + "x64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "darwin" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-arm": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.2.4.tgz", + "integrity": "sha512-bFI7xcKFELdiNCVov8e44Ia4u2byA+l3XtsAj+Q8tfCwO6BQ8iDojYdvoPMqsKDkuoOo+X6HZA0s0q11ANMQ8A==", + "cpu": [ + "arm" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-arm64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.2.4.tgz", + "integrity": "sha512-excjX8DfsIcJ10x1Kzr4RcWe1edC9PquDRRPx3YVCvQv+U5p7Yin2s32ftzikXojb1PIFc/9Mt28/y+iRklkrw==", + "cpu": [ + "arm64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-ppc64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-ppc64/-/sharp-libvips-linux-ppc64-1.2.4.tgz", + "integrity": "sha512-FMuvGijLDYG6lW+b/UvyilUWu5Ayu+3r2d1S8notiGCIyYU/76eig1UfMmkZ7vwgOrzKzlQbFSuQfgm7GYUPpA==", + "cpu": [ + "ppc64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-riscv64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-riscv64/-/sharp-libvips-linux-riscv64-1.2.4.tgz", + "integrity": "sha512-oVDbcR4zUC0ce82teubSm+x6ETixtKZBh/qbREIOcI3cULzDyb18Sr/Wcyx7NRQeQzOiHTNbZFF1UwPS2scyGA==", + "cpu": [ + "riscv64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-s390x": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.2.4.tgz", + "integrity": "sha512-qmp9VrzgPgMoGZyPvrQHqk02uyjA0/QrTO26Tqk6l4ZV0MPWIW6LTkqOIov+J1yEu7MbFQaDpwdwJKhbJvuRxQ==", + "cpu": [ + "s390x" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-x64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.2.4.tgz", + "integrity": "sha512-tJxiiLsmHc9Ax1bz3oaOYBURTXGIRDODBqhveVHonrHJ9/+k89qbLl0bcJns+e4t4rvaNBxaEZsFtSfAdquPrw==", + "cpu": [ + "x64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linuxmusl-arm64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.2.4.tgz", + "integrity": "sha512-FVQHuwx1IIuNow9QAbYUzJ+En8KcVm9Lk5+uGUQJHaZmMECZmOlix9HnH7n1TRkXMS0pGxIJokIVB9SuqZGGXw==", + "cpu": [ + "arm64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linuxmusl-x64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.2.4.tgz", + "integrity": "sha512-+LpyBk7L44ZIXwz/VYfglaX/okxezESc6UxDSoyo2Ks6Jxc4Y7sGjpgU9s4PMgqgjj1gZCylTieNamqA1MF7Dg==", + "cpu": [ + "x64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-linux-arm": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.34.5.tgz", + "integrity": "sha512-9dLqsvwtg1uuXBGZKsxem9595+ujv0sJ6Vi8wcTANSFpwV/GONat5eCkzQo/1O6zRIkh0m/8+5BjrRr7jDUSZw==", + "cpu": [ + "arm" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-arm": "1.2.4" + } + }, + "node_modules/@img/sharp-linux-arm64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.34.5.tgz", + "integrity": "sha512-bKQzaJRY/bkPOXyKx5EVup7qkaojECG6NLYswgktOZjaXecSAeCWiZwwiFf3/Y+O1HrauiE3FVsGxFg8c24rZg==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-arm64": "1.2.4" + } + }, + "node_modules/@img/sharp-linux-ppc64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-ppc64/-/sharp-linux-ppc64-0.34.5.tgz", + "integrity": "sha512-7zznwNaqW6YtsfrGGDA6BRkISKAAE1Jo0QdpNYXNMHu2+0dTrPflTLNkpc8l7MUP5M16ZJcUvysVWWrMefZquA==", + "cpu": [ + "ppc64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-ppc64": "1.2.4" + } + }, + "node_modules/@img/sharp-linux-riscv64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-riscv64/-/sharp-linux-riscv64-0.34.5.tgz", + "integrity": "sha512-51gJuLPTKa7piYPaVs8GmByo7/U7/7TZOq+cnXJIHZKavIRHAP77e3N2HEl3dgiqdD/w0yUfiJnII77PuDDFdw==", + "cpu": [ + "riscv64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-riscv64": "1.2.4" + } + }, + "node_modules/@img/sharp-linux-s390x": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.34.5.tgz", + "integrity": "sha512-nQtCk0PdKfho3eC5MrbQoigJ2gd1CgddUMkabUj+rBevs8tZ2cULOx46E7oyX+04WGfABgIwmMC0VqieTiR4jg==", + "cpu": [ + "s390x" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-s390x": "1.2.4" + } + }, + "node_modules/@img/sharp-linux-x64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.34.5.tgz", + "integrity": "sha512-MEzd8HPKxVxVenwAa+JRPwEC7QFjoPWuS5NZnBt6B3pu7EG2Ge0id1oLHZpPJdn3OQK+BQDiw9zStiHBTJQQQQ==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-x64": "1.2.4" + } + }, + "node_modules/@img/sharp-linuxmusl-arm64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.34.5.tgz", + "integrity": "sha512-fprJR6GtRsMt6Kyfq44IsChVZeGN97gTD331weR1ex1c1rypDEABN6Tm2xa1wE6lYb5DdEnk03NZPqA7Id21yg==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linuxmusl-arm64": "1.2.4" + } + }, + "node_modules/@img/sharp-linuxmusl-x64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.34.5.tgz", + "integrity": "sha512-Jg8wNT1MUzIvhBFxViqrEhWDGzqymo3sV7z7ZsaWbZNDLXRJZoRGrjulp60YYtV4wfY8VIKcWidjojlLcWrd8Q==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linuxmusl-x64": "1.2.4" + } + }, + "node_modules/@img/sharp-wasm32": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.34.5.tgz", + "integrity": "sha512-OdWTEiVkY2PHwqkbBI8frFxQQFekHaSSkUIJkwzclWZe64O1X4UlUjqqqLaPbUpMOQk6FBu/HtlGXNblIs0huw==", + "cpu": [ + "wasm32" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT", + "optional": true, + "dependencies": { + "@emnapi/runtime": "^1.7.0" + }, + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-arm64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-arm64/-/sharp-win32-arm64-0.34.5.tgz", + "integrity": "sha512-WQ3AgWCWYSb2yt+IG8mnC6Jdk9Whs7O0gxphblsLvdhSpSTtmu69ZG1Gkb6NuvxsNACwiPV6cNSZNzt0KPsw7g==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-ia32": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.34.5.tgz", + "integrity": "sha512-FV9m/7NmeCmSHDD5j4+4pNI8Cp3aW+JvLoXcTUo0IqyjSfAZJ8dIUmijx1qaJsIiU+Hosw6xM5KijAWRJCSgNg==", + "cpu": [ + "ia32" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-x64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.34.5.tgz", + "integrity": "sha512-+29YMsqY2/9eFEiW93eqWnuLcWcufowXewwSNIT6UwZdUUCrM3oFjMWH/Z6/TMmb4hlFenmfAVbpWeup2jryCw==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "license": "MIT" + }, + "node_modules/@mdx-js/mdx": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@mdx-js/mdx/-/mdx-3.1.1.tgz", + "integrity": "sha512-f6ZO2ifpwAQIpzGWaBQT2TXxPv6z3RBzQKpVftEWN78Vl/YweF1uwussDx8ECAXVtr3Rs89fKyG9YlzUs9DyGQ==", + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "@types/estree-jsx": "^1.0.0", + "@types/hast": "^3.0.0", + "@types/mdx": "^2.0.0", + "acorn": "^8.0.0", + "collapse-white-space": "^2.0.0", + "devlop": "^1.0.0", + "estree-util-is-identifier-name": "^3.0.0", + "estree-util-scope": "^1.0.0", + "estree-walker": "^3.0.0", + "hast-util-to-jsx-runtime": "^2.0.0", + "markdown-extensions": "^2.0.0", + "recma-build-jsx": "^1.0.0", + "recma-jsx": "^1.0.0", + "recma-stringify": "^1.0.0", + "rehype-recma": "^1.0.0", + "remark-mdx": "^3.0.0", + "remark-parse": "^11.0.0", + "remark-rehype": "^11.0.0", + "source-map": "^0.7.0", + "unified": "^11.0.0", + "unist-util-position-from-estree": "^2.0.0", + "unist-util-stringify-position": "^4.0.0", + "unist-util-visit": "^5.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/@oslojs/encoding": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@oslojs/encoding/-/encoding-1.1.0.tgz", + "integrity": "sha512-70wQhgYmndg4GCPxPPxPGevRKqTIJ2Nh4OkiMWmDAVYsTQ+Ta7Sq+rPevXyXGdzr30/qZBnyOalCszoMxlyldQ==", + "license": "MIT" + }, + "node_modules/@pagefind/darwin-arm64": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@pagefind/darwin-arm64/-/darwin-arm64-1.4.0.tgz", + "integrity": "sha512-2vMqkbv3lbx1Awea90gTaBsvpzgRs7MuSgKDxW0m9oV1GPZCZbZBJg/qL83GIUEN2BFlY46dtUZi54pwH+/pTQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@pagefind/darwin-x64": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@pagefind/darwin-x64/-/darwin-x64-1.4.0.tgz", + "integrity": "sha512-e7JPIS6L9/cJfow+/IAqknsGqEPjJnVXGjpGm25bnq+NPdoD3c/7fAwr1OXkG4Ocjx6ZGSCijXEV4ryMcH2E3A==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@pagefind/default-ui": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@pagefind/default-ui/-/default-ui-1.4.0.tgz", + "integrity": "sha512-wie82VWn3cnGEdIjh4YwNESyS1G6vRHwL6cNjy9CFgNnWW/PGRjsLq300xjVH5sfPFK3iK36UxvIBymtQIEiSQ==", + "license": "MIT" + }, + "node_modules/@pagefind/freebsd-x64": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@pagefind/freebsd-x64/-/freebsd-x64-1.4.0.tgz", + "integrity": "sha512-WcJVypXSZ+9HpiqZjFXMUobfFfZZ6NzIYtkhQ9eOhZrQpeY5uQFqNWLCk7w9RkMUwBv1HAMDW3YJQl/8OqsV0Q==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@pagefind/linux-arm64": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@pagefind/linux-arm64/-/linux-arm64-1.4.0.tgz", + "integrity": "sha512-PIt8dkqt4W06KGmQjONw7EZbhDF+uXI7i0XtRLN1vjCUxM9vGPdtJc2mUyVPevjomrGz5M86M8bqTr6cgDp1Uw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@pagefind/linux-x64": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@pagefind/linux-x64/-/linux-x64-1.4.0.tgz", + "integrity": "sha512-z4oddcWwQ0UHrTHR8psLnVlz6USGJ/eOlDPTDYZ4cI8TK8PgwRUPQZp9D2iJPNIPcS6Qx/E4TebjuGJOyK8Mmg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@pagefind/windows-x64": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@pagefind/windows-x64/-/windows-x64-1.4.0.tgz", + "integrity": "sha512-NkT+YAdgS2FPCn8mIA9bQhiBs+xmniMGq1LFPDhcFn0+2yIUEiIG06t7bsZlhdjknEQRTSdT7YitP6fC5qwP0g==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/pluginutils": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.3.0.tgz", + "integrity": "sha512-5EdhGZtnu3V88ces7s53hhfK5KSASnJZv8Lulpc04cWO3REESroJXg73DFsOmgbU2BhwV0E20bu2IDZb3VKW4Q==", + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "estree-walker": "^2.0.2", + "picomatch": "^4.0.2" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } + } + }, + "node_modules/@rollup/pluginutils/node_modules/estree-walker": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", + "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", + "license": "MIT" + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.60.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.60.0.tgz", + "integrity": "sha512-WOhNW9K8bR3kf4zLxbfg6Pxu2ybOUbB2AjMDHSQx86LIF4rH4Ft7vmMwNt0loO0eonglSNy4cpD3MKXXKQu0/A==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.60.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.60.0.tgz", + "integrity": "sha512-u6JHLll5QKRvjciE78bQXDmqRqNs5M/3GVqZeMwvmjaNODJih/WIrJlFVEihvV0MiYFmd+ZyPr9wxOVbPAG2Iw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.60.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.60.0.tgz", + "integrity": "sha512-qEF7CsKKzSRc20Ciu2Zw1wRrBz4g56F7r/vRwY430UPp/nt1x21Q/fpJ9N5l47WWvJlkNCPJz3QRVw008fi7yA==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.60.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.60.0.tgz", + "integrity": "sha512-WADYozJ4QCnXCH4wPB+3FuGmDPoFseVCUrANmA5LWwGmC6FL14BWC7pcq+FstOZv3baGX65tZ378uT6WG8ynTw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.60.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.60.0.tgz", + "integrity": "sha512-6b8wGHJlDrGeSE3aH5mGNHBjA0TTkxdoNHik5EkvPHCt351XnigA4pS7Wsj/Eo9Y8RBU6f35cjN9SYmCFBtzxw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.60.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.60.0.tgz", + "integrity": "sha512-h25Ga0t4jaylMB8M/JKAyrvvfxGRjnPQIR8lnCayyzEjEOx2EJIlIiMbhpWxDRKGKF8jbNH01NnN663dH638mA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.60.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.60.0.tgz", + "integrity": "sha512-RzeBwv0B3qtVBWtcuABtSuCzToo2IEAIQrcyB/b2zMvBWVbjo8bZDjACUpnaafaxhTw2W+imQbP2BD1usasK4g==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.60.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.60.0.tgz", + "integrity": "sha512-Sf7zusNI2CIU1HLzuu9Tc5YGAHEZs5Lu7N1ssJG4Tkw6e0MEsN7NdjUDDfGNHy2IU+ENyWT+L2obgWiguWibWQ==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.60.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.60.0.tgz", + "integrity": "sha512-DX2x7CMcrJzsE91q7/O02IJQ5/aLkVtYFryqCjduJhUfGKG6yJV8hxaw8pZa93lLEpPTP/ohdN4wFz7yp/ry9A==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.60.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.60.0.tgz", + "integrity": "sha512-09EL+yFVbJZlhcQfShpswwRZ0Rg+z/CsSELFCnPt3iK+iqwGsI4zht3secj5vLEs957QvFFXnzAT0FFPIxSrkQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.60.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.60.0.tgz", + "integrity": "sha512-i9IcCMPr3EXm8EQg5jnja0Zyc1iFxJjZWlb4wr7U2Wx/GrddOuEafxRdMPRYVaXjgbhvqalp6np07hN1w9kAKw==", + "cpu": [ + "loong64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-musl": { + "version": "4.60.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.60.0.tgz", + "integrity": "sha512-DGzdJK9kyJ+B78MCkWeGnpXJ91tK/iKA6HwHxF4TAlPIY7GXEvMe8hBFRgdrR9Ly4qebR/7gfUs9y2IoaVEyog==", + "cpu": [ + "loong64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.60.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.60.0.tgz", + "integrity": "sha512-RwpnLsqC8qbS8z1H1AxBA1H6qknR4YpPR9w2XX0vo2Sz10miu57PkNcnHVaZkbqyw/kUWfKMI73jhmfi9BRMUQ==", + "cpu": [ + "ppc64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-musl": { + "version": "4.60.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.60.0.tgz", + "integrity": "sha512-Z8pPf54Ly3aqtdWC3G4rFigZgNvd+qJlOE52fmko3KST9SoGfAdSRCwyoyG05q1HrrAblLbk1/PSIV+80/pxLg==", + "cpu": [ + "ppc64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.60.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.60.0.tgz", + "integrity": "sha512-3a3qQustp3COCGvnP4SvrMHnPQ9d1vzCakQVRTliaz8cIp/wULGjiGpbcqrkv0WrHTEp8bQD/B3HBjzujVWLOA==", + "cpu": [ + "riscv64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.60.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.60.0.tgz", + "integrity": "sha512-pjZDsVH/1VsghMJ2/kAaxt6dL0psT6ZexQVrijczOf+PeP2BUqTHYejk3l6TlPRydggINOeNRhvpLa0AYpCWSQ==", + "cpu": [ + "riscv64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.60.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.60.0.tgz", + "integrity": "sha512-3ObQs0BhvPgiUVZrN7gqCSvmFuMWvWvsjG5ayJ3Lraqv+2KhOsp+pUbigqbeWqueGIsnn+09HBw27rJ+gYK4VQ==", + "cpu": [ + "s390x" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.60.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.60.0.tgz", + "integrity": "sha512-EtylprDtQPdS5rXvAayrNDYoJhIz1/vzN2fEubo3yLE7tfAw+948dO0g4M0vkTVFhKojnF+n6C8bDNe+gDRdTg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.60.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.60.0.tgz", + "integrity": "sha512-k09oiRCi/bHU9UVFqD17r3eJR9bn03TyKraCrlz5ULFJGdJGi7VOmm9jl44vOJvRJ6P7WuBi/s2A97LxxHGIdw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openbsd-x64": { + "version": "4.60.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.60.0.tgz", + "integrity": "sha512-1o/0/pIhozoSaDJoDcec+IVLbnRtQmHwPV730+AOD29lHEEo4F5BEUB24H0OBdhbBBDwIOSuf7vgg0Ywxdfiiw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.60.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.60.0.tgz", + "integrity": "sha512-pESDkos/PDzYwtyzB5p/UoNU/8fJo68vcXM9ZW2V0kjYayj1KaaUfi1NmTUTUpMn4UhU4gTuK8gIaFO4UGuMbA==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.60.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.60.0.tgz", + "integrity": "sha512-hj1wFStD7B1YBeYmvY+lWXZ7ey73YGPcViMShYikqKT1GtstIKQAtfUI6yrzPjAy/O7pO0VLXGmUVWXQMaYgTQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.60.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.60.0.tgz", + "integrity": "sha512-SyaIPFoxmUPlNDq5EHkTbiKzmSEmq/gOYFI/3HHJ8iS/v1mbugVa7dXUzcJGQfoytp9DJFLhHH4U3/eTy2Bq4w==", + "cpu": [ + "ia32" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.60.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.60.0.tgz", + "integrity": "sha512-RdcryEfzZr+lAr5kRm2ucN9aVlCCa2QNq4hXelZxb8GG0NJSazq44Z3PCCc8wISRuCVnGs0lQJVX5Vp6fKA+IA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.60.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.60.0.tgz", + "integrity": "sha512-PrsWNQ8BuE00O3Xsx3ALh2Df8fAj9+cvvX9AIA6o4KpATR98c9mud4XtDWVvsEuyia5U4tVSTKygawyJkjm60w==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@shikijs/core": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@shikijs/core/-/core-4.0.2.tgz", + "integrity": "sha512-hxT0YF4ExEqB8G/qFdtJvpmHXBYJ2lWW7qTHDarVkIudPFE6iCIrqdgWxGn5s+ppkGXI0aEGlibI0PAyzP3zlw==", + "license": "MIT", + "dependencies": { + "@shikijs/primitive": "4.0.2", + "@shikijs/types": "4.0.2", + "@shikijs/vscode-textmate": "^10.0.2", + "@types/hast": "^3.0.4", + "hast-util-to-html": "^9.0.5" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/@shikijs/engine-javascript": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@shikijs/engine-javascript/-/engine-javascript-4.0.2.tgz", + "integrity": "sha512-7PW0Nm49DcoUIQEXlJhNNBHyoGMjalRETTCcjMqEaMoJRLljy1Bi/EGV3/qLBgLKQejdspiiYuHGQW6dX94Nag==", + "license": "MIT", + "dependencies": { + "@shikijs/types": "4.0.2", + "@shikijs/vscode-textmate": "^10.0.2", + "oniguruma-to-es": "^4.3.4" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/@shikijs/engine-oniguruma": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@shikijs/engine-oniguruma/-/engine-oniguruma-4.0.2.tgz", + "integrity": "sha512-UpCB9Y2sUKlS9z8juFSKz7ZtysmeXCgnRF0dlhXBkmQnek7lAToPte8DkxmEYGNTMii72zU/lyXiCB6StuZeJg==", + "license": "MIT", + "dependencies": { + "@shikijs/types": "4.0.2", + "@shikijs/vscode-textmate": "^10.0.2" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/@shikijs/langs": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@shikijs/langs/-/langs-4.0.2.tgz", + "integrity": "sha512-KaXby5dvoeuZzN0rYQiPMjFoUrz4hgwIE+D6Du9owcHcl6/g16/yT5BQxSW5cGt2MZBz6Hl0YuRqf12omRfUUg==", + "license": "MIT", + "dependencies": { + "@shikijs/types": "4.0.2" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/@shikijs/primitive": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@shikijs/primitive/-/primitive-4.0.2.tgz", + "integrity": "sha512-M6UMPrSa3fN5ayeJwFVl9qWofl273wtK1VG8ySDZ1mQBfhCpdd8nEx7nPZ/tk7k+TYcpqBZzj/AnwxT9lO+HJw==", + "license": "MIT", + "dependencies": { + "@shikijs/types": "4.0.2", + "@shikijs/vscode-textmate": "^10.0.2", + "@types/hast": "^3.0.4" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/@shikijs/themes": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@shikijs/themes/-/themes-4.0.2.tgz", + "integrity": "sha512-mjCafwt8lJJaVSsQvNVrJumbnnj1RI8jbUKrPKgE6E3OvQKxnuRoBaYC51H4IGHePsGN/QtALglWBU7DoKDFnA==", + "license": "MIT", + "dependencies": { + "@shikijs/types": "4.0.2" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/@shikijs/types": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@shikijs/types/-/types-4.0.2.tgz", + "integrity": "sha512-qzbeRooUTPnLE+sHD/Z8DStmaDgnbbc/pMrU203950aRqjX/6AFHeDYT+j00y2lPdz0ywJKx7o/7qnqTivtlXg==", + "license": "MIT", + "dependencies": { + "@shikijs/vscode-textmate": "^10.0.2", + "@types/hast": "^3.0.4" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/@shikijs/vscode-textmate": { + "version": "10.0.2", + "resolved": "https://registry.npmjs.org/@shikijs/vscode-textmate/-/vscode-textmate-10.0.2.tgz", + "integrity": "sha512-83yeghZ2xxin3Nj8z1NMd/NCuca+gsYXswywDy5bHvwlWL8tpTQmzGeUuHd9FC3E/SBEMvzJRwWEOz5gGes9Qg==", + "license": "MIT" + }, + "node_modules/@types/debug": { + "version": "4.1.13", + "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.13.tgz", + "integrity": "sha512-KSVgmQmzMwPlmtljOomayoR89W4FynCAi3E8PPs7vmDVPe84hT+vGPKkJfThkmXs0x0jAaa9U8uW8bbfyS2fWw==", + "license": "MIT", + "dependencies": { + "@types/ms": "*" + } + }, + "node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "license": "MIT" + }, + "node_modules/@types/estree-jsx": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@types/estree-jsx/-/estree-jsx-1.0.5.tgz", + "integrity": "sha512-52CcUVNFyfb1A2ALocQw/Dd1BQFNmSdkuC3BkZ6iqhdMfQz7JWOFRuJFloOzjk+6WijU56m9oKXFAXc7o3Towg==", + "license": "MIT", + "dependencies": { + "@types/estree": "*" + } + }, + "node_modules/@types/hast": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.4.tgz", + "integrity": "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==", + "license": "MIT", + "dependencies": { + "@types/unist": "*" + } + }, + "node_modules/@types/js-yaml": { + "version": "4.0.9", + "resolved": "https://registry.npmjs.org/@types/js-yaml/-/js-yaml-4.0.9.tgz", + "integrity": "sha512-k4MGaQl5TGo/iipqb2UDG2UwjXziSWkh0uysQelTlJpX1qGlpUZYm8PnO4DxG1qBomtJUdYJ6qR6xdIah10JLg==", + "license": "MIT" + }, + "node_modules/@types/mdast": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-4.0.4.tgz", + "integrity": "sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==", + "license": "MIT", + "dependencies": { + "@types/unist": "*" + } + }, + "node_modules/@types/mdx": { + "version": "2.0.13", + "resolved": "https://registry.npmjs.org/@types/mdx/-/mdx-2.0.13.tgz", + "integrity": "sha512-+OWZQfAYyio6YkJb3HLxDrvnx6SWWDbC0zVPfBRzUk0/nqoDyf6dNxQi3eArPe8rJ473nobTMQ/8Zk+LxJ+Yuw==", + "license": "MIT" + }, + "node_modules/@types/ms": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@types/ms/-/ms-2.1.0.tgz", + "integrity": "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==", + "license": "MIT" + }, + "node_modules/@types/nlcst": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@types/nlcst/-/nlcst-2.0.3.tgz", + "integrity": "sha512-vSYNSDe6Ix3q+6Z7ri9lyWqgGhJTmzRjZRqyq15N0Z/1/UnVsno9G/N40NBijoYx2seFDIl0+B2mgAb9mezUCA==", + "license": "MIT", + "dependencies": { + "@types/unist": "*" + } + }, + "node_modules/@types/node": { + "version": "24.12.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.12.0.tgz", + "integrity": "sha512-GYDxsZi3ChgmckRT9HPU0WEhKLP08ev/Yfcq2AstjrDASOYCSXeyjDsHg4v5t4jOj7cyDX3vmprafKlWIG9MXQ==", + "license": "MIT", + "dependencies": { + "undici-types": "~7.16.0" + } + }, + "node_modules/@types/sax": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@types/sax/-/sax-1.2.7.tgz", + "integrity": "sha512-rO73L89PJxeYM3s3pPPjiPgVVcymqU490g0YO5n5By0k2Erzj6tay/4lr1CHAAU4JyOWd1rpQ8bCf6cZfHU96A==", + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/unist": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz", + "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==", + "license": "MIT" + }, + "node_modules/@ungap/structured-clone": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.0.tgz", + "integrity": "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==", + "license": "ISC" + }, + "node_modules/acorn": { + "version": "8.16.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz", + "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "license": "MIT", + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "license": "ISC", + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/anymatch/node_modules/picomatch": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", + "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/arg": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz", + "integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==", + "license": "MIT" + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "license": "Python-2.0" + }, + "node_modules/aria-query": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.2.tgz", + "integrity": "sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==", + "license": "Apache-2.0", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/array-iterate": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/array-iterate/-/array-iterate-2.0.1.tgz", + "integrity": "sha512-I1jXZMjAgCMmxT4qxXfPXa6SthSoE8h6gkSI9BGGNv8mP8G/v0blc+qFnZu6K42vTOiuME596QaLO0TP3Lk0xg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/astring": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/astring/-/astring-1.9.0.tgz", + "integrity": "sha512-LElXdjswlqjWrPpJFg1Fx4wpkOCxj1TDHlSV4PlaRxHGWko024xICaa97ZkMfs6DRKlCguiAI+rbXv5GWwXIkg==", + "license": "MIT", + "bin": { + "astring": "bin/astring" + } + }, + "node_modules/astro": { + "version": "6.0.8", + "resolved": "https://registry.npmjs.org/astro/-/astro-6.0.8.tgz", + "integrity": "sha512-DCPeb8GKOoFWh+8whB7Qi/kKWD/6NcQ9nd1QVNzJFxgHkea3WYrNroQRq4whmBdjhkYPTLS/1gmUAl2iA2Es2g==", + "license": "MIT", + "dependencies": { + "@astrojs/compiler": "^3.0.0", + "@astrojs/internal-helpers": "0.8.0", + "@astrojs/markdown-remark": "7.0.1", + "@astrojs/telemetry": "3.3.0", + "@capsizecss/unpack": "^4.0.0", + "@clack/prompts": "^1.0.1", + "@oslojs/encoding": "^1.1.0", + "@rollup/pluginutils": "^5.3.0", + "aria-query": "^5.3.2", + "axobject-query": "^4.1.0", + "ci-info": "^4.4.0", + "clsx": "^2.1.1", + "common-ancestor-path": "^2.0.0", + "cookie": "^1.1.1", + "devalue": "^5.6.3", + "diff": "^8.0.3", + "dlv": "^1.1.3", + "dset": "^3.1.4", + "es-module-lexer": "^2.0.0", + "esbuild": "^0.27.3", + "flattie": "^1.1.1", + "fontace": "~0.4.1", + "github-slugger": "^2.0.0", + "html-escaper": "3.0.3", + "http-cache-semantics": "^4.2.0", + "js-yaml": "^4.1.1", + "magic-string": "^0.30.21", + "magicast": "^0.5.2", + "mrmime": "^2.0.1", + "neotraverse": "^0.6.18", + "obug": "^2.1.1", + "p-limit": "^7.3.0", + "p-queue": "^9.1.0", + "package-manager-detector": "^1.6.0", + "piccolore": "^0.1.3", + "picomatch": "^4.0.3", + "rehype": "^13.0.2", + "semver": "^7.7.4", + "shiki": "^4.0.0", + "smol-toml": "^1.6.0", + "svgo": "^4.0.0", + "tinyclip": "^0.1.6", + "tinyexec": "^1.0.2", + "tinyglobby": "^0.2.15", + "tsconfck": "^3.1.6", + "ultrahtml": "^1.6.0", + "unifont": "~0.7.4", + "unist-util-visit": "^5.1.0", + "unstorage": "^1.17.4", + "vfile": "^6.0.3", + "vite": "^7.3.1", + "vitefu": "^1.1.2", + "xxhash-wasm": "^1.1.0", + "yargs-parser": "^22.0.0", + "zod": "^4.3.6" + }, + "bin": { + "astro": "bin/astro.mjs" + }, + "engines": { + "node": ">=22.12.0", + "npm": ">=9.6.5", + "pnpm": ">=7.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/astrodotbuild" + }, + "optionalDependencies": { + "sharp": "^0.34.0" + } + }, + "node_modules/astro-expressive-code": { + "version": "0.41.7", + "resolved": "https://registry.npmjs.org/astro-expressive-code/-/astro-expressive-code-0.41.7.tgz", + "integrity": "sha512-hUpogGc6DdAd+I7pPXsctyYPRBJDK7Q7d06s4cyP0Vz3OcbziP3FNzN0jZci1BpCvLn9675DvS7B9ctKKX64JQ==", + "license": "MIT", + "dependencies": { + "rehype-expressive-code": "^0.41.7" + }, + "peerDependencies": { + "astro": "^4.0.0-beta || ^5.0.0-beta || ^3.3.0 || ^6.0.0-beta" + } + }, + "node_modules/axobject-query": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-4.1.0.tgz", + "integrity": "sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==", + "license": "Apache-2.0", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/bail": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/bail/-/bail-2.0.2.tgz", + "integrity": "sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/bcp-47": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/bcp-47/-/bcp-47-2.1.0.tgz", + "integrity": "sha512-9IIS3UPrvIa1Ej+lVDdDwO7zLehjqsaByECw0bu2RRGP73jALm6FYbzI5gWbgHLvNdkvfXB5YrSbocZdOS0c0w==", + "license": "MIT", + "dependencies": { + "is-alphabetical": "^2.0.0", + "is-alphanumerical": "^2.0.0", + "is-decimal": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/bcp-47-match": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/bcp-47-match/-/bcp-47-match-2.0.3.tgz", + "integrity": "sha512-JtTezzbAibu8G0R9op9zb3vcWZd9JF6M0xOYGPn0fNCd7wOpRB1mU2mH9T8gaBGbAAyIIVgB2G7xG0GP98zMAQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/boolbase": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", + "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==", + "license": "ISC" + }, + "node_modules/ccount": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/ccount/-/ccount-2.0.1.tgz", + "integrity": "sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-entities": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/character-entities/-/character-entities-2.0.2.tgz", + "integrity": "sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-entities-html4": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/character-entities-html4/-/character-entities-html4-2.1.0.tgz", + "integrity": "sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-entities-legacy": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/character-entities-legacy/-/character-entities-legacy-3.0.0.tgz", + "integrity": "sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-reference-invalid": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/character-reference-invalid/-/character-reference-invalid-2.0.1.tgz", + "integrity": "sha512-iBZ4F4wRbyORVsu0jPV7gXkOsGYjGHPmAyv+HiHG8gi5PtC9KI2j1+v8/tlibRvjoWX027ypmG/n0HtO5t7unw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/chokidar": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-5.0.0.tgz", + "integrity": "sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw==", + "license": "MIT", + "dependencies": { + "readdirp": "^5.0.0" + }, + "engines": { + "node": ">= 20.19.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/ci-info": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-4.4.0.tgz", + "integrity": "sha512-77PSwercCZU2Fc4sX94eF8k8Pxte6JAwL4/ICZLFjJLqegs7kCuAsqqj/70NQF6TvDpgFjkubQB2FW2ZZddvQg==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/sibiraj-s" + } + ], + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/clsx": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", + "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/collapse-white-space": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/collapse-white-space/-/collapse-white-space-2.1.0.tgz", + "integrity": "sha512-loKTxY1zCOuG4j9f6EPnuyyYkf58RnhhWTvRoZEokgB+WbdXehfjFviyOVYkqzEWz1Q5kRiZdBYS5SwxbQYwzw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/comma-separated-tokens": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-2.0.3.tgz", + "integrity": "sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/commander": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-11.1.0.tgz", + "integrity": "sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ==", + "license": "MIT", + "engines": { + "node": ">=16" + } + }, + "node_modules/common-ancestor-path": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/common-ancestor-path/-/common-ancestor-path-2.0.0.tgz", + "integrity": "sha512-dnN3ibLeoRf2HNC+OlCiNc5d2zxbLJXOtiZUudNFSXZrNSydxcCsSpRzXwfu7BBWCIfHPw+xTayeBvJCP/D8Ng==", + "license": "BlueOak-1.0.0", + "engines": { + "node": ">= 18" + } + }, + "node_modules/cookie": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.1.1.tgz", + "integrity": "sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/cookie-es": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/cookie-es/-/cookie-es-1.2.2.tgz", + "integrity": "sha512-+W7VmiVINB+ywl1HGXJXmrqkOhpKrIiVZV6tQuV54ZyQC7MMuBt81Vc336GMLoHBq5hV/F9eXgt5Mnx0Rha5Fg==", + "license": "MIT" + }, + "node_modules/crossws": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/crossws/-/crossws-0.3.5.tgz", + "integrity": "sha512-ojKiDvcmByhwa8YYqbQI/hg7MEU0NC03+pSdEq4ZUnZR9xXpwk7E43SMNGkn+JxJGPFtNvQ48+vV2p+P1ml5PA==", + "license": "MIT", + "dependencies": { + "uncrypto": "^0.1.3" + } + }, + "node_modules/css-select": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-5.2.2.tgz", + "integrity": "sha512-TizTzUddG/xYLA3NXodFM0fSbNizXjOKhqiQQwvhlspadZokn1KDy0NZFS0wuEubIYAV5/c1/lAr0TaaFXEXzw==", + "license": "BSD-2-Clause", + "dependencies": { + "boolbase": "^1.0.0", + "css-what": "^6.1.0", + "domhandler": "^5.0.2", + "domutils": "^3.0.1", + "nth-check": "^2.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/css-selector-parser": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/css-selector-parser/-/css-selector-parser-3.3.0.tgz", + "integrity": "sha512-Y2asgMGFqJKF4fq4xHDSlFYIkeVfRsm69lQC1q9kbEsH5XtnINTMrweLkjYMeaUgiXBy/uvKeO/a1JHTNnmB2g==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/mdevils" + }, + { + "type": "patreon", + "url": "https://patreon.com/mdevils" + } + ], + "license": "MIT" + }, + "node_modules/css-tree": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-3.2.1.tgz", + "integrity": "sha512-X7sjQzceUhu1u7Y/ylrRZFU2FS6LRiFVp6rKLPg23y3x3c3DOKAwuXGDp+PAGjh6CSnCjYeAul8pcT8bAl+lSA==", + "license": "MIT", + "dependencies": { + "mdn-data": "2.27.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0" + } + }, + "node_modules/css-what": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.2.2.tgz", + "integrity": "sha512-u/O3vwbptzhMs3L1fQE82ZSLHQQfto5gyZzwteVIEyeaY5Fc7R4dapF/BvRoSYFeqfBk4m0V1Vafq5Pjv25wvA==", + "license": "BSD-2-Clause", + "engines": { + "node": ">= 6" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/cssesc": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", + "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", + "license": "MIT", + "bin": { + "cssesc": "bin/cssesc" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/csso": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/csso/-/csso-5.0.5.tgz", + "integrity": "sha512-0LrrStPOdJj+SPCCrGhzryycLjwcgUSHBtxNA8aIDxf0GLsRh1cKYhB00Gd1lDOS4yGH69+SNn13+TWbVHETFQ==", + "license": "MIT", + "dependencies": { + "css-tree": "~2.2.0" + }, + "engines": { + "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0", + "npm": ">=7.0.0" + } + }, + "node_modules/csso/node_modules/css-tree": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-2.2.1.tgz", + "integrity": "sha512-OA0mILzGc1kCOCSJerOeqDxDQ4HOh+G8NbOJFOTgOCzpw7fCBubk0fEyxp8AgOL/jvLgYA/uV0cMbe43ElF1JA==", + "license": "MIT", + "dependencies": { + "mdn-data": "2.0.28", + "source-map-js": "^1.0.1" + }, + "engines": { + "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0", + "npm": ">=7.0.0" + } + }, + "node_modules/csso/node_modules/mdn-data": { + "version": "2.0.28", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.28.tgz", + "integrity": "sha512-aylIc7Z9y4yzHYAJNuESG3hfhC+0Ibp/MAMiaOZgNv4pmEdFyfZhhhny4MNiAfWdBQ1RQ2mfDWmM1x8SvGyp8g==", + "license": "CC0-1.0" + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/decode-named-character-reference": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/decode-named-character-reference/-/decode-named-character-reference-1.3.0.tgz", + "integrity": "sha512-GtpQYB283KrPp6nRw50q3U9/VfOutZOe103qlN7BPP6Ad27xYnOIWv4lPzo8HCAL+mMZofJ9KEy30fq6MfaK6Q==", + "license": "MIT", + "dependencies": { + "character-entities": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/defu": { + "version": "6.1.4", + "resolved": "https://registry.npmjs.org/defu/-/defu-6.1.4.tgz", + "integrity": "sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==", + "license": "MIT" + }, + "node_modules/dequal": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", + "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/destr": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/destr/-/destr-2.0.5.tgz", + "integrity": "sha512-ugFTXCtDZunbzasqBxrK93Ik/DRYsO6S/fedkWEMKqt04xZ4csmnmwGDBAb07QWNaGMAmnTIemsYZCksjATwsA==", + "license": "MIT" + }, + "node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "license": "Apache-2.0", + "optional": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/devalue": { + "version": "5.6.4", + "resolved": "https://registry.npmjs.org/devalue/-/devalue-5.6.4.tgz", + "integrity": "sha512-Gp6rDldRsFh/7XuouDbxMH3Mx8GMCcgzIb1pDTvNyn8pZGQ22u+Wa+lGV9dQCltFQ7uVw0MhRyb8XDskNFOReA==", + "license": "MIT" + }, + "node_modules/devlop": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/devlop/-/devlop-1.1.0.tgz", + "integrity": "sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==", + "license": "MIT", + "dependencies": { + "dequal": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/diff": { + "version": "8.0.4", + "resolved": "https://registry.npmjs.org/diff/-/diff-8.0.4.tgz", + "integrity": "sha512-DPi0FmjiSU5EvQV0++GFDOJ9ASQUVFh5kD+OzOnYdi7n3Wpm9hWWGfB/O2blfHcMVTL5WkQXSnRiK9makhrcnw==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/direction": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/direction/-/direction-2.0.1.tgz", + "integrity": "sha512-9S6m9Sukh1cZNknO1CWAr2QAWsbKLafQiyM5gZ7VgXHeuaoUwffKN4q6NC4A/Mf9iiPlOXQEKW/Mv/mh9/3YFA==", + "license": "MIT", + "bin": { + "direction": "cli.js" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/dlv": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz", + "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==", + "license": "MIT" + }, + "node_modules/dom-serializer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz", + "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==", + "license": "MIT", + "dependencies": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.2", + "entities": "^4.2.0" + }, + "funding": { + "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" + } + }, + "node_modules/dom-serializer/node_modules/entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/domelementtype": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", + "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ], + "license": "BSD-2-Clause" + }, + "node_modules/domhandler": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz", + "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==", + "license": "BSD-2-Clause", + "dependencies": { + "domelementtype": "^2.3.0" + }, + "engines": { + "node": ">= 4" + }, + "funding": { + "url": "https://github.com/fb55/domhandler?sponsor=1" + } + }, + "node_modules/domutils": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.2.2.tgz", + "integrity": "sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==", + "license": "BSD-2-Clause", + "dependencies": { + "dom-serializer": "^2.0.0", + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3" + }, + "funding": { + "url": "https://github.com/fb55/domutils?sponsor=1" + } + }, + "node_modules/dset": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/dset/-/dset-3.1.4.tgz", + "integrity": "sha512-2QF/g9/zTaPDc3BjNcVTGoBbXBgYfMTTceLaYcFJ/W9kggFUkhxD/hMEeuLKbugyef9SqAx8cpgwlIP/jinUTA==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/entities": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz", + "integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/es-module-lexer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.0.0.tgz", + "integrity": "sha512-5POEcUuZybH7IdmGsD8wlf0AI55wMecM9rVBTI/qEAy2c1kTOm3DjFYjrBdI2K3BaJjJYfYFeRtM0t9ssnRuxw==", + "license": "MIT" + }, + "node_modules/esast-util-from-estree": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/esast-util-from-estree/-/esast-util-from-estree-2.0.0.tgz", + "integrity": "sha512-4CyanoAudUSBAn5K13H4JhsMH6L9ZP7XbLVe/dKybkxMO7eDyLsT8UHl9TRNrU2Gr9nz+FovfSIjuXWJ81uVwQ==", + "license": "MIT", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "devlop": "^1.0.0", + "estree-util-visit": "^2.0.0", + "unist-util-position-from-estree": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/esast-util-from-js": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/esast-util-from-js/-/esast-util-from-js-2.0.1.tgz", + "integrity": "sha512-8Ja+rNJ0Lt56Pcf3TAmpBZjmx8ZcK5Ts4cAzIOjsjevg9oSXJnl6SUQ2EevU8tv3h6ZLWmoKL5H4fgWvdvfETw==", + "license": "MIT", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "acorn": "^8.0.0", + "esast-util-from-estree": "^2.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/esbuild": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.4.tgz", + "integrity": "sha512-Rq4vbHnYkK5fws5NF7MYTU68FPRE1ajX7heQ/8QXXWqNgqqJ/GkmmyxIzUnf2Sr/bakf8l54716CcMGHYhMrrQ==", + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.27.4", + "@esbuild/android-arm": "0.27.4", + "@esbuild/android-arm64": "0.27.4", + "@esbuild/android-x64": "0.27.4", + "@esbuild/darwin-arm64": "0.27.4", + "@esbuild/darwin-x64": "0.27.4", + "@esbuild/freebsd-arm64": "0.27.4", + "@esbuild/freebsd-x64": "0.27.4", + "@esbuild/linux-arm": "0.27.4", + "@esbuild/linux-arm64": "0.27.4", + "@esbuild/linux-ia32": "0.27.4", + "@esbuild/linux-loong64": "0.27.4", + "@esbuild/linux-mips64el": "0.27.4", + "@esbuild/linux-ppc64": "0.27.4", + "@esbuild/linux-riscv64": "0.27.4", + "@esbuild/linux-s390x": "0.27.4", + "@esbuild/linux-x64": "0.27.4", + "@esbuild/netbsd-arm64": "0.27.4", + "@esbuild/netbsd-x64": "0.27.4", + "@esbuild/openbsd-arm64": "0.27.4", + "@esbuild/openbsd-x64": "0.27.4", + "@esbuild/openharmony-arm64": "0.27.4", + "@esbuild/sunos-x64": "0.27.4", + "@esbuild/win32-arm64": "0.27.4", + "@esbuild/win32-ia32": "0.27.4", + "@esbuild/win32-x64": "0.27.4" + } + }, + "node_modules/escape-string-regexp": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz", + "integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/estree-util-attach-comments": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/estree-util-attach-comments/-/estree-util-attach-comments-3.0.0.tgz", + "integrity": "sha512-cKUwm/HUcTDsYh/9FgnuFqpfquUbwIqwKM26BVCGDPVgvaCl/nDCCjUfiLlx6lsEZ3Z4RFxNbOQ60pkaEwFxGw==", + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/estree-util-build-jsx": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/estree-util-build-jsx/-/estree-util-build-jsx-3.0.1.tgz", + "integrity": "sha512-8U5eiL6BTrPxp/CHbs2yMgP8ftMhR5ww1eIKoWRMlqvltHF8fZn5LRDvTKuxD3DUn+shRbLGqXemcP51oFCsGQ==", + "license": "MIT", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "devlop": "^1.0.0", + "estree-util-is-identifier-name": "^3.0.0", + "estree-walker": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/estree-util-is-identifier-name": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/estree-util-is-identifier-name/-/estree-util-is-identifier-name-3.0.0.tgz", + "integrity": "sha512-hFtqIDZTIUZ9BXLb8y4pYGyk6+wekIivNVTcmvk8NoOh+VeRn5y6cEHzbURrWbfp1fIqdVipilzj+lfaadNZmg==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/estree-util-scope": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/estree-util-scope/-/estree-util-scope-1.0.0.tgz", + "integrity": "sha512-2CAASclonf+JFWBNJPndcOpA8EMJwa0Q8LUFJEKqXLW6+qBvbFZuF5gItbQOs/umBUkjviCSDCbBwU2cXbmrhQ==", + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "devlop": "^1.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/estree-util-to-js": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/estree-util-to-js/-/estree-util-to-js-2.0.0.tgz", + "integrity": "sha512-WDF+xj5rRWmD5tj6bIqRi6CkLIXbbNQUcxQHzGysQzvHmdYG2G7p/Tf0J0gpxGgkeMZNTIjT/AoSvC9Xehcgdg==", + "license": "MIT", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "astring": "^1.8.0", + "source-map": "^0.7.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/estree-util-visit": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/estree-util-visit/-/estree-util-visit-2.0.0.tgz", + "integrity": "sha512-m5KgiH85xAhhW8Wta0vShLcUvOsh3LLPI2YVwcbio1l7E09NTLL1EyMZFM1OyWowoH0skScNbhOPl4kcBgzTww==", + "license": "MIT", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/estree-walker": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0" + } + }, + "node_modules/eventemitter3": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.4.tgz", + "integrity": "sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw==", + "license": "MIT" + }, + "node_modules/expressive-code": { + "version": "0.41.7", + "resolved": "https://registry.npmjs.org/expressive-code/-/expressive-code-0.41.7.tgz", + "integrity": "sha512-2wZjC8OQ3TaVEMcBtYY4Va3lo6J+Ai9jf3d4dbhURMJcU4Pbqe6EcHe424MIZI0VHUA1bR6xdpoHYi3yxokWqA==", + "license": "MIT", + "dependencies": { + "@expressive-code/core": "^0.41.7", + "@expressive-code/plugin-frames": "^0.41.7", + "@expressive-code/plugin-shiki": "^0.41.7", + "@expressive-code/plugin-text-markers": "^0.41.7" + } + }, + "node_modules/extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", + "license": "MIT" + }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/flattie": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/flattie/-/flattie-1.1.1.tgz", + "integrity": "sha512-9UbaD6XdAL97+k/n+N7JwX46K/M6Zc6KcFYskrYL8wbBV/Uyk0CTAMY0VT+qiK5PM7AIc9aTWYtq65U7T+aCNQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/fontace": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/fontace/-/fontace-0.4.1.tgz", + "integrity": "sha512-lDMvbAzSnHmbYMTEld5qdtvNH2/pWpICOqpean9IgC7vUbUJc3k+k5Dokp85CegamqQpFbXf0rAVkbzpyTA8aw==", + "license": "MIT", + "dependencies": { + "fontkitten": "^1.0.2" + } + }, + "node_modules/fontkitten": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/fontkitten/-/fontkitten-1.0.3.tgz", + "integrity": "sha512-Wp1zXWPVUPBmfoa3Cqc9ctaKuzKAV6uLstRqlR56kSjplf5uAce+qeyYym7F+PHbGTk+tCEdkCW6RD7DX/gBZw==", + "license": "MIT", + "dependencies": { + "tiny-inflate": "^1.0.3" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/github-slugger": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/github-slugger/-/github-slugger-2.0.0.tgz", + "integrity": "sha512-IaOQ9puYtjrkq7Y0Ygl9KDZnrf/aiUJYUpVf89y8kyaxbRG7Y1SrX/jaumrv81vc61+kiMempujsM3Yw7w5qcw==", + "license": "ISC" + }, + "node_modules/h3": { + "version": "1.15.10", + "resolved": "https://registry.npmjs.org/h3/-/h3-1.15.10.tgz", + "integrity": "sha512-YzJeWSkDZxAhvmp8dexjRK5hxziRO7I9m0N53WhvYL5NiWfkUkzssVzY9jvGu0HBoLFW6+duYmNSn6MaZBCCtg==", + "license": "MIT", + "dependencies": { + "cookie-es": "^1.2.2", + "crossws": "^0.3.5", + "defu": "^6.1.4", + "destr": "^2.0.5", + "iron-webcrypto": "^1.2.1", + "node-mock-http": "^1.0.4", + "radix3": "^1.1.2", + "ufo": "^1.6.3", + "uncrypto": "^0.1.3" + } + }, + "node_modules/hast-util-embedded": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/hast-util-embedded/-/hast-util-embedded-3.0.0.tgz", + "integrity": "sha512-naH8sld4Pe2ep03qqULEtvYr7EjrLK2QHY8KJR6RJkTUjPGObe1vnx585uzem2hGra+s1q08DZZpfgDVYRbaXA==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "hast-util-is-element": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-format": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/hast-util-format/-/hast-util-format-1.1.0.tgz", + "integrity": "sha512-yY1UDz6bC9rDvCWHpx12aIBGRG7krurX0p0Fm6pT547LwDIZZiNr8a+IHDogorAdreULSEzP82Nlv5SZkHZcjA==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "hast-util-embedded": "^3.0.0", + "hast-util-minify-whitespace": "^1.0.0", + "hast-util-phrasing": "^3.0.0", + "hast-util-whitespace": "^3.0.0", + "html-whitespace-sensitive-tag-names": "^3.0.0", + "unist-util-visit-parents": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-from-html": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/hast-util-from-html/-/hast-util-from-html-2.0.3.tgz", + "integrity": "sha512-CUSRHXyKjzHov8yKsQjGOElXy/3EKpyX56ELnkHH34vDVw1N1XSQ1ZcAvTyAPtGqLTuKP/uxM+aLkSPqF/EtMw==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "devlop": "^1.1.0", + "hast-util-from-parse5": "^8.0.0", + "parse5": "^7.0.0", + "vfile": "^6.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-from-parse5": { + "version": "8.0.3", + "resolved": "https://registry.npmjs.org/hast-util-from-parse5/-/hast-util-from-parse5-8.0.3.tgz", + "integrity": "sha512-3kxEVkEKt0zvcZ3hCRYI8rqrgwtlIOFMWkbclACvjlDw8Li9S2hk/d51OI0nr/gIpdMHNepwgOKqZ/sy0Clpyg==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/unist": "^3.0.0", + "devlop": "^1.0.0", + "hastscript": "^9.0.0", + "property-information": "^7.0.0", + "vfile": "^6.0.0", + "vfile-location": "^5.0.0", + "web-namespaces": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-has-property": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/hast-util-has-property/-/hast-util-has-property-3.0.0.tgz", + "integrity": "sha512-MNilsvEKLFpV604hwfhVStK0usFY/QmM5zX16bo7EjnAEGofr5YyI37kzopBlZJkHD4t887i+q/C8/tr5Q94cA==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-is-body-ok-link": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/hast-util-is-body-ok-link/-/hast-util-is-body-ok-link-3.0.1.tgz", + "integrity": "sha512-0qpnzOBLztXHbHQenVB8uNuxTnm/QBFUOmdOSsEn7GnBtyY07+ENTWVFBAnXd/zEgd9/SUG3lRY7hSIBWRgGpQ==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-is-element": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/hast-util-is-element/-/hast-util-is-element-3.0.0.tgz", + "integrity": "sha512-Val9mnv2IWpLbNPqc/pUem+a7Ipj2aHacCwgNfTiK0vJKl0LF+4Ba4+v1oPHFpf3bLYmreq0/l3Gud9S5OH42g==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-minify-whitespace": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/hast-util-minify-whitespace/-/hast-util-minify-whitespace-1.0.1.tgz", + "integrity": "sha512-L96fPOVpnclQE0xzdWb/D12VT5FabA7SnZOUMtL1DbXmYiHJMXZvFkIZfiMmTCNJHUeO2K9UYNXoVyfz+QHuOw==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "hast-util-embedded": "^3.0.0", + "hast-util-is-element": "^3.0.0", + "hast-util-whitespace": "^3.0.0", + "unist-util-is": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-parse-selector": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/hast-util-parse-selector/-/hast-util-parse-selector-4.0.0.tgz", + "integrity": "sha512-wkQCkSYoOGCRKERFWcxMVMOcYE2K1AaNLU8DXS9arxnLOUEWbOXKXiJUNzEpqZ3JOKpnha3jkFrumEjVliDe7A==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-phrasing": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/hast-util-phrasing/-/hast-util-phrasing-3.0.1.tgz", + "integrity": "sha512-6h60VfI3uBQUxHqTyMymMZnEbNl1XmEGtOxxKYL7stY2o601COo62AWAYBQR9lZbYXYSBoxag8UpPRXK+9fqSQ==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "hast-util-embedded": "^3.0.0", + "hast-util-has-property": "^3.0.0", + "hast-util-is-body-ok-link": "^3.0.0", + "hast-util-is-element": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-raw": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/hast-util-raw/-/hast-util-raw-9.1.0.tgz", + "integrity": "sha512-Y8/SBAHkZGoNkpzqqfCldijcuUKh7/su31kEBp67cFY09Wy0mTRgtsLYsiIxMJxlu0f6AA5SUTbDR8K0rxnbUw==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/unist": "^3.0.0", + "@ungap/structured-clone": "^1.0.0", + "hast-util-from-parse5": "^8.0.0", + "hast-util-to-parse5": "^8.0.0", + "html-void-elements": "^3.0.0", + "mdast-util-to-hast": "^13.0.0", + "parse5": "^7.0.0", + "unist-util-position": "^5.0.0", + "unist-util-visit": "^5.0.0", + "vfile": "^6.0.0", + "web-namespaces": "^2.0.0", + "zwitch": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-select": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/hast-util-select/-/hast-util-select-6.0.4.tgz", + "integrity": "sha512-RqGS1ZgI0MwxLaKLDxjprynNzINEkRHY2i8ln4DDjgv9ZhcYVIHN9rlpiYsqtFwrgpYU361SyWDQcGNIBVu3lw==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/unist": "^3.0.0", + "bcp-47-match": "^2.0.0", + "comma-separated-tokens": "^2.0.0", + "css-selector-parser": "^3.0.0", + "devlop": "^1.0.0", + "direction": "^2.0.0", + "hast-util-has-property": "^3.0.0", + "hast-util-to-string": "^3.0.0", + "hast-util-whitespace": "^3.0.0", + "nth-check": "^2.0.0", + "property-information": "^7.0.0", + "space-separated-tokens": "^2.0.0", + "unist-util-visit": "^5.0.0", + "zwitch": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-to-estree": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/hast-util-to-estree/-/hast-util-to-estree-3.1.3.tgz", + "integrity": "sha512-48+B/rJWAp0jamNbAAf9M7Uf//UVqAoMmgXhBdxTDJLGKY+LRnZ99qcG+Qjl5HfMpYNzS5v4EAwVEF34LeAj7w==", + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "@types/estree-jsx": "^1.0.0", + "@types/hast": "^3.0.0", + "comma-separated-tokens": "^2.0.0", + "devlop": "^1.0.0", + "estree-util-attach-comments": "^3.0.0", + "estree-util-is-identifier-name": "^3.0.0", + "hast-util-whitespace": "^3.0.0", + "mdast-util-mdx-expression": "^2.0.0", + "mdast-util-mdx-jsx": "^3.0.0", + "mdast-util-mdxjs-esm": "^2.0.0", + "property-information": "^7.0.0", + "space-separated-tokens": "^2.0.0", + "style-to-js": "^1.0.0", + "unist-util-position": "^5.0.0", + "zwitch": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-to-html": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/hast-util-to-html/-/hast-util-to-html-9.0.5.tgz", + "integrity": "sha512-OguPdidb+fbHQSU4Q4ZiLKnzWo8Wwsf5bZfbvu7//a9oTYoqD/fWpe96NuHkoS9h0ccGOTe0C4NGXdtS0iObOw==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/unist": "^3.0.0", + "ccount": "^2.0.0", + "comma-separated-tokens": "^2.0.0", + "hast-util-whitespace": "^3.0.0", + "html-void-elements": "^3.0.0", + "mdast-util-to-hast": "^13.0.0", + "property-information": "^7.0.0", + "space-separated-tokens": "^2.0.0", + "stringify-entities": "^4.0.0", + "zwitch": "^2.0.4" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-to-jsx-runtime": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/hast-util-to-jsx-runtime/-/hast-util-to-jsx-runtime-2.3.6.tgz", + "integrity": "sha512-zl6s8LwNyo1P9uw+XJGvZtdFF1GdAkOg8ujOw+4Pyb76874fLps4ueHXDhXWdk6YHQ6OgUtinliG7RsYvCbbBg==", + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "@types/hast": "^3.0.0", + "@types/unist": "^3.0.0", + "comma-separated-tokens": "^2.0.0", + "devlop": "^1.0.0", + "estree-util-is-identifier-name": "^3.0.0", + "hast-util-whitespace": "^3.0.0", + "mdast-util-mdx-expression": "^2.0.0", + "mdast-util-mdx-jsx": "^3.0.0", + "mdast-util-mdxjs-esm": "^2.0.0", + "property-information": "^7.0.0", + "space-separated-tokens": "^2.0.0", + "style-to-js": "^1.0.0", + "unist-util-position": "^5.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-to-parse5": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/hast-util-to-parse5/-/hast-util-to-parse5-8.0.1.tgz", + "integrity": "sha512-MlWT6Pjt4CG9lFCjiz4BH7l9wmrMkfkJYCxFwKQic8+RTZgWPuWxwAfjJElsXkex7DJjfSJsQIt931ilUgmwdA==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "comma-separated-tokens": "^2.0.0", + "devlop": "^1.0.0", + "property-information": "^7.0.0", + "space-separated-tokens": "^2.0.0", + "web-namespaces": "^2.0.0", + "zwitch": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-to-string": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/hast-util-to-string/-/hast-util-to-string-3.0.1.tgz", + "integrity": "sha512-XelQVTDWvqcl3axRfI0xSeoVKzyIFPwsAGSLIsKdJKQMXDYJS4WYrBNF/8J7RdhIcFI2BOHgAifggsvsxp/3+A==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-to-text": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/hast-util-to-text/-/hast-util-to-text-4.0.2.tgz", + "integrity": "sha512-KK6y/BN8lbaq654j7JgBydev7wuNMcID54lkRav1P0CaE1e47P72AWWPiGKXTJU271ooYzcvTAn/Zt0REnvc7A==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/unist": "^3.0.0", + "hast-util-is-element": "^3.0.0", + "unist-util-find-after": "^5.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-whitespace": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/hast-util-whitespace/-/hast-util-whitespace-3.0.0.tgz", + "integrity": "sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hastscript": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/hastscript/-/hastscript-9.0.1.tgz", + "integrity": "sha512-g7df9rMFX/SPi34tyGCyUBREQoKkapwdY/T04Qn9TDWfHhAYt4/I0gMVirzK5wEzeUqIjEB+LXC/ypb7Aqno5w==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "comma-separated-tokens": "^2.0.0", + "hast-util-parse-selector": "^4.0.0", + "property-information": "^7.0.0", + "space-separated-tokens": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/html-escaper": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-3.0.3.tgz", + "integrity": "sha512-RuMffC89BOWQoY0WKGpIhn5gX3iI54O6nRA0yC124NYVtzjmFWBIiFd8M0x+ZdX0P9R4lADg1mgP8C7PxGOWuQ==", + "license": "MIT" + }, + "node_modules/html-void-elements": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/html-void-elements/-/html-void-elements-3.0.0.tgz", + "integrity": "sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/html-whitespace-sensitive-tag-names": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/html-whitespace-sensitive-tag-names/-/html-whitespace-sensitive-tag-names-3.0.1.tgz", + "integrity": "sha512-q+310vW8zmymYHALr1da4HyXUQ0zgiIwIicEfotYPWGN0OJVEN/58IJ3A4GBYcEq3LGAZqKb+ugvP0GNB9CEAA==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/http-cache-semantics": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.2.0.tgz", + "integrity": "sha512-dTxcvPXqPvXBQpq5dUr6mEMJX4oIEFv6bwom3FDwKRDsuIjjJGANqhBuoAn9c1RQJIdAKav33ED65E2ys+87QQ==", + "license": "BSD-2-Clause" + }, + "node_modules/i18next": { + "version": "23.16.8", + "resolved": "https://registry.npmjs.org/i18next/-/i18next-23.16.8.tgz", + "integrity": "sha512-06r/TitrM88Mg5FdUXAKL96dJMzgqLE5dv3ryBAra4KCwD9mJ4ndOTS95ZuymIGoE+2hzfdaMak2X11/es7ZWg==", + "funding": [ + { + "type": "individual", + "url": "https://locize.com" + }, + { + "type": "individual", + "url": "https://locize.com/i18next.html" + }, + { + "type": "individual", + "url": "https://www.i18next.com/how-to/faq#i18next-is-awesome.-how-can-i-support-the-project" + } + ], + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.23.2" + } + }, + "node_modules/inline-style-parser": { + "version": "0.2.7", + "resolved": "https://registry.npmjs.org/inline-style-parser/-/inline-style-parser-0.2.7.tgz", + "integrity": "sha512-Nb2ctOyNR8DqQoR0OwRG95uNWIC0C1lCgf5Naz5H6Ji72KZ8OcFZLz2P5sNgwlyoJ8Yif11oMuYs5pBQa86csA==", + "license": "MIT" + }, + "node_modules/iron-webcrypto": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/iron-webcrypto/-/iron-webcrypto-1.2.1.tgz", + "integrity": "sha512-feOM6FaSr6rEABp/eDfVseKyTMDt+KGpeB35SkVn9Tyn0CqvVsY3EwI0v5i8nMHyJnzCIQf7nsy3p41TPkJZhg==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/brc-dd" + } + }, + "node_modules/is-alphabetical": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-alphabetical/-/is-alphabetical-2.0.1.tgz", + "integrity": "sha512-FWyyY60MeTNyeSRpkM2Iry0G9hpr7/9kD40mD/cGQEuilcZYS4okz8SN2Q6rLCJ8gbCt6fN+rC+6tMGS99LaxQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-alphanumerical": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-alphanumerical/-/is-alphanumerical-2.0.1.tgz", + "integrity": "sha512-hmbYhX/9MUMF5uh7tOXyK/n0ZvWpad5caBA17GsC6vyuCqaWliRG5K1qS9inmUhEMaOBIW7/whAnSwveW/LtZw==", + "license": "MIT", + "dependencies": { + "is-alphabetical": "^2.0.0", + "is-decimal": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-decimal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-decimal/-/is-decimal-2.0.1.tgz", + "integrity": "sha512-AAB9hiomQs5DXWcRB1rqsxGUstbRroFOPPVAomNk/3XHR5JyEZChOyTWe2oayKnsSsr/kcGqF+z6yuH6HHpN0A==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-docker": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-3.0.0.tgz", + "integrity": "sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==", + "license": "MIT", + "bin": { + "is-docker": "cli.js" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-hexadecimal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-hexadecimal/-/is-hexadecimal-2.0.1.tgz", + "integrity": "sha512-DgZQp241c8oO6cA1SbTEWiXeoxV42vlcJxgH+B3hi1AiqqKruZR3ZGF8In3fj4+/y/7rHvlOZLZtgJ/4ttYGZg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-inside-container": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-inside-container/-/is-inside-container-1.0.0.tgz", + "integrity": "sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==", + "license": "MIT", + "dependencies": { + "is-docker": "^3.0.0" + }, + "bin": { + "is-inside-container": "cli.js" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-plain-obj": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz", + "integrity": "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-wsl": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-3.1.1.tgz", + "integrity": "sha512-e6rvdUCiQCAuumZslxRJWR/Doq4VpPR82kqclvcS0efgt430SlGIk05vdCN58+VrzgtIcfNODjozVielycD4Sw==", + "license": "MIT", + "dependencies": { + "is-inside-container": "^1.0.0" + }, + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/js-yaml": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", + "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/klona": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/klona/-/klona-2.0.6.tgz", + "integrity": "sha512-dhG34DXATL5hSxJbIexCft8FChFXtmskoZYnoPWjXQuebWYCNkVeV3KkGegCK9CP1oswI/vQibS2GY7Em/sJJA==", + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/longest-streak": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/longest-streak/-/longest-streak-3.1.0.tgz", + "integrity": "sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/lru-cache": { + "version": "11.2.7", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.7.tgz", + "integrity": "sha512-aY/R+aEsRelme17KGQa/1ZSIpLpNYYrhcrepKTZgE+W3WM16YMCaPwOHLHsmopZHELU0Ojin1lPVxKR0MihncA==", + "license": "BlueOak-1.0.0", + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/magic-string": { + "version": "0.30.21", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, + "node_modules/magicast": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/magicast/-/magicast-0.5.2.tgz", + "integrity": "sha512-E3ZJh4J3S9KfwdjZhe2afj6R9lGIN5Pher1pF39UGrXRqq/VDaGVIGN13BjHd2u8B61hArAGOnso7nBOouW3TQ==", + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.0", + "@babel/types": "^7.29.0", + "source-map-js": "^1.2.1" + } + }, + "node_modules/markdown-extensions": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/markdown-extensions/-/markdown-extensions-2.0.0.tgz", + "integrity": "sha512-o5vL7aDWatOTX8LzaS1WMoaoxIiLRQJuIKKe2wAw6IeULDHaqbiqiggmx+pKvZDb1Sj+pE46Sn1T7lCqfFtg1Q==", + "license": "MIT", + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/markdown-table": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/markdown-table/-/markdown-table-3.0.4.tgz", + "integrity": "sha512-wiYz4+JrLyb/DqW2hkFJxP7Vd7JuTDm77fvbM8VfEQdmSMqcImWeeRbHwZjBjIFki/VaMK2BhFi7oUUZeM5bqw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/mdast-util-definitions": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-definitions/-/mdast-util-definitions-6.0.0.tgz", + "integrity": "sha512-scTllyX6pnYNZH/AIp/0ePz6s4cZtARxImwoPJ7kS42n+MnVsI4XbnG6d4ibehRIldYMWM2LD7ImQblVhUejVQ==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "@types/unist": "^3.0.0", + "unist-util-visit": "^5.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-directive": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/mdast-util-directive/-/mdast-util-directive-3.1.0.tgz", + "integrity": "sha512-I3fNFt+DHmpWCYAT7quoM6lHf9wuqtI+oCOfvILnoicNIqjh5E3dEJWiXuYME2gNe8vl1iMQwyUHa7bgFmak6Q==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "@types/unist": "^3.0.0", + "ccount": "^2.0.0", + "devlop": "^1.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0", + "parse-entities": "^4.0.0", + "stringify-entities": "^4.0.0", + "unist-util-visit-parents": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-find-and-replace": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/mdast-util-find-and-replace/-/mdast-util-find-and-replace-3.0.2.tgz", + "integrity": "sha512-Tmd1Vg/m3Xz43afeNxDIhWRtFZgM2VLyaf4vSTYwudTyeuTneoL3qtWMA5jeLyz/O1vDJmmV4QuScFCA2tBPwg==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "escape-string-regexp": "^5.0.0", + "unist-util-is": "^6.0.0", + "unist-util-visit-parents": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-from-markdown": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/mdast-util-from-markdown/-/mdast-util-from-markdown-2.0.3.tgz", + "integrity": "sha512-W4mAWTvSlKvf8L6J+VN9yLSqQ9AOAAvHuoDAmPkz4dHf553m5gVj2ejadHJhoJmcmxEnOv6Pa8XJhpxE93kb8Q==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "@types/unist": "^3.0.0", + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "mdast-util-to-string": "^4.0.0", + "micromark": "^4.0.0", + "micromark-util-decode-numeric-character-reference": "^2.0.0", + "micromark-util-decode-string": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0", + "unist-util-stringify-position": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/mdast-util-gfm/-/mdast-util-gfm-3.1.0.tgz", + "integrity": "sha512-0ulfdQOM3ysHhCJ1p06l0b0VKlhU0wuQs3thxZQagjcjPrlFRqY215uZGHHJan9GEAXd9MbfPjFJz+qMkVR6zQ==", + "license": "MIT", + "dependencies": { + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-gfm-autolink-literal": "^2.0.0", + "mdast-util-gfm-footnote": "^2.0.0", + "mdast-util-gfm-strikethrough": "^2.0.0", + "mdast-util-gfm-table": "^2.0.0", + "mdast-util-gfm-task-list-item": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm-autolink-literal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-autolink-literal/-/mdast-util-gfm-autolink-literal-2.0.1.tgz", + "integrity": "sha512-5HVP2MKaP6L+G6YaxPNjuL0BPrq9orG3TsrZ9YXbA3vDw/ACI4MEsnoDpn6ZNm7GnZgtAcONJyPhOP8tNJQavQ==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "ccount": "^2.0.0", + "devlop": "^1.0.0", + "mdast-util-find-and-replace": "^3.0.0", + "micromark-util-character": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm-footnote": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-footnote/-/mdast-util-gfm-footnote-2.1.0.tgz", + "integrity": "sha512-sqpDWlsHn7Ac9GNZQMeUzPQSMzR6Wv0WKRNvQRg0KqHh02fpTz69Qc1QSseNX29bhz1ROIyNyxExfawVKTm1GQ==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "devlop": "^1.1.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm-strikethrough": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-strikethrough/-/mdast-util-gfm-strikethrough-2.0.0.tgz", + "integrity": "sha512-mKKb915TF+OC5ptj5bJ7WFRPdYtuHv0yTRxK2tJvi+BDqbkiG7h7u/9SI89nRAYcmap2xHQL9D+QG/6wSrTtXg==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm-table": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-table/-/mdast-util-gfm-table-2.0.0.tgz", + "integrity": "sha512-78UEvebzz/rJIxLvE7ZtDd/vIQ0RHv+3Mh5DR96p7cS7HsBhYIICDBCu8csTNWNO6tBWfqXPWekRuj2FNOGOZg==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "markdown-table": "^3.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm-task-list-item": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-task-list-item/-/mdast-util-gfm-task-list-item-2.0.0.tgz", + "integrity": "sha512-IrtvNvjxC1o06taBAVJznEnkiHxLFTzgonUdy8hzFVeDun0uTjxxrRGVaNFqkU1wJR3RBPEfsxmU6jDWPofrTQ==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-mdx": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-mdx/-/mdast-util-mdx-3.0.0.tgz", + "integrity": "sha512-JfbYLAW7XnYTTbUsmpu0kdBUVe+yKVJZBItEjwyYJiDJuZ9w4eeaqks4HQO+R7objWgS2ymV60GYpI14Ug554w==", + "license": "MIT", + "dependencies": { + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-mdx-expression": "^2.0.0", + "mdast-util-mdx-jsx": "^3.0.0", + "mdast-util-mdxjs-esm": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-mdx-expression": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mdast-util-mdx-expression/-/mdast-util-mdx-expression-2.0.1.tgz", + "integrity": "sha512-J6f+9hUp+ldTZqKRSg7Vw5V6MqjATc+3E4gf3CFNcuZNWD8XdyI6zQ8GqH7f8169MM6P7hMBRDVGnn7oHB9kXQ==", + "license": "MIT", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-mdx-jsx": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/mdast-util-mdx-jsx/-/mdast-util-mdx-jsx-3.2.0.tgz", + "integrity": "sha512-lj/z8v0r6ZtsN/cGNNtemmmfoLAFZnjMbNyLzBafjzikOM+glrjNHPlf6lQDOTccj9n5b0PPihEBbhneMyGs1Q==", + "license": "MIT", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "@types/unist": "^3.0.0", + "ccount": "^2.0.0", + "devlop": "^1.1.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0", + "parse-entities": "^4.0.0", + "stringify-entities": "^4.0.0", + "unist-util-stringify-position": "^4.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-mdxjs-esm": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mdast-util-mdxjs-esm/-/mdast-util-mdxjs-esm-2.0.1.tgz", + "integrity": "sha512-EcmOpxsZ96CvlP03NghtH1EsLtr0n9Tm4lPUJUBccV9RwUOneqSycg19n5HGzCf+10LozMRSObtVr3ee1WoHtg==", + "license": "MIT", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-phrasing": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/mdast-util-phrasing/-/mdast-util-phrasing-4.1.0.tgz", + "integrity": "sha512-TqICwyvJJpBwvGAMZjj4J2n0X8QWp21b9l0o7eXyVJ25YNWYbJDVIyD1bZXE6WtV6RmKJVYmQAKWa0zWOABz2w==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "unist-util-is": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-to-hast": { + "version": "13.2.1", + "resolved": "https://registry.npmjs.org/mdast-util-to-hast/-/mdast-util-to-hast-13.2.1.tgz", + "integrity": "sha512-cctsq2wp5vTsLIcaymblUriiTcZd0CwWtCbLvrOzYCDZoWyMNV8sZ7krj09FSnsiJi3WVsHLM4k6Dq/yaPyCXA==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "@ungap/structured-clone": "^1.0.0", + "devlop": "^1.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "trim-lines": "^3.0.0", + "unist-util-position": "^5.0.0", + "unist-util-visit": "^5.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-to-markdown": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/mdast-util-to-markdown/-/mdast-util-to-markdown-2.1.2.tgz", + "integrity": "sha512-xj68wMTvGXVOKonmog6LwyJKrYXZPvlwabaryTjLh9LuvovB/KAH+kvi8Gjj+7rJjsFi23nkUxRQv1KqSroMqA==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "@types/unist": "^3.0.0", + "longest-streak": "^3.0.0", + "mdast-util-phrasing": "^4.0.0", + "mdast-util-to-string": "^4.0.0", + "micromark-util-classify-character": "^2.0.0", + "micromark-util-decode-string": "^2.0.0", + "unist-util-visit": "^5.0.0", + "zwitch": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-to-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-to-string/-/mdast-util-to-string-4.0.0.tgz", + "integrity": "sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdn-data": { + "version": "2.27.1", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.27.1.tgz", + "integrity": "sha512-9Yubnt3e8A0OKwxYSXyhLymGW4sCufcLG6VdiDdUGVkPhpqLxlvP5vl1983gQjJl3tqbrM731mjaZaP68AgosQ==", + "license": "CC0-1.0" + }, + "node_modules/micromark": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/micromark/-/micromark-4.0.2.tgz", + "integrity": "sha512-zpe98Q6kvavpCr1NPVSCMebCKfD7CA2NqZ+rykeNhONIJBpc1tFKt9hucLGwha3jNTNI8lHpctWJWoimVF4PfA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "@types/debug": "^4.0.0", + "debug": "^4.0.0", + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "micromark-core-commonmark": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-combine-extensions": "^2.0.0", + "micromark-util-decode-numeric-character-reference": "^2.0.0", + "micromark-util-encode": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-resolve-all": "^2.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "micromark-util-subtokenize": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-core-commonmark": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/micromark-core-commonmark/-/micromark-core-commonmark-2.0.3.tgz", + "integrity": "sha512-RDBrHEMSxVFLg6xvnXmb1Ayr2WzLAWjeSATAoxwKYJV94TeNavgoIdA0a9ytzDSVzBy2YKFK+emCPOEibLeCrg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "micromark-factory-destination": "^2.0.0", + "micromark-factory-label": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-factory-title": "^2.0.0", + "micromark-factory-whitespace": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-classify-character": "^2.0.0", + "micromark-util-html-tag-name": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-resolve-all": "^2.0.0", + "micromark-util-subtokenize": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-extension-directive": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/micromark-extension-directive/-/micromark-extension-directive-3.0.2.tgz", + "integrity": "sha512-wjcXHgk+PPdmvR58Le9d7zQYWy+vKEU9Se44p2CrCDPiLr2FMyiT4Fyb5UFKFC66wGB3kPlgD7q3TnoqPS7SZA==", + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-factory-whitespace": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0", + "parse-entities": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm/-/micromark-extension-gfm-3.0.0.tgz", + "integrity": "sha512-vsKArQsicm7t0z2GugkCKtZehqUm31oeGBV/KVSorWSy8ZlNAv7ytjFhvaryUiCUJYqs+NoE6AFhpQvBTM6Q4w==", + "license": "MIT", + "dependencies": { + "micromark-extension-gfm-autolink-literal": "^2.0.0", + "micromark-extension-gfm-footnote": "^2.0.0", + "micromark-extension-gfm-strikethrough": "^2.0.0", + "micromark-extension-gfm-table": "^2.0.0", + "micromark-extension-gfm-tagfilter": "^2.0.0", + "micromark-extension-gfm-task-list-item": "^2.0.0", + "micromark-util-combine-extensions": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-autolink-literal": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-autolink-literal/-/micromark-extension-gfm-autolink-literal-2.1.0.tgz", + "integrity": "sha512-oOg7knzhicgQ3t4QCjCWgTmfNhvQbDDnJeVu9v81r7NltNCVmhPy1fJRX27pISafdjL+SVc4d3l48Gb6pbRypw==", + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-footnote": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-footnote/-/micromark-extension-gfm-footnote-2.1.0.tgz", + "integrity": "sha512-/yPhxI1ntnDNsiHtzLKYnE3vf9JZ6cAisqVDauhp4CEHxlb4uoOTxOCJ+9s51bIB8U1N1FJ1RXOKTIlD5B/gqw==", + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-core-commonmark": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-strikethrough": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-strikethrough/-/micromark-extension-gfm-strikethrough-2.1.0.tgz", + "integrity": "sha512-ADVjpOOkjz1hhkZLlBiYA9cR2Anf8F4HqZUO6e5eDcPQd0Txw5fxLzzxnEkSkfnD0wziSGiv7sYhk/ktvbf1uw==", + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-classify-character": "^2.0.0", + "micromark-util-resolve-all": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-table": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-table/-/micromark-extension-gfm-table-2.1.1.tgz", + "integrity": "sha512-t2OU/dXXioARrC6yWfJ4hqB7rct14e8f7m0cbI5hUmDyyIlwv5vEtooptH8INkbLzOatzKuVbQmAYcbWoyz6Dg==", + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-tagfilter": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-tagfilter/-/micromark-extension-gfm-tagfilter-2.0.0.tgz", + "integrity": "sha512-xHlTOmuCSotIA8TW1mDIM6X2O1SiX5P9IuDtqGonFhEK0qgRI4yeC6vMxEV2dgyr2TiD+2PQ10o+cOhdVAcwfg==", + "license": "MIT", + "dependencies": { + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-task-list-item": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-task-list-item/-/micromark-extension-gfm-task-list-item-2.1.0.tgz", + "integrity": "sha512-qIBZhqxqI6fjLDYFTBIa4eivDMnP+OZqsNwmQ3xNLE4Cxwc+zfQEfbs6tzAo2Hjq+bh6q5F+Z8/cksrLFYWQQw==", + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-mdx-expression": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/micromark-extension-mdx-expression/-/micromark-extension-mdx-expression-3.0.1.tgz", + "integrity": "sha512-dD/ADLJ1AeMvSAKBwO22zG22N4ybhe7kFIZ3LsDI0GlsNr2A3KYxb0LdC1u5rj4Nw+CHKY0RVdnHX8vj8ejm4Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "devlop": "^1.0.0", + "micromark-factory-mdx-expression": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-events-to-acorn": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-extension-mdx-jsx": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/micromark-extension-mdx-jsx/-/micromark-extension-mdx-jsx-3.0.2.tgz", + "integrity": "sha512-e5+q1DjMh62LZAJOnDraSSbDMvGJ8x3cbjygy2qFEi7HCeUT4BDKCvMozPozcD6WmOt6sVvYDNBKhFSz3kjOVQ==", + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "devlop": "^1.0.0", + "estree-util-is-identifier-name": "^3.0.0", + "micromark-factory-mdx-expression": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-events-to-acorn": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-mdx-md": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-extension-mdx-md/-/micromark-extension-mdx-md-2.0.0.tgz", + "integrity": "sha512-EpAiszsB3blw4Rpba7xTOUptcFeBFi+6PY8VnJ2hhimH+vCQDirWgsMpz7w1XcZE7LVrSAUGb9VJpG9ghlYvYQ==", + "license": "MIT", + "dependencies": { + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-mdxjs": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/micromark-extension-mdxjs/-/micromark-extension-mdxjs-3.0.0.tgz", + "integrity": "sha512-A873fJfhnJ2siZyUrJ31l34Uqwy4xIFmvPY1oj+Ean5PHcPBYzEsvqvWGaWcfEIr11O5Dlw3p2y0tZWpKHDejQ==", + "license": "MIT", + "dependencies": { + "acorn": "^8.0.0", + "acorn-jsx": "^5.0.0", + "micromark-extension-mdx-expression": "^3.0.0", + "micromark-extension-mdx-jsx": "^3.0.0", + "micromark-extension-mdx-md": "^2.0.0", + "micromark-extension-mdxjs-esm": "^3.0.0", + "micromark-util-combine-extensions": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-mdxjs-esm": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/micromark-extension-mdxjs-esm/-/micromark-extension-mdxjs-esm-3.0.0.tgz", + "integrity": "sha512-DJFl4ZqkErRpq/dAPyeWp15tGrcrrJho1hKK5uBS70BCtfrIFg81sqcTVu3Ta+KD1Tk5vAtBNElWxtAa+m8K9A==", + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "devlop": "^1.0.0", + "micromark-core-commonmark": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-events-to-acorn": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0", + "unist-util-position-from-estree": "^2.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-factory-destination": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-destination/-/micromark-factory-destination-2.0.1.tgz", + "integrity": "sha512-Xe6rDdJlkmbFRExpTOmRj9N3MaWmbAgdpSrBQvCFqhezUn4AHqJHbaEnfbVYYiexVSs//tqOdY/DxhjdCiJnIA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-label": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-label/-/micromark-factory-label-2.0.1.tgz", + "integrity": "sha512-VFMekyQExqIW7xIChcXn4ok29YE3rnuyveW3wZQWWqF4Nv9Wk5rgJ99KzPvHjkmPXF93FXIbBp6YdW3t71/7Vg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-mdx-expression": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/micromark-factory-mdx-expression/-/micromark-factory-mdx-expression-2.0.3.tgz", + "integrity": "sha512-kQnEtA3vzucU2BkrIa8/VaSAsP+EJ3CKOvhMuJgOEGg9KDC6OAY6nSnNDVRiVNRqj7Y4SlSzcStaH/5jge8JdQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "devlop": "^1.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-events-to-acorn": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0", + "unist-util-position-from-estree": "^2.0.0", + "vfile-message": "^4.0.0" + } + }, + "node_modules/micromark-factory-space": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.1.tgz", + "integrity": "sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-title": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-title/-/micromark-factory-title-2.0.1.tgz", + "integrity": "sha512-5bZ+3CjhAd9eChYTHsjy6TGxpOFSKgKKJPJxr293jTbfry2KDoWkhBb6TcPVB4NmzaPhMs1Frm9AZH7OD4Cjzw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-whitespace": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-whitespace/-/micromark-factory-whitespace-2.0.1.tgz", + "integrity": "sha512-Ob0nuZ3PKt/n0hORHyvoD9uZhr+Za8sFoP+OnMcnWK5lngSzALgQYKMr9RJVOWLqQYuyn6ulqGWSXdwf6F80lQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-character": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", + "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-chunked": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-chunked/-/micromark-util-chunked-2.0.1.tgz", + "integrity": "sha512-QUNFEOPELfmvv+4xiNg2sRYeS/P84pTW0TCgP5zc9FpXetHY0ab7SxKyAQCNCc1eK0459uoLI1y5oO5Vc1dbhA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-classify-character": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-classify-character/-/micromark-util-classify-character-2.0.1.tgz", + "integrity": "sha512-K0kHzM6afW/MbeWYWLjoHQv1sgg2Q9EccHEDzSkxiP/EaagNzCm7T/WMKZ3rjMbvIpvBiZgwR3dKMygtA4mG1Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-combine-extensions": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-combine-extensions/-/micromark-util-combine-extensions-2.0.1.tgz", + "integrity": "sha512-OnAnH8Ujmy59JcyZw8JSbK9cGpdVY44NKgSM7E9Eh7DiLS2E9RNQf0dONaGDzEG9yjEl5hcqeIsj4hfRkLH/Bg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-chunked": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-decode-numeric-character-reference": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/micromark-util-decode-numeric-character-reference/-/micromark-util-decode-numeric-character-reference-2.0.2.tgz", + "integrity": "sha512-ccUbYk6CwVdkmCQMyr64dXz42EfHGkPQlBj5p7YVGzq8I7CtjXZJrubAYezf7Rp+bjPseiROqe7G6foFd+lEuw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-decode-string": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-decode-string/-/micromark-util-decode-string-2.0.1.tgz", + "integrity": "sha512-nDV/77Fj6eH1ynwscYTOsbK7rR//Uj0bZXBwJZRfaLEJ1iGBR6kIfNmlNqaqJf649EP0F3NWNdeJi03elllNUQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "decode-named-character-reference": "^1.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-decode-numeric-character-reference": "^2.0.0", + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-encode": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-encode/-/micromark-util-encode-2.0.1.tgz", + "integrity": "sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-util-events-to-acorn": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/micromark-util-events-to-acorn/-/micromark-util-events-to-acorn-2.0.3.tgz", + "integrity": "sha512-jmsiEIiZ1n7X1Rr5k8wVExBQCg5jy4UXVADItHmNk1zkwEVhBuIUKRu3fqv+hs4nxLISi2DQGlqIOGiFxgbfHg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "@types/unist": "^3.0.0", + "devlop": "^1.0.0", + "estree-util-visit": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0", + "vfile-message": "^4.0.0" + } + }, + "node_modules/micromark-util-html-tag-name": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-html-tag-name/-/micromark-util-html-tag-name-2.0.1.tgz", + "integrity": "sha512-2cNEiYDhCWKI+Gs9T0Tiysk136SnR13hhO8yW6BGNyhOC4qYFnwF1nKfD3HFAIXA5c45RrIG1ub11GiXeYd1xA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-util-normalize-identifier": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-normalize-identifier/-/micromark-util-normalize-identifier-2.0.1.tgz", + "integrity": "sha512-sxPqmo70LyARJs0w2UclACPUUEqltCkJ6PhKdMIDuJ3gSf/Q+/GIe3WKl0Ijb/GyH9lOpUkRAO2wp0GVkLvS9Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-resolve-all": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-resolve-all/-/micromark-util-resolve-all-2.0.1.tgz", + "integrity": "sha512-VdQyxFWFT2/FGJgwQnJYbe1jjQoNTS4RjglmSjTUlpUMa95Htx9NHeYW4rGDJzbjvCsl9eLjMQwGeElsqmzcHg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-sanitize-uri": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-2.0.1.tgz", + "integrity": "sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-encode": "^2.0.0", + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-subtokenize": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-subtokenize/-/micromark-util-subtokenize-2.1.0.tgz", + "integrity": "sha512-XQLu552iSctvnEcgXw6+Sx75GflAPNED1qx7eBJ+wydBb2KCbRZe+NwvIEEMM83uml1+2WSXpBAcp9IUCgCYWA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-symbol": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-util-types": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-2.0.2.tgz", + "integrity": "sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/mrmime": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mrmime/-/mrmime-2.0.1.tgz", + "integrity": "sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ==", + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/nanoid": { + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/neotraverse": { + "version": "0.6.18", + "resolved": "https://registry.npmjs.org/neotraverse/-/neotraverse-0.6.18.tgz", + "integrity": "sha512-Z4SmBUweYa09+o6pG+eASabEpP6QkQ70yHj351pQoEXIs8uHbaU2DWVmzBANKgflPa47A50PtB2+NgRpQvr7vA==", + "license": "MIT", + "engines": { + "node": ">= 10" + } + }, + "node_modules/nlcst-to-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/nlcst-to-string/-/nlcst-to-string-4.0.0.tgz", + "integrity": "sha512-YKLBCcUYKAg0FNlOBT6aI91qFmSiFKiluk655WzPF+DDMA02qIyy8uiRqI8QXtcFpEvll12LpL5MXqEmAZ+dcA==", + "license": "MIT", + "dependencies": { + "@types/nlcst": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/node-fetch-native": { + "version": "1.6.7", + "resolved": "https://registry.npmjs.org/node-fetch-native/-/node-fetch-native-1.6.7.tgz", + "integrity": "sha512-g9yhqoedzIUm0nTnTqAQvueMPVOuIY16bqgAJJC8XOOubYFNwz6IER9qs0Gq2Xd0+CecCKFjtdDTMA4u4xG06Q==", + "license": "MIT" + }, + "node_modules/node-mock-http": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/node-mock-http/-/node-mock-http-1.0.4.tgz", + "integrity": "sha512-8DY+kFsDkNXy1sJglUfuODx1/opAGJGyrTuFqEoN90oRc2Vk0ZbD4K2qmKXBBEhZQzdKHIVfEJpDU8Ak2NJEvQ==", + "license": "MIT" + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/nth-check": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", + "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==", + "license": "BSD-2-Clause", + "dependencies": { + "boolbase": "^1.0.0" + }, + "funding": { + "url": "https://github.com/fb55/nth-check?sponsor=1" + } + }, + "node_modules/obug": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/obug/-/obug-2.1.1.tgz", + "integrity": "sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ==", + "funding": [ + "https://github.com/sponsors/sxzz", + "https://opencollective.com/debug" + ], + "license": "MIT" + }, + "node_modules/ofetch": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/ofetch/-/ofetch-1.5.1.tgz", + "integrity": "sha512-2W4oUZlVaqAPAil6FUg/difl6YhqhUR7x2eZY4bQCko22UXg3hptq9KLQdqFClV+Wu85UX7hNtdGTngi/1BxcA==", + "license": "MIT", + "dependencies": { + "destr": "^2.0.5", + "node-fetch-native": "^1.6.7", + "ufo": "^1.6.1" + } + }, + "node_modules/ohash": { + "version": "2.0.11", + "resolved": "https://registry.npmjs.org/ohash/-/ohash-2.0.11.tgz", + "integrity": "sha512-RdR9FQrFwNBNXAr4GixM8YaRZRJ5PUWbKYbE5eOsrwAjJW0q2REGcf79oYPsLyskQCZG1PLN+S/K1V00joZAoQ==", + "license": "MIT" + }, + "node_modules/oniguruma-parser": { + "version": "0.12.1", + "resolved": "https://registry.npmjs.org/oniguruma-parser/-/oniguruma-parser-0.12.1.tgz", + "integrity": "sha512-8Unqkvk1RYc6yq2WBYRj4hdnsAxVze8i7iPfQr8e4uSP3tRv0rpZcbGUDvxfQQcdwHt/e9PrMvGCsa8OqG9X3w==", + "license": "MIT" + }, + "node_modules/oniguruma-to-es": { + "version": "4.3.5", + "resolved": "https://registry.npmjs.org/oniguruma-to-es/-/oniguruma-to-es-4.3.5.tgz", + "integrity": "sha512-Zjygswjpsewa0NLTsiizVuMQZbp0MDyM6lIt66OxsF21npUDlzpHi1Mgb/qhQdkb+dWFTzJmFbEWdvZgRho8eQ==", + "license": "MIT", + "dependencies": { + "oniguruma-parser": "^0.12.1", + "regex": "^6.1.0", + "regex-recursion": "^6.0.2" + } + }, + "node_modules/p-limit": { + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-7.3.0.tgz", + "integrity": "sha512-7cIXg/Z0M5WZRblrsOla88S4wAK+zOQQWeBYfV3qJuJXMr+LnbYjaadrFaS0JILfEDPVqHyKnZ1Z/1d6J9VVUw==", + "license": "MIT", + "dependencies": { + "yocto-queue": "^1.2.1" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-queue": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/p-queue/-/p-queue-9.1.0.tgz", + "integrity": "sha512-O/ZPaXuQV29uSLbxWBGGZO1mCQXV2BLIwUr59JUU9SoH76mnYvtms7aafH/isNSNGwuEfP6W/4xD0/TJXxrizw==", + "license": "MIT", + "dependencies": { + "eventemitter3": "^5.0.1", + "p-timeout": "^7.0.0" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-timeout": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-7.0.1.tgz", + "integrity": "sha512-AxTM2wDGORHGEkPCt8yqxOTMgpfbEHqF51f/5fJCmwFC3C/zNcGT63SymH2ttOAaiIws2zVg4+izQCjrakcwHg==", + "license": "MIT", + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/package-manager-detector": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/package-manager-detector/-/package-manager-detector-1.6.0.tgz", + "integrity": "sha512-61A5ThoTiDG/C8s8UMZwSorAGwMJ0ERVGj2OjoW5pAalsNOg15+iQiPzrLJ4jhZ1HJzmC2PIHT2oEiH3R5fzNA==", + "license": "MIT" + }, + "node_modules/pagefind": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/pagefind/-/pagefind-1.4.0.tgz", + "integrity": "sha512-z2kY1mQlL4J8q5EIsQkLzQjilovKzfNVhX8De6oyE6uHpfFtyBaqUpcl/XzJC/4fjD8vBDyh1zolimIcVrCn9g==", + "license": "MIT", + "bin": { + "pagefind": "lib/runner/bin.cjs" + }, + "optionalDependencies": { + "@pagefind/darwin-arm64": "1.4.0", + "@pagefind/darwin-x64": "1.4.0", + "@pagefind/freebsd-x64": "1.4.0", + "@pagefind/linux-arm64": "1.4.0", + "@pagefind/linux-x64": "1.4.0", + "@pagefind/windows-x64": "1.4.0" + } + }, + "node_modules/parse-entities": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/parse-entities/-/parse-entities-4.0.2.tgz", + "integrity": "sha512-GG2AQYWoLgL877gQIKeRPGO1xF9+eG1ujIb5soS5gPvLQ1y2o8FL90w2QWNdf9I361Mpp7726c+lj3U0qK1uGw==", + "license": "MIT", + "dependencies": { + "@types/unist": "^2.0.0", + "character-entities-legacy": "^3.0.0", + "character-reference-invalid": "^2.0.0", + "decode-named-character-reference": "^1.0.0", + "is-alphanumerical": "^2.0.0", + "is-decimal": "^2.0.0", + "is-hexadecimal": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/parse-entities/node_modules/@types/unist": { + "version": "2.0.11", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.11.tgz", + "integrity": "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==", + "license": "MIT" + }, + "node_modules/parse-latin": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/parse-latin/-/parse-latin-7.0.0.tgz", + "integrity": "sha512-mhHgobPPua5kZ98EF4HWiH167JWBfl4pvAIXXdbaVohtK7a6YBOy56kvhCqduqyo/f3yrHFWmqmiMg/BkBkYYQ==", + "license": "MIT", + "dependencies": { + "@types/nlcst": "^2.0.0", + "@types/unist": "^3.0.0", + "nlcst-to-string": "^4.0.0", + "unist-util-modify-children": "^4.0.0", + "unist-util-visit-children": "^3.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/parse5": { + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.3.0.tgz", + "integrity": "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==", + "license": "MIT", + "dependencies": { + "entities": "^6.0.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/piccolore": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/piccolore/-/piccolore-0.1.3.tgz", + "integrity": "sha512-o8bTeDWjE086iwKrROaDf31K0qC/BENdm15/uH9usSC/uZjJOKb2YGiVHfLY4GhwsERiPI1jmwI2XrA7ACOxVw==", + "license": "ISC" + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/postcss": { + "version": "8.5.8", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.8.tgz", + "integrity": "sha512-OW/rX8O/jXnm82Ey1k44pObPtdblfiuWnrd8X7GJ7emImCOstunGbXUpp7HdBrFQX6rJzn3sPT397Wp5aCwCHg==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/postcss-nested": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-6.2.0.tgz", + "integrity": "sha512-HQbt28KulC5AJzG+cZtj9kvKB93CFCdLvog1WFLf1D+xmMvPGlBstkpTEZfK5+AN9hfJocyBFCNiqyS48bpgzQ==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "postcss-selector-parser": "^6.1.1" + }, + "engines": { + "node": ">=12.0" + }, + "peerDependencies": { + "postcss": "^8.2.14" + } + }, + "node_modules/postcss-selector-parser": { + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz", + "integrity": "sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==", + "license": "MIT", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/prismjs": { + "version": "1.30.0", + "resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.30.0.tgz", + "integrity": "sha512-DEvV2ZF2r2/63V+tK8hQvrR2ZGn10srHbXviTlcv7Kpzw8jWiNTqbVgjO3IY8RxrrOUF8VPMQQFysYYYv0YZxw==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/property-information": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/property-information/-/property-information-7.1.0.tgz", + "integrity": "sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/radix3": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/radix3/-/radix3-1.1.2.tgz", + "integrity": "sha512-b484I/7b8rDEdSDKckSSBA8knMpcdsXudlE/LNL639wFoHKwLbEkQFZHWEYwDC0wa0FKUcCY+GAF73Z7wxNVFA==", + "license": "MIT" + }, + "node_modules/readdirp": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-5.0.0.tgz", + "integrity": "sha512-9u/XQ1pvrQtYyMpZe7DXKv2p5CNvyVwzUB6uhLAnQwHMSgKMBR62lc7AHljaeteeHXn11XTAaLLUVZYVZyuRBQ==", + "license": "MIT", + "engines": { + "node": ">= 20.19.0" + }, + "funding": { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/recma-build-jsx": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/recma-build-jsx/-/recma-build-jsx-1.0.0.tgz", + "integrity": "sha512-8GtdyqaBcDfva+GUKDr3nev3VpKAhup1+RvkMvUxURHpW7QyIvk9F5wz7Vzo06CEMSilw6uArgRqhpiUcWp8ew==", + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "estree-util-build-jsx": "^3.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/recma-jsx": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/recma-jsx/-/recma-jsx-1.0.1.tgz", + "integrity": "sha512-huSIy7VU2Z5OLv6oFLosQGGDqPqdO1iq6bWNAdhzMxSJP7RAso4fCZ1cKu8j9YHCZf3TPrq4dw3okhrylgcd7w==", + "license": "MIT", + "dependencies": { + "acorn-jsx": "^5.0.0", + "estree-util-to-js": "^2.0.0", + "recma-parse": "^1.0.0", + "recma-stringify": "^1.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + }, + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/recma-parse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/recma-parse/-/recma-parse-1.0.0.tgz", + "integrity": "sha512-OYLsIGBB5Y5wjnSnQW6t3Xg7q3fQ7FWbw/vcXtORTnyaSFscOtABg+7Pnz6YZ6c27fG1/aN8CjfwoUEUIdwqWQ==", + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "esast-util-from-js": "^2.0.0", + "unified": "^11.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/recma-stringify": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/recma-stringify/-/recma-stringify-1.0.0.tgz", + "integrity": "sha512-cjwII1MdIIVloKvC9ErQ+OgAtwHBmcZ0Bg4ciz78FtbT8In39aAYbaA7zvxQ61xVMSPE8WxhLwLbhif4Js2C+g==", + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "estree-util-to-js": "^2.0.0", + "unified": "^11.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/regex": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/regex/-/regex-6.1.0.tgz", + "integrity": "sha512-6VwtthbV4o/7+OaAF9I5L5V3llLEsoPyq9P1JVXkedTP33c7MfCG0/5NOPcSJn0TzXcG9YUrR0gQSWioew3LDg==", + "license": "MIT", + "dependencies": { + "regex-utilities": "^2.3.0" + } + }, + "node_modules/regex-recursion": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/regex-recursion/-/regex-recursion-6.0.2.tgz", + "integrity": "sha512-0YCaSCq2VRIebiaUviZNs0cBz1kg5kVS2UKUfNIx8YVs1cN3AV7NTctO5FOKBA+UT2BPJIWZauYHPqJODG50cg==", + "license": "MIT", + "dependencies": { + "regex-utilities": "^2.3.0" + } + }, + "node_modules/regex-utilities": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/regex-utilities/-/regex-utilities-2.3.0.tgz", + "integrity": "sha512-8VhliFJAWRaUiVvREIiW2NXXTmHs4vMNnSzuJVhscgmGav3g9VDxLrQndI3dZZVVdp0ZO/5v0xmX516/7M9cng==", + "license": "MIT" + }, + "node_modules/rehype": { + "version": "13.0.2", + "resolved": "https://registry.npmjs.org/rehype/-/rehype-13.0.2.tgz", + "integrity": "sha512-j31mdaRFrwFRUIlxGeuPXXKWQxet52RBQRvCmzl5eCefn/KGbomK5GMHNMsOJf55fgo3qw5tST5neDuarDYR2A==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "rehype-parse": "^9.0.0", + "rehype-stringify": "^10.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/rehype-expressive-code": { + "version": "0.41.7", + "resolved": "https://registry.npmjs.org/rehype-expressive-code/-/rehype-expressive-code-0.41.7.tgz", + "integrity": "sha512-25f8ZMSF1d9CMscX7Cft0TSQIqdwjce2gDOvQ+d/w0FovsMwrSt3ODP4P3Z7wO1jsIJ4eYyaDRnIR/27bd/EMQ==", + "license": "MIT", + "dependencies": { + "expressive-code": "^0.41.7" + } + }, + "node_modules/rehype-format": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/rehype-format/-/rehype-format-5.0.1.tgz", + "integrity": "sha512-zvmVru9uB0josBVpr946OR8ui7nJEdzZobwLOOqHb/OOD88W0Vk2SqLwoVOj0fM6IPCCO6TaV9CvQvJMWwukFQ==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "hast-util-format": "^1.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/rehype-parse": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/rehype-parse/-/rehype-parse-9.0.1.tgz", + "integrity": "sha512-ksCzCD0Fgfh7trPDxr2rSylbwq9iYDkSn8TCDmEJ49ljEUBxDVCzCHv7QNzZOfODanX4+bWQ4WZqLCRWYLfhag==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "hast-util-from-html": "^2.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/rehype-raw": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/rehype-raw/-/rehype-raw-7.0.0.tgz", + "integrity": "sha512-/aE8hCfKlQeA8LmyeyQvQF3eBiLRGNlfBJEvWH7ivp9sBqs7TNqBL5X3v157rM4IFETqDnIOO+z5M/biZbo9Ww==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "hast-util-raw": "^9.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/rehype-recma": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/rehype-recma/-/rehype-recma-1.0.0.tgz", + "integrity": "sha512-lqA4rGUf1JmacCNWWZx0Wv1dHqMwxzsDWYMTowuplHF3xH0N/MmrZ/G3BDZnzAkRmxDadujCjaKM2hqYdCBOGw==", + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "@types/hast": "^3.0.0", + "hast-util-to-estree": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/rehype-stringify": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/rehype-stringify/-/rehype-stringify-10.0.1.tgz", + "integrity": "sha512-k9ecfXHmIPuFVI61B9DeLPN0qFHfawM6RsuX48hoqlaKSF61RskNjSm1lI8PhBEM0MRdLxVVm4WmTqJQccH9mA==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "hast-util-to-html": "^9.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-directive": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/remark-directive/-/remark-directive-3.0.1.tgz", + "integrity": "sha512-gwglrEQEZcZYgVyG1tQuA+h58EZfq5CSULw7J90AFuCTyib1thgHPoqQ+h9iFvU6R+vnZ5oNFQR5QKgGpk741A==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "mdast-util-directive": "^3.0.0", + "micromark-extension-directive": "^3.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-gfm": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/remark-gfm/-/remark-gfm-4.0.1.tgz", + "integrity": "sha512-1quofZ2RQ9EWdeN34S79+KExV1764+wCUGop5CPL1WGdD0ocPpu91lzPGbwWMECpEpd42kJGQwzRfyov9j4yNg==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "mdast-util-gfm": "^3.0.0", + "micromark-extension-gfm": "^3.0.0", + "remark-parse": "^11.0.0", + "remark-stringify": "^11.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-mdx": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/remark-mdx/-/remark-mdx-3.1.1.tgz", + "integrity": "sha512-Pjj2IYlUY3+D8x00UJsIOg5BEvfMyeI+2uLPn9VO9Wg4MEtN/VTIq2NEJQfde9PnX15KgtHyl9S0BcTnWrIuWg==", + "license": "MIT", + "dependencies": { + "mdast-util-mdx": "^3.0.0", + "micromark-extension-mdxjs": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-parse": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/remark-parse/-/remark-parse-11.0.0.tgz", + "integrity": "sha512-FCxlKLNGknS5ba/1lmpYijMUzX2esxW5xQqjWxw2eHFfS2MSdaHVINFmhjo+qN1WhZhNimq0dZATN9pH0IDrpA==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "mdast-util-from-markdown": "^2.0.0", + "micromark-util-types": "^2.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-rehype": { + "version": "11.1.2", + "resolved": "https://registry.npmjs.org/remark-rehype/-/remark-rehype-11.1.2.tgz", + "integrity": "sha512-Dh7l57ianaEoIpzbp0PC9UKAdCSVklD8E5Rpw7ETfbTl3FqcOOgq5q2LVDhgGCkaBv7p24JXikPdvhhmHvKMsw==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "mdast-util-to-hast": "^13.0.0", + "unified": "^11.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-smartypants": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/remark-smartypants/-/remark-smartypants-3.0.2.tgz", + "integrity": "sha512-ILTWeOriIluwEvPjv67v7Blgrcx+LZOkAUVtKI3putuhlZm84FnqDORNXPPm+HY3NdZOMhyDwZ1E+eZB/Df5dA==", + "license": "MIT", + "dependencies": { + "retext": "^9.0.0", + "retext-smartypants": "^6.0.0", + "unified": "^11.0.4", + "unist-util-visit": "^5.0.0" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/remark-stringify": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/remark-stringify/-/remark-stringify-11.0.0.tgz", + "integrity": "sha512-1OSmLd3awB/t8qdoEOMazZkNsfVTeY4fTsgzcQFdXNq8ToTN4ZGwrMnlda4K6smTFKD+GRV6O48i6Z4iKgPPpw==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "mdast-util-to-markdown": "^2.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/retext": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/retext/-/retext-9.0.0.tgz", + "integrity": "sha512-sbMDcpHCNjvlheSgMfEcVrZko3cDzdbe1x/e7G66dFp0Ff7Mldvi2uv6JkJQzdRcvLYE8CA8Oe8siQx8ZOgTcA==", + "license": "MIT", + "dependencies": { + "@types/nlcst": "^2.0.0", + "retext-latin": "^4.0.0", + "retext-stringify": "^4.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/retext-latin": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/retext-latin/-/retext-latin-4.0.0.tgz", + "integrity": "sha512-hv9woG7Fy0M9IlRQloq/N6atV82NxLGveq+3H2WOi79dtIYWN8OaxogDm77f8YnVXJL2VD3bbqowu5E3EMhBYA==", + "license": "MIT", + "dependencies": { + "@types/nlcst": "^2.0.0", + "parse-latin": "^7.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/retext-smartypants": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/retext-smartypants/-/retext-smartypants-6.2.0.tgz", + "integrity": "sha512-kk0jOU7+zGv//kfjXEBjdIryL1Acl4i9XNkHxtM7Tm5lFiCog576fjNC9hjoR7LTKQ0DsPWy09JummSsH1uqfQ==", + "license": "MIT", + "dependencies": { + "@types/nlcst": "^2.0.0", + "nlcst-to-string": "^4.0.0", + "unist-util-visit": "^5.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/retext-stringify": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/retext-stringify/-/retext-stringify-4.0.0.tgz", + "integrity": "sha512-rtfN/0o8kL1e+78+uxPTqu1Klt0yPzKuQ2BfWwwfgIUSayyzxpM1PJzkKt4V8803uB9qSy32MvI7Xep9khTpiA==", + "license": "MIT", + "dependencies": { + "@types/nlcst": "^2.0.0", + "nlcst-to-string": "^4.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/rollup": { + "version": "4.60.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.60.0.tgz", + "integrity": "sha512-yqjxruMGBQJ2gG4HtjZtAfXArHomazDHoFwFFmZZl0r7Pdo7qCIXKqKHZc8yeoMgzJJ+pO6pEEHa+V7uzWlrAQ==", + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.8" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.60.0", + "@rollup/rollup-android-arm64": "4.60.0", + "@rollup/rollup-darwin-arm64": "4.60.0", + "@rollup/rollup-darwin-x64": "4.60.0", + "@rollup/rollup-freebsd-arm64": "4.60.0", + "@rollup/rollup-freebsd-x64": "4.60.0", + "@rollup/rollup-linux-arm-gnueabihf": "4.60.0", + "@rollup/rollup-linux-arm-musleabihf": "4.60.0", + "@rollup/rollup-linux-arm64-gnu": "4.60.0", + "@rollup/rollup-linux-arm64-musl": "4.60.0", + "@rollup/rollup-linux-loong64-gnu": "4.60.0", + "@rollup/rollup-linux-loong64-musl": "4.60.0", + "@rollup/rollup-linux-ppc64-gnu": "4.60.0", + "@rollup/rollup-linux-ppc64-musl": "4.60.0", + "@rollup/rollup-linux-riscv64-gnu": "4.60.0", + "@rollup/rollup-linux-riscv64-musl": "4.60.0", + "@rollup/rollup-linux-s390x-gnu": "4.60.0", + "@rollup/rollup-linux-x64-gnu": "4.60.0", + "@rollup/rollup-linux-x64-musl": "4.60.0", + "@rollup/rollup-openbsd-x64": "4.60.0", + "@rollup/rollup-openharmony-arm64": "4.60.0", + "@rollup/rollup-win32-arm64-msvc": "4.60.0", + "@rollup/rollup-win32-ia32-msvc": "4.60.0", + "@rollup/rollup-win32-x64-gnu": "4.60.0", + "@rollup/rollup-win32-x64-msvc": "4.60.0", + "fsevents": "~2.3.2" + } + }, + "node_modules/sax": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.6.0.tgz", + "integrity": "sha512-6R3J5M4AcbtLUdZmRv2SygeVaM7IhrLXu9BmnOGmmACak8fiUtOsYNWUS4uK7upbmHIBbLBeFeI//477BKLBzA==", + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=11.0.0" + } + }, + "node_modules/semver": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/sharp": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.34.5.tgz", + "integrity": "sha512-Ou9I5Ft9WNcCbXrU9cMgPBcCK8LiwLqcbywW3t4oDV37n1pzpuNLsYiAV8eODnjbtQlSDwZ2cUEeQz4E54Hltg==", + "hasInstallScript": true, + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "@img/colour": "^1.0.0", + "detect-libc": "^2.1.2", + "semver": "^7.7.3" + }, + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-darwin-arm64": "0.34.5", + "@img/sharp-darwin-x64": "0.34.5", + "@img/sharp-libvips-darwin-arm64": "1.2.4", + "@img/sharp-libvips-darwin-x64": "1.2.4", + "@img/sharp-libvips-linux-arm": "1.2.4", + "@img/sharp-libvips-linux-arm64": "1.2.4", + "@img/sharp-libvips-linux-ppc64": "1.2.4", + "@img/sharp-libvips-linux-riscv64": "1.2.4", + "@img/sharp-libvips-linux-s390x": "1.2.4", + "@img/sharp-libvips-linux-x64": "1.2.4", + "@img/sharp-libvips-linuxmusl-arm64": "1.2.4", + "@img/sharp-libvips-linuxmusl-x64": "1.2.4", + "@img/sharp-linux-arm": "0.34.5", + "@img/sharp-linux-arm64": "0.34.5", + "@img/sharp-linux-ppc64": "0.34.5", + "@img/sharp-linux-riscv64": "0.34.5", + "@img/sharp-linux-s390x": "0.34.5", + "@img/sharp-linux-x64": "0.34.5", + "@img/sharp-linuxmusl-arm64": "0.34.5", + "@img/sharp-linuxmusl-x64": "0.34.5", + "@img/sharp-wasm32": "0.34.5", + "@img/sharp-win32-arm64": "0.34.5", + "@img/sharp-win32-ia32": "0.34.5", + "@img/sharp-win32-x64": "0.34.5" + } + }, + "node_modules/shiki": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/shiki/-/shiki-4.0.2.tgz", + "integrity": "sha512-eAVKTMedR5ckPo4xne/PjYQYrU3qx78gtJZ+sHlXEg5IHhhoQhMfZVzetTYuaJS0L2Ef3AcCRzCHV8T0WI6nIQ==", + "license": "MIT", + "dependencies": { + "@shikijs/core": "4.0.2", + "@shikijs/engine-javascript": "4.0.2", + "@shikijs/engine-oniguruma": "4.0.2", + "@shikijs/langs": "4.0.2", + "@shikijs/themes": "4.0.2", + "@shikijs/types": "4.0.2", + "@shikijs/vscode-textmate": "^10.0.2", + "@types/hast": "^3.0.4" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/sisteransi": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", + "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", + "license": "MIT" + }, + "node_modules/sitemap": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/sitemap/-/sitemap-9.0.1.tgz", + "integrity": "sha512-S6hzjGJSG3d6if0YoF5kTyeRJvia6FSTBroE5fQ0bu1QNxyJqhhinfUsXi9fH3MgtXODWvwo2BDyQSnhPQ88uQ==", + "license": "MIT", + "dependencies": { + "@types/node": "^24.9.2", + "@types/sax": "^1.2.1", + "arg": "^5.0.0", + "sax": "^1.4.1" + }, + "bin": { + "sitemap": "dist/esm/cli.js" + }, + "engines": { + "node": ">=20.19.5", + "npm": ">=10.8.2" + } + }, + "node_modules/smol-toml": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/smol-toml/-/smol-toml-1.6.1.tgz", + "integrity": "sha512-dWUG8F5sIIARXih1DTaQAX4SsiTXhInKf1buxdY9DIg4ZYPZK5nGM1VRIYmEbDbsHt7USo99xSLFu5Q1IqTmsg==", + "license": "BSD-3-Clause", + "engines": { + "node": ">= 18" + }, + "funding": { + "url": "https://github.com/sponsors/cyyynthia" + } + }, + "node_modules/source-map": { + "version": "0.7.6", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.6.tgz", + "integrity": "sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ==", + "license": "BSD-3-Clause", + "engines": { + "node": ">= 12" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/space-separated-tokens": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/space-separated-tokens/-/space-separated-tokens-2.0.2.tgz", + "integrity": "sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/stream-replace-string": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/stream-replace-string/-/stream-replace-string-2.0.0.tgz", + "integrity": "sha512-TlnjJ1C0QrmxRNrON00JvaFFlNh5TTG00APw23j74ET7gkQpTASi6/L2fuiav8pzK715HXtUeClpBTw2NPSn6w==", + "license": "MIT" + }, + "node_modules/stringify-entities": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/stringify-entities/-/stringify-entities-4.0.4.tgz", + "integrity": "sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==", + "license": "MIT", + "dependencies": { + "character-entities-html4": "^2.0.0", + "character-entities-legacy": "^3.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/style-to-js": { + "version": "1.1.21", + "resolved": "https://registry.npmjs.org/style-to-js/-/style-to-js-1.1.21.tgz", + "integrity": "sha512-RjQetxJrrUJLQPHbLku6U/ocGtzyjbJMP9lCNK7Ag0CNh690nSH8woqWH9u16nMjYBAok+i7JO1NP2pOy8IsPQ==", + "license": "MIT", + "dependencies": { + "style-to-object": "1.0.14" + } + }, + "node_modules/style-to-object": { + "version": "1.0.14", + "resolved": "https://registry.npmjs.org/style-to-object/-/style-to-object-1.0.14.tgz", + "integrity": "sha512-LIN7rULI0jBscWQYaSswptyderlarFkjQ+t79nzty8tcIAceVomEVlLzH5VP4Cmsv6MtKhs7qaAiwlcp+Mgaxw==", + "license": "MIT", + "dependencies": { + "inline-style-parser": "0.2.7" + } + }, + "node_modules/svgo": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/svgo/-/svgo-4.0.1.tgz", + "integrity": "sha512-XDpWUOPC6FEibaLzjfe0ucaV0YrOjYotGJO1WpF0Zd+n6ZGEQUsSugaoLq9QkEZtAfQIxT42UChcssDVPP3+/w==", + "license": "MIT", + "dependencies": { + "commander": "^11.1.0", + "css-select": "^5.1.0", + "css-tree": "^3.0.1", + "css-what": "^6.1.0", + "csso": "^5.0.5", + "picocolors": "^1.1.1", + "sax": "^1.5.0" + }, + "bin": { + "svgo": "bin/svgo.js" + }, + "engines": { + "node": ">=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/svgo" + } + }, + "node_modules/tiny-inflate": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/tiny-inflate/-/tiny-inflate-1.0.3.tgz", + "integrity": "sha512-pkY1fj1cKHb2seWDy0B16HeWyczlJA9/WW3u3c4z/NiWDsO3DOU5D7nhTLE9CF0yXv/QZFY7sEJmj24dK+Rrqw==", + "license": "MIT" + }, + "node_modules/tinyclip": { + "version": "0.1.12", + "resolved": "https://registry.npmjs.org/tinyclip/-/tinyclip-0.1.12.tgz", + "integrity": "sha512-Ae3OVUqifDw0wBriIBS7yVaW44Dp6eSHQcyq4Igc7eN2TJH/2YsicswaW+J/OuMvhpDPOKEgpAZCjkb4hpoyeA==", + "license": "MIT", + "engines": { + "node": "^16.14.0 || >= 17.3.0" + } + }, + "node_modules/tinyexec": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.0.4.tgz", + "integrity": "sha512-u9r3uZC0bdpGOXtlxUIdwf9pkmvhqJdrVCH9fapQtgy/OeTTMZ1nqH7agtvEfmGui6e1XxjcdrlxvxJvc3sMqw==", + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.15", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", + "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.3" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/trim-lines": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/trim-lines/-/trim-lines-3.0.1.tgz", + "integrity": "sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/trough": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/trough/-/trough-2.2.0.tgz", + "integrity": "sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/tsconfck": { + "version": "3.1.6", + "resolved": "https://registry.npmjs.org/tsconfck/-/tsconfck-3.1.6.tgz", + "integrity": "sha512-ks6Vjr/jEw0P1gmOVwutM3B7fWxoWBL2KRDb1JfqGVawBmO5UsvmWOQFGHBPl5yxYz4eERr19E6L7NMv+Fej4w==", + "license": "MIT", + "bin": { + "tsconfck": "bin/tsconfck.js" + }, + "engines": { + "node": "^18 || >=20" + }, + "peerDependencies": { + "typescript": "^5.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD", + "optional": true + }, + "node_modules/ufo": { + "version": "1.6.3", + "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.6.3.tgz", + "integrity": "sha512-yDJTmhydvl5lJzBmy/hyOAA0d+aqCBuwl818haVdYCRrWV84o7YyeVm4QlVHStqNrrJSTb6jKuFAVqAFsr+K3Q==", + "license": "MIT" + }, + "node_modules/ultrahtml": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/ultrahtml/-/ultrahtml-1.6.0.tgz", + "integrity": "sha512-R9fBn90VTJrqqLDwyMph+HGne8eqY1iPfYhPzZrvKpIfwkWZbcYlfpsb8B9dTvBfpy1/hqAD7Wi8EKfP9e8zdw==", + "license": "MIT" + }, + "node_modules/uncrypto": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/uncrypto/-/uncrypto-0.1.3.tgz", + "integrity": "sha512-Ql87qFHB3s/De2ClA9e0gsnS6zXG27SkTiSJwjCc9MebbfapQfuPzumMIUMi38ezPZVNFcHI9sUIepeQfw8J8Q==", + "license": "MIT" + }, + "node_modules/undici-types": { + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", + "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", + "license": "MIT" + }, + "node_modules/unified": { + "version": "11.0.5", + "resolved": "https://registry.npmjs.org/unified/-/unified-11.0.5.tgz", + "integrity": "sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "bail": "^2.0.0", + "devlop": "^1.0.0", + "extend": "^3.0.0", + "is-plain-obj": "^4.0.0", + "trough": "^2.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unifont": { + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/unifont/-/unifont-0.7.4.tgz", + "integrity": "sha512-oHeis4/xl42HUIeHuNZRGEvxj5AaIKR+bHPNegRq5LV1gdc3jundpONbjglKpihmJf+dswygdMJn3eftGIMemg==", + "license": "MIT", + "dependencies": { + "css-tree": "^3.1.0", + "ofetch": "^1.5.1", + "ohash": "^2.0.11" + } + }, + "node_modules/unist-util-find-after": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/unist-util-find-after/-/unist-util-find-after-5.0.0.tgz", + "integrity": "sha512-amQa0Ep2m6hE2g72AugUItjbuM8X8cGQnFoHk0pGfrFeT9GZhzN5SW8nRsiGKK7Aif4CrACPENkA6P/Lw6fHGQ==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-is": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-is": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-6.0.1.tgz", + "integrity": "sha512-LsiILbtBETkDz8I9p1dQ0uyRUWuaQzd/cuEeS1hoRSyW5E5XGmTzlwY1OrNzzakGowI9Dr/I8HVaw4hTtnxy8g==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-modify-children": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/unist-util-modify-children/-/unist-util-modify-children-4.0.0.tgz", + "integrity": "sha512-+tdN5fGNddvsQdIzUF3Xx82CU9sMM+fA0dLgR9vOmT0oPT2jH+P1nd5lSqfCfXAw+93NhcXNY2qqvTUtE4cQkw==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "array-iterate": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-position": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/unist-util-position/-/unist-util-position-5.0.0.tgz", + "integrity": "sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-position-from-estree": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unist-util-position-from-estree/-/unist-util-position-from-estree-2.0.0.tgz", + "integrity": "sha512-KaFVRjoqLyF6YXCbVLNad/eS4+OfPQQn2yOd7zF/h5T/CSL2v8NpN6a5TPvtbXthAGw5nG+PuTtq+DdIZr+cRQ==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-remove-position": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/unist-util-remove-position/-/unist-util-remove-position-5.0.0.tgz", + "integrity": "sha512-Hp5Kh3wLxv0PHj9m2yZhhLt58KzPtEYKQQ4yxfYFEO7EvHwzyDYnduhHnY1mDxoqr7VUwVuHXk9RXKIiYS1N8Q==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-visit": "^5.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-stringify-position": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-4.0.0.tgz", + "integrity": "sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-visit": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-5.1.0.tgz", + "integrity": "sha512-m+vIdyeCOpdr/QeQCu2EzxX/ohgS8KbnPDgFni4dQsfSCtpz8UqDyY5GjRru8PDKuYn7Fq19j1CQ+nJSsGKOzg==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-is": "^6.0.0", + "unist-util-visit-parents": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-visit-children": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/unist-util-visit-children/-/unist-util-visit-children-3.0.0.tgz", + "integrity": "sha512-RgmdTfSBOg04sdPcpTSD1jzoNBjt9a80/ZCzp5cI9n1qPzLZWF9YdvWGN2zmTumP1HWhXKdUWexjy/Wy/lJ7tA==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-visit-parents": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-6.0.2.tgz", + "integrity": "sha512-goh1s1TBrqSqukSc8wrjwWhL0hiJxgA8m4kFxGlQ+8FYQ3C/m11FcTs4YYem7V664AhHVvgoQLk890Ssdsr2IQ==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-is": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unstorage": { + "version": "1.17.4", + "resolved": "https://registry.npmjs.org/unstorage/-/unstorage-1.17.4.tgz", + "integrity": "sha512-fHK0yNg38tBiJKp/Vgsq4j0JEsCmgqH58HAn707S7zGkArbZsVr/CwINoi+nh3h98BRCwKvx1K3Xg9u3VV83sw==", + "license": "MIT", + "dependencies": { + "anymatch": "^3.1.3", + "chokidar": "^5.0.0", + "destr": "^2.0.5", + "h3": "^1.15.5", + "lru-cache": "^11.2.0", + "node-fetch-native": "^1.6.7", + "ofetch": "^1.5.1", + "ufo": "^1.6.3" + }, + "peerDependencies": { + "@azure/app-configuration": "^1.8.0", + "@azure/cosmos": "^4.2.0", + "@azure/data-tables": "^13.3.0", + "@azure/identity": "^4.6.0", + "@azure/keyvault-secrets": "^4.9.0", + "@azure/storage-blob": "^12.26.0", + "@capacitor/preferences": "^6 || ^7 || ^8", + "@deno/kv": ">=0.9.0", + "@netlify/blobs": "^6.5.0 || ^7.0.0 || ^8.1.0 || ^9.0.0 || ^10.0.0", + "@planetscale/database": "^1.19.0", + "@upstash/redis": "^1.34.3", + "@vercel/blob": ">=0.27.1", + "@vercel/functions": "^2.2.12 || ^3.0.0", + "@vercel/kv": "^1 || ^2 || ^3", + "aws4fetch": "^1.0.20", + "db0": ">=0.2.1", + "idb-keyval": "^6.2.1", + "ioredis": "^5.4.2", + "uploadthing": "^7.4.4" + }, + "peerDependenciesMeta": { + "@azure/app-configuration": { + "optional": true + }, + "@azure/cosmos": { + "optional": true + }, + "@azure/data-tables": { + "optional": true + }, + "@azure/identity": { + "optional": true + }, + "@azure/keyvault-secrets": { + "optional": true + }, + "@azure/storage-blob": { + "optional": true + }, + "@capacitor/preferences": { + "optional": true + }, + "@deno/kv": { + "optional": true + }, + "@netlify/blobs": { + "optional": true + }, + "@planetscale/database": { + "optional": true + }, + "@upstash/redis": { + "optional": true + }, + "@vercel/blob": { + "optional": true + }, + "@vercel/functions": { + "optional": true + }, + "@vercel/kv": { + "optional": true + }, + "aws4fetch": { + "optional": true + }, + "db0": { + "optional": true + }, + "idb-keyval": { + "optional": true + }, + "ioredis": { + "optional": true + }, + "uploadthing": { + "optional": true + } + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "license": "MIT" + }, + "node_modules/vfile": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/vfile/-/vfile-6.0.3.tgz", + "integrity": "sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/vfile-location": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/vfile-location/-/vfile-location-5.0.3.tgz", + "integrity": "sha512-5yXvWDEgqeiYiBe1lbxYF7UMAIm/IcopxMHrMQDq3nvKcjPKIhZklUKL+AE7J7uApI4kwe2snsK+eI6UTj9EHg==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/vfile-message": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-4.0.3.tgz", + "integrity": "sha512-QTHzsGd1EhbZs4AsQ20JX1rC3cOlt/IWJruk893DfLRr57lcnOeMaWG4K0JrRta4mIJZKth2Au3mM3u03/JWKw==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-stringify-position": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/vite": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/vite/-/vite-7.3.1.tgz", + "integrity": "sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA==", + "license": "MIT", + "dependencies": { + "esbuild": "^0.27.0", + "fdir": "^6.5.0", + "picomatch": "^4.0.3", + "postcss": "^8.5.6", + "rollup": "^4.43.0", + "tinyglobby": "^0.2.15" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^20.19.0 || >=22.12.0", + "jiti": ">=1.21.0", + "less": "^4.0.0", + "lightningcss": "^1.21.0", + "sass": "^1.70.0", + "sass-embedded": "^1.70.0", + "stylus": ">=0.54.8", + "sugarss": "^5.0.0", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/vitefu": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vitefu/-/vitefu-1.1.2.tgz", + "integrity": "sha512-zpKATdUbzbsycPFBN71nS2uzBUQiVnFoOrr2rvqv34S1lcAgMKKkjWleLGeiJlZ8lwCXvtWaRn7R3ZC16SYRuw==", + "license": "MIT", + "workspaces": [ + "tests/deps/*", + "tests/projects/*", + "tests/projects/workspace/packages/*" + ], + "peerDependencies": { + "vite": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-beta.0" + }, + "peerDependenciesMeta": { + "vite": { + "optional": true + } + } + }, + "node_modules/web-namespaces": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/web-namespaces/-/web-namespaces-2.0.1.tgz", + "integrity": "sha512-bKr1DkiNa2krS7qxNtdrtHAmzuYGFQLiQ13TsorsdT6ULTkPLKuu5+GsFpDlg6JFjUTwX2DyhMPG2be8uPrqsQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/which-pm-runs": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/which-pm-runs/-/which-pm-runs-1.1.0.tgz", + "integrity": "sha512-n1brCuqClxfFfq/Rb0ICg9giSZqCS+pLtccdag6C2HyufBrh3fBOiy9nb6ggRMvWOVH5GrdJskj5iGTZNxd7SA==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/xxhash-wasm": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/xxhash-wasm/-/xxhash-wasm-1.1.0.tgz", + "integrity": "sha512-147y/6YNh+tlp6nd/2pWq38i9h6mz/EuQ6njIrmW8D1BS5nCqs0P6DG+m6zTGnNz5I+uhZ0SHxBs9BsPrwcKDA==", + "license": "MIT" + }, + "node_modules/yargs-parser": { + "version": "22.0.0", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-22.0.0.tgz", + "integrity": "sha512-rwu/ClNdSMpkSrUb+d6BRsSkLUq1fmfsY6TOpYzTwvwkg1/NRG85KBy3kq++A8LKQwX6lsu+aWad+2khvuXrqw==", + "license": "ISC", + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=23" + } + }, + "node_modules/yocto-queue": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-1.2.2.tgz", + "integrity": "sha512-4LCcse/U2MHZ63HAJVE+v71o7yOdIe4cZ70Wpf8D/IyjDKYQLV5GD46B+hSTjJsvV5PztjvHoU580EftxjDZFQ==", + "license": "MIT", + "engines": { + "node": ">=12.20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/zod": { + "version": "4.3.6", + "resolved": "https://registry.npmjs.org/zod/-/zod-4.3.6.tgz", + "integrity": "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, + "node_modules/zwitch": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/zwitch/-/zwitch-2.0.4.tgz", + "integrity": "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + } + } +} diff --git a/website/package.json b/website/package.json new file mode 100644 index 0000000..2149bf3 --- /dev/null +++ b/website/package.json @@ -0,0 +1,20 @@ +{ + "name": "website", + "version": "1.0.0", + "description": "", + "main": "index.js", + "scripts": { + "dev": "astro dev", + "build": "astro build", + "preview": "astro preview" + }, + "keywords": [], + "author": "", + "license": "ISC", + "type": "module", + "dependencies": { + "@astrojs/sitemap": "^3.7.1", + "@astrojs/starlight": "^0.38.2", + "astro": "^6.0.8" + } +} diff --git a/website/public/favicon.svg b/website/public/favicon.svg new file mode 100644 index 0000000..9256271 --- /dev/null +++ b/website/public/favicon.svg @@ -0,0 +1,5 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32" fill="none"> + <ellipse cx="16" cy="8" rx="12" ry="5" fill="#3b82f6" opacity="0.9"/> + <path d="M4 8v8c0 2.76 5.37 5 12 5s12-2.24 12-5V8" stroke="#3b82f6" stroke-width="2" fill="none"/> + <path d="M4 16v8c0 2.76 5.37 5 12 5s12-2.24 12-5v-8" stroke="#3b82f6" stroke-width="2" fill="none"/> +</svg> diff --git a/website/src/content.config.ts b/website/src/content.config.ts new file mode 100644 index 0000000..be250fb --- /dev/null +++ b/website/src/content.config.ts @@ -0,0 +1,20 @@ +import { defineCollection, z } from 'astro:content'; +import { docsLoader } from '@astrojs/starlight/loaders'; +import { docsSchema } from '@astrojs/starlight/schema'; +import { glob } from 'astro/loaders'; + +const blog = defineCollection({ + loader: glob({ pattern: '**/*.{md,mdx}', base: './src/content/blog' }), + schema: z.object({ + title: z.string(), + date: z.date(), + author: z.string(), + description: z.string(), + tags: z.array(z.string()).optional(), + }), +}); + +export const collections = { + docs: defineCollection({ loader: docsLoader(), schema: docsSchema() }), + blog, +}; diff --git a/website/src/content/docs/advanced/cloning.md b/website/src/content/docs/advanced/cloning.md new file mode 100644 index 0000000..24223d7 --- /dev/null +++ b/website/src/content/docs/advanced/cloning.md @@ -0,0 +1,6 @@ +--- +title: Cloning +description: Cloning documentation +--- + +Coming soon. diff --git a/website/src/content/docs/advanced/compiling.md b/website/src/content/docs/advanced/compiling.md new file mode 100644 index 0000000..879d377 --- /dev/null +++ b/website/src/content/docs/advanced/compiling.md @@ -0,0 +1,6 @@ +--- +title: Compiling from Source +description: Compiling from Source documentation +--- + +Coming soon. diff --git a/website/src/content/docs/advanced/concurrent.md b/website/src/content/docs/advanced/concurrent.md new file mode 100644 index 0000000..1c4b1ee --- /dev/null +++ b/website/src/content/docs/advanced/concurrent.md @@ -0,0 +1,6 @@ +--- +title: Concurrent Deployment +description: Concurrent Deployment documentation +--- + +Coming soon. diff --git a/website/src/content/docs/advanced/go-library.md b/website/src/content/docs/advanced/go-library.md new file mode 100644 index 0000000..51fffdd --- /dev/null +++ b/website/src/content/docs/advanced/go-library.md @@ -0,0 +1,6 @@ +--- +title: Using as a Go Library +description: Using as a Go Library documentation +--- + +Coming soon. diff --git a/website/src/content/docs/advanced/importing.md b/website/src/content/docs/advanced/importing.md new file mode 100644 index 0000000..08d3d87 --- /dev/null +++ b/website/src/content/docs/advanced/importing.md @@ -0,0 +1,6 @@ +--- +title: Importing Databases +description: Importing Databases documentation +--- + +Coming soon. diff --git a/website/src/content/docs/advanced/inter-sandbox-replication.md b/website/src/content/docs/advanced/inter-sandbox-replication.md new file mode 100644 index 0000000..48f231b --- /dev/null +++ b/website/src/content/docs/advanced/inter-sandbox-replication.md @@ -0,0 +1,6 @@ +--- +title: Inter-Sandbox Replication +description: Inter-Sandbox Replication documentation +--- + +Coming soon. diff --git a/website/src/content/docs/concepts/environment-variables.md b/website/src/content/docs/concepts/environment-variables.md new file mode 100644 index 0000000..8f5b907 --- /dev/null +++ b/website/src/content/docs/concepts/environment-variables.md @@ -0,0 +1,6 @@ +--- +title: Environment Variables +description: Environment Variables documentation +--- + +Coming soon. diff --git a/website/src/content/docs/concepts/flavors.md b/website/src/content/docs/concepts/flavors.md new file mode 100644 index 0000000..935a679 --- /dev/null +++ b/website/src/content/docs/concepts/flavors.md @@ -0,0 +1,6 @@ +--- +title: Versions & Flavors +description: Versions & Flavors documentation +--- + +Coming soon. diff --git a/website/src/content/docs/concepts/ports.md b/website/src/content/docs/concepts/ports.md new file mode 100644 index 0000000..bc09f10 --- /dev/null +++ b/website/src/content/docs/concepts/ports.md @@ -0,0 +1,6 @@ +--- +title: Ports & Networking +description: Ports & Networking documentation +--- + +Coming soon. diff --git a/website/src/content/docs/concepts/sandboxes.md b/website/src/content/docs/concepts/sandboxes.md new file mode 100644 index 0000000..330fadb --- /dev/null +++ b/website/src/content/docs/concepts/sandboxes.md @@ -0,0 +1,6 @@ +--- +title: Sandboxes +description: Sandboxes documentation +--- + +Coming soon. diff --git a/website/src/content/docs/deploying/fan-in-all-masters.md b/website/src/content/docs/deploying/fan-in-all-masters.md new file mode 100644 index 0000000..750860e --- /dev/null +++ b/website/src/content/docs/deploying/fan-in-all-masters.md @@ -0,0 +1,6 @@ +--- +title: Fan-In & All-Masters +description: Fan-In & All-Masters documentation +--- + +Coming soon. diff --git a/website/src/content/docs/deploying/group-replication.md b/website/src/content/docs/deploying/group-replication.md new file mode 100644 index 0000000..98c9468 --- /dev/null +++ b/website/src/content/docs/deploying/group-replication.md @@ -0,0 +1,6 @@ +--- +title: Group Replication +description: Group Replication documentation +--- + +Coming soon. diff --git a/website/src/content/docs/deploying/multiple.md b/website/src/content/docs/deploying/multiple.md new file mode 100644 index 0000000..1106a6b --- /dev/null +++ b/website/src/content/docs/deploying/multiple.md @@ -0,0 +1,6 @@ +--- +title: Multiple Sandboxes +description: Multiple Sandboxes documentation +--- + +Coming soon. diff --git a/website/src/content/docs/deploying/ndb-cluster.md b/website/src/content/docs/deploying/ndb-cluster.md new file mode 100644 index 0000000..a6481f7 --- /dev/null +++ b/website/src/content/docs/deploying/ndb-cluster.md @@ -0,0 +1,6 @@ +--- +title: NDB Cluster +description: NDB Cluster documentation +--- + +Coming soon. diff --git a/website/src/content/docs/deploying/replication.md b/website/src/content/docs/deploying/replication.md new file mode 100644 index 0000000..8b867b6 --- /dev/null +++ b/website/src/content/docs/deploying/replication.md @@ -0,0 +1,6 @@ +--- +title: Replication +description: Replication documentation +--- + +Coming soon. diff --git a/website/src/content/docs/deploying/single.md b/website/src/content/docs/deploying/single.md new file mode 100644 index 0000000..9ffb532 --- /dev/null +++ b/website/src/content/docs/deploying/single.md @@ -0,0 +1,6 @@ +--- +title: Single Sandbox +description: Single Sandbox documentation +--- + +Coming soon. diff --git a/website/src/content/docs/getting-started/installation.md b/website/src/content/docs/getting-started/installation.md new file mode 100644 index 0000000..02486dc --- /dev/null +++ b/website/src/content/docs/getting-started/installation.md @@ -0,0 +1,6 @@ +--- +title: Installation +description: Installation documentation +--- + +Coming soon. diff --git a/website/src/content/docs/getting-started/quickstart-mysql-replication.md b/website/src/content/docs/getting-started/quickstart-mysql-replication.md new file mode 100644 index 0000000..6f344c6 --- /dev/null +++ b/website/src/content/docs/getting-started/quickstart-mysql-replication.md @@ -0,0 +1,6 @@ +--- +title: "Quick Start: MySQL Replication" +description: "Quick Start: MySQL Replication documentation" +--- + +Coming soon. diff --git a/website/src/content/docs/getting-started/quickstart-mysql-single.md b/website/src/content/docs/getting-started/quickstart-mysql-single.md new file mode 100644 index 0000000..8145429 --- /dev/null +++ b/website/src/content/docs/getting-started/quickstart-mysql-single.md @@ -0,0 +1,6 @@ +--- +title: "Quick Start: MySQL Single" +description: "Quick Start: MySQL Single documentation" +--- + +Coming soon. diff --git a/website/src/content/docs/getting-started/quickstart-postgresql.md b/website/src/content/docs/getting-started/quickstart-postgresql.md new file mode 100644 index 0000000..24d0a4d --- /dev/null +++ b/website/src/content/docs/getting-started/quickstart-postgresql.md @@ -0,0 +1,6 @@ +--- +title: "Quick Start: PostgreSQL" +description: "Quick Start: PostgreSQL documentation" +--- + +Coming soon. diff --git a/website/src/content/docs/getting-started/quickstart-proxysql.md b/website/src/content/docs/getting-started/quickstart-proxysql.md new file mode 100644 index 0000000..d5177c3 --- /dev/null +++ b/website/src/content/docs/getting-started/quickstart-proxysql.md @@ -0,0 +1,6 @@ +--- +title: "Quick Start: ProxySQL Integration" +description: "Quick Start: ProxySQL Integration documentation" +--- + +Coming soon. diff --git a/website/src/content/docs/index.mdx b/website/src/content/docs/index.mdx new file mode 100644 index 0000000..17f24cd --- /dev/null +++ b/website/src/content/docs/index.mdx @@ -0,0 +1,6 @@ +--- +title: dbdeployer Documentation +description: Deploy MySQL & PostgreSQL sandboxes in seconds +--- + +Welcome to dbdeployer documentation. Use the sidebar to navigate. diff --git a/website/src/content/docs/managing/customization.md b/website/src/content/docs/managing/customization.md new file mode 100644 index 0000000..863b14c --- /dev/null +++ b/website/src/content/docs/managing/customization.md @@ -0,0 +1,6 @@ +--- +title: Customization +description: Customization documentation +--- + +Coming soon. diff --git a/website/src/content/docs/managing/deletion.md b/website/src/content/docs/managing/deletion.md new file mode 100644 index 0000000..40a9f9c --- /dev/null +++ b/website/src/content/docs/managing/deletion.md @@ -0,0 +1,6 @@ +--- +title: Deletion & Cleanup +description: Deletion & Cleanup documentation +--- + +Coming soon. diff --git a/website/src/content/docs/managing/logs.md b/website/src/content/docs/managing/logs.md new file mode 100644 index 0000000..6f4fea1 --- /dev/null +++ b/website/src/content/docs/managing/logs.md @@ -0,0 +1,6 @@ +--- +title: Logs +description: Logs documentation +--- + +Coming soon. diff --git a/website/src/content/docs/managing/starting-stopping.md b/website/src/content/docs/managing/starting-stopping.md new file mode 100644 index 0000000..d6855c1 --- /dev/null +++ b/website/src/content/docs/managing/starting-stopping.md @@ -0,0 +1,6 @@ +--- +title: Starting & Stopping +description: Starting & Stopping documentation +--- + +Coming soon. diff --git a/website/src/content/docs/managing/users.md b/website/src/content/docs/managing/users.md new file mode 100644 index 0000000..11d3a7d --- /dev/null +++ b/website/src/content/docs/managing/users.md @@ -0,0 +1,6 @@ +--- +title: Database Users +description: Database Users documentation +--- + +Coming soon. diff --git a/website/src/content/docs/managing/using.md b/website/src/content/docs/managing/using.md new file mode 100644 index 0000000..4b48331 --- /dev/null +++ b/website/src/content/docs/managing/using.md @@ -0,0 +1,6 @@ +--- +title: Using Sandboxes +description: Using Sandboxes documentation +--- + +Coming soon. diff --git a/website/src/content/docs/providers/mysql.md b/website/src/content/docs/providers/mysql.md new file mode 100644 index 0000000..000a781 --- /dev/null +++ b/website/src/content/docs/providers/mysql.md @@ -0,0 +1,6 @@ +--- +title: MySQL +description: MySQL documentation +--- + +Coming soon. diff --git a/website/src/content/docs/providers/postgresql.md b/website/src/content/docs/providers/postgresql.md new file mode 100644 index 0000000..01e5858 --- /dev/null +++ b/website/src/content/docs/providers/postgresql.md @@ -0,0 +1,6 @@ +--- +title: PostgreSQL +description: PostgreSQL documentation +--- + +Coming soon. diff --git a/website/src/content/docs/providers/proxysql.md b/website/src/content/docs/providers/proxysql.md new file mode 100644 index 0000000..4c29635 --- /dev/null +++ b/website/src/content/docs/providers/proxysql.md @@ -0,0 +1,6 @@ +--- +title: ProxySQL +description: ProxySQL documentation +--- + +Coming soon. diff --git a/website/src/content/docs/providers/pxc.md b/website/src/content/docs/providers/pxc.md new file mode 100644 index 0000000..9b50158 --- /dev/null +++ b/website/src/content/docs/providers/pxc.md @@ -0,0 +1,6 @@ +--- +title: Percona XtraDB Cluster +description: Percona XtraDB Cluster documentation +--- + +Coming soon. diff --git a/website/src/content/docs/reference/api-changelog.md b/website/src/content/docs/reference/api-changelog.md new file mode 100644 index 0000000..99359d7 --- /dev/null +++ b/website/src/content/docs/reference/api-changelog.md @@ -0,0 +1,6 @@ +--- +title: API Changelog +description: API Changelog documentation +--- + +Coming soon. diff --git a/website/src/content/docs/reference/cli-commands.md b/website/src/content/docs/reference/cli-commands.md new file mode 100644 index 0000000..5b1902a --- /dev/null +++ b/website/src/content/docs/reference/cli-commands.md @@ -0,0 +1,6 @@ +--- +title: CLI Commands +description: CLI Commands documentation +--- + +Coming soon. diff --git a/website/src/content/docs/reference/configuration.md b/website/src/content/docs/reference/configuration.md new file mode 100644 index 0000000..05ee9de --- /dev/null +++ b/website/src/content/docs/reference/configuration.md @@ -0,0 +1,6 @@ +--- +title: Configuration +description: Configuration documentation +--- + +Coming soon. diff --git a/website/tsconfig.json b/website/tsconfig.json new file mode 100644 index 0000000..bcbf8b5 --- /dev/null +++ b/website/tsconfig.json @@ -0,0 +1,3 @@ +{ + "extends": "astro/tsconfigs/strict" +} From 17178ac5bfd3e20a115327ce5aeba258ff4c2e76 Mon Sep 17 00:00:00 2001 From: Rene Cannao <rene@proxysql.com> Date: Tue, 24 Mar 2026 17:40:03 +0000 Subject: [PATCH 08/15] feat: add wiki content pipeline script (copy-wiki.sh) Copies 24 wiki markdown files into the Starlight content collection, injecting frontmatter and stripping [[HOME]] navigation links. --- website/scripts/copy-wiki.sh | 75 +++ website/src/content/docs/advanced/cloning.md | 23 +- .../src/content/docs/advanced/compiling.md | 15 +- .../src/content/docs/advanced/concurrent.md | 11 +- .../src/content/docs/advanced/go-library.md | 9 +- .../src/content/docs/advanced/importing.md | 132 ++++- .../advanced/inter-sandbox-replication.md | 177 ++++++- .../docs/concepts/environment-variables.md | 39 +- website/src/content/docs/concepts/flavors.md | 33 +- website/src/content/docs/concepts/ports.md | 42 +- .../src/content/docs/concepts/sandboxes.md | 56 ++- .../src/content/docs/deploying/multiple.md | 24 +- .../src/content/docs/deploying/replication.md | 37 +- website/src/content/docs/deploying/single.md | 352 +++++++++++++- .../docs/getting-started/installation.md | 49 +- .../content/docs/managing/customization.md | 127 ++++- website/src/content/docs/managing/deletion.md | 43 +- website/src/content/docs/managing/logs.md | 13 +- .../docs/managing/starting-stopping.md | 148 +++++- website/src/content/docs/managing/users.md | 102 +++- website/src/content/docs/managing/using.md | 17 +- website/src/content/docs/providers/mysql.md | 29 +- .../src/content/docs/providers/proxysql.md | 460 +++++++++++++++++- .../content/docs/reference/cli-commands.md | 34 +- .../content/docs/reference/configuration.md | 36 +- 25 files changed, 2011 insertions(+), 72 deletions(-) create mode 100755 website/scripts/copy-wiki.sh diff --git a/website/scripts/copy-wiki.sh b/website/scripts/copy-wiki.sh new file mode 100755 index 0000000..096e0b0 --- /dev/null +++ b/website/scripts/copy-wiki.sh @@ -0,0 +1,75 @@ +#!/bin/bash +set -euo pipefail + +REPO_ROOT="$(cd "$(dirname "$0")/../.." && pwd)" +WIKI_DIR="$REPO_ROOT/docs/wiki" +DOCS_DIR="$(cd "$(dirname "$0")/.." && pwd)/src/content/docs" + +# Function to copy a wiki file with frontmatter and basic link cleanup +copy_wiki() { + local src="$1" + local dst="$2" + local title="$3" + + if [ ! -f "$src" ]; then + echo " WARNING: Source file not found: $src" + return + fi + + mkdir -p "$(dirname "$dst")" + + # Inject frontmatter, strip [[HOME]] nav, basic link cleanup + { + echo "---" + echo "title: \"$title\"" + echo "---" + echo "" + # Strip lines containing [[HOME]] or [[home]] style wiki nav + sed -E '/\[\[HOME\]\]/Id' "$src" \ + | sed -E '/^\[Home\]/d' + } > "$dst" + + echo " Copied: $(basename "$src") -> $(basename "$dst")" +} + +echo "=== Copying wiki pages ===" + +# Getting Started +copy_wiki "$WIKI_DIR/installation.md" "$DOCS_DIR/getting-started/installation.md" "Installation" + +# Core Concepts +copy_wiki "$WIKI_DIR/default-sandbox.md" "$DOCS_DIR/concepts/sandboxes.md" "Sandboxes" +copy_wiki "$WIKI_DIR/database-server-flavors.md" "$DOCS_DIR/concepts/flavors.md" "Versions & Flavors" +copy_wiki "$WIKI_DIR/ports-management.md" "$DOCS_DIR/concepts/ports.md" "Ports & Networking" +copy_wiki "$REPO_ROOT/docs/env_variables.md" "$DOCS_DIR/concepts/environment-variables.md" "Environment Variables" + +# Deploying +copy_wiki "$WIKI_DIR/main-operations.md" "$DOCS_DIR/deploying/single.md" "Single Sandbox" +copy_wiki "$WIKI_DIR/multiple-sandboxes,-same-version-and-type.md" "$DOCS_DIR/deploying/multiple.md" "Multiple Sandboxes" +copy_wiki "$WIKI_DIR/replication-topologies.md" "$DOCS_DIR/deploying/replication.md" "Replication" + +# Providers +copy_wiki "$WIKI_DIR/standard-and-non-standard-basedir-names.md" "$DOCS_DIR/providers/mysql.md" "MySQL" +copy_wiki "$REPO_ROOT/docs/proxysql-guide.md" "$DOCS_DIR/providers/proxysql.md" "ProxySQL" + +# Managing Sandboxes +copy_wiki "$WIKI_DIR/sandbox-management.md" "$DOCS_DIR/managing/starting-stopping.md" "Starting & Stopping" +copy_wiki "$WIKI_DIR/using-the-latest-sandbox.md" "$DOCS_DIR/managing/using.md" "Using Sandboxes" +copy_wiki "$WIKI_DIR/sandbox-customization.md" "$DOCS_DIR/managing/customization.md" "Customization" +copy_wiki "$WIKI_DIR/database-users.md" "$DOCS_DIR/managing/users.md" "Database Users" +copy_wiki "$WIKI_DIR/database-logs-management..md" "$DOCS_DIR/managing/logs.md" "Logs" +copy_wiki "$WIKI_DIR/sandbox-deletion.md" "$DOCS_DIR/managing/deletion.md" "Deletion & Cleanup" + +# Advanced +copy_wiki "$WIKI_DIR/concurrent-deployment-and-deletion.md" "$DOCS_DIR/advanced/concurrent.md" "Concurrent Deployment" +copy_wiki "$WIKI_DIR/importing-databases-into-sandboxes.md" "$DOCS_DIR/advanced/importing.md" "Importing Databases" +copy_wiki "$WIKI_DIR/replication-between-sandboxes.md" "$DOCS_DIR/advanced/inter-sandbox-replication.md" "Inter-Sandbox Replication" +copy_wiki "$WIKI_DIR/cloning-databases.md" "$DOCS_DIR/advanced/cloning.md" "Cloning" +copy_wiki "$WIKI_DIR/using-dbdeployer-source-for-other-projects.md" "$DOCS_DIR/advanced/go-library.md" "Using as a Go Library" +copy_wiki "$WIKI_DIR/compiling-dbdeployer.md" "$DOCS_DIR/advanced/compiling.md" "Compiling from Source" + +# Reference +copy_wiki "$WIKI_DIR/command-line-completion.md" "$DOCS_DIR/reference/cli-commands.md" "CLI Commands" +copy_wiki "$WIKI_DIR/initializing-the-environment.md" "$DOCS_DIR/reference/configuration.md" "Configuration" + +echo "=== Done ===" diff --git a/website/src/content/docs/advanced/cloning.md b/website/src/content/docs/advanced/cloning.md index 24223d7..215a167 100644 --- a/website/src/content/docs/advanced/cloning.md +++ b/website/src/content/docs/advanced/cloning.md @@ -1,6 +1,23 @@ --- -title: Cloning -description: Cloning documentation +title: "Cloning" --- -Coming soon. +# Cloning databases +[[HOME](https://github.com/ProxySQL/dbdeployer/wiki)] + +In addition to [replicating between sandboxes](#replication-between-sandboxes), we can also clone a database, if it is +of version 8.0.17+ and [meets the prerequisites](https://dev.mysql.com/doc/refman/8.0/en/clone-plugin-remote.html). + +Every sandbox using version 8.0.17 or later will also have a script named `clone_from`, which works like `replicate_from`. + +For example, this command will clone from a master-slave sandbox into a single sandbox: + +``` +$ ~/sandboxes/msb_8_0_17/clone_from rsandbox_8_0_17 + Installing clone plugin in recipient sandbox + Installing clone plugin in donor sandbox + Cloning from rsandbox_8_0_17/master + Giving time to cloned server to restart + . +``` + diff --git a/website/src/content/docs/advanced/compiling.md b/website/src/content/docs/advanced/compiling.md index 879d377..6d6fd9e 100644 --- a/website/src/content/docs/advanced/compiling.md +++ b/website/src/content/docs/advanced/compiling.md @@ -1,6 +1,15 @@ --- -title: Compiling from Source -description: Compiling from Source documentation +title: "Compiling from Source" --- -Coming soon. +# Compiling dbdeployer +[[HOME](https://github.com/ProxySQL/dbdeployer/wiki)] + +Should you need to compile your own binaries for dbdeployer, follow these steps: + +1. Make sure you have go 1.11+ installed in your system. +2. Run `git clone https://github.com/ProxySQL/dbdeployer.git`. This will import all the code that is needed to build dbdeployer. +3. Change directory to `./dbdeployer`. +4. Run ./scripts/build.sh {linux|OSX}` +5. If you need the docs enabled binaries (see the section "Generating additional documentation") run `MKDOCS=1 ./scripts/build.sh {linux|OSX}` + diff --git a/website/src/content/docs/advanced/concurrent.md b/website/src/content/docs/advanced/concurrent.md index 1c4b1ee..a6ec267 100644 --- a/website/src/content/docs/advanced/concurrent.md +++ b/website/src/content/docs/advanced/concurrent.md @@ -1,6 +1,11 @@ --- -title: Concurrent Deployment -description: Concurrent Deployment documentation +title: "Concurrent Deployment" --- -Coming soon. +# Concurrent deployment and deletion +[[HOME](https://github.com/ProxySQL/dbdeployer/wiki)] + +Starting with version 0.3.0, dbdeployer can deploy groups of sandboxes (``deploy replication``, ``deploy multiple``) with the flag ``--concurrent``. When this flag is used, dbdeployer will run operations concurrently. +The same flag can be used with the ``delete`` command. It is useful when there are several sandboxes to be deleted at once. +Concurrent operations run from 2 to 5 times faster than sequential ones, depending on the version of the server and the number of nodes. + diff --git a/website/src/content/docs/advanced/go-library.md b/website/src/content/docs/advanced/go-library.md index 51fffdd..2d120f1 100644 --- a/website/src/content/docs/advanced/go-library.md +++ b/website/src/content/docs/advanced/go-library.md @@ -1,6 +1,9 @@ --- -title: Using as a Go Library -description: Using as a Go Library documentation +title: "Using as a Go Library" --- -Coming soon. +# Using dbdeployer source for other projects +[[HOME](https://github.com/ProxySQL/dbdeployer/wiki)] + +If you need to create sandboxes from other Go apps, see [dbdeployer-as-library.md](https://github.com/ProxySQL/dbdeployer/blob/master/docs/coding/dbdeployer-as-library.md). + diff --git a/website/src/content/docs/advanced/importing.md b/website/src/content/docs/advanced/importing.md index 08d3d87..861de97 100644 --- a/website/src/content/docs/advanced/importing.md +++ b/website/src/content/docs/advanced/importing.md @@ -1,6 +1,132 @@ --- -title: Importing Databases -description: Importing Databases documentation +title: "Importing Databases" --- -Coming soon. +# Importing databases into sandboxes +[[HOME](https://github.com/ProxySQL/dbdeployer/wiki)] + +With dbdeployer 1.39.0, you have the ability of importing an existing database into a sandbox. +The *importing* doesn't involve any re-installation or data transfer: the resulting sandbox will access the existing +database server using the standard sandbox scripts. + +Syntax: `dbdeployer import single hostIP/name port username password` + +For example, + +``` +dbdeployer import single 192.168.0.164 5000 public nOtMyPassW0rd + detected: 5.7.22 + # Using client version 5.7.22 + Database installed in $HOME/sandboxes/imp_msb_5_7_22 + run 'dbdeployer usage single' for basic instructions'` +``` + +We connect to a server running at IP address 192.168.0.164, listening to port 5000. We pass user name and password on +the command line, and dbdeployer, detecting that the database runs version 5.7.22, uses the client of the closest +version to connect to it, and builds a sandbox, which we can access by the usual scripts: + +``` +~/sandboxes/imp_msb_5_7_22/use +Welcome to the MySQL monitor. Commands end with ; or \g. +Your MySQL connection id is 19 +Server version: 5.7.22 MySQL Community Server (GPL) + +Copyright (c) 2000, 2018, Oracle and/or its affiliates. All rights reserved. + +Oracle is a registered trademark of Oracle Corporation and/or its +affiliates. Other names may be trademarks of their respective +owners. + +Type 'help;' or '\h' for help. Type '\c' to clear the current input statement. + +mysql [192.168.0.164:5000] {public} ((none)) > select host, user, authentication_string from mysql.user; ++-----------+---------------+-------------------------------------------+ +| host | user | authentication_string | ++-----------+---------------+-------------------------------------------+ +| localhost | root | *14E65567ABDB5135D0CFD9A70B3032C179A49EE7 | +| localhost | mysql.session | *THISISNOTAVALIDPASSWORDTHATCANBEUSEDHERE | +| localhost | mysql.sys | *THISISNOTAVALIDPASSWORDTHATCANBEUSEDHERE | +| localhost | healthchecker | *36C82179AFA394C4B9655005DD2E482D30A4BDF7 | +| % | public | *129FD0B9224690392BCF7523AC6E6420109E5F70 | ++-----------+---------------+-------------------------------------------+ +5 rows in set (0.00 sec) +``` + +You have to keep in mind that several assumptions that are taken for granted in regular sandboxes may not hold for an +imported one. This sandbox refers to an out-of-the-box MySQL deployment that lacks some settings that are expected in +a regular sandbox: + +``` +$ ~/sandboxes/imp_msb_5_7_22/test_sb +ok - version '5.7.22' +ok - version is 5.7.22 as expected +ok - query was successful for user public: 'select 1' +ok - query was successful for user public: 'select 1' +ok - query was successful for user public: 'use mysql; select count(*) from information_schema.tables where table_schema=schema()' +ok - query was successful for user public: 'use mysql; select count(*) from information_schema.tables where table_schema=schema()' +not ok - query failed for user public: 'create table if not exists test.txyz(i int)' +ok - query was successful for user public: 'drop table if exists test.txyz' +# Tests : 8 +# pass : 7 +# FAIL : 1 +``` + +In the above example, the `test` database, which exists in every sandbox, was not found, and the test failed. + +There could be bigger limitations. Here's an attempt with a [db4free.net](https://db4free.net) account that works fine +but has bigger problems than the previous one: + +``` +$ dbdeployer import single db4free.net 3306 dbdeployer $(cat ~/.db4free.pwd) +detected: 8.0.17 +# Using client version 8.0.17 +Database installed in $HOME/sandboxes/imp_msb_8_0_17 +run 'dbdeployer usage single' for basic instructions' +``` + +A db4free account can only access the user database, and nothing else. Specifically, it can't create databases, access +databases `information_schema` or `mysql`, or start replication. + +Speaking of replication, we can use imported sandboxes to start replication between a remote server and a sandbox, or +between a sandbox and a remote server, or even, if both sandboxes are imported, start replication between two remote +servers (provided that the credentials used for importing have the necessary privileges.) + +``` +$ ~/sandboxes/msb_8_0_17/replicate_from imp_msb_5_7_22 +Connecting to /Users/gmax/sandboxes/imp_msb_5_7_22 +-------------- +CHANGE MASTER TO master_host="192.168.0.164", +master_port=5000, +master_user="public", +master_password="nOtMyPassW0rd" +, master_log_file="d6db0cd349b8-bin.000001", master_log_pos=154 +-------------- + +-------------- +start slave +-------------- + + Master_Log_File: d6db0cd349b8-bin.000001 + Read_Master_Log_Pos: 154 + Slave_IO_Running: Yes + Slave_SQL_Running: Yes + Exec_Master_Log_Pos: 154 + Retrieved_Gtid_Set: + Executed_Gtid_Set: + Auto_Position: 0 +``` + + $ dbdeployer import single --help + Imports an existing (local or remote) server into a sandbox, + so that it can be used with the usual sandbox scripts. + Requires host, port, user, password. + + Usage: + dbdeployer import single host port user password [flags] + + Flags: + -h, --help help for single + + + + diff --git a/website/src/content/docs/advanced/inter-sandbox-replication.md b/website/src/content/docs/advanced/inter-sandbox-replication.md index 48f231b..0bad2af 100644 --- a/website/src/content/docs/advanced/inter-sandbox-replication.md +++ b/website/src/content/docs/advanced/inter-sandbox-replication.md @@ -1,6 +1,177 @@ --- -title: Inter-Sandbox Replication -description: Inter-Sandbox Replication documentation +title: "Inter-Sandbox Replication" --- -Coming soon. +# Replication between sandboxes +[[HOME](https://github.com/ProxySQL/dbdeployer/wiki)] + +Every sandbox (created by dbdeployer 1.26.0+) includes a script called `replicate_from`, which allows replication from another sandbox, provided that both sandboxes are well configured to start replication. + +Here's an example: + +``` +# deploying a sandbox with binary log and server ID +$ dbdeployer deploy single 8.0 --master +# 8.0 => 8.0.15 +Database installed in $HOME/sandboxes/msb_8_0_15 +run 'dbdeployer usage single' for basic instructions' +. sandbox server started + +# Same, for version 5.7 +$ dbdeployer deploy single 5.7 --master +# 5.7 => 5.7.25 +Database installed in $HOME/sandboxes/msb_5_7_25 +run 'dbdeployer usage single' for basic instructions' +. sandbox server started + +# deploying a sandbox without binary log and server ID +$ dbdeployer deploy single 5.6 +# 5.6 => 5.6.41 +Database installed in $HOME/sandboxes/msb_5_6_41 +run 'dbdeployer usage single' for basic instructions' +. sandbox server started + +# situation: +$ dbdeployer sandboxes --full-info +.------------.--------.---------.---------------.--------.-------.--------. +| name | type | version | ports | flavor | nodes | locked | ++------------+--------+---------+---------------+--------+-------+--------+ +| msb_5_6_41 | single | 5.6.41 | [5641 ] | mysql | 0 | | +| msb_5_7_25 | single | 5.7.25 | [5725 ] | mysql | 0 | | +| msb_8_0_15 | single | 8.0.15 | [8015 18015 ] | mysql | 0 | | +'------------'--------'---------'---------------'--------'-------'--------' + +# Try replicating from the sandbox without binlogs and server ID. It fails +$ ~/sandboxes/msb_5_7_25/replicate_from msb_5_6_41 +No binlog information found in /Users/gmax/sandboxes/msb_5_6_41 + +# Try replicating from a master of a bigger version than the slave. It fails +$ ~/sandboxes/msb_5_7_25/replicate_from msb_8_0_15 +Master major version should be lower than slave version (or equal) + +# Try replicating from 5.7 to 8.0. It succeeds + +$ ~/sandboxes/msb_8_0_15/replicate_from msb_5_7_25 +Connecting to /Users/gmax/sandboxes/msb_5_7_25 +-------------- +CHANGE MASTER TO master_host="127.0.0.1", +master_port=5725, +master_user="rsandbox", +master_password="rsandbox" +, master_log_file="mysql-bin.000001", master_log_pos=4089 +-------------- + +-------------- +start slave +-------------- + + Master_Log_File: mysql-bin.000001 + Read_Master_Log_Pos: 4089 + Slave_IO_Running: Yes + Slave_SQL_Running: Yes + Exec_Master_Log_Pos: 4089 + Retrieved_Gtid_Set: + Executed_Gtid_Set: + Auto_Position: 0 + +``` + +The same method can be used to replicate between composite sandboxes. However, some extra steps may be necessary when replicating between clusters, as conflicts and pipeline blocks may happen. +There are at least three things to keep in mind: + +1. As seen above, the version of the slave must be either the same as the master or higher. +2. Some topologies need the activation of `log-slave-updates` for this kind of replication to work correctly. For example, `PXC` and `master-slave` need this option to get replication from another cluster to all their nodes. +3. **dbdeployer composite sandboxes have all the same server_id**. When replicating to another entity, we get a conflict, and replication does not start. To avoid this problem, we need to use the option `--port-as-server-id` when deploying the cluster. + +Here are examples of a few complex replication scenarios: + +## a. NDB to NDB + +Here we need to make sure that the server IDs are different. + +``` +$ dbdeployer deploy replication ndb8.0.14 --topology=ndb \ + --port-as-server-id \ + --sandbox-directory=ndb_ndb8_0_14_1 --concurrent +[...] +$ dbdeployer deploy replication ndb8.0.14 --topology=ndb \ + --port-as-server-id \ + --sandbox-directory=ndb_ndb8_0_14_2 --concurrent +[...] + +$ dbdeployer sandboxes --full-info +.-----------------.--------.-----------.----------------------------------------------.--------.-------.--------. +| name | type | version | ports | flavor | nodes | locked | ++-----------------+--------+-----------+----------------------------------------------+--------+-------+--------+ +| ndb_ndb8_0_14_1 | ndb | ndb8.0.14 | [21400 28415 38415 28416 38416 28417 38417 ] | ndb | 3 | | +| ndb_ndb8_0_14_2 | ndb | ndb8.0.14 | [21401 28418 38418 28419 38419 28420 38420 ] | ndb | 3 | | +'-----------------'--------'-----------'----------------------------------------------'--------'-------'--------' + +$ ~/sandboxes/ndb_ndb8_0_14_1/replicate_from ndb_ndb8_0_14_2 +[...] +``` + +## b. Group replication to group replication + +Also here, the only caveat is to ensure uniqueness of server IDs. +``` +$ dbdeployer deploy replication 8.0.15 --topology=group \ + --concurrent --port-as-server-id \ + --sandbox-directory=group_8_0_15_1 +[...] + +$ dbdeployer deploy replication 8.0.15 --topology=group \ + --concurrent --port-as-server-id \ + --sandbox-directory=group_8_0_15_2 +[...] + +$ ~/sandboxes/group_8_0_15_1/replicate_from group_8_0_15_2 +[...] +``` + +## c. Master/slave to master/slave. + +In addition to caring about the server ID, we also need to make sure that the replication spreads to the slaves. + +``` +$ dbdeployer deploy replication 8.0.15 --topology=master-slave \ + --concurrent --port-as-server-id \ + --sandbox-directory=ms_8_0_15_1 \ + -c log-slave-updates +[...] + +$ dbdeployer deploy replication 8.0.15 --topology=master-slave \ + --concurrent --port-as-server-id \ + --sandbox-directory=ms_8_0_15_2 \ + -c log-slave-updates +[...] + +$ ~/sandboxes/ms_8_0_15_1/replicate_from ms_8_0_15_2 +[...] +``` + +## d. Hybrid replication + +Using the same methods, we can replicate from a cluster to a single sandbox (e,g. group replication to single 8.0 sandbox) or the other way around (single 8.0 sandbox to group replication). +We only need to make sure there are no conflicts as mentioned above. The script `replicate_from` can catch some issues, but I am sure there is still room for mistakes. For example, replicating from an NDB cluster to a single sandbox won't work, as the single one can't process the `ndbengine` tables. + +Examples: + +``` +# group replication to single +~/sandboxes/msb_8_0_15/replicate_from group_8_0_15_2 + +# single to master/slave +~/sandboxes/ms_8_0_15_1/replicate_from msb_8_0_15 + +# master/slave to group +~/sandboxes/group_8_0_15_2/replicate_from ms_8_0_15_1 +``` + +## e. Cloning + +When both master and slave run version 8.0.17+, the script `replicate_from` allows an extra option `clone`. When this +option is given, and both sandboxes meet the [cloning pre-requisites](https://dev.mysql.com/doc/refman/8.0/en/clone-plugin-remote.html), +the script will try to clone the donor before starting replication. If successful, it will use the clone coordinates to +initialize the slave. + diff --git a/website/src/content/docs/concepts/environment-variables.md b/website/src/content/docs/concepts/environment-variables.md index 8f5b907..1e5d8a7 100644 --- a/website/src/content/docs/concepts/environment-variables.md +++ b/website/src/content/docs/concepts/environment-variables.md @@ -1,6 +1,39 @@ --- -title: Environment Variables -description: Environment Variables documentation +title: "Environment Variables" --- -Coming soon. +# Environmental variables + +The following variables are used by dbdeployer. + +## Abbreviations + +* ``DEBUG_ABBR`` Enables debug information for abbreviations engine. +* ``SKIP_ABBR`` Disables the abbreviations engine. +* ``SILENT_ABBR`` Disables the verbosity with abbreviations. +* ``DBDEPLOYER_ABBR_FILE`` Changes the abbreviations file + +## Concurrency + +* ``RUN_CONCURRENTLY`` Run operations concurrently (multiple sandboxes and deletions) +* ``DEBUG_CONCURRENCY`` Enables debug information for concurrent operations +* ``VERBOSE_CONCURRENCY`` Gives more info during concurrency. + +## Catalog + +* ``SKIP_DBDEPLOYER_CATALOG`` Stops using the centralized JSON catalog. + +## Ports management + +* ``SHOW_CHANGED_PORTS`` will show which ports were changed to avoid clashes. + +## Sandbox deployment + +* ``HOME`` Used to initialize sandboxes components: ``SANDBOX_HOME`` and ``SANDBOX_BINARY`` depend on this one. +* ``TMPDIR`` Used to define where the socket file will be located. +* ``USER`` Used to define which user will be used to run the database server. +* ``SANDBOX_BINARY`` Where the MySQL unpacked binaries are stored. +* ``SANDBOX_HOME`` Where the sandboxes are deployed. +* ``INIT_OPTIONS`` Options to be added to the initialization script command line. +* ``MY_CNF_OPTIONS`` Options to be added to the sandbox configuration file. +* ``MY_CNF_FILE`` Alternate file to be used as source for the sandbox my.cnf. diff --git a/website/src/content/docs/concepts/flavors.md b/website/src/content/docs/concepts/flavors.md index 935a679..7f0a135 100644 --- a/website/src/content/docs/concepts/flavors.md +++ b/website/src/content/docs/concepts/flavors.md @@ -1,6 +1,33 @@ --- -title: Versions & Flavors -description: Versions & Flavors documentation +title: "Versions & Flavors" --- -Coming soon. +# Database server flavors +[[HOME](https://github.com/ProxySQL/dbdeployer/wiki)] + +Before version 1.19.0, dbdeployer assumed that it was dealing to some version of MySQL, using the version to decide which features it would support. In version 1.19.0 dbdeployer started using the concept of **capabilities**, which is a combination of server **flavor** + a version. Some flavors currently supported are + +* `mysql` : the classic MySQL server +* `percona` : Percona Server, any version. For the purposes of deployment, it has the same capabilities as MySQL +* `mariadb`: MariaDB server. Mostly the same as MySQL, but with differences in deployment methods. +* `pxc`: Percona Xtradb Cluster +* `ndb`: MySQL Cluster (NDB) +* `tidb`: A stand-alone TiDB server. + +To see what every flavor can do, you can use the command `dbdeployer admin capabilities`. + +To see the features of a given flavor: `dbdeployer admin capabilities FLAVOR`. + +And to see what a given version of a flavor can do, you can use `dbdeployer admin capabilities FLAVOR VERSION`. + +For example + +```shell +$ dbdeployer admin capabilities + +$ dbdeployer admin capabilities percona + +$ dbdeployer admin capabilities mysql 5.7.11 +$ dbdeployer admin capabilities mysql 5.7.13 +``` + diff --git a/website/src/content/docs/concepts/ports.md b/website/src/content/docs/concepts/ports.md index bc09f10..7c3cd98 100644 --- a/website/src/content/docs/concepts/ports.md +++ b/website/src/content/docs/concepts/ports.md @@ -1,6 +1,42 @@ --- -title: Ports & Networking -description: Ports & Networking documentation +title: "Ports & Networking" --- -Coming soon. +# Ports management +[[HOME](https://github.com/ProxySQL/dbdeployer/wiki)] + +dbdeployer will try using the default port for each sandbox whenever possible. For single sandboxes, the port will be the version number without dots: 5.7.22 will deploy on port 5722. For multiple sandboxes, the port number is defined by using a prefix number (visible in the defaults: ``dbdeployer defaults list``) + the port number + the revision number (for some topologies multiplied by 100.) +For example, single-primary group replication with MySQL 8.0.11 will compute the ports like this: + + base port = 8011 (version number) + 13000 (prefix) + 11 (revision) * 100 = 22111 + node1 port = base port + 1 = 22112 + node2 port = base port + 2 = 22113 + node3 port = base port + 2 = 22114 + +For group replication we need to calculate the group port, and we use the ``group-port-delta`` (= 125) to obtain it from the regular port: + + node1 group port = 22112 + 125 = 22237 + node2 group port = 22113 + 125 = 22238 + node3 group port = 22114 + 125 = 22239 + +For MySQL 8.0.11+, we also need to assign a port for the XPlugin, and we compute that using the regular port + the ``mysqlx-port-delta`` (=10000). + +Thus, for MySQL 8.0.11 group replication deployments, you would see this listing: + + $ dbdeployer sandboxes --header + name type version ports + ---------------- ------- ------- ----- + group_msb_8_0_11 : group-multi-primary 8.0.11 [20023 20148 30023 20024 20149 30024 20025 20150 30025] + group_sp_msb_8_0_11 : group-single-primary 8.0.11 [22112 22237 32112 22113 22238 32113 22114 22239 32114] + +This method makes port clashes unlikely when using the same version in different deployments, but there is a risk of port clashes when deploying many multiple sandboxes of close-by versions. +Furthermore, dbdeployer doesn't let the clash happen. Thanks to its central catalog of sandboxes, it knows which ports were already used, and will search for free ones whenever a potential clash is detected. +Bear in mind that the concept of "used" is only related to sandboxes. dbdeployer does not know if ports may be used by other applications. +You can minimize risks by telling dbdeployer which ports may be occupied. The defaults have a field ``reserved-ports``, containing the ports that should not be used. You can add to that list by modifying the defaults. For example, if you want to exclude port 7001, 10000, and 15000 from being used, you can run + + dbdeployer defaults update reserved-ports '7001,10000,15000' + +or, if you want to preserve the ones that are reserved by default: + + dbdeployer defaults update reserved-ports '1186,3306,33060,7001,10000,15000' + diff --git a/website/src/content/docs/concepts/sandboxes.md b/website/src/content/docs/concepts/sandboxes.md index 330fadb..4185265 100644 --- a/website/src/content/docs/concepts/sandboxes.md +++ b/website/src/content/docs/concepts/sandboxes.md @@ -1,6 +1,56 @@ --- -title: Sandboxes -description: Sandboxes documentation +title: "Sandboxes" --- -Coming soon. +# Default sandbox +[[HOME](https://github.com/ProxySQL/dbdeployer/wiki)] + +You can set a default sandbox using the command `dbdeployer admin set-default sandbox_name` + + $ dbdeployer admin set-default -h + Sets a given sandbox as default, so that it can be used with $SANDBOX_HOME/default + + Usage: + dbdeployer admin set-default sandbox_name [flags] + + Flags: + --default-sandbox-executable string Name of the executable to run commands in the default sandbox (default "default") + -h, --help help for set-default + + + + +For example: + + $ dbdeployer admin set-default msb_8_0_20 + +This command creates a script `$HOME/sandboxes/default` that will point to the sandbox you have chosen. +After that, you can use the sandbox using `~/sandboxes/default command`, such as + + $ ~/sandboxes/default status + $ ~/sandboxes/default use # will get the `mysql` prompt + $ ~/sandboxes/default use -e 'select version()' + + +If the sandbox chosen as default is a multiple or replication sandbox, you can use the commands that are available there + + $ ~/sandboxes/default status_all + $ ~/sandboxes/default use_all 'select @@version, @@server_id, @@port' + + +You can have more than one default sandbox, using the option `--default-sandbox-executable=name`. +For example: + + + $ dbdeployer admin set-default msb_8_0_20 --default-sandbox-executable=single + $ dbdeployer admin set-default repl_8_0_20 --default-sandbox-executable=repl + $ dbdeployer admin set-default group_msb_8_0_20 --default-sandbox-executable=group + +With the above commands, you will have three executables in ~/sandboxes, named `single`, `repl`, and `group`. +You can use them just like the `default` executable: + + $ ~/sandboxes/single status + $ ~/sandboxes/repl check_slaves + $ ~/sandboxes/group check_nodes + + diff --git a/website/src/content/docs/deploying/multiple.md b/website/src/content/docs/deploying/multiple.md index 1106a6b..8529829 100644 --- a/website/src/content/docs/deploying/multiple.md +++ b/website/src/content/docs/deploying/multiple.md @@ -1,6 +1,24 @@ --- -title: Multiple Sandboxes -description: Multiple Sandboxes documentation +title: "Multiple Sandboxes" --- -Coming soon. +# Multiple sandboxes, same version and type +[[HOME](https://github.com/ProxySQL/dbdeployer/wiki)] + +If you want to deploy several instances of the same version and the same type (for example two single sandboxes of 8.0.4, or two replication instances with different settings) you can specify the data directory name and the ports manually. + + $ dbdeployer deploy single 8.0.4 + # will deploy in msb_8_0_4 using port 8004 + + $ dbdeployer deploy single 8.0.4 --sandbox-directory=msb2_8_0_4 + # will deploy in msb2_8_0_4 using port 8005 (which dbdeployer detects and uses) + + $ dbdeployer deploy replication 8.0.4 --concurrent + # will deploy replication in rsandbox_8_0_4 using default calculated ports 19009, 19010, 19011 + + $ dbdeployer deploy replication 8.0.4 \ + --gtid \ + --sandbox-directory=rsandbox2_8_0_4 \ + --base-port=18600 --concurrent + # will deploy replication in rsandbox2_8_0_4 using ports 18601, 18602, 18603 + diff --git a/website/src/content/docs/deploying/replication.md b/website/src/content/docs/deploying/replication.md index 8b867b6..8eac2a0 100644 --- a/website/src/content/docs/deploying/replication.md +++ b/website/src/content/docs/deploying/replication.md @@ -1,6 +1,37 @@ --- -title: Replication -description: Replication documentation +title: "Replication" --- -Coming soon. +# Replication topologies +[[HOME](https://github.com/ProxySQL/dbdeployer/wiki)] + +Multiple sandboxes can be deployed using replication with several topologies (using ``dbdeployer deploy replication --topology=xxxxx``: + +* **master-slave** is the default topology. It will install one master and two slaves. More slaves can be added with the option ``--nodes``. +* **group** will deploy three peer nodes in group replication. If you want to use a single primary deployment, add the option ``--single-primary``. Available for MySQL 5.7 and later. +* **fan-in** is the opposite of master-slave. Here we have one slave and several masters. This topology requires MySQL 5.7 or higher. +* **all-masters** is a special case of fan-in, where all nodes are masters and are also slaves of all nodes. + +It is possible to tune the flow of data in multi-source topologies. The default for fan-in is three nodes, where 1 and 2 are masters, and 2 are slaves. You can change the predefined settings by providing the list of components: + + $ dbdeployer deploy replication --topology=fan-in \ + --nodes=5 --master-list="1,2 3" \ + --slave-list="4,5" 8.0.4 \ + --concurrent +In the above example, we get 5 nodes instead of 3. The first three are master (``--master-list="1,2,3"``) and the last two are slaves (``--slave-list="4,5"``) which will receive data from all the masters. There is a test automatically generated to check the replication flow. In our case it shows the following: + + $ ~/sandboxes/fan_in_msb_8_0_4/test_replication + # master 1 + # master 2 + # master 3 + # slave 4 + ok - '3' == '3' - Slaves received tables from all masters + # slave 5 + ok - '3' == '3' - Slaves received tables from all masters + # pass: 2 + # fail: 0 + +The first three lines show that each master has done something. In our case, each master has created a different table. Slaves in nodes 5 and 6 then count how many tables they found, and if they got the tables from all masters, the test succeeds. + +Two more topologies, **ndb** and **pxc** require binaries of dedicated flavors, respectively _MySQL Cluster_ and _Percona Xtradb Cluster_. dbdeployer detects whether an expanded tarball satisfies the flavor requirements, and deploys only when the criteria are met. + diff --git a/website/src/content/docs/deploying/single.md b/website/src/content/docs/deploying/single.md index 9ffb532..3ea45af 100644 --- a/website/src/content/docs/deploying/single.md +++ b/website/src/content/docs/deploying/single.md @@ -1,6 +1,352 @@ --- -title: Single Sandbox -description: Single Sandbox documentation +title: "Single Sandbox" --- -Coming soon. +# Main operations +[[HOME](https://github.com/ProxySQL/dbdeployer/wiki)] + +(See this ASCIIcast for a demo of its operations.) +[![asciicast](https://asciinema.org/a/165707.png)](https://asciinema.org/a/165707) + +## Overview + +With dbdeployer, you can deploy a single sandbox, or many sandboxes at once, with or without replication. + +The main command is ``deploy`` with its subcommands ``single``, ``replication``, and ``multiple``, which work with MySQL tarball that have been unpacked into the _sandbox-binary_ directory (by default, $HOME/opt/mysql.) + +To use a tarball, you must first run the ``get`` then the ``unpack`` commands, which will unpack the tarball into the right directory. + +For example: + + $ dbdeployer downloads get mysql-8.0.4-rc-linux-glibc2.12-x86_64.tar.gz + $ dbdeployer unpack mysql-8.0.4-rc-linux-glibc2.12-x86_64.tar.gz + Unpacking tarball mysql-8.0.4-rc-linux-glibc2.12-x86_64.tar.gz to $HOME/opt/mysql/8.0.4 + .........100.........200.........292 + + $ dbdeployer deploy single 8.0.4 + Database installed in $HOME/sandboxes/msb_8_0_4 + . sandbox server started + + +The program doesn't have any dependencies. Everything is included in the binary. Calling *dbdeployer* without arguments or with ``--help`` will show the main help screen. + + $ dbdeployer --version + dbdeployer version 1.66.0 + + + $ dbdeployer -h + dbdeployer makes MySQL server installation an easy task. + Runs single, multiple, and replicated sandboxes. + + Usage: + dbdeployer [command] + + Available Commands: + admin sandbox management tasks + cookbook Shows dbdeployer samples + data-load tasks related to dbdeployer data loading + defaults tasks related to dbdeployer defaults + delete delete an installed sandbox + delete-binaries delete an expanded tarball + deploy deploy sandboxes + downloads Manages remote tarballs + export Exports the command structure in JSON format + global Runs a given command in every sandbox + help Help about any command + import imports one or more MySQL servers into a sandbox + info Shows information about dbdeployer environment samples + init initializes dbdeployer environment + sandboxes List installed sandboxes + unpack unpack a tarball into the binary directory + update Gets dbdeployer newest version + usage Shows usage of installed sandboxes + use uses a sandbox + versions List available versions + + Flags: + --config string configuration file (default "$HOME/.dbdeployer/config.json") + -h, --help help for dbdeployer + --sandbox-binary string Binary repository (default "$HOME/opt/mysql") + --sandbox-home string Sandbox deployment directory (default "$HOME/sandboxes") + --shell-path string Path to Bash, used for generated scripts (default "/usr/local/bin/bash") + --skip-library-check Skip check for needed libraries (may cause nasty errors) + -v, --version version for dbdeployer + + Use "dbdeployer [command] --help" for more information about a command. + + +The flags listed in the main screen can be used with any commands. +The flags ``--my-cnf-options`` and ``--init-options`` can be used several times. + +## Unpack + +If you don't have any tarballs installed in your system, you should first ``unpack`` it (see an example above). + + $ dbdeployer unpack -h + If you want to create a sandbox from a tarball (.tar.gz or .tar.xz), you first need to unpack it + into the sandbox-binary directory. This command carries out that task, so that afterwards + you can call 'deploy single', 'deploy multiple', and 'deploy replication' commands with only + the MySQL version for that tarball. + If the version is not contained in the tarball name, it should be supplied using --unpack-version. + If there is already an expanded tarball with the same version, a new one can be differentiated with --prefix. + + Usage: + dbdeployer unpack MySQL-tarball [flags] + + Aliases: + unpack, extract, untar, unzip, inflate, expand + + Examples: + + $ dbdeployer unpack mysql-8.0.4-rc-linux-glibc2.12-x86_64.tar.gz + Unpacking tarball mysql-8.0.4-rc-linux-glibc2.12-x86_64.tar.gz to $HOME/opt/mysql/8.0.4 + + $ dbdeployer unpack --prefix=ps Percona-Server-5.7.21-linux.tar.gz + Unpacking tarball Percona-Server-5.7.21-linux.tar.gz to $HOME/opt/mysql/ps5.7.21 + + $ dbdeployer unpack --unpack-version=8.0.18 --prefix=bld mysql-mybuild.tar.gz + Unpacking tarball mysql-mybuild.tar.gz to $HOME/opt/mysql/bld8.0.18 + + + Flags: + --dry-run Show unpack operations, but do not run them + --flavor string Defines the tarball flavor (MySQL, NDB, Percona Server, etc) + -h, --help help for unpack + --overwrite Overwrite the destination directory if already exists + --prefix string Prefix for the final expanded directory + --shell Unpack a shell tarball into the corresponding server directory + --target-server string Uses a different server to unpack a shell tarball + --unpack-version string which version is contained in the tarball + --verbosity int Level of verbosity during unpack (0=none, 2=maximum) (default 1) + + + +## Deploy single + +The easiest command is ``deploy single``, which installs a single sandbox. + + $ dbdeployer deploy -h + Deploys single, multiple, or replicated sandboxes + + Usage: + dbdeployer deploy [command] + + Available Commands: + multiple create multiple sandbox + replication create replication sandbox + single deploys a single sandbox + + Flags: + --base-port int Overrides default base-port (for multiple sandboxes) + --base-server-id int Overrides default server_id (for multiple sandboxes) + --binary-version string Specifies the version when the basedir directory name does not contain it (i.e. it is not x.x.xx) + --bind-address string defines the database bind-address (default "127.0.0.1") + --client-from string Where to get the client binaries from + --concurrent Runs multiple sandbox deployments concurrently + --custom-mysqld string Uses an alternative mysqld (must be in the same directory as regular mysqld) + --custom-role-extra string Extra instructions for custom role (8.0+) (default "WITH GRANT OPTION") + --custom-role-name string Name for custom role (8.0+) (default "R_CUSTOM") + --custom-role-privileges string Privileges for custom role (8.0+) (default "ALL PRIVILEGES") + --custom-role-target string Target for custom role (8.0+) (default "*.*") + -p, --db-password string database password (default "msandbox") + -u, --db-user string database user (default "msandbox") + --default-role string Which role to assign to default user (8.0+) (default "R_DO_IT_ALL") + --defaults stringArray Change defaults on-the-fly (--defaults=label:value) + --disable-mysqlx Disable MySQLX plugin (8.0.11+) + --enable-admin-address Enables admin address (8.0.14+) + --enable-general-log Enables general log for the sandbox (MySQL 5.1+) + --enable-mysqlx Enables MySQLX plugin (5.7.12+) + --expose-dd-tables In MySQL 8.0+ shows data dictionary tables + --flavor string Defines the tarball flavor (MySQL, NDB, Percona Server, etc) + --flavor-in-prompt Add flavor values to prompt + --force If a destination sandbox already exists, it will be overwritten + --gtid enables GTID + -h, --help help for deploy + --history-dir string Where to store mysql client history (default: in sandbox directory) + --init-general-log uses general log during initialization (MySQL 5.1+) + -i, --init-options stringArray mysqld options to run during initialization + --keep-server-uuid Does not change the server UUID + --log-directory string Where to store dbdeployer logs (default "$HOME/sandboxes/logs") + --log-sb-operations Logs sandbox operations to a file + --my-cnf-file string Alternative source file for my.sandbox.cnf + -c, --my-cnf-options stringArray mysqld options to add to my.sandbox.cnf + --native-auth-plugin in 8.0.4+, uses the native password auth plugin + --port int Overrides default port + --port-as-server-id Use the port number as server ID + --post-grants-sql stringArray SQL queries to run after loading grants + --post-grants-sql-file string SQL file to run after loading grants + --pre-grants-sql stringArray SQL queries to run before loading grants + --pre-grants-sql-file string SQL file to run before loading grants + --remote-access string defines the database access (default "127.%") + --repl-crash-safe enables Replication crash safe + --rpl-password string replication password (default "rsandbox") + --rpl-user string replication user (default "rsandbox") + --sandbox-directory string Changes the default name of the sandbox directory + --skip-load-grants Does not load the grants + --skip-report-host Does not include report host in my.sandbox.cnf + --skip-report-port Does not include report port in my.sandbox.cnf + --skip-start Does not start the database server + --socket-in-datadir Create socket in datadir instead of $TMPDIR + --task-user string Task user to be created (8.0+) + --task-user-role string Role to be assigned to task user (8.0+) + --use-template stringArray [template_name:file_name] Replace existing template with one from file + + + + $ dbdeployer deploy single -h + single installs a sandbox and creates useful scripts for its use. + MySQL-Version is in the format x.x.xx, and it refers to a directory named after the version + containing an unpacked tarball. The place where these directories are found is defined by + --sandbox-binary (default: $HOME/opt/mysql.) + For example: + dbdeployer deploy single 5.7 # deploys the latest release of 5.7.x + dbdeployer deploy single 5.7.21 # deploys a specific release + dbdeployer deploy single /path/to/5.7.21 # deploys a specific release in a given path + + For this command to work, there must be a directory $HOME/opt/mysql/5.7.21, containing + the binary files from mysql-5.7.21-$YOUR_OS-x86_64.tar.gz + Use the "unpack" command to get the tarball into the right directory. + + Usage: + dbdeployer deploy single MySQL-Version [flags] + + Flags: + -h, --help help for single + --master Make the server replication ready + --prompt string Default prompt for the single client (default "mysql") + --server-id int Overwrite default server-id + + + +## Deploy multiple + +If you want more than one sandbox of the same version, without any replication relationship, use the ``deploy multiple`` command with an optional ``--nodes`` flag (default: 3). + + $ dbdeployer deploy multiple -h + Creates several sandboxes of the same version, + without any replication relationship. + For this command to work, there must be a directory $HOME/opt/mysql/5.7.21, containing + the binary files from mysql-5.7.21-$YOUR_OS-x86_64.tar.gz + Use the "unpack" command to get the tarball into the right directory. + + Usage: + dbdeployer deploy multiple MySQL-Version [flags] + + Examples: + + $ dbdeployer deploy multiple 5.7.21 + + + Flags: + -h, --help help for multiple + -n, --nodes int How many nodes will be installed (default 3) + + + +## Deploy replication + +The ``deploy replication`` command will install a master and two or more slaves, with replication started. You can change the topology to *group* and get three nodes in peer replication, or compose multi-source topologies with *all-masters* or *fan-in*. + + $ dbdeployer deploy replication -h + The replication command allows you to deploy several nodes in replication. + Allowed topologies are "master-slave" for all versions, and "group", "all-masters", "fan-in" + for 5.7.17+. + Topologies "pcx" and "ndb" are available for binaries of type Percona Xtradb Cluster and MySQL Cluster. + For this command to work, there must be a directory $HOME/opt/mysql/5.7.21, containing + the binary files from mysql-5.7.21-$YOUR_OS-x86_64.tar.gz + Use the "unpack" command to get the tarball into the right directory. + + Usage: + dbdeployer deploy replication MySQL-Version [flags] + + Examples: + + $ dbdeployer deploy replication 5.7 # deploys highest revision for 5.7 + $ dbdeployer deploy replication 5.7.21 # deploys a specific revision + $ dbdeployer deploy replication /path/to/5.7.21 # deploys a specific revision in a given path + # (implies topology = master-slave) + + $ dbdeployer deploy --topology=master-slave replication 5.7 + # (explicitly setting topology) + + $ dbdeployer deploy --topology=group replication 5.7 + $ dbdeployer deploy --topology=group replication 8.0 --single-primary + $ dbdeployer deploy --topology=all-masters replication 5.7 + $ dbdeployer deploy --topology=fan-in replication 5.7 + $ dbdeployer deploy --topology=pxc replication pxc5.7.25 + $ dbdeployer deploy --topology=ndb replication ndb8.0.14 + + + Flags: + --change-master-options stringArray options to add to CHANGE MASTER TO + -h, --help help for replication + --master-ip string Which IP the slaves will connect to (default "127.0.0.1") + --master-list string Which nodes are masters in a multi-source deployment + --ndb-nodes int How many NDB nodes will be installed (default 3) + -n, --nodes int How many nodes will be installed (default 3) + --read-only-slaves Set read-only for slaves + --repl-history-dir uses the replication directory to store mysql client history + --semi-sync Use semi-synchronous plugin + --single-primary Using single primary for group replication + --slave-list string Which nodes are slaves in a multi-source deployment + --super-read-only-slaves Set super-read-only for slaves + -t, --topology string Which topology will be installed (default "master-slave") + + + +As of version 1.21.0, you can use Percona Xtradb Cluster tarballs to deploy replication of type *pxc*. This deployment only works on Linux. + +## Re-deploy a sandbox + +If you run a deploy statement a second time, the command will fail, because the sandbox exists already. To overcome the restriction, you can repeat the operation with the option `--force`. + +``` +$ dbdeployer deploy single 8.0 +# 8.0 => 8.0.22 +Database installed in $HOME/sandboxes/msb_8_0_22 +run 'dbdeployer usage single' for basic instructions' +. sandbox server started + +$ dbdeployer deploy single 8.0 +# 8.0 => 8.0.22 +error creating sandbox: 'check directory directory $HOME/sandboxes/msb_8_0_22 already exists. Use --force to override' + +$ dbdeployer deploy single 8.0 --force +# 8.0 => 8.0.22 +Overwriting directory $HOME/sandboxes/msb_8_0_22 +stop $HOME/sandboxes/msb_8_0_22 +Database installed in $HOME/sandboxes/msb_8_0_22 +run 'dbdeployer usage single' for basic instructions' +. sandbox server started +``` + +For a quicker reset, all single sandboxes have a command `wipe_and_restart`. The replication sandboxes (except NDB and PXC) have a command `wipe_and_restart_all`. +Running such command will delete the data directory in all nodes, and re-create them. + +``` +$ ~/sandboxes/msb_8_0_22/wipe_and_restart +Terminating the server immediately --- kill -9 72889 +[...] +Database installed in /home/gmax/sandboxes/msb_8_0_22 +. sandbox server started + +$ ~/sandboxes/rsandbox_8_0_22/wipe_and_restart_all +# executing 'send_kill' on /home/gmax/sandboxes/rsandbox_8_0_22 +[...] +Database installed in /home/gmax/sandboxes/rsandbox_8_0_22/master +[...] +Database installed in /home/gmax/sandboxes/rsandbox_8_0_22/node1 +[...] +Database installed in /home/gmax/sandboxes/rsandbox_8_0_22/node2 +[...] +# executing 'start' on /home/gmax/sandboxes/rsandbox_8_0_22 +executing 'start' on master +. sandbox server started +executing 'start' on slave 1 +. sandbox server started +executing 'start' on slave 2 +. sandbox server started +initializing slave 1 +initializing slave 2 +``` + diff --git a/website/src/content/docs/getting-started/installation.md b/website/src/content/docs/getting-started/installation.md index 02486dc..d6d2eb2 100644 --- a/website/src/content/docs/getting-started/installation.md +++ b/website/src/content/docs/getting-started/installation.md @@ -1,6 +1,49 @@ --- -title: Installation -description: Installation documentation +title: "Installation" --- -Coming soon. +# Installation +[[HOME](https://github.com/ProxySQL/dbdeployer/wiki)] + +## Manual installation + +The installation is simple, as the only thing you will need is a binary executable for your operating system. +Get the one for your O.S. from [dbdeployer releases](https://github.com/ProxySQL/dbdeployer/releases) and place it in a directory in your $PATH. +(There are no binaries for Windows. See the [features list](https://github.com/ProxySQL/dbdeployer/blob/master/docs/features.md) for more info.) + +For example: + + $ VERSION=1.66.0 + $ OS=linux + $ origin=https://github.com/ProxySQL/dbdeployer/releases/download/v$VERSION + $ wget $origin/dbdeployer-$VERSION.$OS.tar.gz + $ tar -xzf dbdeployer-$VERSION.$OS.tar.gz + $ chmod +x dbdeployer-$VERSION.$OS + $ sudo mv dbdeployer-$VERSION.$OS /usr/local/bin/dbdeployer + +## Installation via script + +![installation](https://raw.githubusercontent.com/ProxySQL/dbdeployer/master/docs/dbdeployer-installation.gif) + +You can download the [installation script](https://raw.githubusercontent.com/ProxySQL/dbdeployer/master/scripts/dbdeployer-install.sh), and run it in your computer. +The script will find the latest version, download the corresponding binaries, check the SHA256 checksum, and - if given privileges - copy the executable to a directory within `$PATH`. + +``` +$ curl -s https://raw.githubusercontent.com/ProxySQL/dbdeployer/master/scripts/dbdeployer-install.sh | bash +``` + +A shortcut is available via the bit.ly service: + +``` +$ curl -L -s https://bit.ly/dbdeployer | bash +``` + +Finally, there is a third-party service that installs any Go tool. The command to use it for dbdeployer is + +``` +$ curl -sf https://gobinaries.com/ProxySQL/dbdeployer | sh +``` + +Please see [gobinaries.com](https://gobinaries.com) for more info. + + diff --git a/website/src/content/docs/managing/customization.md b/website/src/content/docs/managing/customization.md index 863b14c..1a83bbb 100644 --- a/website/src/content/docs/managing/customization.md +++ b/website/src/content/docs/managing/customization.md @@ -1,6 +1,127 @@ --- -title: Customization -description: Customization documentation +title: "Customization" --- -Coming soon. +# Sandbox customization +[[HOME](https://github.com/ProxySQL/dbdeployer/wiki)] + +There are several ways of changing the default behavior of a sandbox. + +1. You can add options to the sandbox being deployed using ``--my-cnf-options="some mysqld directive"``. This option can be used many times. The supplied options are added to my.sandbox.cnf +2. You can specify a my.cnf template (``--my-cnf-file=filename``) instead of defining options line by line. dbdeployer will skip all the options that are needed for the sandbox functioning. +3. You can run SQL statements or SQL files before or after the grants were loaded (``--pre-grants-sql``, ``--pre-grants-sql-file``, etc). You can also use these options to peek into the state of the sandbox and see what is happening at every stage. +4. For more advanced needs, you can look at the templates being used for the deployment, and load your own instead of the original s(``--use-template=TemplateName:FileName``.) + +For example: + + $ dbdeployer deploy single 5.6.33 --my-cnf-options="general_log=1" \ + --pre-grants-sql="select host, user, password from mysql.user" \ + --post-grants-sql="select @@general_log" + + $ dbdeployer defaults templates list + $ dbdeployer defaults templates show templateName > mytemplate.txt + # edit the template + $ dbdeployer deploy single --use-template=templateName:mytemplate.txt 5.7.21 + +dbdeployer will use your template instead of the original. + +5. You can also export the templates, edit them, and ask dbdeployer to edit your changes. + +Example: + + $ dbdeployer defaults templates export single my_templates + # Will export all the templates for the "single" group to the directory my_templates/single + $ dbdeployer defaults templates export ALL my_templates + # exports all templates into my_templates, one directory for each group + # Edit the templates that you want to change. You can also remove the ones that you want to leave untouched. + $ dbdeployer defaults templates import single my_templates + # Will import all templates from my_templates/single + +Warning: modifying templates may block the regular work of the sandboxes. Use this feature with caution! + +6. Finally, you can modify the defaults for the application, using the "defaults" command. You can export the defaults, import them from a modified JSON file, or update a single one on-the-fly. + +Here's how: + + $ dbdeployer defaults show + # Internal values: +```json +{ + "version": "1.5.0", + "sandbox-home": "$HOME/sandboxes", + "sandbox-binary": "$HOME/opt/mysql", + "use-sandbox-catalog": true, + "master-slave-base-port": 11000, + "group-replication-base-port": 12000, + "group-replication-sp-base-port": 13000, + "fan-in-replication-base-port": 14000, + "all-masters-replication-base-port": 15000, + "multiple-base-port": 16000, + "group-port-delta": 125, + "mysqlx-port-delta": 10000, + "master-name": "master", + "master-abbr": "m", + "node-prefix": "node", + "slave-prefix": "slave", + "slave-abbr": "s", + "sandbox-prefix": "msb_", + "master-slave-prefix": "rsandbox_", + "group-prefix": "group_msb_", + "group-sp-prefix": "group_sp_msb_", + "multiple-prefix": "multi_msb_", + "fan-in-prefix": "fan_in_msb_", + "all-masters-prefix": "all_masters_msb_", + "reserved-ports": [ + 1186, + 3306, + 33060 + ], + "timestamp": "Sat May 12 14:37:53 CEST 2018" + } +``` + + $ dbdeployer defaults update master-slave-base-port 15000 + # Updated master-slave-base-port -> "15000" + # Configuration file: $HOME/.dbdeployer/config.json +```json +{ + "version": "1.5.0", + "sandbox-home": "$HOME/sandboxes", + "sandbox-binary": "$HOME/opt/mysql", + "use-sandbox-catalog": true, + "master-slave-base-port": 15000, + "group-replication-base-port": 12000, + "group-replication-sp-base-port": 13000, + "fan-in-replication-base-port": 14000, + "all-masters-replication-base-port": 15000, + "multiple-base-port": 16000, + "group-port-delta": 125, + "mysqlx-port-delta": 10000, + "master-name": "master", + "master-abbr": "m", + "node-prefix": "node", + "slave-prefix": "slave", + "slave-abbr": "s", + "sandbox-prefix": "msb_", + "master-slave-prefix": "rsandbox_", + "group-prefix": "group_msb_", + "group-sp-prefix": "group_sp_msb_", + "multiple-prefix": "multi_msb_", + "fan-in-prefix": "fan_in_msb_", + "all-masters-prefix": "all_masters_msb_", + "reserved-ports": [ + 1186, + 3306, + 33060 + ], + "timestamp": "Sat May 12 14:37:53 CEST 2018" +} +``` + +Another way of modifying the defaults, which does not store the new values in dbdeployer's configuration file, is through the ``--defaults`` flag. The above change could be done like this: + + $ dbdeployer --defaults=master-slave-base-port:15000 \ + deploy replication 5.7.21 + +The difference is that using ``dbdeployer defaults update`` the value is changed permanently for the next commands, or until you run a ``dbdeployer defaults reset``. Using the ``--defaults`` flag, instead, will modify the defaults only for the active command. + diff --git a/website/src/content/docs/managing/deletion.md b/website/src/content/docs/managing/deletion.md index 40a9f9c..14e50ac 100644 --- a/website/src/content/docs/managing/deletion.md +++ b/website/src/content/docs/managing/deletion.md @@ -1,6 +1,43 @@ --- -title: Deletion & Cleanup -description: Deletion & Cleanup documentation +title: "Deletion & Cleanup" --- -Coming soon. +# Sandbox deletion +[[HOME](https://github.com/ProxySQL/dbdeployer/wiki)] + +The sandboxes can also be deleted, either one by one or all at once: + + $ dbdeployer delete -h + Halts the sandbox (and its depending sandboxes, if any), and removes it. + Warning: this command is irreversible! + + Usage: + dbdeployer delete sandbox_name (or "ALL") [flags] + + Aliases: + delete, remove, destroy + + Examples: + + $ dbdeployer delete msb_8_0_4 + $ dbdeployer delete rsandbox_5_7_21 + + Flags: + --concurrent Runs multiple deletion tasks concurrently. + --confirm Requires confirmation. + -h, --help help for delete + --skip-confirm Skips confirmation with multiple deletions. + --use-stop Use 'stop' instead of 'send_kill destroy' to halt the database servers + + + +You can lock one or more sandboxes to prevent deletion. Use this command to make the sandbox non-deletable. + + $ dbdeployer admin lock sandbox_name + +A locked sandbox will not be deleted, even when running ``dbdeployer delete ALL``. + +The lock can also be reverted using + + $ dbdeployer admin unlock sandbox_name + diff --git a/website/src/content/docs/managing/logs.md b/website/src/content/docs/managing/logs.md index 6f4fea1..41f161f 100644 --- a/website/src/content/docs/managing/logs.md +++ b/website/src/content/docs/managing/logs.md @@ -1,6 +1,13 @@ --- -title: Logs -description: Logs documentation +title: "Logs" --- -Coming soon. +# Database logs management. +[[HOME](https://github.com/ProxySQL/dbdeployer/wiki)] + +Sometimes, when using sandboxes for testing, it makes sense to enable the general log, either during initialization or for regular operation. While you can do that with ``--my-cnf-options=general-log=1`` or ``--my-init-options=--general-log=1``, as of version 1.4.0 you have two simple boolean shortcuts: ``--init-general-log`` and ``--enable-general-log`` that will start the general log when requested. + +Additionally, each sandbox has a convenience script named ``show_log`` that can easily display either the error log or the general log. Run `./show_log -h` for usage info. + +For replication, you also have ``show_binlog`` and ``show_relaylog`` in every sandbox as a shortcut to display replication logs easily. + diff --git a/website/src/content/docs/managing/starting-stopping.md b/website/src/content/docs/managing/starting-stopping.md index d6855c1..cf50a5e 100644 --- a/website/src/content/docs/managing/starting-stopping.md +++ b/website/src/content/docs/managing/starting-stopping.md @@ -1,6 +1,148 @@ --- -title: Starting & Stopping -description: Starting & Stopping documentation +title: "Starting & Stopping" --- -Coming soon. +# Sandbox management +[[HOME](https://github.com/ProxySQL/dbdeployer/wiki)] + +You can list the available MySQL versions with + + $ dbdeployer versions # Alias: available + +Optionally, you can ask for only the versions of a given flavor (`dndeployer versions --flavor=ndb`) or to show all the versions distinct by flavor (`dbdeployer versions --by-flavor`) + +And you can list which sandboxes were already installed + + $ dbdeployer sandboxes # Aliases: installed, deployed + +The command "usage" shows how to use the scripts that were installed with each sandbox. + + $ dbdeployer usage + + USING A SANDBOX + + Change directory to the newly created one (default: $SANDBOX_HOME/msb_VERSION + for single sandboxes) + [ $SANDBOX_HOME = $HOME/sandboxes unless modified with flag --sandbox-home ] + + The sandbox directory of the instance you just created contains some handy + scripts to manage your server easily and in isolation. + + "./start", "./status", "./restart", and "./stop" do what their name suggests. + start and restart accept parameters that are eventually passed to the server. + e.g.: + + ./start --server-id=1001 + + ./restart --event-scheduler=disabled + + "./use" calls the command line client with the appropriate parameters, + Example: + + ./use -BN -e "select @@server_id" + ./use -u root + + "./clear" stops the server and removes everything from the data directory, + letting you ready to start from scratch. (Warning! It's irreversible!) + + "./send_kill [destroy]" does almost the same as "./stop", as it sends a SIGTERM (-15) kill + to shut down the server. Additionally, when the regular kill fails, it will + send an unfriendly SIGKILL (-9) to the unresponsive server. + The argument "destroy" will immediately kill the server with SIGKILL (-9). + + "./add_option" will add one or more options to my.sandbox.cnf, and restarts the + server to apply the changes. + + "init_db" and "load_grants" are used during the server initialization, and should not be used + in normal operations. They are nonetheless useful to see which operations were performed + to set up the server. + + "./show_binlog" and "./show_relaylog" will show the latest binary log or relay-log. + + "./my" is a prefix script to invoke any command named "my*" from the + MySQL /bin directory. It is important to use it rather than the + corresponding globally installed tool, because this guarantees + that you will be using the tool for the version you have deployed. + Examples: + + ./my sqldump db_name + ./my sqlbinlog somefile + + "./mysqlsh" invokes the mysql shell. Unlike other commands, this one only works + if mysqlsh was installed, with preference to the binaries found in "basedir". + This script is created only if the X plugin was enabled (5.7.12+ with --enable-mysqlx + or 8.0.11+ without --disable-mysqlx) + + "./use_admin" is created when the sandbox is deployed with --enable-admin-address (8.0.14+) + and allows using the database as administrator, with a dedicated port. + + USING MULTIPLE SERVER SANDBOX + On a replication sandbox, you have the same commands (run "dbdeployer usage single"), + with an "_all" suffix, meaning that you propagate the command to all the members. + Then you have "./m" as a shortcut to use the master, "./s1" and "./s2" to access + the slaves (and "s3", "s4" ... if you define more). + + In group sandboxes without a master slave relationship (group replication and + multiple sandboxes) the nodes can be accessed by ./n1, ./n2, ./n3, and so on. + + start_all [options] > starts all nodes + status_all > get the status of all nodes + restart_all [options] > restarts all nodes + stop_all > stops all nodes + use_all "SQL" > runs a SQL statement in all nodes + use_all_masters "SQL" > runs a SQL statement in all masters + use_all_slaves "SQL" > runs a SQL statement in all slaves + clear_all > stops all nodes and removes all data + m > invokes MySQL client in the master + s1, s2, n1, n2 > invokes MySQL client in slave 1, 2, node 1, 2 + + The scripts "check_slaves" or "check_nodes" give the status of replication in the sandbox. + + When the sandbox is deployed with --enable-admin-address (8.0.14+) the following scripts + are also created: + + ma > invokes the MySQL client in the master as admin + sa1, sa2, na1, na2 > invokes MySQL client as admin in slave 1, 2, node 1, 2 + use_all_admin "SQL" > runs a SQL statement in all nodes as admin + + +Every sandbox has a file named ``sbdescription.json``, containing important information on the sandbox. It is useful to determine where the binaries come from and on which conditions it was installed. + +For example, a description file for a single sandbox would show: + +```json +{ + "basedir": "/home/dbuser/opt/mysql/5.7.22", + "type": "single", + "version": "5.7.22", + "port": [ + 5722 + ], + "nodes": 0, + "node_num": 0, + "dbdeployer-version": "1.5.0", + "timestamp": "Sat May 12 14:26:41 CEST 2018", + "command-line": "dbdeployer deploy single 5.7.22" +} +``` + +And for replication: + +```json +{ + "basedir": "/home/dbuser/opt/mysql/5.7.22", + "type": "master-slave", + "version": "5.7.22", + "port": [ + 16745, + 16746, + 16747 + ], + "nodes": 2, + "node_num": 0, + "dbdeployer-version": "1.5.0", + "timestamp": "Sat May 12 14:27:04 CEST 2018", + "command-line": "dbdeployer deploy replication 5.7.22 --gtid --concurrent" +} +``` + diff --git a/website/src/content/docs/managing/users.md b/website/src/content/docs/managing/users.md index 11d3a7d..719d91d 100644 --- a/website/src/content/docs/managing/users.md +++ b/website/src/content/docs/managing/users.md @@ -1,6 +1,102 @@ --- -title: Database Users -description: Database Users documentation +title: "Database Users" --- -Coming soon. +# Database users +[[HOME](https://github.com/ProxySQL/dbdeployer/wiki)] + +The default users for each server deployed by dbdeployer are: + +* `root`, with the default grants as given by the server version being installed. +* `msandbox`, with all privileges except GRANT option. +* `msandbox_rw`, with minimum read/write privileges. +* `msandbox_ro`, with read-only privileges. +* `rsandbox`, with only replication related privileges (password: `rsandbox`) + +The main user name (`msandbox`) and password (`msandbox`) can be changed using options `--db-user` and `db-password` respectively. + +Every user is assigned by default to a limited scope (`127.%`) so that they can only communicate with the local host. +The scope can be changed using options `--bind-address` and `--remote-access`. + +In MySQL 8.0 the above users are instantiated using roles. You can also define a custom role, and assign it to the main user. + +You can create a different role and assign it to the default user with options like the following: + +``` +dbdeployer deploy single 8.0.19 \ + --custom-role-name=R_POWERFUL \ + --custom-role-privileges='ALL PRIVILEGES' \ + --custom-role-target='*.*' \ + --custom-role-extra='WITH GRANT OPTION' \ + --default-role=R_POWERFUL \ + --bind-address=0.0.0.0 \ + --remote-access='%' \ + --db-user=differentuser \ + --db-password=somethingdifferent +``` + +The result of this operation will be: + +``` +$ ~/sandboxes/msb_8_0_19/use +Welcome to the MySQL monitor. Commands end with ; or \g. +Your MySQL connection id is 9 +Server version: 8.0.19 MySQL Community Server - GPL + +Copyright (c) 2000, 2020, Oracle and/or its affiliates. All rights reserved. + +Oracle is a registered trademark of Oracle Corporation and/or its +affiliates. Other names may be trademarks of their respective +owners. + +Type 'help;' or '\h' for help. Type '\c' to clear the current input statement. + +mysql [localhost:8019] {differentuser} ((none)) > show grants\G +*************************** 1. row *************************** +Grants for differentuser@localhost: GRANT SELECT, INSERT, UPDATE, DELETE, CREATE, +DROP, RELOAD, SHUTDOWN, PROCESS, FILE, REFERENCES, INDEX, ALTER, SHOW DATABASES, +SUPER, CREATE TEMPORARY TABLES, LOCK TABLES, EXECUTE, REPLICATION SLAVE, REPLICATION CLIENT, +CREATE VIEW, SHOW VIEW, CREATE ROUTINE, ALTER ROUTINE, CREATE USER, EVENT, TRIGGER, +CREATE TABLESPACE, CREATE ROLE, DROP ROLE ON *.* TO `differentuser`@`localhost` WITH GRANT OPTION +*************************** 2. row *************************** +Grants for differentuser@localhost: GRANT APPLICATION_PASSWORD_ADMIN,AUDIT_ADMIN,BACKUP_ADMIN,BINLOG_ADMIN, +BINLOG_ENCRYPTION_ADMIN,CLONE_ADMIN,CONNECTION_ADMIN,ENCRYPTION_KEY_ADMIN,GROUP_REPLICATION_ADMIN, +INNODB_REDO_LOG_ARCHIVE,PERSIST_RO_VARIABLES_ADMIN,REPLICATION_APPLIER,REPLICATION_SLAVE_ADMIN, +RESOURCE_GROUP_ADMIN,RESOURCE_GROUP_USER,ROLE_ADMIN,SERVICE_CONNECTION_ADMIN,SESSION_VARIABLES_ADMIN, +SET_USER_ID,SYSTEM_USER,SYSTEM_VARIABLES_ADMIN,TABLE_ENCRYPTION_ADMIN,XA_RECOVER_ADMIN +ON *.* TO `differentuser`@`localhost` WITH GRANT OPTION +*************************** 3. row *************************** +Grants for differentuser@localhost: GRANT `R_POWERFUL`@`%` TO `differentuser`@`localhost` +3 rows in set (0.01 sec) +``` + +Instead of assigning the custom role to the default user, you can also create a task user. + +``` +$ dbdeployer deploy single 8.0 \ + --task-user=task_user \ + --custom-role-name=R_ADMIN \ + --task-user-role=R_ADMIN +``` + +The options shown in this section only apply to MySQL 8.0. + +There is a method of creating users during deployment in any versions: + +1. create a SQL file containing the `CREATE USER` and `GRANT` statements you want to run +2. use the option `--post-grants-sql-file` to load the instructions. + +``` +cat << EOF > orchestrator.sql + +CREATE DATABASE IF NOT EXISTS orchestrator; +CREATE USER orchestrator IDENTIFIED BY 'msandbox'; +GRANT ALL PRIVILEGES ON orchestrator.* TO orchestrator; +GRANT SELECT ON mysql.slave_master_info TO orchestrator; + +EOF + +$ dbdeployer deploy single 5.7 \ + --post-grants-sql-file=$PWD/orchestrator.sql +``` + diff --git a/website/src/content/docs/managing/using.md b/website/src/content/docs/managing/using.md index 4b48331..68fbd05 100644 --- a/website/src/content/docs/managing/using.md +++ b/website/src/content/docs/managing/using.md @@ -1,6 +1,17 @@ --- -title: Using Sandboxes -description: Using Sandboxes documentation +title: "Using Sandboxes" --- -Coming soon. +# Using the latest sandbox +[[HOME](https://github.com/ProxySQL/dbdeployer/wiki)] + +With the command `dbdeployer use`, you will use the latest sandbox that was deployed. If it is a single sandbox, dbdeployer will invoke the `./use` command. If it is a compound sandbox, it will run the `./n1` command. +If you don't want the latest sandbox, you can indicate a specific one: + +``` +$ dbdeployer use msb_5_7_31 +``` + +If that sandbox was stopped, this command will restart it. + + diff --git a/website/src/content/docs/providers/mysql.md b/website/src/content/docs/providers/mysql.md index 000a781..db4eca7 100644 --- a/website/src/content/docs/providers/mysql.md +++ b/website/src/content/docs/providers/mysql.md @@ -1,6 +1,29 @@ --- -title: MySQL -description: MySQL documentation +title: "MySQL" --- -Coming soon. +# Standard and non-standard basedir names +[[HOME](https://github.com/ProxySQL/dbdeployer/wiki)] + +dbdeployer expects to get the binaries from ``$HOME/opt/mysql/x.x.xx``. For example, when you run the command ``dbdeployer deploy single 8.0.11``, you must have the binaries for MySQL 8.0.11 expanded into a directory named ``$HOME/opt/mysql/8.0.11``. + +If you want to keep several directories with the same version, you can differentiate them using a **prefix**: + + $HOME/opt/mysql/ + 8.0.11 + lab_8.0.11 + ps_8.0.11 + myown_8.0.11 + +In the above cases, running ``dbdeployer deploy single lab_8.0.11`` will do what you expect, i.e. dbdeployer will use the binaries in ``lab_8.0.11`` and recognize ``8.0.11`` as the version for the database. + +When the extracted tarball directory name that you want to use doesn't contain the full version number (such as ``/home/dbuser/build/path/5.7-extra``) you need to provide the version using the option ``--binary-version``. For example: + + dbdeployer deploy single 5.7-extra \ + --sandbox-binary=/home/dbuser/build/path \ + --binary-version=5.7.22 + +In the above command, ``--sandbox-binary`` indicates where to search for the binaries, ``5.7-extra`` is where the binaries are, and ``--binary-version`` indicates which version should be used. + +Just to be clear, dbdeployer will recognize the directory as containing a version if it is only "x.x.x" or if it **ends** with "x.x.x" (as in ``lab_8.0.11``.) + diff --git a/website/src/content/docs/providers/proxysql.md b/website/src/content/docs/providers/proxysql.md index 4c29635..1d0e921 100644 --- a/website/src/content/docs/providers/proxysql.md +++ b/website/src/content/docs/providers/proxysql.md @@ -1,6 +1,460 @@ --- -title: ProxySQL -description: ProxySQL documentation +title: "ProxySQL" --- -Coming soon. +# ProxySQL Integration Guide + +dbdeployer can deploy ProxySQL alongside MySQL sandboxes, automatically configuring backends, hostgroups, and monitoring. + +## Table of Contents + +- [Quick Start (5 Minutes)](#quick-start-5-minutes) +- [Prerequisites](#prerequisites) +- [Standalone ProxySQL](#standalone-proxysql) +- [Single MySQL + ProxySQL](#single-mysql--proxysql) +- [Replication + ProxySQL](#replication--proxysql) +- [Group Replication](#group-replication) +- [ProxySQL Sandbox Structure](#proxysql-sandbox-structure) +- [Connecting to ProxySQL](#connecting-to-proxysql) +- [Configuration Details](#configuration-details) +- [Topology-Aware Hostgroups](#topology-aware-hostgroups) +- [Managing Sandboxes](#managing-sandboxes) +- [Troubleshooting](#troubleshooting) +- [Reference](#reference) + +--- + +## Quick Start (5 Minutes) + +Deploy a MySQL replication cluster with ProxySQL in front: + +```bash +# 1. Install prerequisites +# - Go 1.22+ (for building from source) +# - ProxySQL binary in PATH +# - MySQL tarballs unpacked + +# 2. Build dbdeployer +go build -o dbdeployer . +export PATH=$PWD:$PATH + +# 3. Unpack a MySQL tarball +dbdeployer unpack /path/to/mysql-8.4.4-linux-glibc2.17-x86_64.tar.xz + +# 4. Deploy replication with ProxySQL +dbdeployer deploy replication 8.4.4 --with-proxysql + +# 5. Connect through ProxySQL +~/sandboxes/rsandbox_8_4_4/proxysql/use_proxy -e "SELECT @@version, @@port" + +# 6. Check backends +~/sandboxes/rsandbox_8_4_4/proxysql/use -e "SELECT hostgroup_id, hostname, port, status FROM mysql_servers" + +# 7. Clean up when done +dbdeployer delete all +``` + +--- + +## Prerequisites + +### ProxySQL Binary + +dbdeployer uses a system-installed ProxySQL binary. It must be in your `PATH`: + +```bash +# Verify ProxySQL is available +which proxysql +proxysql --version + +# Check dbdeployer sees it +dbdeployer providers +# Output: +# mysql (base port: 3306, ports per instance: 3) +# proxysql (base port: 6032, ports per instance: 2) +``` + +If ProxySQL is not in PATH, you can add it temporarily: + +```bash +export PATH=/path/to/proxysql/bin:$PATH +``` + +### MySQL Binaries + +MySQL tarballs must be unpacked into the sandbox binary directory (default `~/opt/mysql/`): + +```bash +dbdeployer unpack /path/to/mysql-8.4.4-linux-glibc2.17-x86_64.tar.xz +dbdeployer versions +``` + +### Supported MySQL Versions + +- MySQL 8.0.x (fully supported) +- MySQL 8.4.x LTS (fully supported, recommended) +- MySQL 9.x Innovation (fully supported) + +--- + +## Standalone ProxySQL + +Deploy a ProxySQL instance without any MySQL backends: + +```bash +dbdeployer deploy proxysql +# or with custom port: +dbdeployer deploy proxysql --port 16032 +``` + +### Options + +| Flag | Default | Description | +|------|---------|-------------| +| `--port` | 6032 | ProxySQL admin port (mysql port = admin + 1) | +| `--admin-user` | admin | Admin interface username | +| `--admin-password` | admin | Admin interface password | +| `--skip-start` | false | Create sandbox without starting ProxySQL | + +### Usage + +```bash +# Connect to admin interface +~/sandboxes/proxysql_6032/use + +# Check status +~/sandboxes/proxysql_6032/status + +# Stop / Start +~/sandboxes/proxysql_6032/stop +~/sandboxes/proxysql_6032/start +``` + +--- + +## Single MySQL + ProxySQL + +Deploy a single MySQL instance with ProxySQL in front: + +```bash +dbdeployer deploy single 8.4.4 --with-proxysql +``` + +This creates: +- A MySQL sandbox at `~/sandboxes/msb_8_4_4/` +- A ProxySQL sandbox at `~/sandboxes/msb_8_4_4/proxysql/` +- ProxySQL configured with one backend in hostgroup 0 + +```bash +# Connect directly to MySQL +~/sandboxes/msb_8_4_4/use -e "SELECT VERSION()" + +# Connect through ProxySQL +~/sandboxes/msb_8_4_4/proxysql/use_proxy -e "SELECT VERSION()" + +# ProxySQL admin +~/sandboxes/msb_8_4_4/proxysql/use -e "SELECT * FROM mysql_servers" +``` + +--- + +## Replication + ProxySQL + +Deploy a MySQL master-slave replication cluster with ProxySQL: + +```bash +dbdeployer deploy replication 8.4.4 --with-proxysql +``` + +This creates: +- MySQL master + 2 slaves +- ProxySQL with topology-aware configuration: + - **Hostgroup 0**: writer (master) + - **Hostgroup 1**: readers (slaves) + - Monitor user: `msandbox` (checks backend health) + +```bash +# Check MySQL replication +~/sandboxes/rsandbox_8_4_4/check_slaves + +# Check ProxySQL backends +~/sandboxes/rsandbox_8_4_4/proxysql/use -e \ + "SELECT hostgroup_id, hostname, port, status FROM mysql_servers" +# Output: +# hostgroup_id | hostname | port | status +# 0 | 127.0.0.1 | 19805 | ONLINE (master) +# 1 | 127.0.0.1 | 19806 | ONLINE (slave1) +# 1 | 127.0.0.1 | 19807 | ONLINE (slave2) + +# Connect through ProxySQL (routes to master by default) +~/sandboxes/rsandbox_8_4_4/proxysql/use_proxy -e "SELECT @@port" + +# ProxySQL admin — add query rules, check stats, etc. +~/sandboxes/rsandbox_8_4_4/proxysql/use +``` + +### Adding Query Rules + +ProxySQL sandboxes are deployed without query rules — you configure routing yourself: + +```bash +# Example: route SELECT to readers (hostgroup 1) +~/sandboxes/rsandbox_8_4_4/proxysql/use -e " + INSERT INTO mysql_query_rules (active, match_pattern, destination_hostgroup) + VALUES (1, '^SELECT', 1); + LOAD MYSQL QUERY RULES TO RUNTIME; + SAVE MYSQL QUERY RULES TO DISK; +" +``` + +--- + +## Group Replication + +Deploy a MySQL Group Replication cluster: + +```bash +# Multi-primary (default) +dbdeployer deploy replication 8.4.4 --topology=group + +# Single-primary +dbdeployer deploy replication 8.4.4 --topology=group --single-primary +``` + +```bash +# Check group members +~/sandboxes/group_msb_8_4_4/check_nodes + +# Connect to a node +~/sandboxes/group_msb_8_4_4/n1 -e "SELECT * FROM performance_schema.replication_group_members" +``` + +--- + +## ProxySQL Sandbox Structure + +``` +~/sandboxes/rsandbox_8_4_4/ +├── master/ # MySQL master sandbox +├── node1/ # MySQL slave 1 +├── node2/ # MySQL slave 2 +├── proxysql/ # ProxySQL sandbox +│ ├── proxysql.cnf # Generated configuration +│ ├── data/ # ProxySQL SQLite data directory +│ │ └── proxysql.pid # PID file (written by ProxySQL) +│ ├── start # Start ProxySQL +│ ├── stop # Stop ProxySQL +│ ├── status # Check if running +│ ├── use # Connect to admin interface +│ └── use_proxy # Connect through ProxySQL's MySQL port +├── check_slaves # Check replication status +├── start_all # Start all MySQL nodes +├── stop_all # Stop all MySQL nodes +└── ... +``` + +--- + +## Connecting to ProxySQL + +### Admin Interface + +The `use` script connects to ProxySQL's admin port: + +```bash +~/sandboxes/rsandbox_8_4_4/proxysql/use +# ProxySQL Admin> +``` + +From here you can manage backends, query rules, users, and all ProxySQL configuration. + +### MySQL Protocol (Through Proxy) + +The `use_proxy` script connects through ProxySQL's MySQL port, which routes to backends: + +```bash +~/sandboxes/rsandbox_8_4_4/proxysql/use_proxy -e "SELECT @@hostname, @@port" +``` + +### Manual Connection + +```bash +# Admin (default port: 6032) +mysql -h 127.0.0.1 -P 6032 -u admin -padmin + +# Through proxy (default port: 6033) +mysql -h 127.0.0.1 -P 6033 -u msandbox -pmsandbox +``` + +--- + +## Configuration Details + +### Generated proxysql.cnf + +```ini +datadir="/home/user/sandboxes/rsandbox_8_4_4/proxysql/data" + +admin_variables= +{ + admin_credentials="admin:admin" + mysql_ifaces="127.0.0.1:6032" +} + +mysql_variables= +{ + interfaces="127.0.0.1:6033" + monitor_username="msandbox" + monitor_password="msandbox" + monitor_connect_interval=2000 + monitor_ping_interval=2000 +} + +mysql_servers= +( + { address="127.0.0.1" port=19805 hostgroup=0 max_connections=200 }, + { address="127.0.0.1" port=19806 hostgroup=1 max_connections=200 }, + { address="127.0.0.1" port=19807 hostgroup=1 max_connections=200 } +) + +mysql_users= +( + { username="msandbox" password="msandbox" default_hostgroup=0 } +) +``` + +### Default Credentials + +| Component | User | Password | Purpose | +|-----------|------|----------|---------| +| ProxySQL Admin | admin | admin | Admin interface management | +| MySQL / Monitor | msandbox | msandbox | Backend connections and health monitoring | +| Replication | rsandbox | rsandbox | MySQL replication user | + +--- + +## Topology-Aware Hostgroups + +| Topology | Hostgroup 0 | Hostgroup 1 | Monitoring | +|----------|-------------|-------------|------------| +| Single | 1 backend | — | Basic health | +| Replication | Writer (master) | Readers (slaves) | read_only check | +| Group Replication | (configure manually) | (configure manually) | — | + +--- + +## Managing Sandboxes + +### List Deployed Sandboxes + +```bash +dbdeployer sandboxes +``` + +### Delete a Specific Sandbox + +```bash +dbdeployer delete rsandbox_8_4_4 +``` + +ProxySQL is automatically stopped before the directory is removed. + +### Delete All Sandboxes + +```bash +dbdeployer delete all +``` + +### List Available Providers + +```bash +dbdeployer providers +# mysql (base port: 3306, ports per instance: 3) +# proxysql (base port: 6032, ports per instance: 2) +``` + +--- + +## Troubleshooting + +### "proxysql binary not found in PATH" + +ProxySQL must be installed and available in your PATH: + +```bash +which proxysql || echo "Not found — install ProxySQL or add to PATH" +export PATH=/path/to/proxysql:$PATH +``` + +### ProxySQL fails to start + +Check the data directory for errors: + +```bash +ls ~/sandboxes/*/proxysql/data/ +# Look for proxysql.pid — if missing, startup failed +``` + +### Backends showing SHUNNED + +ProxySQL detected the backend is unhealthy. Check if MySQL is running: + +```bash +~/sandboxes/rsandbox_8_4_4/node1/status +~/sandboxes/rsandbox_8_4_4/node1/start # restart if needed +``` + +### Port conflicts + +If deployment fails with port errors, clean up stale processes: + +```bash +dbdeployer delete all +pkill -u $USER proxysql +pkill -u $USER mysqld +``` + +--- + +## Reference + +### CLI Commands + +``` +dbdeployer deploy proxysql [flags] + --port int ProxySQL admin port (default 6032) + --admin-user string Admin username (default "admin") + --admin-password string Admin password (default "admin") + --skip-start Don't start ProxySQL after creation + +dbdeployer deploy single <version> [flags] + --with-proxysql Deploy ProxySQL alongside MySQL + +dbdeployer deploy replication <version> [flags] + --with-proxysql Deploy ProxySQL alongside replication cluster + --topology=group Use group replication instead of master-slave + --single-primary Single-primary mode for group replication + +dbdeployer providers + Lists all registered providers (mysql, proxysql) + +dbdeployer delete <sandbox-name> + Deletes sandbox (stops ProxySQL automatically) +``` + +### ProxySQL Sandbox Scripts + +| Script | Purpose | +|--------|---------| +| `start` | Start ProxySQL (waits for PID file) | +| `stop` | Stop ProxySQL (kills process and children) | +| `status` | Check if ProxySQL is running | +| `use` | Connect to admin interface via mysql client | +| `use_proxy` | Connect through ProxySQL's MySQL port | + +### Environment Variables + +| Variable | Default | Description | +|----------|---------|-------------| +| `SANDBOX_HOME` | `~/sandboxes` | Where sandboxes are created | +| `SANDBOX_BINARY` | `~/opt/mysql` | Where MySQL binaries are stored | diff --git a/website/src/content/docs/reference/cli-commands.md b/website/src/content/docs/reference/cli-commands.md index 5b1902a..395ccf7 100644 --- a/website/src/content/docs/reference/cli-commands.md +++ b/website/src/content/docs/reference/cli-commands.md @@ -1,6 +1,34 @@ --- -title: CLI Commands -description: CLI Commands documentation +title: "CLI Commands" --- -Coming soon. +# Command line completion +[[HOME](https://github.com/ProxySQL/dbdeployer/wiki)] + +There is a file ``./docs/dbdeployer_completion.sh``, which is automatically generated with dbdeployer API documentation. If you want to use bash completion on the command line, copy the file to the bash completion directory. For example: + + # Linux + $ sudo cp ./docs/dbdeployer_completion.sh /etc/bash_completion.d + $ source /etc/bash_completion + + # OSX + $ sudo cp ./docs/dbdeployer_completion.sh /usr/local/etc/bash_completion.d + $ source /usr/local/etc/bash_completion + +There is a dbdeployer command that does all the above for you: + +``` +dbdeployer defaults enable-bash-completion --remote --run-it +``` + +When completion is enabled, you can use it as follows: + + $ dbdeployer [tab] + admin defaults delete deploy global sandboxes unpack usage versions + $ dbdeployer dep[tab] + $ dbdeployer deploy [tab][tab] + multiple replication single + $ dbdeployer deploy s[tab] + $ dbdeployer deploy single --b[tab][tab] + --base-port= --bind-address= + diff --git a/website/src/content/docs/reference/configuration.md b/website/src/content/docs/reference/configuration.md index 05ee9de..3a5ed57 100644 --- a/website/src/content/docs/reference/configuration.md +++ b/website/src/content/docs/reference/configuration.md @@ -1,6 +1,36 @@ --- -title: Configuration -description: Configuration documentation +title: "Configuration" --- -Coming soon. +# Initializing the environment +[[HOME](https://github.com/ProxySQL/dbdeployer/wiki)] + +Immediately after installing dbdeployer, you can get the environment ready for operations using the command + +``` +$ dbdeployer init +``` + +This command creates the necessary directories, then downloads the latest MySQL binaries, and expands them in the right place. It also enables [command line completion](#command-line-completion). + +Running the command without options is what most users need. Advanced ones may look at the documentation to fine tune the initialization. + + $ dbdeployer init -h + Initializes dbdeployer environment: + * creates $SANDBOX_HOME and $SANDBOX_BINARY directories + * downloads and expands the latest MySQL tarball + * installs shell completion file + + Usage: + dbdeployer init [flags] + + Flags: + --dry-run Show operations but don't run them + -h, --help help for init + --skip-all-downloads Do not download any file (skip both MySQL tarball and shell completion file) + --skip-shell-completion Do not download shell completion file + --skip-tarball-download Do not download MySQL tarball + + + + From 3ee4856474f898ed8c0263a2bde9f389348f01d1 Mon Sep 17 00:00:00 2001 From: Rene Cannao <rene@proxysql.com> Date: Tue, 24 Mar 2026 17:42:00 +0000 Subject: [PATCH 09/15] feat: add getting started quickstart guides MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Four polished quickstart guides covering MySQL single sandbox, MySQL replication, PostgreSQL (deb-based unpack), and ProxySQL integration with read/write split. Each guide follows a numbered Install → Deploy → Connect → Clean up pattern with copy-pasteable bash and links to deeper docs. --- .../quickstart-mysql-replication.md | 75 ++++++++++++++++++- .../quickstart-mysql-single.md | 59 ++++++++++++++- .../getting-started/quickstart-postgresql.md | 75 ++++++++++++++++++- .../getting-started/quickstart-proxysql.md | 71 +++++++++++++++++- 4 files changed, 272 insertions(+), 8 deletions(-) diff --git a/website/src/content/docs/getting-started/quickstart-mysql-replication.md b/website/src/content/docs/getting-started/quickstart-mysql-replication.md index 6f344c6..6249621 100644 --- a/website/src/content/docs/getting-started/quickstart-mysql-replication.md +++ b/website/src/content/docs/getting-started/quickstart-mysql-replication.md @@ -1,6 +1,77 @@ --- title: "Quick Start: MySQL Replication" -description: "Quick Start: MySQL Replication documentation" +description: "Deploy a master-slave MySQL replication topology in under 2 minutes." --- -Coming soon. +Deploy a three-node master/slave replication setup from a single command. Assumes you have already downloaded MySQL 8.4 (see [Quick Start: MySQL Single](/getting-started/quickstart-mysql-single)). + +## Prerequisites + +- dbdeployer installed and MySQL 8.4 unpacked in `~/opt/mysql/` +- If you skipped the single-sandbox guide: `dbdeployer downloads get-by-version 8.4` + +## 1. Deploy replication + +```bash +dbdeployer deploy replication 8.4.4 +``` + +Expected output: + +``` +Replication directory installed in $HOME/sandboxes/rsandbox_8_4_4 +master on port 20192 +slave1 on port 20193 +slave2 on port 20194 +``` + +dbdeployer starts one master and two slaves and wires up replication automatically. + +## 2. Check replication status + +```bash +~/sandboxes/rsandbox_8_4_4/check_slaves +``` + +You should see `Seconds_Behind_Master: 0` for each slave, confirming replication is healthy. + +## 3. Connect to master and slaves + +Connect to the master: + +```bash +~/sandboxes/rsandbox_8_4_4/m +``` + +Connect to slave 1 or slave 2: + +```bash +~/sandboxes/rsandbox_8_4_4/s1 +~/sandboxes/rsandbox_8_4_4/s2 +``` + +Try writing on the master and reading on a slave: + +```sql +-- on master +CREATE DATABASE demo; +USE demo; +CREATE TABLE t (id INT); +INSERT INTO t VALUES (1); + +-- on slave (s1 or s2) +USE demo; +SELECT * FROM t; +``` + +## 4. Clean up + +```bash +dbdeployer delete rsandbox_8_4_4 +``` + +## What's next? + +- [Replication topologies](/deploying/replication) — fan-in, all-masters, semi-sync +- [Group Replication](/deploying/group-replication) — single-primary and multi-primary +- [Quick Start: ProxySQL Integration](/getting-started/quickstart-proxysql) diff --git a/website/src/content/docs/getting-started/quickstart-mysql-single.md b/website/src/content/docs/getting-started/quickstart-mysql-single.md index 8145429..fe44ecb 100644 --- a/website/src/content/docs/getting-started/quickstart-mysql-single.md +++ b/website/src/content/docs/getting-started/quickstart-mysql-single.md @@ -1,6 +1,61 @@ --- title: "Quick Start: MySQL Single" -description: "Quick Start: MySQL Single documentation" +description: "Deploy a standalone MySQL 8.4 sandbox in under 2 minutes." --- -Coming soon. +Get a fully functional MySQL 8.4 instance running on your laptop in under 2 minutes — no root access, no Docker, no permanent changes to your system. + +## Prerequisites + +- dbdeployer installed ([Installation guide](/getting-started/installation)) +- Internet access to download the MySQL binary + +## 1. Download MySQL 8.4 + +```bash +dbdeployer downloads get-by-version 8.4 +``` + +This downloads the MySQL 8.4 tarball and unpacks it into `~/opt/mysql/`. The version number shown (e.g. `8.4.4`) is what you use in the next step. + +## 2. Deploy a single sandbox + +```bash +dbdeployer deploy single 8.4.4 +``` + +Expected output: + +``` +Database installed in $HOME/sandboxes/msb_8_4_4 +``` + +The sandbox starts automatically. + +## 3. Connect and run a query + +```bash +~/sandboxes/msb_8_4_4/use +``` + +You are now inside the MySQL shell. Try a quick query: + +```sql +SELECT VERSION(); +SHOW DATABASES; +EXIT; +``` + +## 4. Clean up + +```bash +dbdeployer delete msb_8_4_4 +``` + +This stops the server and removes all sandbox files. Your MySQL binary in `~/opt/mysql/` is untouched. + +## What's next? + +- [Deploying a single sandbox](/deploying/single) — ports, passwords, options +- [Managing sandboxes](/managing/using) — start, stop, restart +- [Quick Start: MySQL Replication](/getting-started/quickstart-mysql-replication) diff --git a/website/src/content/docs/getting-started/quickstart-postgresql.md b/website/src/content/docs/getting-started/quickstart-postgresql.md index 24d0a4d..a600b17 100644 --- a/website/src/content/docs/getting-started/quickstart-postgresql.md +++ b/website/src/content/docs/getting-started/quickstart-postgresql.md @@ -1,6 +1,77 @@ --- title: "Quick Start: PostgreSQL" -description: "Quick Start: PostgreSQL documentation" +description: "Deploy a standalone PostgreSQL 16 sandbox from .deb packages." --- -Coming soon. +dbdeployer supports PostgreSQL sandboxes using the same workflow as MySQL. Because there is no official PostgreSQL tarball distribution, you extract binaries from `.deb` packages. + +## Prerequisites + +- dbdeployer installed ([Installation guide](/getting-started/installation)) +- `dpkg-deb` available (standard on Debian/Ubuntu) +- `apt-get` or `apt` for downloading packages (no root needed for `apt-get download`) + +## 1. Download PostgreSQL packages + +```bash +apt-get download postgresql-16 postgresql-client-16 +``` + +This downloads two `.deb` files into your current directory — no installation occurs. + +## 2. Unpack into dbdeployer's binary directory + +```bash +dbdeployer unpack --provider=postgresql \ + postgresql-16_*.deb \ + postgresql-client-16_*.deb +``` + +Expected output: + +``` +Unpacking postgresql-16 into $HOME/opt/postgresql/16.13 +``` + +## 3. Deploy a PostgreSQL sandbox + +```bash +dbdeployer deploy postgresql 16.13 +``` + +Or equivalently: + +```bash +dbdeployer deploy single 16.13 --provider=postgresql +``` + +Expected output: + +``` +Database installed in $HOME/sandboxes/pg_sandbox_16613 +``` + +## 4. Connect with psql + +```bash +~/sandboxes/pg_sandbox_16613/use +``` + +You are now in the `psql` shell: + +```sql +SELECT version(); +\l +\q +``` + +## 5. Clean up + +```bash +dbdeployer delete pg_sandbox_16613 +``` + +## What's next? + +- [PostgreSQL provider details](/providers/postgresql) — replication, options, limitations +- [Managing sandboxes](/managing/using) — start, stop, status diff --git a/website/src/content/docs/getting-started/quickstart-proxysql.md b/website/src/content/docs/getting-started/quickstart-proxysql.md index d5177c3..534a615 100644 --- a/website/src/content/docs/getting-started/quickstart-proxysql.md +++ b/website/src/content/docs/getting-started/quickstart-proxysql.md @@ -1,6 +1,73 @@ --- title: "Quick Start: ProxySQL Integration" -description: "Quick Start: ProxySQL Integration documentation" +description: "Deploy MySQL replication with a ProxySQL load balancer in one command." --- -Coming soon. +dbdeployer can deploy a ProxySQL instance alongside a replication topology, pre-configured with read/write splitting. Assumes MySQL 8.4 is already unpacked (see [Quick Start: MySQL Single](/getting-started/quickstart-mysql-single)). + +## Prerequisites + +- dbdeployer installed with MySQL 8.4 unpacked in `~/opt/mysql/` +- ProxySQL binary available on `PATH` (e.g. `/usr/bin/proxysql`) + +## 1. Deploy replication with ProxySQL + +```bash +dbdeployer deploy replication 8.4.4 --with-proxysql +``` + +Expected output: + +``` +Replication directory installed in $HOME/sandboxes/rsandbox_8_4_4 +master on port 20192 +slave1 on port 20193 +slave2 on port 20194 +ProxySQL admin on port 6032 +ProxySQL proxy on port 6033 +``` + +dbdeployer starts the MySQL topology, launches ProxySQL, and configures hostgroups for write (master) and read (slaves). + +## 2. Open the ProxySQL admin interface + +```bash +~/sandboxes/rsandbox_8_4_4/proxysql/use +``` + +You are now in the ProxySQL admin shell. Inspect the hostgroups: + +```sql +SELECT hostgroup_id, hostname, port, status FROM runtime_mysql_servers; +``` + +## 3. Connect through the proxy + +```bash +~/sandboxes/rsandbox_8_4_4/proxysql/use_proxy +``` + +This opens a MySQL connection routed through ProxySQL on port 6033. Writes are sent to the master; reads are distributed across the slaves automatically. + +Try it: + +```sql +-- this SELECT will be routed to a slave +SELECT @@hostname; + +-- DML is routed to the master +CREATE DATABASE proxydemo; +``` + +## 4. Clean up + +```bash +dbdeployer delete rsandbox_8_4_4 +``` + +This stops ProxySQL and all MySQL nodes and removes the sandbox directory. + +## What's next? + +- [ProxySQL integration guide](/providers/proxysql) — hostgroups, query rules, custom config +- [Replication topologies](/deploying/replication) — fan-in, all-masters, group replication From ea82d1c9ff84a884c3000f1cf0e9c95201474909 Mon Sep 17 00:00:00 2001 From: Rene Cannao <rene@proxysql.com> Date: Tue, 24 Mar 2026 17:45:31 +0000 Subject: [PATCH 10/15] feat: add marketing landing page with hero, features, and terminal demo Adds a custom Astro landing page (not Starlight) at the site root with a dark-themed design: sticky nav, animated hero with gradient title and copy-able install snippet, 4-card feature grid, and a styled terminal demo showing MySQL replication deployment. Includes global CSS custom properties and responsive layout. --- website/src/components/FeatureGrid.astro | 185 +++++++++++++ website/src/components/Hero.astro | 238 ++++++++++++++++ website/src/components/Terminal.astro | 251 +++++++++++++++++ website/src/layouts/Landing.astro | 328 +++++++++++++++++++++++ website/src/pages/index.astro | 15 ++ website/src/styles/global.css | 121 +++++++++ 6 files changed, 1138 insertions(+) create mode 100644 website/src/components/FeatureGrid.astro create mode 100644 website/src/components/Hero.astro create mode 100644 website/src/components/Terminal.astro create mode 100644 website/src/layouts/Landing.astro create mode 100644 website/src/pages/index.astro create mode 100644 website/src/styles/global.css diff --git a/website/src/components/FeatureGrid.astro b/website/src/components/FeatureGrid.astro new file mode 100644 index 0000000..b338ea5 --- /dev/null +++ b/website/src/components/FeatureGrid.astro @@ -0,0 +1,185 @@ +--- +const features = [ + { + icon: 'topology', + title: 'Any Topology', + description: + 'Deploy single instances, primary/replica replication, group replication (single-primary and multi-primary), fan-in, and all-masters topologies with a single command.', + accent: '#3b82f6', + }, + { + icon: 'database', + title: 'Multiple Databases', + description: + 'Supports MySQL, PostgreSQL, Percona Server, MariaDB, Percona XtraDB Cluster, and more. Use the same workflow regardless of flavor.', + accent: '#22c55e', + }, + { + icon: 'proxysql', + title: 'ProxySQL Integration', + description: + 'Deploy a read/write split stack with ProxySQL in front of a replication topology in one command. Perfect for integration testing.', + accent: '#f59e0b', + }, + { + icon: 'userspace', + title: 'No Root, No Docker', + description: + 'Runs entirely in userspace. No sudo, no containers, no package managers. Each sandbox is a self-contained directory with start/stop scripts.', + accent: '#a855f7', + }, +]; +--- + +<section class="features"> + <div class="features-container"> + <div class="features-header"> + <h2 class="features-title">Everything you need for database testing</h2> + <p class="features-subtitle"> + dbdeployer handles the complexity so you can focus on your application. + </p> + </div> + + <div class="features-grid"> + {features.map(f => ( + <div class="feature-card" style={`--accent: ${f.accent}`}> + <div class="feature-icon"> + {f.icon === 'topology' && ( + <svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" aria-hidden="true"> + <circle cx="12" cy="5" r="2"/> + <circle cx="5" cy="19" r="2"/> + <circle cx="19" cy="19" r="2"/> + <path d="M12 7v4M12 11l-5 6M12 11l5 6"/> + </svg> + )} + {f.icon === 'database' && ( + <svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" aria-hidden="true"> + <ellipse cx="12" cy="5" rx="9" ry="3"/> + <path d="M3 5v14c0 1.66 4.03 3 9 3s9-1.34 9-3V5"/> + <path d="M3 12c0 1.66 4.03 3 9 3s9-1.34 9-3"/> + </svg> + )} + {f.icon === 'proxysql' && ( + <svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" aria-hidden="true"> + <rect x="2" y="3" width="20" height="6" rx="1"/> + <rect x="2" y="15" width="9" height="6" rx="1"/> + <rect x="13" y="15" width="9" height="6" rx="1"/> + <path d="M7 9v6M17 9v6"/> + </svg> + )} + {f.icon === 'userspace' && ( + <svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" aria-hidden="true"> + <path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"/> + <path d="m9 12 2 2 4-4"/> + </svg> + )} + </div> + <h3 class="feature-title">{f.title}</h3> + <p class="feature-desc">{f.description}</p> + </div> + ))} + </div> + </div> +</section> + +<style> + .features { + padding: var(--section-padding); + background-color: var(--color-bg-secondary); + border-top: 1px solid var(--color-border); + border-bottom: 1px solid var(--color-border); + } + + .features-container { + max-width: var(--max-width); + margin: 0 auto; + } + + .features-header { + text-align: center; + margin-bottom: 3rem; + } + + .features-title { + font-size: clamp(1.5rem, 3vw, 2.25rem); + font-weight: 700; + color: var(--color-text); + margin-bottom: 0.75rem; + } + + .features-subtitle { + color: var(--color-text-muted); + font-size: 1.05rem; + max-width: 500px; + margin: 0 auto; + } + + .features-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)); + gap: 1.5rem; + } + + .feature-card { + background-color: var(--color-bg); + border: 1px solid var(--color-border); + border-radius: var(--radius-lg); + padding: 1.75rem; + transition: border-color 0.2s ease, transform 0.2s ease, box-shadow 0.2s ease; + position: relative; + overflow: hidden; + } + + .feature-card::before { + content: ''; + position: absolute; + top: 0; + left: 0; + right: 0; + height: 3px; + background: var(--accent); + opacity: 0; + transition: opacity 0.2s ease; + } + + .feature-card:hover { + border-color: color-mix(in srgb, var(--accent) 40%, var(--color-border)); + transform: translateY(-3px); + box-shadow: var(--shadow-lg); + } + + .feature-card:hover::before { + opacity: 1; + } + + .feature-icon { + display: inline-flex; + align-items: center; + justify-content: center; + width: 48px; + height: 48px; + border-radius: var(--radius); + background-color: color-mix(in srgb, var(--accent) 12%, transparent); + color: var(--accent); + margin-bottom: 1rem; + } + + .feature-title { + font-size: 1.1rem; + font-weight: 600; + color: var(--color-text); + margin-bottom: 0.5rem; + } + + .feature-desc { + color: var(--color-text-muted); + font-size: 0.9rem; + line-height: 1.65; + } + + @media (max-width: 640px) { + .features-grid { + grid-template-columns: 1fr; + } + } +</style> diff --git a/website/src/components/Hero.astro b/website/src/components/Hero.astro new file mode 100644 index 0000000..43ff3c6 --- /dev/null +++ b/website/src/components/Hero.astro @@ -0,0 +1,238 @@ +--- +const base = '/dbdeployer'; +const githubUrl = 'https://github.com/ProxySQL/dbdeployer'; +const installCmd = 'curl -sSL https://raw.githubusercontent.com/ProxySQL/dbdeployer/main/scripts/install.sh | bash'; +--- + +<section class="hero"> + <div class="hero-container"> + <div class="hero-badge"> + <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true"> + <circle cx="12" cy="12" r="10"/> + <path d="m9 12 2 2 4-4"/> + </svg> + No root. No Docker. No hassle. + </div> + + <h1 class="hero-title"> + Deploy MySQL & PostgreSQL<br /> + <span class="hero-title-accent">sandboxes in seconds</span> + </h1> + + <p class="hero-subtitle"> + Create single instances, replication topologies, and full testing stacks + — locally, without root, without Docker. + </p> + + <div class="hero-ctas"> + <a href={`${base}/docs/getting-started/quickstart-mysql-single/`} class="btn btn-primary"> + Get Started + <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" aria-hidden="true"> + <path d="M5 12h14M12 5l7 7-7 7"/> + </svg> + </a> + <a href={githubUrl} class="btn btn-outline" target="_blank" rel="noopener noreferrer"> + <svg width="18" height="18" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"> + <path d="M12 2C6.477 2 2 6.484 2 12.017c0 4.425 2.865 8.18 6.839 9.504.5.092.682-.217.682-.483 0-.237-.008-.868-.013-1.703-2.782.605-3.369-1.343-3.369-1.343-.454-1.158-1.11-1.466-1.11-1.466-.908-.62.069-.608.069-.608 1.003.07 1.531 1.032 1.531 1.032.892 1.53 2.341 1.088 2.91.832.092-.647.35-1.088.636-1.338-2.22-.253-4.555-1.113-4.555-4.951 0-1.093.39-1.988 1.029-2.688-.103-.253-.446-1.272.098-2.65 0 0 .84-.27 2.75 1.026A9.564 9.564 0 0112 6.844c.85.004 1.705.115 2.504.337 1.909-1.296 2.747-1.027 2.747-1.027.546 1.379.202 2.398.1 2.651.64.7 1.028 1.595 1.028 2.688 0 3.848-2.339 4.695-4.566 4.943.359.309.678.92.678 1.855 0 1.338-.012 2.419-.012 2.747 0 .268.18.58.688.482A10.019 10.019 0 0022 12.017C22 6.484 17.522 2 12 2z"/> + </svg> + View on GitHub + </a> + </div> + + <div class="hero-install"> + <p class="hero-install-label">Quick install:</p> + <div class="hero-install-block"> + <code class="hero-install-code">{installCmd}</code> + <button class="hero-copy-btn" data-copy={installCmd} aria-label="Copy install command" title="Copy to clipboard"> + <svg class="copy-icon" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true"> + <rect x="9" y="9" width="13" height="13" rx="2"/> + <path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"/> + </svg> + <svg class="check-icon" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" aria-hidden="true" style="display:none"> + <path d="M20 6 9 17l-5-5"/> + </svg> + </button> + </div> + </div> + </div> + + <!-- Decorative background blobs --> + <div class="hero-bg-blob hero-bg-blob-1" aria-hidden="true"></div> + <div class="hero-bg-blob hero-bg-blob-2" aria-hidden="true"></div> +</section> + +<script> + document.querySelectorAll<HTMLButtonElement>('.hero-copy-btn').forEach(btn => { + btn.addEventListener('click', async () => { + const text = btn.dataset.copy; + if (!text) return; + try { + await navigator.clipboard.writeText(text); + const copy = btn.querySelector<SVGElement>('.copy-icon'); + const check = btn.querySelector<SVGElement>('.check-icon'); + if (copy) copy.style.display = 'none'; + if (check) check.style.display = 'block'; + setTimeout(() => { + if (copy) copy.style.display = 'block'; + if (check) check.style.display = 'none'; + }, 2000); + } catch { + // Clipboard not available + } + }); + }); +</script> + +<style> + .hero { + position: relative; + overflow: hidden; + padding: 7rem 1.5rem 6rem; + text-align: center; + } + + .hero-container { + position: relative; + z-index: 1; + max-width: 760px; + margin: 0 auto; + display: flex; + flex-direction: column; + align-items: center; + gap: 1.5rem; + } + + .hero-badge { + display: inline-flex; + align-items: center; + gap: 0.4rem; + padding: 0.35rem 0.85rem; + background-color: rgba(59, 130, 246, 0.1); + border: 1px solid rgba(59, 130, 246, 0.3); + border-radius: 999px; + color: var(--color-accent-light); + font-size: 0.8rem; + font-weight: 500; + } + + .hero-title { + font-size: clamp(2rem, 5vw, 3.5rem); + font-weight: 800; + line-height: 1.15; + letter-spacing: -0.02em; + color: var(--color-text); + } + + .hero-title-accent { + background: linear-gradient(135deg, #3b82f6 0%, #60a5fa 50%, #93c5fd 100%); + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; + background-clip: text; + } + + .hero-subtitle { + font-size: clamp(1rem, 2.5vw, 1.2rem); + color: var(--color-text-muted); + max-width: 600px; + line-height: 1.7; + } + + .hero-ctas { + display: flex; + gap: 0.875rem; + flex-wrap: wrap; + justify-content: center; + } + + .hero-install { + margin-top: 0.5rem; + display: flex; + flex-direction: column; + align-items: center; + gap: 0.5rem; + } + + .hero-install-label { + color: var(--color-text-muted); + font-size: 0.8rem; + font-weight: 500; + text-transform: uppercase; + letter-spacing: 0.05em; + } + + .hero-install-block { + display: flex; + align-items: center; + gap: 0.5rem; + background-color: var(--color-code-bg); + border: 1px solid var(--color-border); + border-radius: var(--radius); + padding: 0.6rem 0.75rem 0.6rem 1rem; + max-width: 100%; + overflow: hidden; + } + + .hero-install-code { + font-family: var(--font-mono); + font-size: 0.8rem; + color: var(--color-green); + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + flex: 1; + min-width: 0; + } + + .hero-copy-btn { + flex-shrink: 0; + background: none; + border: none; + color: var(--color-text-muted); + cursor: pointer; + padding: 0.25rem; + border-radius: 4px; + transition: color 0.15s ease; + display: flex; + align-items: center; + } + + .hero-copy-btn:hover { + color: var(--color-text); + } + + /* Background decorations */ + .hero-bg-blob { + position: absolute; + border-radius: 50%; + filter: blur(80px); + opacity: 0.15; + pointer-events: none; + } + + .hero-bg-blob-1 { + width: 600px; + height: 600px; + background: radial-gradient(circle, #3b82f6 0%, transparent 70%); + top: -200px; + left: 50%; + transform: translateX(-50%); + } + + .hero-bg-blob-2 { + width: 400px; + height: 400px; + background: radial-gradient(circle, #8b5cf6 0%, transparent 70%); + bottom: -100px; + right: 10%; + } + + @media (max-width: 480px) { + .hero { + padding: 4rem 1.5rem 3rem; + } + + .hero-install-block { + max-width: calc(100vw - 3rem); + } + } +</style> diff --git a/website/src/components/Terminal.astro b/website/src/components/Terminal.astro new file mode 100644 index 0000000..dbdd976 --- /dev/null +++ b/website/src/components/Terminal.astro @@ -0,0 +1,251 @@ +--- +--- + +<section class="terminal-section"> + <div class="terminal-container"> + <div class="terminal-header-text"> + <h2 class="terminal-title">See it in action</h2> + <p class="terminal-subtitle"> + Deploy a full MySQL 8.4 replication topology in one command, + then connect to the primary and run queries. + </p> + </div> + + <div class="terminal-window"> + <!-- Window chrome --> + <div class="terminal-chrome"> + <div class="terminal-dots"> + <span class="dot dot-red"></span> + <span class="dot dot-yellow"></span> + <span class="dot dot-green"></span> + </div> + <span class="terminal-title-bar">bash</span> + </div> + + <!-- Terminal body --> + <div class="terminal-body" role="region" aria-label="Terminal demo"> + <div class="terminal-line"> + <span class="t-prompt">$</span> + <span class="t-cmd"> dbdeployer deploy replication 8.4.4</span> + </div> + <div class="terminal-line t-output"> + <span class="t-success">✓</span> Primary deployed <span class="t-muted">(port:</span> <span class="t-port">8404</span><span class="t-muted">)</span> + </div> + <div class="terminal-line t-output"> + <span class="t-success">✓</span> Replica 1 deployed <span class="t-muted">(port:</span> <span class="t-port">8405</span><span class="t-muted">)</span> + </div> + <div class="terminal-line t-output"> + <span class="t-success">✓</span> Replica 2 deployed <span class="t-muted">(port:</span> <span class="t-port">8406</span><span class="t-muted">)</span> + </div> + <div class="terminal-line t-spacer"></div> + <div class="terminal-line"> + <span class="t-prompt">$</span> + <span class="t-cmd"> ~/sandboxes/rsandbox_8_4_4/m</span> + <span class="t-comment"> # connect to primary</span> + </div> + <div class="terminal-line"> + <span class="t-mysql-prompt">mysql></span> + <span class="t-sql"> SELECT @@version;</span> + </div> + <div class="terminal-line t-output"> + <pre class="t-table">+-----------+ +| @@version | ++-----------+ +| 8.4.4 | ++-----------+ +1 row in set (0.00 sec)</pre> + </div> + <div class="terminal-line t-spacer"></div> + <div class="terminal-line"> + <span class="t-prompt">$</span> + <span class="t-cmd"> ~/sandboxes/rsandbox_8_4_4/check_slaves</span> + </div> + <div class="terminal-line t-output"> + <span class="t-success">✓</span> <span class="t-muted">node1: Slave running — Seconds_Behind_Master: 0</span> + </div> + <div class="terminal-line t-output"> + <span class="t-success">✓</span> <span class="t-muted">node2: Slave running — Seconds_Behind_Master: 0</span> + </div> + <div class="terminal-cursor" aria-hidden="true"></div> + </div> + </div> + </div> +</section> + +<style> + .terminal-section { + padding: var(--section-padding); + background-color: var(--color-bg); + } + + .terminal-container { + max-width: var(--max-width); + margin: 0 auto; + display: grid; + grid-template-columns: 1fr 1.4fr; + gap: 3.5rem; + align-items: center; + } + + .terminal-header-text { + display: flex; + flex-direction: column; + gap: 1rem; + } + + .terminal-title { + font-size: clamp(1.5rem, 3vw, 2.25rem); + font-weight: 700; + color: var(--color-text); + line-height: 1.25; + } + + .terminal-subtitle { + color: var(--color-text-muted); + font-size: 1rem; + line-height: 1.7; + } + + /* Terminal window */ + .terminal-window { + background-color: var(--color-code-bg); + border: 1px solid var(--color-border); + border-radius: var(--radius-lg); + overflow: hidden; + box-shadow: var(--shadow-lg), 0 0 0 1px rgba(255,255,255,0.04); + } + + .terminal-chrome { + display: flex; + align-items: center; + gap: 0.5rem; + padding: 0.75rem 1rem; + background-color: #1a2234; + border-bottom: 1px solid var(--color-border); + } + + .terminal-dots { + display: flex; + gap: 6px; + } + + .dot { + width: 12px; + height: 12px; + border-radius: 50%; + } + + .dot-red { background-color: #ff5f57; } + .dot-yellow { background-color: #febc2e; } + .dot-green { background-color: #28c840; } + + .terminal-title-bar { + color: var(--color-text-muted); + font-size: 0.8rem; + font-family: var(--font-mono); + flex: 1; + text-align: center; + } + + .terminal-body { + padding: 1.25rem 1.5rem 1.5rem; + font-family: var(--font-mono); + font-size: 0.83rem; + line-height: 1.6; + } + + .terminal-line { + display: flex; + align-items: baseline; + gap: 0; + flex-wrap: wrap; + } + + .t-spacer { + height: 0.75rem; + } + + .t-output { + padding-left: 1.2rem; + color: var(--color-text-muted); + } + + .t-prompt { + color: var(--color-green); + font-weight: 700; + user-select: none; + } + + .t-mysql-prompt { + color: #f59e0b; + font-weight: 600; + user-select: none; + } + + .t-cmd { + color: var(--color-text); + } + + .t-sql { + color: #93c5fd; + } + + .t-comment { + color: #475569; + margin-left: 0.25rem; + font-style: italic; + } + + .t-success { + color: var(--color-green); + margin-right: 0.4rem; + } + + .t-port { + color: #f59e0b; + } + + .t-muted { + color: var(--color-text-muted); + } + + .t-table { + font-family: var(--font-mono); + font-size: 0.83rem; + color: var(--color-text-muted); + line-height: 1.5; + background: none; + border: none; + padding: 0; + margin: 0.25rem 0; + white-space: pre; + } + + .terminal-cursor { + display: inline-block; + width: 8px; + height: 1em; + background-color: var(--color-accent-light); + animation: blink 1.2s step-end infinite; + vertical-align: text-bottom; + margin-left: 2px; + } + + @keyframes blink { + 0%, 100% { opacity: 1; } + 50% { opacity: 0; } + } + + @media (max-width: 800px) { + .terminal-container { + grid-template-columns: 1fr; + } + } + + @media (max-width: 480px) { + .terminal-body { + font-size: 0.72rem; + padding: 1rem; + } + } +</style> diff --git a/website/src/layouts/Landing.astro b/website/src/layouts/Landing.astro new file mode 100644 index 0000000..e015881 --- /dev/null +++ b/website/src/layouts/Landing.astro @@ -0,0 +1,328 @@ +--- +export interface Props { + title?: string; + description?: string; +} + +const { + title = 'dbdeployer — Deploy MySQL & PostgreSQL sandboxes in seconds', + description = 'Create single instances, replication topologies, and full testing stacks — locally, without root, without Docker.', +} = Astro.props; + +const base = '/dbdeployer'; +const githubUrl = 'https://github.com/ProxySQL/dbdeployer'; +--- + +<!DOCTYPE html> +<html lang="en"> + <head> + <meta charset="UTF-8" /> + <meta name="viewport" content="width=device-width, initial-scale=1.0" /> + <meta name="description" content={description} /> + + <!-- Open Graph --> + <meta property="og:title" content={title} /> + <meta property="og:description" content={description} /> + <meta property="og:type" content="website" /> + <meta property="og:url" content="https://proxysql.github.io/dbdeployer/" /> + + <!-- Twitter Card --> + <meta name="twitter:card" content="summary_large_image" /> + <meta name="twitter:title" content={title} /> + <meta name="twitter:description" content={description} /> + + <title>{title} + + + + + + + + +
+ +
+ + + + + + + + + diff --git a/website/src/pages/index.astro b/website/src/pages/index.astro new file mode 100644 index 0000000..71e676f --- /dev/null +++ b/website/src/pages/index.astro @@ -0,0 +1,15 @@ +--- +import Landing from '../layouts/Landing.astro'; +import Hero from '../components/Hero.astro'; +import FeatureGrid from '../components/FeatureGrid.astro'; +import Terminal from '../components/Terminal.astro'; +--- + + + + + + diff --git a/website/src/styles/global.css b/website/src/styles/global.css new file mode 100644 index 0000000..891b64b --- /dev/null +++ b/website/src/styles/global.css @@ -0,0 +1,121 @@ +/* CSS Custom Properties */ +:root { + --color-bg: #0f172a; + --color-bg-secondary: #1e293b; + --color-bg-card: #1e293b; + --color-border: #334155; + --color-text: #f1f5f9; + --color-text-muted: #94a3b8; + --color-accent: #3b82f6; + --color-accent-hover: #2563eb; + --color-accent-light: #60a5fa; + --color-green: #22c55e; + --color-yellow: #eab308; + --color-code-bg: #020617; + --color-code-text: #e2e8f0; + --font-sans: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif; + --font-mono: 'JetBrains Mono', 'Fira Code', 'Cascadia Code', 'Menlo', 'Monaco', 'Courier New', monospace; + --radius: 8px; + --radius-lg: 12px; + --shadow: 0 4px 6px -1px rgb(0 0 0 / 0.3), 0 2px 4px -2px rgb(0 0 0 / 0.3); + --shadow-lg: 0 10px 15px -3px rgb(0 0 0 / 0.4), 0 4px 6px -4px rgb(0 0 0 / 0.4); + --max-width: 1100px; + --section-padding: 5rem 1.5rem; +} + +/* Reset & Base */ +*, *::before, *::after { + box-sizing: border-box; + margin: 0; + padding: 0; +} + +html { + scroll-behavior: smooth; +} + +body { + background-color: var(--color-bg); + color: var(--color-text); + font-family: var(--font-sans); + font-size: 1rem; + line-height: 1.6; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +a { + color: var(--color-accent-light); + text-decoration: none; + transition: color 0.2s ease; +} + +a:hover { + color: var(--color-text); +} + +img { + max-width: 100%; + height: auto; +} + +/* Utility classes */ +.container { + max-width: var(--max-width); + margin: 0 auto; + padding: 0 1.5rem; +} + +.sr-only { + position: absolute; + width: 1px; + height: 1px; + padding: 0; + margin: -1px; + overflow: hidden; + clip: rect(0, 0, 0, 0); + white-space: nowrap; + border-width: 0; +} + +/* Button styles */ +.btn { + display: inline-flex; + align-items: center; + gap: 0.5rem; + padding: 0.75rem 1.5rem; + border-radius: var(--radius); + font-size: 1rem; + font-weight: 600; + cursor: pointer; + transition: all 0.2s ease; + border: 2px solid transparent; + text-decoration: none; +} + +.btn-primary { + background-color: var(--color-accent); + color: #fff; + border-color: var(--color-accent); +} + +.btn-primary:hover { + background-color: var(--color-accent-hover); + border-color: var(--color-accent-hover); + color: #fff; + transform: translateY(-1px); + box-shadow: 0 4px 12px rgb(59 130 246 / 0.4); +} + +.btn-outline { + background-color: transparent; + color: var(--color-text); + border-color: var(--color-border); +} + +.btn-outline:hover { + background-color: var(--color-bg-secondary); + border-color: var(--color-text-muted); + color: var(--color-text); + transform: translateY(-1px); +} From b480325bd7c95b128e94c8e95cc5fd9d6a67301e Mon Sep 17 00:00:00 2001 From: Rene Cannao Date: Tue, 24 Mar 2026 17:47:23 +0000 Subject: [PATCH 11/15] feat: add providers comparison page Adds a providers comparison page at /providers with a feature matrix table, per-provider cards (MySQL, PostgreSQL, ProxySQL) with command snippets, and a coming-soon Orchestrator teaser section. --- website/src/components/ProviderCard.astro | 135 ++++++++ website/src/pages/providers.astro | 364 ++++++++++++++++++++++ 2 files changed, 499 insertions(+) create mode 100644 website/src/components/ProviderCard.astro create mode 100644 website/src/pages/providers.astro diff --git a/website/src/components/ProviderCard.astro b/website/src/components/ProviderCard.astro new file mode 100644 index 0000000..f80f6f3 --- /dev/null +++ b/website/src/components/ProviderCard.astro @@ -0,0 +1,135 @@ +--- +export interface Props { + name: string; + description: string; + command: string; + docsLink: string; + accent: string; +} + +const { name, description, command, docsLink, accent } = Astro.props; +--- + +
+
+
+

{name}

+

{description}

+
+ +
+
+ Example +
+
{command}
+
+ + +
+ + diff --git a/website/src/pages/providers.astro b/website/src/pages/providers.astro new file mode 100644 index 0000000..1f98430 --- /dev/null +++ b/website/src/pages/providers.astro @@ -0,0 +1,364 @@ +--- +import Landing from '../layouts/Landing.astro'; +import ProviderCard from '../components/ProviderCard.astro'; + +const base = '/dbdeployer'; + +const providers = [ + { + name: 'MySQL', + description: 'The original. All topologies, all flavors — MySQL, Percona Server, MariaDB, NDB Cluster, PXC, and TiDB.', + command: 'dbdeployer deploy replication 8.4.4', + docsLink: `${base}/docs/providers/mysql/`, + accent: '#3b82f6', + }, + { + name: 'PostgreSQL', + description: 'Streaming replication, deb-based binary management. Deploy a fully wired Postgres topology with one command.', + command: 'dbdeployer deploy replication 16.13 --provider=postgresql', + docsLink: `${base}/docs/providers/postgresql/`, + accent: '#22c55e', + }, + { + name: 'ProxySQL', + description: 'Deploy standalone or wire into any topology with --with-proxysql. Perfect for read/write split testing.', + command: 'dbdeployer deploy replication 8.4.4 --with-proxysql', + docsLink: `${base}/docs/providers/proxysql/`, + accent: '#f59e0b', + }, +]; + +const matrix = [ + { feature: 'Single sandbox', mysql: true, postgresql: true, proxysql: false }, + { feature: 'Multiple sandboxes', mysql: true, postgresql: true, proxysql: false }, + { feature: 'Replication', mysql: true, postgresql: 'streaming', proxysql: false }, + { feature: 'Group replication', mysql: true, postgresql: false, proxysql: false }, + { feature: 'ProxySQL wiring', mysql: true, postgresql: true, proxysql: false }, + { feature: 'Binary source', mysql: 'Tarballs', postgresql: '.deb extraction', proxysql: 'System binary' }, +]; +--- + + + +
+
+
Providers
+

One tool, multiple backends

+

+ dbdeployer's provider architecture lets you deploy different database systems with the same CLI workflow. + One tool, multiple backends. +

+
+
+ + +
+
+

Provider comparison

+
+ + + + + + + + + + + {matrix.map((row, i) => ( + + + + + + + ))} + +
FeatureMySQLPostgreSQLProxySQL
{row.feature} + {row.mysql === true + ? + : row.mysql === false + ? + : {row.mysql}} + + {row.postgresql === true + ? + : row.postgresql === false + ? + : {row.postgresql}} + + {row.proxysql === true + ? + : row.proxysql === false + ? + : {row.proxysql}} +
+
+

+ MariaDB and Percona Server are MySQL-compatible flavors and use the same MySQL provider. +

+
+
+ + +
+
+

Explore providers

+
+ {providers.map(p => ( + + ))} +
+
+
+ + +
+
+
+ +
+

Coming soon: Orchestrator integration

+

+ Orchestrator integration is on the roadmap — a complete failover-testable environment in one command. +

+
+
+
+
+
+ + From 1dc2461679209b1d37ef9f9f54e51844f4bbe84f Mon Sep 17 00:00:00 2001 From: Rene Cannao Date: Tue, 24 Mar 2026 17:49:57 +0000 Subject: [PATCH 12/15] feat: add blog with launch posts and landing page integration - BlogPost.astro layout wrapping Landing.astro with title, date, author, tags, and back-link - BlogPostCard.astro preview card component with hover effects - blog/index.astro listing posts in reverse chronological order - blog/[...slug].astro dynamic route using getCollection + render - Two launch posts: new maintainership announcement and PostgreSQL support - index.astro "What's New" section showing latest 2 blog posts --- website/src/components/BlogPostCard.astro | 132 ++++++++++ .../blog/2026-03-24-new-maintainership.md | 31 +++ .../blog/2026-03-24-postgresql-support.md | 45 ++++ website/src/layouts/BlogPost.astro | 230 ++++++++++++++++++ website/src/pages/blog/[...slug].astro | 25 ++ website/src/pages/blog/index.astro | 79 ++++++ website/src/pages/index.astro | 79 ++++++ 7 files changed, 621 insertions(+) create mode 100644 website/src/components/BlogPostCard.astro create mode 100644 website/src/content/blog/2026-03-24-new-maintainership.md create mode 100644 website/src/content/blog/2026-03-24-postgresql-support.md create mode 100644 website/src/layouts/BlogPost.astro create mode 100644 website/src/pages/blog/[...slug].astro create mode 100644 website/src/pages/blog/index.astro diff --git a/website/src/components/BlogPostCard.astro b/website/src/components/BlogPostCard.astro new file mode 100644 index 0000000..50d6b1e --- /dev/null +++ b/website/src/components/BlogPostCard.astro @@ -0,0 +1,132 @@ +--- +export interface Props { + title: string; + date: Date; + author: string; + description: string; + tags?: string[]; + slug: string; +} + +const { title, date, author, description, tags = [], slug } = Astro.props; + +const base = '/dbdeployer'; + +const formattedDate = date.toLocaleDateString('en-US', { + year: 'numeric', + month: 'long', + day: 'numeric', +}); +--- + + + + diff --git a/website/src/content/blog/2026-03-24-new-maintainership.md b/website/src/content/blog/2026-03-24-new-maintainership.md new file mode 100644 index 0000000..c229597 --- /dev/null +++ b/website/src/content/blog/2026-03-24-new-maintainership.md @@ -0,0 +1,31 @@ +--- +title: "dbdeployer Under New Maintainership" +date: 2026-03-24 +author: "Rene Cannao" +description: "The ProxySQL team takes over dbdeployer with modern MySQL support, a provider architecture, and PostgreSQL on the horizon." +tags: ["announcement", "roadmap"] +--- + +We're excited to announce that dbdeployer is now maintained by the ProxySQL team. + +## What's Changed + +Since taking over in March 2026, we've: + +- **Modernized the stack** — Go 1.22+, refreshed dependencies, fixed all CVEs +- **Added MySQL 8.4 LTS and 9.x support** — full compatibility with modern MySQL +- **Built a provider architecture** — extensible system for deploying different database types +- **Integrated ProxySQL** — deploy read/write split stacks with `--with-proxysql` +- **Added PostgreSQL support** — streaming replication, deb-based binary management + +## Why ProxySQL? + +We use dbdeployer daily to test ProxySQL against every MySQL topology. When we took over maintainership, we saw an opportunity to make it useful for a much wider audience — anyone who needs local database sandboxes for development and testing. + +## What's Next + +- Orchestrator integration for failover testing +- More PostgreSQL topologies +- A proper website (you're looking at it!) + +Follow the [GitHub repository](https://github.com/ProxySQL/dbdeployer) for updates. diff --git a/website/src/content/blog/2026-03-24-postgresql-support.md b/website/src/content/blog/2026-03-24-postgresql-support.md new file mode 100644 index 0000000..1bb07b2 --- /dev/null +++ b/website/src/content/blog/2026-03-24-postgresql-support.md @@ -0,0 +1,45 @@ +--- +title: "PostgreSQL Support is Here" +date: 2026-03-24 +author: "Rene Cannao" +description: "dbdeployer now supports PostgreSQL sandboxes with streaming replication and ProxySQL integration." +tags: ["release", "postgresql"] +--- + +dbdeployer now speaks PostgreSQL. You can deploy single instances, streaming replication topologies, and even wire ProxySQL in front — all with the same CLI you already know. + +## Quick Start + +Get PostgreSQL binaries from your system's packages: + +```bash +apt-get download postgresql-16 postgresql-client-16 +dbdeployer unpack --provider=postgresql postgresql-16_*.deb postgresql-client-16_*.deb +``` + +Deploy a single sandbox: + +```bash +dbdeployer deploy postgresql 16.13 +~/sandboxes/pg_sandbox_16613/use +``` + +## Streaming Replication + +```bash +dbdeployer deploy replication 16.13 --provider=postgresql +``` + +This creates a primary and two streaming replicas using `pg_basebackup`. The replicas start automatically and connect to the primary's WAL stream. + +## ProxySQL + PostgreSQL + +```bash +dbdeployer deploy replication 16.13 --provider=postgresql --with-proxysql +``` + +ProxySQL is configured with `pgsql_servers` pointing to your primary (hostgroup 0) and replicas (hostgroup 1). + +## How It Works + +Under the hood, the PostgreSQL provider uses `initdb` for initialization, `pg_ctl` for lifecycle management, and `pg_basebackup` for replica creation. Check out the [PostgreSQL provider docs](/dbdeployer/docs/providers/postgresql/) for the full reference. diff --git a/website/src/layouts/BlogPost.astro b/website/src/layouts/BlogPost.astro new file mode 100644 index 0000000..26d5d36 --- /dev/null +++ b/website/src/layouts/BlogPost.astro @@ -0,0 +1,230 @@ +--- +import Landing from './Landing.astro'; + +export interface Props { + title: string; + date: Date; + author: string; + description: string; + tags?: string[]; +} + +const { title, date, author, description, tags = [] } = Astro.props; + +const base = '/dbdeployer'; + +const formattedDate = date.toLocaleDateString('en-US', { + year: 'numeric', + month: 'long', + day: 'numeric', +}); +--- + + +
+
+ + + Back to blog + + +
+
+ {tags.length > 0 && ( + + )} +

{title}

+ +
+ +
+ +
+
+ + +
+
+
+ + diff --git a/website/src/pages/blog/[...slug].astro b/website/src/pages/blog/[...slug].astro new file mode 100644 index 0000000..a018567 --- /dev/null +++ b/website/src/pages/blog/[...slug].astro @@ -0,0 +1,25 @@ +--- +import { getCollection, render } from 'astro:content'; +import BlogPost from '../../layouts/BlogPost.astro'; + +export async function getStaticPaths() { + const posts = await getCollection('blog'); + return posts.map((post) => ({ + params: { slug: post.id }, + props: { post }, + })); +} + +const { post } = Astro.props; +const { Content } = await render(post); +--- + + + + diff --git a/website/src/pages/blog/index.astro b/website/src/pages/blog/index.astro new file mode 100644 index 0000000..81101bd --- /dev/null +++ b/website/src/pages/blog/index.astro @@ -0,0 +1,79 @@ +--- +import { getCollection } from 'astro:content'; +import Landing from '../../layouts/Landing.astro'; +import BlogPostCard from '../../components/BlogPostCard.astro'; + +const posts = (await getCollection('blog')).sort( + (a, b) => b.data.date.getTime() - a.data.date.getTime() +); +--- + + +
+
+
+

Blog

+

News, releases, and technical deep-dives from the dbdeployer team.

+
+ + {posts.length === 0 ? ( +

No posts yet. Check back soon.

+ ) : ( +
+ {posts.map((post) => ( + + ))} +
+ )} +
+
+
+ + diff --git a/website/src/pages/index.astro b/website/src/pages/index.astro index 71e676f..225f46c 100644 --- a/website/src/pages/index.astro +++ b/website/src/pages/index.astro @@ -3,6 +3,12 @@ import Landing from '../layouts/Landing.astro'; import Hero from '../components/Hero.astro'; import FeatureGrid from '../components/FeatureGrid.astro'; import Terminal from '../components/Terminal.astro'; +import BlogPostCard from '../components/BlogPostCard.astro'; +import { getCollection } from 'astro:content'; + +const posts = (await getCollection('blog')) + .sort((a, b) => b.data.date.getTime() - a.data.date.getTime()) + .slice(0, 2); --- + + {posts.length > 0 && ( +
+
+
+

What's New

+ View all posts → +
+
+ {posts.map((post) => ( + + ))} +
+
+
+ )}
+ + From 3c64c9bd489f67b35716537f40541bd33be81359 Mon Sep 17 00:00:00 2001 From: Rene Cannao Date: Tue, 24 Mar 2026 17:51:10 +0000 Subject: [PATCH 13/15] feat: add 404 page and OG social image placeholder Custom 404 page uses the Landing layout with dark theme styling, a large accent-colored 404 heading, and links to Home and Docs. SVG OG image placeholder (1200x630) added to public/ for social card metadata; replace with a real PNG before launch. --- website/public/og-image.svg | 7 +++++++ website/src/pages/404.astro | 16 ++++++++++++++++ 2 files changed, 23 insertions(+) create mode 100644 website/public/og-image.svg create mode 100644 website/src/pages/404.astro diff --git a/website/public/og-image.svg b/website/public/og-image.svg new file mode 100644 index 0000000..e00787d --- /dev/null +++ b/website/public/og-image.svg @@ -0,0 +1,7 @@ + + + + dbdeployer + Deploy MySQL sandboxes in seconds + github.com/datacharmer/dbdeployer + diff --git a/website/src/pages/404.astro b/website/src/pages/404.astro new file mode 100644 index 0000000..6b13843 --- /dev/null +++ b/website/src/pages/404.astro @@ -0,0 +1,16 @@ +--- +import Landing from '../layouts/Landing.astro'; +--- + + +
+
+

404

+

This page doesn't exist.

+ +
+
+
From 1d677839c7a458afebb6e3eee7af87993c3fa478 Mon Sep 17 00:00:00 2001 From: Rene Cannao Date: Tue, 24 Mar 2026 17:51:54 +0000 Subject: [PATCH 14/15] ci: add GitHub Actions workflow for website deployment --- .github/workflows/deploy-website.yml | 61 ++++++++++++++++++++++++++++ 1 file changed, 61 insertions(+) create mode 100644 .github/workflows/deploy-website.yml diff --git a/.github/workflows/deploy-website.yml b/.github/workflows/deploy-website.yml new file mode 100644 index 0000000..f2ef305 --- /dev/null +++ b/.github/workflows/deploy-website.yml @@ -0,0 +1,61 @@ +name: Deploy Website + +on: + push: + branches: [master] + paths: + - 'website/**' + - 'docs/wiki/**' + - 'docs/proxysql-guide.md' + - 'docs/env_variables.md' + workflow_dispatch: + +permissions: + contents: read + pages: write + id-token: write + +concurrency: + group: "pages" + cancel-in-progress: false + +jobs: + build: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: '20' + cache: 'npm' + cache-dependency-path: website/package-lock.json + + - name: Install dependencies + working-directory: website + run: npm ci + + - name: Copy wiki content + run: bash website/scripts/copy-wiki.sh + + - name: Build website + working-directory: website + run: npm run build + + - name: Upload artifact + uses: actions/upload-pages-artifact@v3 + with: + path: website/dist + + deploy: + needs: build + runs-on: ubuntu-latest + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + steps: + - name: Deploy to GitHub Pages + id: deployment + uses: actions/deploy-pages@v4 From 9f17ae7baad49cca592850e195e343bc76e4a321 Mon Sep 17 00:00:00 2001 From: Rene Cannao Date: Tue, 24 Mar 2026 17:52:36 +0000 Subject: [PATCH 15/15] chore: add website build artifacts to root gitignore --- .gitignore | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.gitignore b/.gitignore index f73d566..0dce521 100644 --- a/.gitignore +++ b/.gitignore @@ -11,3 +11,6 @@ dbdeployer-*.osx* dbdeployer-*.linux* ts/testdata/ .worktrees/ +website/node_modules/ +website/dist/ +website/.astro/