From 7dfcbef774e2c74c2d59a1ec0526096fcbc58f82 Mon Sep 17 00:00:00 2001 From: Aarnav Tale Date: Fri, 30 May 2025 19:35:47 -0400 Subject: [PATCH 01/97] feat: initial webssh tooling --- agent/cmd/hp_agent/hp_agent.go | 4 + agent/internal/hpagent/handler.go | 170 ++++++++++++++++++-------- agent/internal/sshutil/connect.go | 161 +++++++++++++++++++++++++ agent/internal/sshutil/encoder.go | 41 +++++++ agent/internal/sshutil/handler.go | 56 +++++++++ agent/internal/sshutil/sessions.go | 67 +++++++++++ agent/internal/util/logger.go | 37 ++++-- app/server/README.md | 4 + app/server/agent/dispatcher.ts | 36 ++++++ app/server/agent/ssh.ts | 185 +++++++++++++++++++++++++++++ app/server/index.ts | 24 +++- app/server/web/agent.ts | 14 ++- go.mod | 12 +- go.sum | 12 ++ package.json | 2 + pnpm-lock.yaml | 50 +++++--- 16 files changed, 792 insertions(+), 83 deletions(-) create mode 100644 agent/internal/sshutil/connect.go create mode 100644 agent/internal/sshutil/encoder.go create mode 100644 agent/internal/sshutil/handler.go create mode 100644 agent/internal/sshutil/sessions.go create mode 100644 app/server/agent/dispatcher.ts create mode 100644 app/server/agent/ssh.ts diff --git a/agent/cmd/hp_agent/hp_agent.go b/agent/cmd/hp_agent/hp_agent.go index 8212059..d4dcfe5 100644 --- a/agent/cmd/hp_agent/hp_agent.go +++ b/agent/cmd/hp_agent/hp_agent.go @@ -1,9 +1,12 @@ package main import ( + "os" + _ "github.com/joho/godotenv/autoload" "github.com/tale/headplane/agent/internal/config" "github.com/tale/headplane/agent/internal/hpagent" + "github.com/tale/headplane/agent/internal/sshutil" "github.com/tale/headplane/agent/internal/tsnet" "github.com/tale/headplane/agent/internal/util" ) @@ -31,5 +34,6 @@ func main() { ID: agent.ID, }) + sshutil.StartInputReader(os.NewFile(3, "sshin")) hpagent.FollowMaster(agent) } diff --git a/agent/internal/hpagent/handler.go b/agent/internal/hpagent/handler.go index 1228194..76868d6 100644 --- a/agent/internal/hpagent/handler.go +++ b/agent/internal/hpagent/handler.go @@ -2,13 +2,16 @@ package hpagent import ( "bufio" - "encoding/json" + "bytes" + // "encoding/json" "os" - "sync" + // "sync" + "github.com/fxamacker/cbor/v2" + "github.com/tale/headplane/agent/internal/sshutil" "github.com/tale/headplane/agent/internal/tsnet" "github.com/tale/headplane/agent/internal/util" - "tailscale.com/tailcfg" + // "tailscale.com/tailcfg" ) // Represents messages from the Headplane master @@ -16,6 +19,25 @@ type RecvMessage struct { NodeIDs []string } +type CborMessage struct { + Op string `cbor:"op"` + Payload cbor.RawMessage `cbor:"payload"` +} + +type SSHConnect struct { + SessionId string `cbor:"sessionId"` + Username string `cbor:"username"` + Hostname string `cbor:"hostname"` + Port int `cbor:"port"` +} + +type SSHMessage struct { + op string + username string + hostname string + Id string +} + type SendMessage struct { Type string Data any @@ -25,63 +47,111 @@ type SendMessage struct { func FollowMaster(agent *tsnet.TSAgent) { log := util.GetLogger() scanner := bufio.NewScanner(os.Stdin) + log.Info("Listening for messages from Headplane master on stdin") for scanner.Scan() { line := scanner.Bytes() + log.Info("Got bytes delimited by newline"); + + var msg CborMessage + decoder := cbor.NewDecoder(bytes.NewReader(line)) + err := decoder.Decode(&msg) - var msg RecvMessage - err := json.Unmarshal(line, &msg) if err != nil { - log.Error("Unable to unmarshal message: %s", err) - log.Debug("Full Error: %v", err) - continue + log.Error("Unable to decode message from master: %s", err) + continue; } - log.Debug("Recieved message from master: %v", line) - - if len(msg.NodeIDs) == 0 { - log.Debug("Message recieved had no node IDs") - log.Debug("Full message: %s", line) - continue + log.Debug("Received message from master: %s", msg) + var sshPayload SSHConnect + err = cbor.Unmarshal(msg.Payload, &sshPayload) + if err != nil { + log.Error("Unable to unmarshal SSH connect payload: %s", err) + continue; } - // Accumulate the results since we invoke via gofunc - results := make(map[string]*tailcfg.HostinfoView) - mu := sync.Mutex{} - wg := sync.WaitGroup{} - - for _, nodeID := range msg.NodeIDs { - wg.Add(1) - go func(nodeID string) { - defer wg.Done() - result, err := agent.GetStatusForPeer(nodeID) - if err != nil { - log.Error("Unable to get status for node %s: %s", nodeID, err) - return - } - - if result == nil { - log.Debug("No status for node %s", nodeID) - return - } - - mu.Lock() - results[nodeID] = result - mu.Unlock() - }(nodeID) - } - - wg.Wait() - - // Send the results back to the Headplane master - log.Debug("Sending status back to master: %v", results) - log.Msg(&SendMessage{ - Type: "status", - Data: results, + log.Info("Opening SSH PTY for session %s to %s@%s:%d", sshPayload.SessionId, sshPayload.Username, sshPayload.Hostname, sshPayload.Port) + sshutil.OpenSshPty(agent, sshutil.SshConnectParams{ + Hostname: sshPayload.Hostname, + Port: sshPayload.Port, + Username: sshPayload.Username, + Id: sshPayload.SessionId, }) } - if err := scanner.Err(); err != nil { - log.Fatal("Error reading from stdin: %s", err) - } + + + // var msg RecvMessage + // err := json.Unmarshal(line, &msg) + // if err != nil { + // var cborMsg CborMessage + // dec := cbor.NewDecoder(bytes.NewReader(line)) + // err := dec.Decode(&cborMsg) + + // if err == nil { + // log.Info("Unmarshalled CBOR message: %s", cborMsg) + // var sshPayload SSHConnect + // err = cbor.Unmarshal(cborMsg.Payload, &sshPayload) + // sshutil.OpenSshPty(agent, sshutil.SshConnectParams{ + // Hostname: sshPayload.Hostname, + // Port: sshPayload.Port, + // Username: sshPayload.Username, + // Id: sshPayload.SessionId, + // }) + + // return; + // } + + // log.Error("Unable to unmarshal message: %s", err) + // log.Debug("Full Error: %v", err) + // continue + // } + + // log.Debug("Recieved message from master: %v", line) + + // if len(msg.NodeIDs) == 0 { + // log.Debug("Message recieved had no node IDs") + // log.Debug("Full message: %s", line) + // continue + // } + + // // Accumulate the results since we invoke via gofunc + // results := make(map[string]*tailcfg.HostinfoView) + // mu := sync.Mutex{} + // wg := sync.WaitGroup{} + + // for _, nodeID := range msg.NodeIDs { + // wg.Add(1) + // go func(nodeID string) { + // defer wg.Done() + // result, err := agent.GetStatusForPeer(nodeID) + // if err != nil { + // log.Error("Unable to get status for node %s: %s", nodeID, err) + // return + // } + + // if result == nil { + // log.Debug("No status for node %s", nodeID) + // return + // } + + // mu.Lock() + // results[nodeID] = result + // mu.Unlock() + // }(nodeID) + // } + + // wg.Wait() + + // // Send the results back to the Headplane master + // log.Debug("Sending status back to master: %v", results) + // log.Msg(&SendMessage{ + // Type: "status", + // Data: results, + // }) + // } + + // if err := scanner.Err(); err != nil { + // log.Fatal("Error reading from stdin: %s", err) + // } } diff --git a/agent/internal/sshutil/connect.go b/agent/internal/sshutil/connect.go new file mode 100644 index 0000000..3e35aa6 --- /dev/null +++ b/agent/internal/sshutil/connect.go @@ -0,0 +1,161 @@ +package sshutil + +import ( + "context" + "errors" + "io" + "os" + "strconv" + "strings" + + "github.com/tale/headplane/agent/internal/tsnet" + "github.com/tale/headplane/agent/internal/util" + "golang.org/x/crypto/ssh" +) + +type SshConnectParams struct { + Hostname string + Port int + Username string + Id string +} + +func dialAndValidateTailscaleSSH(agent *tsnet.TSAgent, params SshConnectParams) (*ssh.Client, error) { + log := util.GetLogger() + + addr := strings.Join([]string{params.Hostname, ":", strconv.Itoa(params.Port)}, "") + + log.Debug("Attempting to dial %s via Tailscale SSH", addr) + conn, err := agent.Dial(context.Background(), "tcp", addr) + if err != nil { + log.Error("Failed to connect to Tailscale SSH: %s", err) + return nil, err + } + + log.Debug("Connected to Tailscale SSH at %s", addr) + config := &ssh.ClientConfig{ + User: params.Username, + HostKeyCallback: ssh.InsecureIgnoreHostKey(), + } + + clientConn, chans, reqs, err := ssh.NewClientConn(conn, addr, config) + if err != nil { + log.Error("Failed to create SSH client connection: %s", err) + conn.Close() + return nil, err + } + + client := ssh.NewClient(clientConn, chans, reqs) + sVer := string(client.ServerVersion()) + + if !strings.Contains(sVer, "Tailscale") { + log.Error("Connected to non-Tailscale SSH server: %s", sVer) + conn.Close() + return nil, errors.New("not a Tailscale SSH server") + } + + log.Info("Connected to SSH server running %s at %s", client.ServerVersion(), addr) + return client, nil +} + +func bindStdinToFd(sess *ssh.Session, fd int) error { + log := util.GetLogger() + + sshIn := os.NewFile(uintptr(fd), "sshInput") + if sshIn == nil { + log.Error("Failed to create file from stdin fd %d", fd) + return errors.New("failed to create file from stdin fd") + } + + stdin, err := sess.StdinPipe() + if err != nil { + log.Error("Failed to get stdin pipe: %s", err) + return err + } + + go io.Copy(stdin, sshIn) // From Node → SSH session + return nil +} + +func bindStdoutToFd(sess *ssh.Session, fd int) error { + log := util.GetLogger() + + sshOut := os.NewFile(uintptr(fd), "sshOutput") + if sshOut == nil { + log.Error("Failed to create file from stdout fd %d", fd) + return errors.New("failed to create file from stdout fd") + } + + stdout, err := sess.StdoutPipe() + if err != nil { + log.Error("Failed to get stdout pipe: %s", err) + return err + } + + go io.Copy(sshOut, stdout) // From SSH → Node + return nil +} + +func OpenSshPty(agent *tsnet.TSAgent, params SshConnectParams) (*ssh.Client, error) { + log := util.GetLogger() + + if agent == nil { + log.Error("Tailscale agent is nil") + return nil, errors.New("tailscale agent is nil") + } + + if params.Hostname == "" || params.Port <= 0 || params.Username == "" { + log.Error("Invalid SSH connection parameters: %+v", params) + return nil, errors.New("invalid SSH connection parameters") + } + + client, err := dialAndValidateTailscaleSSH(agent, params) + if err != nil { + log.Error("Failed to open SSH pty: %s", err) + return nil, err + } + + go func() { + sess, err := client.NewSession() + if err != nil { + log.Error("Failed to create new SSH session: %s", err) + client.Close() + } + + modes := ssh.TerminalModes{ + ssh.ECHO: 1, // enable echoing + ssh.TTY_OP_ISPEED: 14400, + ssh.TTY_OP_OSPEED: 14400, + } + + if err := sess.RequestPty("xterm-256color", 80, 40, modes); err != nil { + log.Error("Failed to request PTY: %s", err) + client.Close() + } + + ctx := addSession(params.Id, sess) + go func() { + for data := range ctx.InputCh { + if _, err := ctx.Stdin.Write(data); err != nil { + log.Error("Failed to write to SSH stdin: %s", err) + return + } + } + }(); + + if err := sess.Shell(); err != nil { + log.Error("Failed to start shell: %s", err) + client.Close() + } + + + log.Info("Successfully opened SSH pty for %s@%s:%d", params.Username, params.Hostname, params.Port) + go streamSSHOutput(params.Id, ctx.Stdout, os.NewFile(4, "sshOutput")) + sess.Wait(); + sess.Close(); + log.Info("SSH session %s closed (goSide)", params.Id) + RemoveSession(params.Id) + }() + + return client, nil +} diff --git a/agent/internal/sshutil/encoder.go b/agent/internal/sshutil/encoder.go new file mode 100644 index 0000000..66d307a --- /dev/null +++ b/agent/internal/sshutil/encoder.go @@ -0,0 +1,41 @@ +package sshutil + +import ( + "encoding/binary" + "fmt" +) + +func encodeFrame(id string, data []byte) ([]byte, error) { + sid := []byte(id) + if len(sid) > 255 { + return nil, fmt.Errorf("session ID too long") + } + + payloadLen := len(data) + buf := make([]byte, 1+len(sid)+4+payloadLen) + + buf[0] = byte(len(sid)) // SID length + copy(buf[1:], sid) // SID + binary.BigEndian.PutUint32(buf[1+len(sid):], uint32(payloadLen)) // Payload length + copy(buf[1+len(sid)+4:], data) // Payload + + return buf, nil +} + +func decodeFrame(buf []byte) (id string, payload []byte, ok bool) { + if len(buf) < 5 { + return "", nil, false + } + sidLen := int(buf[0]) + if len(buf) < 1+sidLen+4 { + return "", nil, false + } + id = string(buf[1 : 1+sidLen]) + payloadLen := int(binary.BigEndian.Uint32(buf[1+sidLen:])) + + if len(buf) < 1+sidLen+4+payloadLen { + return "", nil, false + } + payload = buf[1+sidLen+4 : 1+sidLen+4+payloadLen] + return id, payload, true +} diff --git a/agent/internal/sshutil/handler.go b/agent/internal/sshutil/handler.go new file mode 100644 index 0000000..db2722a --- /dev/null +++ b/agent/internal/sshutil/handler.go @@ -0,0 +1,56 @@ +package sshutil + +import ( + "io" + + "github.com/tale/headplane/agent/internal/util" +) + +func StartInputReader(fd3 io.Reader) { + log := util.GetLogger(); + + log.Info("Starting SSH fd3 input reader") + go func() { + buffer := make([]byte, 8192) + for { + n, err := fd3.Read(buffer) + if err != nil { + log.Error("fd3 read error: %v", err) + return + } + offset := 0 + for offset < n { + id, payload, ok := decodeFrame(buffer[offset:n]) + if !ok { + break // Wait for more data + } + offset += 1 + len(id) + 4 + len(payload) + + sessionsMu.RLock() + sess, ok := sessions[id] + sessionsMu.RUnlock() + if !ok { + log.Error("invalid session id: %s", id) + continue + } + _, err := sess.Stdin.Write(payload) + if err != nil { + log.Error("failed to write to session stdin: %v", err) + continue + } + } + } + }() +} + +func streamSSHOutput(id string, r io.Reader, fd4 io.Writer) { + buf := make([]byte, 1024) + for { + n, err := r.Read(buf) + if err != nil { + break + } + frame, _ := encodeFrame(id, buf[:n]) + fd4.Write(frame) + } +} diff --git a/agent/internal/sshutil/sessions.go b/agent/internal/sshutil/sessions.go new file mode 100644 index 0000000..7c07c39 --- /dev/null +++ b/agent/internal/sshutil/sessions.go @@ -0,0 +1,67 @@ +package sshutil + +import ( + "io" + "sync" + + "golang.org/x/crypto/ssh" +) + +type SessionContext struct { + ID string + Session *ssh.Session + Stdin io.WriteCloser + Stdout io.Reader + InputCh chan []byte +} + +var sessions = make(map[string]*SessionContext) +var sessionsMu sync.RWMutex + +func addSession(id string, session *ssh.Session) *SessionContext { + sessionsMu.Lock() + defer sessionsMu.Unlock() + + if _, exists := sessions[id]; exists { + return nil // Session with this ID already exists + } + + stdin, err := session.StdinPipe() + if err != nil { + return nil // Handle error appropriately in production code + } + + stdout, err := session.StdoutPipe() + if err != nil { + stdin.Close() // Close stdin if stdout pipe creation fails + return nil // Handle error appropriately in production code + } + + sessionContext := &SessionContext{ + ID: id, + Session: session, + Stdin: stdin, + Stdout: stdout, + } + + sessions[id] = sessionContext + return sessionContext +} + +func GetSession(id string) *SessionContext { + sessionsMu.RLock() + defer sessionsMu.RUnlock() + + return sessions[id] // Returns nil if session does not exist +} + +func RemoveSession(id string) { + sessionsMu.Lock() + defer sessionsMu.Unlock() + + if sessionContext, exists := sessions[id]; exists { + sessionContext.Stdin.Close() // Close the stdin pipe + sessionContext.Session.Close() // Close the SSH session + delete(sessions, id) // Remove from the map + } +} diff --git a/agent/internal/util/logger.go b/agent/internal/util/logger.go index 213b452..9754633 100644 --- a/agent/internal/util/logger.go +++ b/agent/internal/util/logger.go @@ -107,11 +107,34 @@ func (l *Logger) Msg(obj any) { } func escapeString(s string) string { - replacer := strings.NewReplacer( - `"`, `\"`, - `\`, `\\`, - "\n", `\n`, - "\t", `\t`, - ) - return replacer.Replace(s) + var b strings.Builder + b.Grow(len(s) + 16) // pre-grow to reduce reallocs + + for i := 0; i < len(s); i++ { + c := s[i] + switch c { + case '"': + b.WriteString(`\"`) + case '\\': + b.WriteString(`\\`) + case '\b': + b.WriteString(`\b`) + case '\f': + b.WriteString(`\f`) + case '\n': + b.WriteString(`\n`) + case '\r': + b.WriteString(`\r`) + case '\t': + b.WriteString(`\t`) + default: + if c < 0x20 { + // Control characters like 0x01, 0x07 (bell), etc. + fmt.Fprintf(&b, `\u%04x`, c) + } else { + b.WriteByte(c) + } + } + } + return b.String() } diff --git a/app/server/README.md b/app/server/README.md index 76d5b9b..ef23e17 100644 --- a/app/server/README.md +++ b/app/server/README.md @@ -7,6 +7,10 @@ many side-effects (in this case, importing a module may run code). ``` server ├── index.ts: Loads everything and starts the web server. +├── agent/ +│ ├── dispatcher.ts: Serializes commands for the agent control fd (stdin). +│ ├── ssh.ts: Manages & multiplexes the active web SSH connections +│ ├── env.ts: Checks the environment variables for custom overrides. ├── config/ │ ├── integration/ │ │ ├── abstract.ts: Defines the abstract class for integrations. diff --git a/app/server/agent/dispatcher.ts b/app/server/agent/dispatcher.ts new file mode 100644 index 0000000..c02d25c --- /dev/null +++ b/app/server/agent/dispatcher.ts @@ -0,0 +1,36 @@ +import type { Writable } from 'node:stream'; +import { decode, encode } from 'cbor2'; + +interface Command { + op: string; + payload: unknown; +} + +interface SSHConnectCommand extends Command { + op: 'ssh_conn'; + payload: { + sessionId: string; + username: string; + hostname: string; + port: number; + }; +} + +type AgentCommand = SSHConnectCommand; + +export async function dispatchCommand( + dispatcher: Writable, + command: AgentCommand, +) { + return new Promise((resolve, reject) => { + const encodedCommand = Buffer.concat([encode(command), Buffer.from('\n')]); + dispatcher.write(encodedCommand, (err) => { + console.log('Command dispatched:', command, err); + if (err) { + reject(err); + } else { + resolve(); + } + }); + }); +} diff --git a/app/server/agent/ssh.ts b/app/server/agent/ssh.ts new file mode 100644 index 0000000..0656aa9 --- /dev/null +++ b/app/server/agent/ssh.ts @@ -0,0 +1,185 @@ +import { ChildProcess } from 'node:child_process'; +import { randomUUID } from 'node:crypto'; +import type { Readable, Writable } from 'node:stream'; +import { Context } from 'hono'; +import { WSContext, WSEvents } from 'hono/ws'; +import log from '~/utils/log'; +import { dispatchCommand } from './dispatcher'; + +interface SSHConnection { + username: string; + hostname: string; + port: number; +} + +interface SSHSession { + connectionDetails: SSHConnection; + connected: boolean; + sessionId: string; + ws: WSContext; +} + +export function createSSHMultiplexer(proc: ChildProcess): SSHMultiplexer { + return new SSHMultiplexer(proc); +} + +export class SSHMultiplexer { + private connections: Map; + private child: ChildProcess; + + constructor(proc: ChildProcess) { + this.connections = new Map(); + this.child = proc; + + this.handleStdout(); + } + + // TODO: Determine if we want to allow multiple connections for the same + // target or attempt to reuse the existing connection (sounds stupid) + private async connect(conn: SSHConnection, ws: WSContext) { + const sessionId = randomUUID(); + const session: SSHSession = { + connectionDetails: conn, + connected: true, + sessionId, + ws, + }; + + log.info('agent', 'Dispatching SSH connection for %s', sessionId); + await dispatchCommand(this.child.stdin, { + op: 'ssh_conn', + payload: { + sessionId, + ...conn, + }, + }); + + this.connections.set(sessionId, session); + return sessionId; + } + + websocketHandler(c: Context): WSEvents { + return { + onOpen: async (_, ws) => { + const { username, hostname, port } = c.req.query(); + if (!username || !hostname || !port) { + ws.close(1008, 'Missing connection parameters'); + return; + } + + const conn: SSHConnection = { + username, + hostname, + port: Number.parseInt(port, 10), + }; + + try { + const sessionId = await this.connect(conn, ws); + ws.raw = sessionId; + ws.send(JSON.stringify({ status: 'connected', sessionId })); + } catch (error) { + ws.close(1011, `Connection failed: ${error.message}`); + } + }, + + onMessage: async (event, ws) => { + const sessionId = ws.raw; + if (!sessionId || !this.connections.has(sessionId)) { + ws.close(1008, 'Invalid session ID'); + return; + } + + const session = this.connections.get(sessionId); + if (!session || !session.connected) { + ws.close(1008, 'Session not connected'); + return; + } + + const encodedFrame = this.encodeFrame(sessionId, event.data); + this.child.stdio[3]?.write(encodedFrame); + }, + + onClose: (_, ws) => { + const sessionId = ws.raw; + if (sessionId && this.connections.has(sessionId)) { + const session = this.connections.get(sessionId); + if (session) { + session.connected = false; + this.connections.delete(sessionId); + } + } + }, + + onError: (event, ws) => { + const sessionId = ws.raw; + if (sessionId && this.connections.has(sessionId)) { + const session = this.connections.get(sessionId); + if (session) { + session.connected = false; + this.connections.delete(sessionId); + } + } + + log.error('agent', 'SSH WebSocket Error with %s', sessionId); + console.log(event); + }, + }; + } + + private encodeFrame(id: string, data: string | Buffer): Buffer { + const sid = Buffer.from(id, 'utf8'); + const payload = Buffer.isBuffer(data) ? data : Buffer.from(data, 'utf8'); + + // FIX: include +4 for the payload length + const frame = Buffer.alloc(1 + sid.length + 4 + payload.length); + frame.writeUint8(sid.length, 0); // 1 byte for sid length + sid.copy(frame, 1); // SID + frame.writeUint32BE(payload.length, 1 + sid.length); // 4 bytes for payload length + payload.copy(frame, 1 + sid.length + 4); // Payload + + return frame; + } + + private decodeFrame(frame: Buffer): { id: string; data: Buffer } | undefined { + if (frame.length < 5) return; + + const sidLength = frame.readUint8(0); + if (frame.length < 1 + sidLength + 4) return; + + const id = frame.slice(1, 1 + sidLength).toString('utf8'); + const payloadLength = frame.readUint32BE(1 + sidLength); + if (frame.length < 1 + sidLength + 4 + payloadLength) return; + + const data = frame.slice( + 1 + sidLength + 4, + 1 + sidLength + 4 + payloadLength, + ); + + return { id, data }; + } + + private handleStdout() { + const stdout = this.child.stdio[4]; + if (!stdout) { + return; + } + + stdout.on('data', (bytes) => { + console.log(Buffer.from(bytes).toString('utf8')); + const decoded = this.decodeFrame(bytes); + if (!decoded) { + return; + } + + const { id, data } = decoded; + console.log(id, data); + const session = this.connections.get(id); + if (!session || !session.connected) { + log.warn('agent', 'Received data for disconnected session %s', id); + return; + } + + session.ws.send(data); + }); + } +} diff --git a/app/server/index.ts b/app/server/index.ts index fc3b8b5..0bf31c4 100644 --- a/app/server/index.ts +++ b/app/server/index.ts @@ -28,6 +28,11 @@ const config = await loadConfig( }), ); +const agentManager = await loadAgentSocket( + config.integration?.agent, + config.headscale.url, +); + // We also use this file to load anything needed by the react router code. // These are usually per-request things that we need access to, like the // helper that can issue and revoke cookies. @@ -56,10 +61,7 @@ const appLoadContext = { config.headscale.tls_cert_path, ), - agents: await loadAgentSocket( - config.integration?.agent, - config.headscale.url, - ), + agents: agentManager, integration: await loadIntegration(config.integration), oidc: config.oidc ? await createOidcClient(config.oidc) : undefined, }; @@ -85,4 +87,18 @@ export default createHonoServer({ listeningListener(info) { log.info('server', 'Running on %s:%s', info.address, info.port); }, + + useWebSocket: true, + configure: (app, { upgradeWebSocket }) => { + if (agentManager === undefined) { + return; + } + + app.get( + '/_ssh_plexer', + upgradeWebSocket((c) => { + return agentManager.multiplexer!.websocketHandler(c); + }), + ); + }, }); diff --git a/app/server/web/agent.ts b/app/server/web/agent.ts index 4d446d4..a17e5c7 100644 --- a/app/server/web/agent.ts +++ b/app/server/web/agent.ts @@ -10,10 +10,12 @@ import { } from 'node:fs/promises'; import { exit } from 'node:process'; import { createInterface } from 'node:readline'; +import { Readable, Writable } from 'node:stream'; import { setTimeout } from 'node:timers/promises'; import { type } from 'arktype'; import { HostInfo } from '~/types'; import log from '~/utils/log'; +import { SSHMultiplexer, createSSHMultiplexer } from '../agent/ssh'; import type { HeadplaneConfig } from '../config/schema'; interface LogResponse { @@ -113,6 +115,7 @@ class AgentManager { >; private spawnProcess: ChildProcess | null; + multiplexer: SSHMultiplexer | null; private agentId: string | null; constructor( @@ -124,6 +127,7 @@ class AgentManager { this.config = config; this.headscaleUrl = headscaleUrl; this.spawnProcess = null; + this.multiplexer = null; this.agentId = null; this.startAgent(); @@ -184,7 +188,7 @@ class AgentManager { ); this.spawnProcess = spawn(this.config.executable_path, [], { detached: false, - stdio: ['pipe', 'pipe', 'pipe'], + stdio: ['pipe', 'pipe', 'pipe', 'pipe', 'pipe'], env: { HOME: process.env.HOME, HEADPLANE_EMBEDDED: 'true', @@ -210,6 +214,14 @@ class AgentManager { return; } + const sshInput = this.spawnProcess.stdio[3]; + const sshOutput = this.spawnProcess.stdio[4]; + + if (sshInput && sshOutput) { + log.info('agent', 'Using SSH multiplexer manager'); + this.multiplexer = createSSHMultiplexer(this.spawnProcess); + } + const rlStdout = createInterface({ input: this.spawnProcess.stdout, crlfDelay: Number.POSITIVE_INFINITY, diff --git a/go.mod b/go.mod index 6e32cbd..b6437af 100644 --- a/go.mod +++ b/go.mod @@ -33,7 +33,7 @@ require ( github.com/coreos/go-iptables v0.7.1-0.20240112124308-65c67c9f46e6 // indirect github.com/dblohm7/wingoes v0.0.0-20240119213807-a09d6be7affa // indirect github.com/digitalocean/go-smbios v0.0.0-20180907143718-390a4f403a8e // indirect - github.com/fxamacker/cbor/v2 v2.6.0 // indirect + github.com/fxamacker/cbor/v2 v2.8.0 // indirect github.com/gaissmai/bart v0.11.1 // indirect github.com/go-json-experiment/json v0.0.0-20231102232822-2e55bd4e08b0 // indirect github.com/go-ole/go-ole v1.3.0 // indirect @@ -76,14 +76,14 @@ require ( github.com/vishvananda/netns v0.0.4 // indirect github.com/x448/float16 v0.8.4 // indirect go4.org/netipx v0.0.0-20231129151722-fdeea329fbba // indirect - golang.org/x/crypto v0.25.0 // indirect + golang.org/x/crypto v0.38.0 // indirect golang.org/x/exp v0.0.0-20240119083558-1b970713d09a // indirect golang.org/x/mod v0.19.0 // indirect golang.org/x/net v0.27.0 // indirect - golang.org/x/sync v0.9.0 // indirect - golang.org/x/sys v0.27.0 // indirect - golang.org/x/term v0.22.0 // indirect - golang.org/x/text v0.16.0 // indirect + golang.org/x/sync v0.14.0 // indirect + golang.org/x/sys v0.33.0 // indirect + golang.org/x/term v0.32.0 // indirect + golang.org/x/text v0.25.0 // indirect golang.org/x/time v0.5.0 // indirect golang.org/x/tools v0.23.0 // indirect golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 // indirect diff --git a/go.sum b/go.sum index d08684a..56d6ef3 100644 --- a/go.sum +++ b/go.sum @@ -63,6 +63,8 @@ github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHk github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= github.com/fxamacker/cbor/v2 v2.6.0 h1:sU6J2usfADwWlYDAFhZBQ6TnLFBHxgesMrQfQgk1tWA= github.com/fxamacker/cbor/v2 v2.6.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ= +github.com/fxamacker/cbor/v2 v2.8.0 h1:fFtUGXUzXPHTIUdne5+zzMPTfffl3RD5qYnkY40vtxU= +github.com/fxamacker/cbor/v2 v2.8.0/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ= github.com/gaissmai/bart v0.11.1 h1:5Uv5XwsaFBRo4E5VBcb9TzY8B7zxFf+U7isDxqOrRfc= github.com/gaissmai/bart v0.11.1/go.mod h1:KHeYECXQiBjTzQz/om2tqn3sZF1J7hw9m6z41ftj3fg= github.com/github/fakeca v0.1.0 h1:Km/MVOFvclqxPM9dZBC4+QE564nU4gz4iZ0D9pMw28I= @@ -194,6 +196,8 @@ go4.org/netipx v0.0.0-20231129151722-fdeea329fbba h1:0b9z3AuHCjxk0x/opv64kcgZLBs go4.org/netipx v0.0.0-20231129151722-fdeea329fbba/go.mod h1:PLyyIXexvUFg3Owu6p/WfdlivPbZJsZdgWZlrGope/Y= golang.org/x/crypto v0.25.0 h1:ypSNr+bnYL2YhwoMt2zPxHFmbAN1KZs/njMG3hxUp30= golang.org/x/crypto v0.25.0/go.mod h1:T+wALwcMOSE0kXgUAnPAHqTLW+XHgcELELW8VaDgm/M= +golang.org/x/crypto v0.38.0 h1:jt+WWG8IZlBnVbomuhg2Mdq0+BBQaHbtqHEFEigjUV8= +golang.org/x/crypto v0.38.0/go.mod h1:MvrbAqul58NNYPKnOra203SB9vpuZW0e+RRZV+Ggqjw= golang.org/x/exp v0.0.0-20240119083558-1b970713d09a h1:Q8/wZp0KX97QFTc2ywcOE0YRjZPVIx+MXInMzdvQqcA= golang.org/x/exp v0.0.0-20240119083558-1b970713d09a/go.mod h1:idGWGoKP1toJGkd5/ig9ZLuPcZBC3ewk7SzmH0uou08= golang.org/x/exp/typeparams v0.0.0-20240314144324-c7f7c6466f7f h1:phY1HzDcf18Aq9A8KkmRtY9WvOFIxN8wgfvy6Zm1DV8= @@ -207,6 +211,8 @@ golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.9.0 h1:fEo0HyrW1GIgZdpbhCRO0PkJajUS5H9IFUztCgEo2jQ= golang.org/x/sync v0.9.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.14.0 h1:woo0S4Yywslg6hp4eUFjTVOyKt0RookbpAHG4c1HmhQ= +golang.org/x/sync v0.14.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= golang.org/x/sys v0.0.0-20200217220822-9197077df867/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200728102440-3e129f6d46b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20220622161953-175b2fd9d664/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -216,10 +222,16 @@ golang.org/x/sys v0.4.1-0.20230131160137-e7d7f63158de/go.mod h1:oPkhp1MJrh7nUepC golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.27.0 h1:wBqf8DvsY9Y/2P8gAfPDEYNuS30J4lPHJxXSb/nJZ+s= golang.org/x/sys v0.27.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw= +golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= golang.org/x/term v0.22.0 h1:BbsgPEJULsl2fV/AT3v15Mjva5yXKQDyKf+TbDz7QJk= golang.org/x/term v0.22.0/go.mod h1:F3qCibpT5AMpCRfhfT53vVJwhLtIVHhB9XDjfFvnMI4= +golang.org/x/term v0.32.0 h1:DR4lr0TjUs3epypdhTOkMmuF5CDFJ/8pOnbzMZPQ7bg= +golang.org/x/term v0.32.0/go.mod h1:uZG1FhGx848Sqfsq4/DlJr3xGGsYMu/L5GW4abiaEPQ= golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4= golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= +golang.org/x/text v0.25.0 h1:qVyWApTSYLk/drJRO5mDlNYskwQznZmkpV2c8q9zls4= +golang.org/x/text v0.25.0/go.mod h1:WEdwpYrmk1qmdHvhkSTNPm3app7v4rsT8F2UD6+VHIA= golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/tools v0.23.0 h1:SGsXPZ+2l4JsgaCKkx+FQ9YZ5XEtA1GZYuoDjenLjvg= diff --git a/package.json b/package.json index 2978657..7d9bd9c 100644 --- a/package.json +++ b/package.json @@ -29,6 +29,7 @@ "@uiw/codemirror-theme-xcode": "^4.23.12", "@uiw/react-codemirror": "^4.23.12", "arktype": "^2.1.20", + "cbor2": "^2.0.1", "clsx": "^2.1.1", "dotenv": "^16.5.0", "isbot": "^5.1.28", @@ -55,6 +56,7 @@ "@react-router/dev": "^7.6.1", "@tailwindcss/vite": "^4.1.8", "@types/websocket": "^1.0.10", + "hono": "^4.7.10", "lefthook": "^1.11.13", "postcss": "^8.5.4", "react-router-dom": "^7.6.1", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 3cd1df9..3546ee5 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -64,6 +64,9 @@ importers: arktype: specifier: ^2.1.20 version: 2.1.20 + cbor2: + specifier: ^2.0.1 + version: 2.0.1 clsx: specifier: ^2.1.1 version: 2.1.1 @@ -137,6 +140,9 @@ importers: '@types/websocket': specifier: ^1.0.10 version: 1.0.10 + hono: + specifier: ^4.7.10 + version: 4.7.10 lefthook: specifier: ^1.11.13 version: 1.11.13 @@ -406,6 +412,10 @@ packages: '@codemirror/view@6.36.8': resolution: {integrity: sha512-yoRo4f+FdnD01fFt4XpfpMCcCAo9QvZOtbrXExn4SqzH32YC6LgzqxfLZw/r6Ge65xyY03mK/UfUqrVw1gFiFg==} + '@cto.af/wtf8@0.0.2': + resolution: {integrity: sha512-ATm4UQiKrdm5GnU6BvIwUDN+LDEtt23zuzKFpnfDT59ULAd0aMYm/nSFzbSO02garLcXumRC13PzNfa7BsfvSg==} + engines: {node: '>=20'} + '@dnd-kit/accessibility@3.1.1': resolution: {integrity: sha512-2P+YgaXF+gRsIihwwY1gCsQSYnu9Zyj2py8kY5fFvUM1qm2WA2u639R6YNVfU4GWr+ZM5mqEsfHZZLoRONbemw==} peerDependencies: @@ -1692,6 +1702,10 @@ packages: caniuse-lite@1.0.30001718: resolution: {integrity: sha512-AflseV1ahcSunK53NfEs9gFWgOEmzr0f+kaMFA4xiLZlr9Hzt7HxcSpIFcnNCUkz6R6dWKa54rUz3HUmI3nVcw==} + cbor2@2.0.1: + resolution: {integrity: sha512-9bE8+tueGxONyxpttNKkAKKcGVtAPeoSJ64AjVTTjEuBOuRaeeP76EN9BbmQqkz1ZeTP0QPvksNBKwvEutIUzQ==} + engines: {node: '>=20'} + chokidar@4.0.3: resolution: {integrity: sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==} engines: {node: '>= 14.16.0'} @@ -1924,8 +1938,8 @@ packages: resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} engines: {node: '>= 0.4'} - hono@4.7.6: - resolution: {integrity: sha512-564rVzELU+9BRqqx5k8sT2NFwGD3I3Vifdb6P7CmM6FiarOSY+fDC+6B+k9wcCb86ReoayteZP2ki0cRLN1jbw==} + hono@4.7.10: + resolution: {integrity: sha512-QkACju9MiN59CKSY5JsGZCYmPZkA6sIW6OFCUp7qDjZu6S6KHtJHhAc9Uy9mV9F8PJ1/HQ3ybZF2yjCa/73fvQ==} engines: {node: '>=16.9.0'} hosted-git-info@6.1.3: @@ -3138,6 +3152,8 @@ snapshots: style-mod: 4.1.2 w3c-keyname: 2.2.8 + '@cto.af/wtf8@0.0.2': {} + '@dnd-kit/accessibility@3.1.1(react@19.1.0)': dependencies: react: 19.1.0 @@ -3275,23 +3291,23 @@ snapshots: dependencies: tslib: 2.8.1 - '@hono/node-server@1.14.0(hono@4.7.6)': + '@hono/node-server@1.14.0(hono@4.7.10)': dependencies: - hono: 4.7.6 + hono: 4.7.10 - '@hono/node-ws@1.1.1(@hono/node-server@1.14.0(hono@4.7.6))(bufferutil@4.0.9)(hono@4.7.6)(utf-8-validate@5.0.10)': + '@hono/node-ws@1.1.1(@hono/node-server@1.14.0(hono@4.7.10))(bufferutil@4.0.9)(hono@4.7.10)(utf-8-validate@5.0.10)': dependencies: - '@hono/node-server': 1.14.0(hono@4.7.6) - hono: 4.7.6 + '@hono/node-server': 1.14.0(hono@4.7.10) + hono: 4.7.10 ws: 8.18.1(bufferutil@4.0.9)(utf-8-validate@5.0.10) transitivePeerDependencies: - bufferutil - utf-8-validate - '@hono/vite-dev-server@0.19.0(hono@4.7.6)': + '@hono/vite-dev-server@0.19.0(hono@4.7.10)': dependencies: - '@hono/node-server': 1.14.0(hono@4.7.6) - hono: 4.7.6 + '@hono/node-server': 1.14.0(hono@4.7.10) + hono: 4.7.10 minimatch: 9.0.5 '@internationalized/date@3.8.1': @@ -4801,6 +4817,10 @@ snapshots: caniuse-lite@1.0.30001718: {} + cbor2@2.0.1: + dependencies: + '@cto.af/wtf8': 0.0.2 + chokidar@4.0.3: dependencies: readdirp: 4.1.2 @@ -5029,7 +5049,7 @@ snapshots: dependencies: function-bind: 1.1.2 - hono@4.7.6: {} + hono@4.7.10: {} hosted-git-info@6.1.3: dependencies: @@ -5423,12 +5443,12 @@ snapshots: react-router-hono-server@2.13.0(patch_hash=6549978df41006e07f1335bfe4ca86224ea36ed40d3f08dfef33143bad54005c)(@react-router/dev@7.6.1(@types/node@22.15.24)(jiti@2.4.2)(lightningcss@1.30.1)(react-router@7.6.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(terser@5.39.0)(tsx@4.19.4)(typescript@5.8.3)(vite@6.3.5(@types/node@22.15.24)(jiti@2.4.2)(lightningcss@1.30.1)(terser@5.39.0)(tsx@4.19.4)(yaml@2.8.0))(yaml@2.8.0))(@types/react@19.1.6)(bufferutil@4.0.9)(react-router@7.6.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(utf-8-validate@5.0.10)(vite@6.3.5(@types/node@22.15.24)(jiti@2.4.2)(lightningcss@1.30.1)(terser@5.39.0)(tsx@4.19.4)(yaml@2.8.0)): dependencies: '@drizzle-team/brocli': 0.11.0 - '@hono/node-server': 1.14.0(hono@4.7.6) - '@hono/node-ws': 1.1.1(@hono/node-server@1.14.0(hono@4.7.6))(bufferutil@4.0.9)(hono@4.7.6)(utf-8-validate@5.0.10) - '@hono/vite-dev-server': 0.19.0(hono@4.7.6) + '@hono/node-server': 1.14.0(hono@4.7.10) + '@hono/node-ws': 1.1.1(@hono/node-server@1.14.0(hono@4.7.10))(bufferutil@4.0.9)(hono@4.7.10)(utf-8-validate@5.0.10) + '@hono/vite-dev-server': 0.19.0(hono@4.7.10) '@react-router/dev': 7.6.1(@types/node@22.15.24)(jiti@2.4.2)(lightningcss@1.30.1)(react-router@7.6.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(terser@5.39.0)(tsx@4.19.4)(typescript@5.8.3)(vite@6.3.5(@types/node@22.15.24)(jiti@2.4.2)(lightningcss@1.30.1)(terser@5.39.0)(tsx@4.19.4)(yaml@2.8.0))(yaml@2.8.0) '@types/react': 19.1.6 - hono: 4.7.6 + hono: 4.7.10 react-router: 7.6.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0) vite: 6.3.5(@types/node@22.15.24)(jiti@2.4.2)(lightningcss@1.30.1)(terser@5.39.0)(tsx@4.19.4)(yaml@2.8.0) transitivePeerDependencies: From 55eacb59e9afc0eca21ff5383df9e88567cb57a1 Mon Sep 17 00:00:00 2001 From: Aarnav Tale Date: Sat, 31 May 2025 09:45:47 -0400 Subject: [PATCH 02/97] feat: expand frame type to support stdout/stderr chan --- agent/cmd/hp_agent/hp_agent.go | 4 +- agent/internal/hpagent/handler.go | 34 +----- agent/internal/sshutil/connect.go | 189 +++++++++++++---------------- agent/internal/sshutil/encoder.go | 130 ++++++++++++++++---- agent/internal/sshutil/handler.go | 140 +++++++++++++++++---- agent/internal/sshutil/sessions.go | 54 ++++++--- app/server/agent/dispatcher.ts | 33 ++++- app/server/agent/encoder.ts | 99 +++++++++++++++ app/server/agent/ssh.ts | 136 +++++++++++---------- app/server/index.ts | 22 +++- package.json | 2 +- pnpm-lock.yaml | 22 ++-- 12 files changed, 580 insertions(+), 285 deletions(-) create mode 100644 app/server/agent/encoder.ts diff --git a/agent/cmd/hp_agent/hp_agent.go b/agent/cmd/hp_agent/hp_agent.go index d4dcfe5..03d90bb 100644 --- a/agent/cmd/hp_agent/hp_agent.go +++ b/agent/cmd/hp_agent/hp_agent.go @@ -1,8 +1,6 @@ package main import ( - "os" - _ "github.com/joho/godotenv/autoload" "github.com/tale/headplane/agent/internal/config" "github.com/tale/headplane/agent/internal/hpagent" @@ -34,6 +32,6 @@ func main() { ID: agent.ID, }) - sshutil.StartInputReader(os.NewFile(3, "sshin")) + sshutil.DispatchSSHStdin() hpagent.FollowMaster(agent) } diff --git a/agent/internal/hpagent/handler.go b/agent/internal/hpagent/handler.go index 76868d6..7968d3d 100644 --- a/agent/internal/hpagent/handler.go +++ b/agent/internal/hpagent/handler.go @@ -20,24 +20,10 @@ type RecvMessage struct { } type CborMessage struct { - Op string `cbor:"op"` + Op string `cbor:"op"` Payload cbor.RawMessage `cbor:"payload"` } -type SSHConnect struct { - SessionId string `cbor:"sessionId"` - Username string `cbor:"username"` - Hostname string `cbor:"hostname"` - Port int `cbor:"port"` -} - -type SSHMessage struct { - op string - username string - hostname string - Id string -} - type SendMessage struct { Type string Data any @@ -51,7 +37,7 @@ func FollowMaster(agent *tsnet.TSAgent) { for scanner.Scan() { line := scanner.Bytes() - log.Info("Got bytes delimited by newline"); + log.Info("Got bytes delimited by newline") var msg CborMessage decoder := cbor.NewDecoder(bytes.NewReader(line)) @@ -59,28 +45,20 @@ func FollowMaster(agent *tsnet.TSAgent) { if err != nil { log.Error("Unable to decode message from master: %s", err) - continue; + continue } log.Debug("Received message from master: %s", msg) - var sshPayload SSHConnect + var sshPayload sshutil.SSHConnectPayload err = cbor.Unmarshal(msg.Payload, &sshPayload) if err != nil { log.Error("Unable to unmarshal SSH connect payload: %s", err) - continue; + continue } - log.Info("Opening SSH PTY for session %s to %s@%s:%d", sshPayload.SessionId, sshPayload.Username, sshPayload.Hostname, sshPayload.Port) - sshutil.OpenSshPty(agent, sshutil.SshConnectParams{ - Hostname: sshPayload.Hostname, - Port: sshPayload.Port, - Username: sshPayload.Username, - Id: sshPayload.SessionId, - }) + sshutil.StartWebSSH(agent, sshPayload) } - - // var msg RecvMessage // err := json.Unmarshal(line, &msg) // if err != nil { diff --git a/agent/internal/sshutil/connect.go b/agent/internal/sshutil/connect.go index 3e35aa6..0fee7c7 100644 --- a/agent/internal/sshutil/connect.go +++ b/agent/internal/sshutil/connect.go @@ -3,8 +3,6 @@ package sshutil import ( "context" "errors" - "io" - "os" "strconv" "strings" @@ -13,149 +11,128 @@ import ( "golang.org/x/crypto/ssh" ) -type SshConnectParams struct { - Hostname string - Port int - Username string - Id string +type SSHConnectPayload struct { + SessionId string `cbor:"sessionId"` + Username string `cbor:"username"` + Hostname string `cbor:"hostname"` + Port int `cbor:"port"` } -func dialAndValidateTailscaleSSH(agent *tsnet.TSAgent, params SshConnectParams) (*ssh.Client, error) { +func connectToTailscaleSSH(agent *tsnet.TSAgent, params SSHConnectPayload) (*ssh.Client, error) { log := util.GetLogger() - addr := strings.Join([]string{params.Hostname, ":", strconv.Itoa(params.Port)}, "") - log.Debug("Attempting to dial %s via Tailscale SSH", addr) - conn, err := agent.Dial(context.Background(), "tcp", addr) + log.Debug("Initiating Tailscale SSH connection to %s@%s", params.Username, addr) + tailnetConn, err := agent.Dial(context.Background(), "tcp", addr) if err != nil { - log.Error("Failed to connect to Tailscale SSH: %s", err) return nil, err } - log.Debug("Connected to Tailscale SSH at %s", addr) + log.Debug("Routed connection via tsnet to %s", addr) config := &ssh.ClientConfig{ User: params.Username, + // This isn't a concern because we are only dialing within the Tailnet + // and every device is trusted and *should* be ACL accessible. HostKeyCallback: ssh.InsecureIgnoreHostKey(), } - clientConn, chans, reqs, err := ssh.NewClientConn(conn, addr, config) + conn, chans, reqs, err := ssh.NewClientConn(tailnetConn, addr, config) if err != nil { - log.Error("Failed to create SSH client connection: %s", err) conn.Close() return nil, err } - client := ssh.NewClient(clientConn, chans, reqs) - sVer := string(client.ServerVersion()) + // At this point we have successfully connected to the node + sshClient := ssh.NewClient(conn, chans, reqs) + version := string(sshClient.ServerVersion()) - if !strings.Contains(sVer, "Tailscale") { - log.Error("Connected to non-Tailscale SSH server: %s", sVer) + if !strings.Contains(version, "Tailscale") { conn.Close() - return nil, errors.New("not a Tailscale SSH server") + return nil, errors.New("server is not running Tailscale SSH") } - log.Info("Connected to SSH server running %s at %s", client.ServerVersion(), addr) - return client, nil + log.Info("Connected to %s@%s:%d via Tailscale SSH (%s)", params.Username, params.Hostname, params.Port, version) + return sshClient, nil } -func bindStdinToFd(sess *ssh.Session, fd int) error { - log := util.GetLogger() - - sshIn := os.NewFile(uintptr(fd), "sshInput") - if sshIn == nil { - log.Error("Failed to create file from stdin fd %d", fd) - return errors.New("failed to create file from stdin fd") - } - - stdin, err := sess.StdinPipe() - if err != nil { - log.Error("Failed to get stdin pipe: %s", err) - return err - } - - go io.Copy(stdin, sshIn) // From Node → SSH session - return nil -} - -func bindStdoutToFd(sess *ssh.Session, fd int) error { - log := util.GetLogger() - - sshOut := os.NewFile(uintptr(fd), "sshOutput") - if sshOut == nil { - log.Error("Failed to create file from stdout fd %d", fd) - return errors.New("failed to create file from stdout fd") - } - - stdout, err := sess.StdoutPipe() - if err != nil { - log.Error("Failed to get stdout pipe: %s", err) - return err - } - - go io.Copy(sshOut, stdout) // From SSH → Node - return nil -} - -func OpenSshPty(agent *tsnet.TSAgent, params SshConnectParams) (*ssh.Client, error) { +func StartWebSSH(agent *tsnet.TSAgent, params SSHConnectPayload) { log := util.GetLogger() if agent == nil { - log.Error("Tailscale agent is nil") - return nil, errors.New("tailscale agent is nil") + log.Error("tsnet.TSAgent is not initialized correctly") + return } - if params.Hostname == "" || params.Port <= 0 || params.Username == "" { - log.Error("Invalid SSH connection parameters: %+v", params) - return nil, errors.New("invalid SSH connection parameters") + if params.Hostname == "" || params.Port <= 0 || params.Username == "" || params.SessionId == "" { + log.Error("Invalid SSH connection parameters: %v", params) + return } - client, err := dialAndValidateTailscaleSSH(agent, params) + client, err := connectToTailscaleSSH(agent, params) if err != nil { - log.Error("Failed to open SSH pty: %s", err) - return nil, err + log.Error("Failed to connect to Tailscale SSH for (%s): %s", params.SessionId, err) + return } + // Everything in the func is related to the SSH session. + // Each session runs in its own goroutine, allowing concurrency. go func() { - sess, err := client.NewSession() - if err != nil { - log.Error("Failed to create new SSH session: %s", err) - client.Close() - } - - modes := ssh.TerminalModes{ - ssh.ECHO: 1, // enable echoing - ssh.TTY_OP_ISPEED: 14400, - ssh.TTY_OP_OSPEED: 14400, - } - - if err := sess.RequestPty("xterm-256color", 80, 40, modes); err != nil { - log.Error("Failed to request PTY: %s", err) - client.Close() - } - - ctx := addSession(params.Id, sess) - go func() { - for data := range ctx.InputCh { - if _, err := ctx.Stdin.Write(data); err != nil { - log.Error("Failed to write to SSH stdin: %s", err) - return - } + log.Debug("Creating SSH session for session ID: %s", params.SessionId) + sess, err := client.NewSession() + if err != nil { + log.Error("Failed to create new SSH session: %s", err) + client.Close() + return } - }(); - if err := sess.Shell(); err != nil { - log.Error("Failed to start shell: %s", err) + modes := ssh.TerminalModes{ + ssh.ECHO: 1, + ssh.TTY_OP_ISPEED: 14400, + ssh.TTY_OP_OSPEED: 14400, + } + + // Resize event is possible via the control channel later + err = sess.RequestPty("xterm-256color", 80, 40, modes) + if err != nil { + log.Error("Failed to request PTY for (%s): %s", params.SessionId, err) + return + } + + ctx, err := registerSessionChans(params.SessionId, sess) + if err != nil { + log.Error("Failed to register session channels for (%s): %s", params.SessionId, err) + client.Close() + return + } + + // Input buffer handler + go func() { + for data := range ctx.InputCh { + _, err := ctx.Stdin.Write(data) + if err != nil { + log.Error("Failed to write to SSH stdin: %s", err) + return + } + } + }() + + // This spawns 2 goroutins for stdout and stderr + dispatchSSHStdout(params.SessionId, ctx.Stdout, ctx.Stderr) + + // Spin up a shell and wait for the pty to terminate + err = sess.Shell() + if err != nil { + log.Error("Failed to start shell for (%s): %s", params.SessionId, err) + client.Close() + return + } + + log.Info("Opened an SSH PTY for %s", params.SessionId) + sess.Wait() + sess.Close() client.Close() - } - - log.Info("Successfully opened SSH pty for %s@%s:%d", params.Username, params.Hostname, params.Port) - go streamSSHOutput(params.Id, ctx.Stdout, os.NewFile(4, "sshOutput")) - sess.Wait(); - sess.Close(); - log.Info("SSH session %s closed (goSide)", params.Id) - RemoveSession(params.Id) + log.Info("SSH session for %s closed", params.SessionId) + RemoveSession(params.SessionId) }() - - return client, nil } diff --git a/agent/internal/sshutil/encoder.go b/agent/internal/sshutil/encoder.go index 66d307a..f90e400 100644 --- a/agent/internal/sshutil/encoder.go +++ b/agent/internal/sshutil/encoder.go @@ -5,37 +5,121 @@ import ( "fmt" ) -func encodeFrame(id string, data []byte) ([]byte, error) { - sid := []byte(id) - if len(sid) > 255 { - return nil, fmt.Errorf("session ID too long") +// An SSH frame is used to wrap raw binary data to and from an SSH session +// in order to allow multiplexing connections over a single file descriptor. +// +// In practice, this is how we can easily support multiple SSH connections +// through the 2 file descriptors created by the parent node process. +// +// This is the format of an SSH frame: +// - Magic: The first 4 bytes are HPLS (0x48504C53) to identify the frame. +// - Version Byte: The first byte is the version of the frame format. +// - Channel Type: The second byte indicates the type of channel. +// - Session ID: The next bytes are the length and actual session ID. +// - Payload: The remaining bytes are the payload length and actual data. +// +// +---------+----------+--------------+-------------+----------+ +// | Magic | Version | Channel Type | SID Length | SID | +// | 4 bytes | 1 byte | 1 byte | 1 byte (S) | S bytes | +// +---------+----------+--------------+------------------------+ +// | Payload Length | Payload | +// | 4 bytes (u32, P) | P bytes | +// +--------------------+---------------------------------------+ + +const ( + MagicString = "HPLS" + VersionByte = 1 +) + +type ChannelType int + +const ( + ChannelTypeStdin ChannelType = iota + ChannelTypeStdout + ChannelTypeStderr +) + +type SSHFrame struct { + ChannelType ChannelType + SessionID string + Payload []byte + + Length func() int +} + +type HPLSFrame1 struct{} + +func (t HPLSFrame1) Encode(frame SSHFrame) ([]byte, error) { + frameChan := frame.ChannelType + switch frameChan { + case ChannelTypeStdin, ChannelTypeStdout, ChannelTypeStderr: + default: + return nil, fmt.Errorf("invalid channel type: %d", frameChan) } - payloadLen := len(data) - buf := make([]byte, 1+len(sid)+4+payloadLen) + if len(frame.SessionID) == 0 { + return nil, fmt.Errorf("session ID cannot be empty") + } - buf[0] = byte(len(sid)) // SID length - copy(buf[1:], sid) // SID - binary.BigEndian.PutUint32(buf[1+len(sid):], uint32(payloadLen)) // Payload length - copy(buf[1+len(sid)+4:], data) // Payload + if len(frame.Payload) == 0 { + return nil, fmt.Errorf("payload cannot be empty") + } + sid := []byte(frame.SessionID) + if len(sid) > 255 { + return nil, fmt.Errorf("session ID exceeds 255 byte limit") + } + + if len(frame.Payload) > 0xFFFFFFFF { + return nil, fmt.Errorf("payload exceeds 4GB limit") + } + + frameLen := 4 // Magic + frameLen += 1 // Version byte + frameLen += 1 // Channel type + frameLen += 1 + len(sid) // Session ID length + SID + frameLen += 4 + len(frame.Payload) // Payload length + Payload + + buf := make([]byte, frameLen) + copy(buf[0:4], []byte(MagicString)) + buf[4] = VersionByte + buf[5] = byte(frameChan) + buf[6] = byte(len(sid)) + + offset := 7 + len(sid) + copy(buf[7:offset], sid) + + binary.BigEndian.PutUint32(buf[offset:offset+4], uint32(len(frame.Payload))) + copy(buf[offset+4:], frame.Payload) return buf, nil } -func decodeFrame(buf []byte) (id string, payload []byte, ok bool) { - if len(buf) < 5 { - return "", nil, false +func (t HPLSFrame1) Decode(buf []byte) (SSHFrame, error) { + frame := SSHFrame{} + if len(buf) < 5 || string(buf[0:4]) != MagicString || buf[4] != VersionByte { + return frame, fmt.Errorf("illegal HPLS1 frame format") } - sidLen := int(buf[0]) - if len(buf) < 1+sidLen+4 { - return "", nil, false - } - id = string(buf[1 : 1+sidLen]) - payloadLen := int(binary.BigEndian.Uint32(buf[1+sidLen:])) - if len(buf) < 1+sidLen+4+payloadLen { - return "", nil, false + frame.ChannelType = ChannelType(buf[5]) + if frame.ChannelType < ChannelTypeStdin || frame.ChannelType > ChannelTypeStderr { + return frame, fmt.Errorf("invalid channel type: %d", frame.ChannelType) } - payload = buf[1+sidLen+4 : 1+sidLen+4+payloadLen] - return id, payload, true + + sidLen := int(buf[6]) + if len(buf) < 7+sidLen+4 { + return frame, fmt.Errorf("buffer too short for session ID and payload length") + } + + frame.SessionID = string(buf[7 : 7+sidLen]) + payloadLen := int(binary.BigEndian.Uint32(buf[7+sidLen:])) + if len(buf) < 7+sidLen+4+payloadLen { + return frame, fmt.Errorf("buffer too short for payload") + } + + frame.Payload = buf[7+sidLen+4 : 7+sidLen+4+payloadLen] + frame.Length = func() int { + return 7 + sidLen + 4 + payloadLen + } + + return frame, nil } diff --git a/agent/internal/sshutil/handler.go b/agent/internal/sshutil/handler.go index db2722a..e9b64ff 100644 --- a/agent/internal/sshutil/handler.go +++ b/agent/internal/sshutil/handler.go @@ -2,55 +2,145 @@ package sshutil import ( "io" + "os" "github.com/tale/headplane/agent/internal/util" ) -func StartInputReader(fd3 io.Reader) { - log := util.GetLogger(); +// The file descriptors attached by the parent node process +const ( + InputFd = 3 + OutputFd = 4 +) - log.Info("Starting SSH fd3 input reader") +// DispatchSSHStdin listens for SSH stdin frames on the InputFd file descriptor +// and writes the payload to the appropriate session's stdin. +// +// This function runs in a goroutine in main and is responsible for dispatching +// to ALL connections, not just its own like dispatchSSHStdout does. +func DispatchSSHStdin() { + log := util.GetLogger() + + log.Debug("Opening file descriptor: %d for SSH stdin", InputFd) + fd := os.NewFile(InputFd, "ssh_stdin") + if fd == nil { + log.Error("Failed to open file descriptor %d for SSH stdin", InputFd) + return + } + + log.Info("Listening for SSH stdin on fd %d", InputFd) go func() { buffer := make([]byte, 8192) + hpls1 := HPLSFrame1{} + for { - n, err := fd3.Read(buffer) + // This is the only check where we can detect if the descriptor + // was closed so we can return and exit the goroutine. + bufCount, err := fd.Read(buffer) if err != nil { - log.Error("fd3 read error: %v", err) + log.Error("Failed to read from SSH stdin: %v", err) return } - offset := 0 - for offset < n { - id, payload, ok := decodeFrame(buffer[offset:n]) - if !ok { - break // Wait for more data - } - offset += 1 + len(id) + 4 + len(payload) - sessionsMu.RLock() - sess, ok := sessions[id] - sessionsMu.RUnlock() - if !ok { - log.Error("invalid session id: %s", id) - continue - } - _, err := sess.Stdin.Write(payload) + // Check if we have 0 EOF, which means the descriptor was closed. + if bufCount == 0 { + log.Info("SSH stdin closed, stopping listener") + return + } + + offset := 0 + for offset < bufCount { + frame, err := hpls1.Decode(buffer[offset:bufCount]) if err != nil { - log.Error("failed to write to session stdin: %v", err) + // We need to wait for more data to decode the frame + break + } + + if frame.ChannelType != ChannelTypeStdin { + log.Error("Received invalid channel type: %d, expected %d", frame.ChannelType, ChannelTypeStdin) continue } + + offset += frame.Length() + log.Debug("Received SSH stdin frame: %s", frame.SessionID) + sess, ok := lookupSession(frame.SessionID) + if !ok { + log.Error("Invalid session ID: %s", frame.SessionID) + continue + } + + // Write the payload to the session's stdin + writeCount, err := sess.Stdin.Write(frame.Payload) + if err != nil { + log.Error("Failed to write to session stdin: %v", err) + continue + } + + log.Debug("Wrote %d bytes to session %s stdin", writeCount, frame.SessionID) } } }() } -func streamSSHOutput(id string, r io.Reader, fd4 io.Writer) { +func dispatchSSHStdout(id string, stdout io.Reader, stderr io.Reader) { + log := util.GetLogger() + + log.Debug("Opening file descriptor: %d for SSH stdout", OutputFd) + fd := os.NewFile(OutputFd, "ssh_stdout") + if fd == nil { + log.Error("Failed to open file descriptor %d for SSH stdout", OutputFd) + return + } + + go readerStreamRoutine(StreamRoutine{ + SessionID: id, + Reader: stdout, + Writer: fd, + ChannelType: ChannelTypeStdout, + }) + + go readerStreamRoutine(StreamRoutine{ + SessionID: id, + Reader: stderr, + Writer: fd, + ChannelType: ChannelTypeStderr, + }) +} + +type StreamRoutine struct { + SessionID string + Reader io.Reader + Writer io.Writer + ChannelType ChannelType +} + +func readerStreamRoutine(routine StreamRoutine) { + hpls1 := HPLSFrame1{} buf := make([]byte, 1024) for { - n, err := r.Read(buf) + byteCount, err := routine.Reader.Read(buf) if err != nil { + if err != io.EOF { + util.GetLogger().Error("Failed to read from reader: %v", err) + } + + break + } + + frame, err := hpls1.Encode(SSHFrame{ + ChannelType: routine.ChannelType, + SessionID: routine.SessionID, + Payload: buf[:byteCount], + }) + + if err != nil { + util.GetLogger().Error("Failed to encode frame: %v", err) + continue + } + + if _, err := routine.Writer.Write(frame); err != nil { + util.GetLogger().Error("Failed to write frame to writer: %v", err) break } - frame, _ := encodeFrame(id, buf[:n]) - fd4.Write(frame) } } diff --git a/agent/internal/sshutil/sessions.go b/agent/internal/sshutil/sessions.go index 7c07c39..3bfb04d 100644 --- a/agent/internal/sshutil/sessions.go +++ b/agent/internal/sshutil/sessions.go @@ -1,9 +1,11 @@ package sshutil import ( + "errors" "io" "sync" + "github.com/tale/headplane/agent/internal/util" "golang.org/x/crypto/ssh" ) @@ -12,56 +14,70 @@ type SessionContext struct { Session *ssh.Session Stdin io.WriteCloser Stdout io.Reader + Stderr io.Reader InputCh chan []byte } var sessions = make(map[string]*SessionContext) -var sessionsMu sync.RWMutex +var sessionsLock sync.RWMutex -func addSession(id string, session *ssh.Session) *SessionContext { - sessionsMu.Lock() - defer sessionsMu.Unlock() +func registerSessionChans(id string, session *ssh.Session) (*SessionContext, error) { + log := util.GetLogger() + + sessionsLock.Lock() + defer sessionsLock.Unlock() if _, exists := sessions[id]; exists { - return nil // Session with this ID already exists + return sessions[id], nil } stdin, err := session.StdinPipe() if err != nil { - return nil // Handle error appropriately in production code + return nil, errors.New("failed to create stdin pipe: " + err.Error()) } stdout, err := session.StdoutPipe() if err != nil { - stdin.Close() // Close stdin if stdout pipe creation fails - return nil // Handle error appropriately in production code + stdin.Close() + return nil, errors.New("failed to create stdout pipe: " + err.Error()) } - sessionContext := &SessionContext{ + stderr, err := session.StderrPipe() + if err != nil { + stdin.Close() + return nil, errors.New("failed to create stderr pipe: " + err.Error()) + } + + ctx := &SessionContext{ ID: id, Session: session, Stdin: stdin, Stdout: stdout, + Stderr: stderr, + // Buffered channel to queue input data + InputCh: make(chan []byte, 256), } - sessions[id] = sessionContext - return sessionContext + sessions[id] = ctx + log.Debug("Registered session %s with stdin, stdout, and stderr pipes", id) + return ctx, nil } -func GetSession(id string) *SessionContext { - sessionsMu.RLock() - defer sessionsMu.RUnlock() +func lookupSession(id string) (*SessionContext, bool) { + sessionsLock.RLock() + defer sessionsLock.RUnlock() - return sessions[id] // Returns nil if session does not exist + sessionContext, exists := sessions[id] + return sessionContext, exists } func RemoveSession(id string) { - sessionsMu.Lock() - defer sessionsMu.Unlock() + sessionsLock.Lock() + defer sessionsLock.Unlock() if sessionContext, exists := sessions[id]; exists { - sessionContext.Stdin.Close() // Close the stdin pipe + sessionContext.Stdin.Close() // Close the stdin pipe sessionContext.Session.Close() // Close the SSH session - delete(sessions, id) // Remove from the map + delete(sessions, id) // Remove from the map } } diff --git a/app/server/agent/dispatcher.ts b/app/server/agent/dispatcher.ts index c02d25c..42290bd 100644 --- a/app/server/agent/dispatcher.ts +++ b/app/server/agent/dispatcher.ts @@ -1,5 +1,7 @@ import type { Writable } from 'node:stream'; -import { decode, encode } from 'cbor2'; +import { encode } from 'cborg'; +import { WSContext } from 'hono/ws'; +import { ChannelType } from './encoder'; interface Command { op: string; @@ -25,7 +27,6 @@ export async function dispatchCommand( return new Promise((resolve, reject) => { const encodedCommand = Buffer.concat([encode(command), Buffer.from('\n')]); dispatcher.write(encodedCommand, (err) => { - console.log('Command dispatched:', command, err); if (err) { reject(err); } else { @@ -34,3 +35,31 @@ export async function dispatchCommand( }); }); } + +interface SSHConnectData extends Command { + op: 'ssh_conn_successful'; + payload: { + sessionId: string; + }; +} + +interface SSHConnectFailedData extends Command { + op: 'ssh_conn_failed'; + payload: { + reason: string; + }; +} + +interface SSHFrameData extends Command { + op: 'ssh_frame'; + payload: { + channel: ChannelType; + frame: Buffer; + }; +} + +type WebData = SSHConnectData | SSHConnectFailedData | SSHFrameData; + +export function dispatchWeb(dispatcher: WSContext, data: WebData) { + return dispatcher.send(encode(data)); +} diff --git a/app/server/agent/encoder.ts b/app/server/agent/encoder.ts new file mode 100644 index 0000000..ab7e43b --- /dev/null +++ b/app/server/agent/encoder.ts @@ -0,0 +1,99 @@ +// Refer to agent/internal/sshutil/encoder.go for more details +// This is the Node.js implementation of the SSH encoder +import log from '~/utils/log'; + +const MAGIC = 'HPLS'; +const VERSION = 1; + +// 0 -> Stdin +// 1 -> Stdout +// 2 -> Stderr +export type ChannelType = 0 | 1 | 2; + +interface SSHFrame { + sessionId: string; + channel: ChannelType; + payload: Blob | ArrayBufferLike | string; +} + +export async function encodeSSHFrame(frame: SSHFrame) { + const sid = Buffer.from(frame.sessionId, 'utf8'); + if (sid.length > 255) { + log.error('agent', 'SSH session ID too long: %s', frame.sessionId); + return; + } + + const payload = Buffer.isBuffer(frame.payload) + ? frame.payload + : typeof frame.payload === 'string' + ? Buffer.from(frame.payload, 'utf8') + : frame.payload instanceof Blob + ? Buffer.from(await frame.payload.arrayBuffer()) + : Buffer.from(frame.payload); + + // Size can only hold 4 bytes + if (payload.length > 0xffffffff) { + log.error('agent', 'SSH payload too large: %d bytes', payload.length); + return; + } + + const frameSize = + 4 + // Magic + 1 + // Version + 1 + // Channel Type + (1 + sid.length) + // Session ID length + SID + (4 + payload.length); // Payload length + Payload + + const buf = Buffer.alloc(frameSize); + buf.write(MAGIC, 0, 'utf8'); + buf.writeUInt8(VERSION, 4); + buf.writeUInt8(frame.channel, 5); + buf.writeUInt8(sid.length, 6); + + const offset = 7 + sid.length; + sid.copy(buf, 7); + + buf.writeUInt32BE(payload.length, offset); + payload.copy(buf, offset + 4); + return buf; +} + +export function decodeSSHFrame(data: Buffer) { + if (data.length < 5) { + log.error('agent', 'SSH frame too short: %d bytes', data.length); + return; + } + + const magic = data.toString('utf8', 0, 4); + const version = data.readUInt8(4); + + if (magic !== MAGIC || version !== VERSION) { + log.error('agent', 'Invalid SSH frame magic or version'); + return; + } + + const channel = data.readUInt8(5) as ChannelType; + const sidLength = data.readUInt8(6); + if (data.length < 7 + sidLength + 4) { + log.error('agent', 'SSH frame too short for session ID and payload'); + return; + } + + const sessionId = data.toString('utf8', 7, 7 + sidLength); + const payloadLength = data.readUInt32BE(7 + sidLength); + if (data.length < 7 + sidLength + 4 + payloadLength) { + log.error('agent', 'SSH frame too short for payload'); + return; + } + + const payload = data.subarray( + 7 + sidLength + 4, + 7 + sidLength + 4 + payloadLength, + ); + + return { + sessionId, + channel, + payload, + }; +} diff --git a/app/server/agent/ssh.ts b/app/server/agent/ssh.ts index 0656aa9..3cb1346 100644 --- a/app/server/agent/ssh.ts +++ b/app/server/agent/ssh.ts @@ -4,7 +4,8 @@ import type { Readable, Writable } from 'node:stream'; import { Context } from 'hono'; import { WSContext, WSEvents } from 'hono/ws'; import log from '~/utils/log'; -import { dispatchCommand } from './dispatcher'; +import { dispatchCommand, dispatchWeb } from './dispatcher'; +import { decodeSSHFrame, encodeSSHFrame } from './encoder'; interface SSHConnection { username: string; @@ -19,19 +20,44 @@ interface SSHSession { ws: WSContext; } +interface FrameDecodeSuccess { + id: string; + data: Buffer; +} + +interface FrameDecodeFailure { + id: undefined; + data: undefined; +} + export function createSSHMultiplexer(proc: ChildProcess): SSHMultiplexer { - return new SSHMultiplexer(proc); + const control = proc.stdin; + const sshInput = proc.stdio[3]; + const sshOutput = proc.stdio[4]; + + if (!control || !sshInput || !sshOutput) { + throw new Error('Invalid SSH multiplexer process: missing stdio streams'); + } + + return new SSHMultiplexer( + control, + sshInput as Writable, + sshOutput as Readable, + ); } export class SSHMultiplexer { private connections: Map; - private child: ChildProcess; + private control: Writable; + private sshInput: Writable; + private sshOutput: Readable; - constructor(proc: ChildProcess) { + constructor(control: Writable, sshInput: Writable, sshOutput: Readable) { this.connections = new Map(); - this.child = proc; - - this.handleStdout(); + this.control = control; + this.sshInput = sshInput; + this.sshOutput = sshOutput; + this.configureStdout(); } // TODO: Determine if we want to allow multiple connections for the same @@ -45,8 +71,8 @@ export class SSHMultiplexer { ws, }; - log.info('agent', 'Dispatching SSH connection for %s', sessionId); - await dispatchCommand(this.child.stdin, { + log.debug('agent', 'Dispatching SSH connection for %s', sessionId); + await dispatchCommand(this.control, { op: 'ssh_conn', payload: { sessionId, @@ -76,9 +102,22 @@ export class SSHMultiplexer { try { const sessionId = await this.connect(conn, ws); ws.raw = sessionId; - ws.send(JSON.stringify({ status: 'connected', sessionId })); + dispatchWeb(ws, { + op: 'ssh_conn_successful', + payload: { sessionId }, + }); } catch (error) { - ws.close(1011, `Connection failed: ${error.message}`); + const errorMessage = + error instanceof Error ? error.message : 'Unknown error'; + + dispatchWeb(ws, { + op: 'ssh_conn_failed', + payload: { + reason: errorMessage, + }, + }); + + ws.close(1011, 'Connection failed'); } }, @@ -95,8 +134,13 @@ export class SSHMultiplexer { return; } - const encodedFrame = this.encodeFrame(sessionId, event.data); - this.child.stdio[3]?.write(encodedFrame); + const encodedFrame = await encodeSSHFrame({ + sessionId, + channel: 0, // stdin + payload: event.data, + }); + + this.sshInput.write(encodedFrame); }, onClose: (_, ws) => { @@ -121,65 +165,35 @@ export class SSHMultiplexer { } log.error('agent', 'SSH WebSocket Error with %s', sessionId); - console.log(event); + log.debug('agent', 'Error details: %o', event); }, }; } - private encodeFrame(id: string, data: string | Buffer): Buffer { - const sid = Buffer.from(id, 'utf8'); - const payload = Buffer.isBuffer(data) ? data : Buffer.from(data, 'utf8'); - - // FIX: include +4 for the payload length - const frame = Buffer.alloc(1 + sid.length + 4 + payload.length); - frame.writeUint8(sid.length, 0); // 1 byte for sid length - sid.copy(frame, 1); // SID - frame.writeUint32BE(payload.length, 1 + sid.length); // 4 bytes for payload length - payload.copy(frame, 1 + sid.length + 4); // Payload - - return frame; - } - - private decodeFrame(frame: Buffer): { id: string; data: Buffer } | undefined { - if (frame.length < 5) return; - - const sidLength = frame.readUint8(0); - if (frame.length < 1 + sidLength + 4) return; - - const id = frame.slice(1, 1 + sidLength).toString('utf8'); - const payloadLength = frame.readUint32BE(1 + sidLength); - if (frame.length < 1 + sidLength + 4 + payloadLength) return; - - const data = frame.slice( - 1 + sidLength + 4, - 1 + sidLength + 4 + payloadLength, - ); - - return { id, data }; - } - - private handleStdout() { - const stdout = this.child.stdio[4]; - if (!stdout) { - return; - } - - stdout.on('data', (bytes) => { - console.log(Buffer.from(bytes).toString('utf8')); - const decoded = this.decodeFrame(bytes); - if (!decoded) { + private configureStdout() { + this.sshOutput.on('data', (bytes) => { + const frame = decodeSSHFrame(bytes); + if (!frame) { return; } - const { id, data } = decoded; - console.log(id, data); - const session = this.connections.get(id); + const session = this.connections.get(frame.sessionId); if (!session || !session.connected) { - log.warn('agent', 'Received data for disconnected session %s', id); + log.warn( + 'agent', + 'Received data for invalid SSH session %s', + frame.sessionId, + ); return; } - session.ws.send(data); + dispatchWeb(session.ws, { + op: 'ssh_frame', + payload: { + channel: frame.channel, + data: frame.payload, + }, + }); }); } } diff --git a/app/server/index.ts b/app/server/index.ts index 0bf31c4..f8c15ea 100644 --- a/app/server/index.ts +++ b/app/server/index.ts @@ -1,6 +1,6 @@ import { env, versions } from 'node:process'; +import type { WSEvents } from 'hono/ws'; import { createHonoServer } from 'react-router-hono-server/node'; - import log from '~/utils/log'; import { configureConfig, configureLogger, envVariables } from './config/env'; import { loadIntegration } from './config/integration'; @@ -97,7 +97,25 @@ export default createHonoServer({ app.get( '/_ssh_plexer', upgradeWebSocket((c) => { - return agentManager.multiplexer!.websocketHandler(c); + // MARK: This is a limitation of the hono NPM module we use + const wsHandler = agentManager.multiplexer?.websocketHandler( + c, + ) as WSEvents; + + return { + onOpen: wsHandler + ? wsHandler.onOpen + : (_, ws) => ws.close(1000, 'Multiplexer not available'), + onClose: wsHandler + ? wsHandler.onClose + : (_, ws) => ws.close(1000, 'Multiplexer not available'), + onMessage: wsHandler + ? wsHandler.onMessage + : (_, ws) => ws.close(1000, 'Multiplexer not available'), + onError: wsHandler + ? wsHandler.onError + : (_, ws) => ws.close(1000, 'Multiplexer error'), + }; }), ); }, diff --git a/package.json b/package.json index 7d9bd9c..d256b0d 100644 --- a/package.json +++ b/package.json @@ -29,7 +29,7 @@ "@uiw/codemirror-theme-xcode": "^4.23.12", "@uiw/react-codemirror": "^4.23.12", "arktype": "^2.1.20", - "cbor2": "^2.0.1", + "cborg": "^4.2.11", "clsx": "^2.1.1", "dotenv": "^16.5.0", "isbot": "^5.1.28", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 3546ee5..dc8f2c0 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -64,9 +64,9 @@ importers: arktype: specifier: ^2.1.20 version: 2.1.20 - cbor2: - specifier: ^2.0.1 - version: 2.0.1 + cborg: + specifier: ^4.2.11 + version: 4.2.11 clsx: specifier: ^2.1.1 version: 2.1.1 @@ -412,10 +412,6 @@ packages: '@codemirror/view@6.36.8': resolution: {integrity: sha512-yoRo4f+FdnD01fFt4XpfpMCcCAo9QvZOtbrXExn4SqzH32YC6LgzqxfLZw/r6Ge65xyY03mK/UfUqrVw1gFiFg==} - '@cto.af/wtf8@0.0.2': - resolution: {integrity: sha512-ATm4UQiKrdm5GnU6BvIwUDN+LDEtt23zuzKFpnfDT59ULAd0aMYm/nSFzbSO02garLcXumRC13PzNfa7BsfvSg==} - engines: {node: '>=20'} - '@dnd-kit/accessibility@3.1.1': resolution: {integrity: sha512-2P+YgaXF+gRsIihwwY1gCsQSYnu9Zyj2py8kY5fFvUM1qm2WA2u639R6YNVfU4GWr+ZM5mqEsfHZZLoRONbemw==} peerDependencies: @@ -1702,9 +1698,9 @@ packages: caniuse-lite@1.0.30001718: resolution: {integrity: sha512-AflseV1ahcSunK53NfEs9gFWgOEmzr0f+kaMFA4xiLZlr9Hzt7HxcSpIFcnNCUkz6R6dWKa54rUz3HUmI3nVcw==} - cbor2@2.0.1: - resolution: {integrity: sha512-9bE8+tueGxONyxpttNKkAKKcGVtAPeoSJ64AjVTTjEuBOuRaeeP76EN9BbmQqkz1ZeTP0QPvksNBKwvEutIUzQ==} - engines: {node: '>=20'} + cborg@4.2.11: + resolution: {integrity: sha512-7gs3iaqtsD9OHowgqzc6ixQGwSBONqosVR2co0Bg0pARgrLap+LCcEIXJuuIz2jHy0WWQeDMFPEsU2r17I2XPQ==} + hasBin: true chokidar@4.0.3: resolution: {integrity: sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==} @@ -3152,8 +3148,6 @@ snapshots: style-mod: 4.1.2 w3c-keyname: 2.2.8 - '@cto.af/wtf8@0.0.2': {} - '@dnd-kit/accessibility@3.1.1(react@19.1.0)': dependencies: react: 19.1.0 @@ -4817,9 +4811,7 @@ snapshots: caniuse-lite@1.0.30001718: {} - cbor2@2.0.1: - dependencies: - '@cto.af/wtf8': 0.0.2 + cborg@4.2.11: {} chokidar@4.0.3: dependencies: From cb3263793898493525b0241aa6133f1ad30f259d Mon Sep 17 00:00:00 2001 From: Aarnav Tale Date: Sat, 31 May 2025 12:56:20 -0400 Subject: [PATCH 03/97] feat: add xterm frontend ui --- agent/internal/hpagent/handler.go | 30 +++++-- agent/internal/sshutil/connect.go | 28 ++++++ app/routes.ts | 1 + app/routes/ssh/overview.tsx | 145 ++++++++++++++++++++++++++++++ app/routes/ssh/xterm.client.tsx | 91 +++++++++++++++++++ app/server/agent/dispatcher.ts | 17 ++-- app/server/agent/ssh.ts | 20 ++++- package.json | 1 + pnpm-lock.yaml | 8 ++ 9 files changed, 325 insertions(+), 16 deletions(-) create mode 100644 app/routes/ssh/overview.tsx create mode 100644 app/routes/ssh/xterm.client.tsx diff --git a/agent/internal/hpagent/handler.go b/agent/internal/hpagent/handler.go index 7968d3d..0b54785 100644 --- a/agent/internal/hpagent/handler.go +++ b/agent/internal/hpagent/handler.go @@ -37,7 +37,6 @@ func FollowMaster(agent *tsnet.TSAgent) { for scanner.Scan() { line := scanner.Bytes() - log.Info("Got bytes delimited by newline") var msg CborMessage decoder := cbor.NewDecoder(bytes.NewReader(line)) @@ -49,14 +48,29 @@ func FollowMaster(agent *tsnet.TSAgent) { } log.Debug("Received message from master: %s", msg) - var sshPayload sshutil.SSHConnectPayload - err = cbor.Unmarshal(msg.Payload, &sshPayload) - if err != nil { - log.Error("Unable to unmarshal SSH connect payload: %s", err) - continue - } + switch msg.Op { + case "ssh_conn": + var sshPayload sshutil.SSHConnectPayload + err = cbor.Unmarshal(msg.Payload, &sshPayload) + if err != nil { + log.Error("Unable to unmarshal SSH connect payload: %s", err) + continue + } - sshutil.StartWebSSH(agent, sshPayload) + sshutil.StartWebSSH(agent, sshPayload) + continue + + case "ssh_term": + var sshPayload sshutil.SSHClosePayload + err = cbor.Unmarshal(msg.Payload, &sshPayload) + if err != nil { + log.Error("Unable to unmarshal SSH close payload: %s", err) + continue + } + + sshutil.CloseWebSSH(agent, sshPayload) + continue + } } // var msg RecvMessage diff --git a/agent/internal/sshutil/connect.go b/agent/internal/sshutil/connect.go index 0fee7c7..1aa45b1 100644 --- a/agent/internal/sshutil/connect.go +++ b/agent/internal/sshutil/connect.go @@ -18,6 +18,10 @@ type SSHConnectPayload struct { Port int `cbor:"port"` } +type SSHClosePayload struct { + SessionId string `cbor:"sessionId"` +} + func connectToTailscaleSSH(agent *tsnet.TSAgent, params SSHConnectPayload) (*ssh.Client, error) { log := util.GetLogger() addr := strings.Join([]string{params.Hostname, ":", strconv.Itoa(params.Port)}, "") @@ -136,3 +140,27 @@ func StartWebSSH(agent *tsnet.TSAgent, params SSHConnectPayload) { RemoveSession(params.SessionId) }() } + +func CloseWebSSH(agent *tsnet.TSAgent, params SSHClosePayload) { + log := util.GetLogger() + + if agent == nil { + log.Error("tsnet.TSAgent is not initialized correctly") + return + } + + if params.SessionId == "" { + log.Error("Invalid SSH close parameters: %v", params) + return + } + + log.Debug("Closing SSH session for session ID: %s", params.SessionId) + ctx, ok := lookupSession(params.SessionId) + if !ok { + log.Info("No active SSH session found for session ID: %s", params.SessionId) + return + } + + RemoveSession(ctx.ID) + log.Info("SSH session for %s closed", params.SessionId) +} diff --git a/app/routes.ts b/app/routes.ts index b921718..9a80c50 100644 --- a/app/routes.ts +++ b/app/routes.ts @@ -25,6 +25,7 @@ export default [ route('/users', 'routes/users/overview.tsx'), route('/acls', 'routes/acls/overview.tsx'), route('/dns', 'routes/dns/overview.tsx'), + route('/ssh', 'routes/ssh/overview.tsx'), ...prefix('/settings', [ index('routes/settings/overview.tsx'), diff --git a/app/routes/ssh/overview.tsx b/app/routes/ssh/overview.tsx new file mode 100644 index 0000000..3cab350 --- /dev/null +++ b/app/routes/ssh/overview.tsx @@ -0,0 +1,145 @@ +import { decode } from 'cborg'; +import { Loader2 } from 'lucide-react'; +import { useEffect, useRef, useState } from 'react'; +import { LoaderFunctionArgs, data, useLoaderData } from 'react-router'; +import { ClientOnly } from 'remix-utils/client-only'; +import { + Command, + SSHConnectData, + SSHConnectFailedData, +} from '~/server/agent/dispatcher'; +import { useLiveData } from '~/utils/live-data'; +import toast from '~/utils/toast'; +import XTerm from './xterm.client'; + +export async function loader({ request }: LoaderFunctionArgs) { + const qp = new URL(request.url).searchParams; + const username = qp.get('username') || undefined; + const hostname = qp.get('hostname') || undefined; + const port = qp.get('port') + ? Number.parseInt(qp.get('port')!, 10) + : undefined; + + if (!username || !hostname || !port) { + throw data('Missing required parameters: username, hostname, port', 400); + } + + const baseUrl = new URL(request.url).origin; + const wsUrl = new URL('/_ssh_plexer', baseUrl); + wsUrl.protocol = wsUrl.protocol === 'https:' ? 'wss:' : 'ws:'; + wsUrl.searchParams.set('username', username); + wsUrl.searchParams.set('hostname', hostname); + wsUrl.searchParams.set('port', port.toString()); + + return { + socketUrl: wsUrl.toString(), + }; +} +type SessionStatus = 'loading' | 'connected' | 'error'; + +export default function Page() { + const { pause } = useLiveData(); + const { socketUrl } = useLoaderData(); + const [socket, setSocket] = useState(null); + const [status, setStatus] = useState('loading'); + const [sessionId, setSessionId] = useState(null); + + const queue = useRef>([]); + const validated = useRef(false); + + useEffect(() => { + // SSH connections should not use stale while revalidate logic. + pause(); + + const ws = new WebSocket(socketUrl); + ws.binaryType = 'arraybuffer'; + + ws.onopen = () => { + setSocket(ws); + setStatus('loading'); + }; + + // We need to wait for the WebSocket to open and respond with the + // connection ID. Without a session ID, we do not have a mux. + const messageHandler = (event: MessageEvent) => { + if (!(event.data instanceof ArrayBuffer)) { + toast('Invalid message received from server'); + return; + } + + const data = new Uint8Array(event.data); + const obj = decode(data) as Command; + + if (obj.op === 'ssh_conn_successful') { + const data = obj as SSHConnectData; + if (!validated.current) { + validated.current = true; + toast( + `SSH connection established with session ID: ${data.payload.sessionId}`, + ); + + setStatus('connected'); + setSessionId(data.payload.sessionId); + } + + return; + } + + if (obj.op === 'ssh_conn_failed') { + const data = obj as SSHConnectFailedData; + if (!validated.current) { + validated.current = true; + toast(`SSH connection failed: ${data.payload.reason}`); + setStatus('error'); + } + + return; + } + + if (obj.op === 'ssh_frame') { + queue.current.push(new Uint8Array(event.data)); + return; + } + }; + + ws.addEventListener('message', messageHandler); + + ws.onerror = (error) => { + setStatus('error'); + toast(`WebSocket error: ${error}`); + }; + + ws.onclose = () => { + if (status !== 'error') { + toast('SSH connection closed'); + } + + setSocket(null); + setStatus('error'); + }; + + return () => { + ws.removeEventListener('message', messageHandler); + ws.close(); + }; + }, [socketUrl]); + + if (socket === null || !sessionId || status === 'loading') { + return ( + + ); + } + + return ( +
+

Session ID: {sessionId}

+ {queue.current.length > 0 && ( +

+ {queue.current.length} frames queued +

+ )} + + +
+ ); +} diff --git a/app/routes/ssh/xterm.client.tsx b/app/routes/ssh/xterm.client.tsx new file mode 100644 index 0000000..9e2f471 --- /dev/null +++ b/app/routes/ssh/xterm.client.tsx @@ -0,0 +1,91 @@ +import * as xterm from '@xterm/xterm'; +import { useEffect, useRef } from 'react'; +import '@xterm/xterm/css/xterm.css'; +import { decode } from 'cborg'; +import type { SSHFrameData } from '~/server/agent/dispatcher'; +import { useLiveData } from '~/utils/live-data'; + +interface XTermProps { + ws: WebSocket; + sessionId: string; + queue: Array; +} + +const RED = new TextEncoder().encode('\x1b[31m'); +const RESET = new TextEncoder().encode('\x1b[0m'); + +export default function XTerm({ ws, sessionId, queue }: XTermProps) { + const { pause } = useLiveData(); + + const container = useRef(null); + const term = useRef(null); + + useEffect(() => { + pause(); + + const terminal = new xterm.Terminal({ + convertEol: true, + fontSize: 14, + theme: { + background: '#1e1e1e', + foreground: '#ffffff', + }, + }); + + terminal.open(container.current!); + terminal.focus(); + term.current = terminal; + + const handleFrame = (data: Uint8Array) => { + try { + const frame: SSHFrameData = decode(data); + if (frame.op !== 'ssh_frame') { + console.warn('Received unexpected frame type:', frame.op); + return; + } + + // If this is stderr, color it red + if (frame.payload.channel === 2) { + terminal.write( + new Uint8Array([...RED, ...frame.payload.frame, ...RESET]), + ); + } else { + terminal.write(frame.payload.frame); + } + } catch (err) { + console.error('Failed to decode CBOR frame:', err); + } + }; + + for (const buffer of queue) { + handleFrame(buffer); + } + + terminal.onData((input) => { + if (ws.readyState === WebSocket.OPEN) { + ws.send(input); + } else { + console.warn('WebSocket is not open, cannot send data'); + } + }); + + const onMessage = (event: MessageEvent) => { + if (!(event.data instanceof ArrayBuffer)) { + console.warn('Received non-binary message from WebSocket'); + return; + } + + const data = new Uint8Array(event.data); + handleFrame(data); + }; + + ws.addEventListener('message', onMessage); + + return () => { + ws.removeEventListener('message', onMessage); + term.current?.dispose(); + }; + }, [ws, queue]); + + return
; +} diff --git a/app/server/agent/dispatcher.ts b/app/server/agent/dispatcher.ts index 42290bd..e9e52cd 100644 --- a/app/server/agent/dispatcher.ts +++ b/app/server/agent/dispatcher.ts @@ -3,7 +3,7 @@ import { encode } from 'cborg'; import { WSContext } from 'hono/ws'; import { ChannelType } from './encoder'; -interface Command { +export interface Command { op: string; payload: unknown; } @@ -18,7 +18,14 @@ interface SSHConnectCommand extends Command { }; } -type AgentCommand = SSHConnectCommand; +interface SSHCloseCommand extends Command { + op: 'ssh_term'; + payload: { + sessionId: string; + }; +} + +type AgentCommand = SSHConnectCommand | SSHCloseCommand; export async function dispatchCommand( dispatcher: Writable, @@ -36,21 +43,21 @@ export async function dispatchCommand( }); } -interface SSHConnectData extends Command { +export interface SSHConnectData extends Command { op: 'ssh_conn_successful'; payload: { sessionId: string; }; } -interface SSHConnectFailedData extends Command { +export interface SSHConnectFailedData extends Command { op: 'ssh_conn_failed'; payload: { reason: string; }; } -interface SSHFrameData extends Command { +export interface SSHFrameData extends Command { op: 'ssh_frame'; payload: { channel: ChannelType; diff --git a/app/server/agent/ssh.ts b/app/server/agent/ssh.ts index 3cb1346..e6bdc08 100644 --- a/app/server/agent/ssh.ts +++ b/app/server/agent/ssh.ts @@ -143,22 +143,36 @@ export class SSHMultiplexer { this.sshInput.write(encodedFrame); }, - onClose: (_, ws) => { + onClose: async (_, ws) => { const sessionId = ws.raw; if (sessionId && this.connections.has(sessionId)) { const session = this.connections.get(sessionId); if (session) { + await dispatchCommand(this.control, { + op: 'ssh_term', + payload: { + sessionId, + }, + }); + session.connected = false; this.connections.delete(sessionId); } } }, - onError: (event, ws) => { + onError: async (event, ws) => { const sessionId = ws.raw; if (sessionId && this.connections.has(sessionId)) { const session = this.connections.get(sessionId); if (session) { + await dispatchCommand(this.control, { + op: 'ssh_term', + payload: { + sessionId, + }, + }); + session.connected = false; this.connections.delete(sessionId); } @@ -191,7 +205,7 @@ export class SSHMultiplexer { op: 'ssh_frame', payload: { channel: frame.channel, - data: frame.payload, + frame: frame.payload, }, }); }); diff --git a/package.json b/package.json index d256b0d..52ab534 100644 --- a/package.json +++ b/package.json @@ -28,6 +28,7 @@ "@uiw/codemirror-theme-github": "^4.23.12", "@uiw/codemirror-theme-xcode": "^4.23.12", "@uiw/react-codemirror": "^4.23.12", + "@xterm/xterm": "^5.5.0", "arktype": "^2.1.20", "cborg": "^4.2.11", "clsx": "^2.1.1", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index dc8f2c0..f7a9b14 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -61,6 +61,9 @@ importers: '@uiw/react-codemirror': specifier: ^4.23.12 version: 4.23.12(@babel/runtime@7.27.3)(@codemirror/autocomplete@6.18.2(@codemirror/language@6.11.0)(@codemirror/state@6.5.2)(@codemirror/view@6.36.8)(@lezer/common@1.2.3))(@codemirror/language@6.11.0)(@codemirror/lint@6.8.2)(@codemirror/search@6.5.7)(@codemirror/state@6.5.2)(@codemirror/theme-one-dark@6.1.2)(@codemirror/view@6.36.8)(codemirror@6.0.1(@lezer/common@1.2.3))(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@xterm/xterm': + specifier: ^5.5.0 + version: 5.5.0 arktype: specifier: ^2.1.20 version: 2.1.20 @@ -1591,6 +1594,9 @@ packages: react: '>=16.8.0' react-dom: '>=16.8.0' + '@xterm/xterm@5.5.0': + resolution: {integrity: sha512-hqJHYaQb5OptNunnyAnkHyM8aCjZ1MEIDTQu1iIbbTD/xops91NB5yq1ZK/dC2JDbVWtF23zUtl9JE2NqwT87A==} + acorn@8.14.1: resolution: {integrity: sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg==} engines: {node: '>=0.4.0'} @@ -4713,6 +4719,8 @@ snapshots: - '@codemirror/lint' - '@codemirror/search' + '@xterm/xterm@5.5.0': {} + acorn@8.14.1: optional: true From a0a80852eb2b6a4d0c286f9786982e7f562878a2 Mon Sep 17 00:00:00 2001 From: Aarnav Tale Date: Sat, 31 May 2025 12:57:18 -0400 Subject: [PATCH 04/97] fix: connect to stdout fd after starting an ssh shell this avoids a race condition that would happen if the goroutine tried to read a file descriptor that wasn't open properly and would lose frame delivery --- agent/internal/sshutil/connect.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/agent/internal/sshutil/connect.go b/agent/internal/sshutil/connect.go index 1aa45b1..6ac8c2d 100644 --- a/agent/internal/sshutil/connect.go +++ b/agent/internal/sshutil/connect.go @@ -120,9 +120,6 @@ func StartWebSSH(agent *tsnet.TSAgent, params SSHConnectPayload) { } }() - // This spawns 2 goroutins for stdout and stderr - dispatchSSHStdout(params.SessionId, ctx.Stdout, ctx.Stderr) - // Spin up a shell and wait for the pty to terminate err = sess.Shell() if err != nil { @@ -131,6 +128,9 @@ func StartWebSSH(agent *tsnet.TSAgent, params SSHConnectPayload) { return } + // This spawns 2 goroutins for stdout and stderr + dispatchSSHStdout(params.SessionId, ctx.Stdout, ctx.Stderr) + log.Info("Opened an SSH PTY for %s", params.SessionId) sess.Wait() sess.Close() From ccde3513dc3751704dc475db819a1b9c7749aeee Mon Sep 17 00:00:00 2001 From: Aarnav Tale Date: Sat, 31 May 2025 16:41:07 -0400 Subject: [PATCH 05/97] feat: support resizing and other xterm.js addons --- agent/internal/hpagent/handler.go | 11 ++++ agent/internal/sshutil/connect.go | 37 +++++++++++- app/routes/ssh/xterm.client.tsx | 94 +++++++++++++++++++++++++++++-- app/server/agent/dispatcher.ts | 23 +++++++- app/server/agent/encoder.ts | 20 ++----- app/server/agent/ssh.ts | 56 +++++++++++++++--- package.json | 5 ++ pnpm-lock.yaml | 66 ++++++++++++++++++++++ 8 files changed, 282 insertions(+), 30 deletions(-) diff --git a/agent/internal/hpagent/handler.go b/agent/internal/hpagent/handler.go index 0b54785..61667d6 100644 --- a/agent/internal/hpagent/handler.go +++ b/agent/internal/hpagent/handler.go @@ -70,6 +70,17 @@ func FollowMaster(agent *tsnet.TSAgent) { sshutil.CloseWebSSH(agent, sshPayload) continue + + case "ssh_resize": + var sshPayload sshutil.SSHResizePayload + err = cbor.Unmarshal(msg.Payload, &sshPayload) + if err != nil { + log.Error("Unable to unmarshal SSH resize payload: %s", err) + continue + } + + sshutil.ResizeWebSSH(agent, sshPayload) + continue } } diff --git a/agent/internal/sshutil/connect.go b/agent/internal/sshutil/connect.go index 6ac8c2d..c4e23cf 100644 --- a/agent/internal/sshutil/connect.go +++ b/agent/internal/sshutil/connect.go @@ -22,6 +22,12 @@ type SSHClosePayload struct { SessionId string `cbor:"sessionId"` } +type SSHResizePayload struct { + SessionId string `cbor:"sessionId"` + Width int `cbor:"width"` + Height int `cbor:"height"` +} + func connectToTailscaleSSH(agent *tsnet.TSAgent, params SSHConnectPayload) (*ssh.Client, error) { log := util.GetLogger() addr := strings.Join([]string{params.Hostname, ":", strconv.Itoa(params.Port)}, "") @@ -96,7 +102,7 @@ func StartWebSSH(agent *tsnet.TSAgent, params SSHConnectPayload) { } // Resize event is possible via the control channel later - err = sess.RequestPty("xterm-256color", 80, 40, modes) + err = sess.RequestPty("xterm-256color", 24, 80, modes) if err != nil { log.Error("Failed to request PTY for (%s): %s", params.SessionId, err) return @@ -164,3 +170,32 @@ func CloseWebSSH(agent *tsnet.TSAgent, params SSHClosePayload) { RemoveSession(ctx.ID) log.Info("SSH session for %s closed", params.SessionId) } + +func ResizeWebSSH(agent *tsnet.TSAgent, params SSHResizePayload) { + log := util.GetLogger() + + if agent == nil { + log.Error("tsnet.TSAgent is not initialized correctly") + return + } + + if params.SessionId == "" || params.Width <= 0 || params.Height <= 0 { + log.Error("Invalid SSH resize parameters: %v", params) + return + } + + log.Debug("Resizing SSH session for session ID: %s to %dx%d", params.SessionId, params.Width, params.Height) + ctx, ok := lookupSession(params.SessionId) + if !ok { + log.Info("No active SSH session found for session ID: %s", params.SessionId) + return + } + + err := ctx.Session.WindowChange(params.Height, params.Width) + if err != nil { + log.Error("Failed to resize SSH session for (%s): %s", params.SessionId, err) + return + } + + log.Info("Resized SSH session for %s to %dx%d", params.SessionId, params.Width, params.Height) +} diff --git a/app/routes/ssh/xterm.client.tsx b/app/routes/ssh/xterm.client.tsx index 9e2f471..1fb8d99 100644 --- a/app/routes/ssh/xterm.client.tsx +++ b/app/routes/ssh/xterm.client.tsx @@ -1,8 +1,18 @@ +import { ClipboardAddon } from '@xterm/addon-clipboard'; +import { FitAddon } from '@xterm/addon-fit'; +import { Unicode11Addon } from '@xterm/addon-unicode11'; +import { WebLinksAddon } from '@xterm/addon-web-links'; +import { WebglAddon } from '@xterm/addon-webgl'; import * as xterm from '@xterm/xterm'; -import { useEffect, useRef } from 'react'; +import { useEffect, useRef, useState } from 'react'; import '@xterm/xterm/css/xterm.css'; -import { decode } from 'cborg'; -import type { SSHFrameData } from '~/server/agent/dispatcher'; +import { decode, encode } from 'cborg'; +import type { + SSHDataCommand, + SSHFrameData, + SSHResizeCommand, +} from '~/server/agent/dispatcher'; +import cn from '~/utils/cn'; import { useLiveData } from '~/utils/live-data'; interface XTermProps { @@ -19,19 +29,40 @@ export default function XTerm({ ws, sessionId, queue }: XTermProps) { const container = useRef(null); const term = useRef(null); + const [isResizing, setIsResizing] = useState(false); useEffect(() => { pause(); const terminal = new xterm.Terminal({ + allowProposedApi: true, + cursorBlink: true, convertEol: true, fontSize: 14, + cols: 80, + rows: 24, theme: { background: '#1e1e1e', foreground: '#ffffff', }, }); + terminal.loadAddon(new Unicode11Addon()); + terminal.loadAddon(new ClipboardAddon()); + terminal.loadAddon(new WebLinksAddon()); + terminal.unicode.activeVersion = '11'; + + const gl = new WebglAddon(); + terminal.loadAddon(gl); + + const fit = new FitAddon(); + terminal.loadAddon(fit); + + gl.onContextLoss(() => { + console.warn('WebGL context lost, falling back to canvas rendering'); + gl.dispose(); + }); + terminal.open(container.current!); terminal.focus(); term.current = terminal; @@ -63,7 +94,15 @@ export default function XTerm({ ws, sessionId, queue }: XTermProps) { terminal.onData((input) => { if (ws.readyState === WebSocket.OPEN) { - ws.send(input); + ws.send( + encode({ + op: 'ssh_data', + payload: { + sessionId, + data: new TextEncoder().encode(input), + }, + } satisfies SSHDataCommand), + ); } else { console.warn('WebSocket is not open, cannot send data'); } @@ -80,12 +119,57 @@ export default function XTerm({ ws, sessionId, queue }: XTermProps) { }; ws.addEventListener('message', onMessage); + const ro = new ResizeObserver(() => { + const before = { + cols: terminal.cols, + rows: terminal.rows, + }; + fit.fit(); + if (before.cols !== terminal.cols || before.rows !== terminal.rows) { + console.log( + `Resized terminal to ${terminal.cols} cols and ${terminal.rows} rows`, + ); + ws.send( + encode({ + op: 'ssh_resize', + payload: { + sessionId, + width: terminal.cols, + height: terminal.rows, + }, + } satisfies SSHResizeCommand), + ); + + setIsResizing(true); + setTimeout(() => { + setIsResizing(false); + }, 1000); + } + }); + + ro.observe(container.current!); return () => { ws.removeEventListener('message', onMessage); term.current?.dispose(); + ro.disconnect(); }; }, [ws, queue]); - return
; + return ( +
+
+ + {term.current && isResizing ? ( +
+ {term.current.cols}x{term.current.rows} +
+ ) : undefined} +
+ ); } diff --git a/app/server/agent/dispatcher.ts b/app/server/agent/dispatcher.ts index e9e52cd..87585c3 100644 --- a/app/server/agent/dispatcher.ts +++ b/app/server/agent/dispatcher.ts @@ -8,7 +8,7 @@ export interface Command { payload: unknown; } -interface SSHConnectCommand extends Command { +export interface SSHConnectCommand extends Command { op: 'ssh_conn'; payload: { sessionId: string; @@ -18,14 +18,31 @@ interface SSHConnectCommand extends Command { }; } -interface SSHCloseCommand extends Command { +export interface SSHCloseCommand extends Command { op: 'ssh_term'; payload: { sessionId: string; }; } -type AgentCommand = SSHConnectCommand | SSHCloseCommand; +export interface SSHResizeCommand extends Command { + op: 'ssh_resize'; + payload: { + sessionId: string; + width: number; + height: number; + }; +} + +export interface SSHDataCommand extends Command { + op: 'ssh_data'; + payload: { + sessionId: string; + data: Uint8Array; + }; +} + +type AgentCommand = SSHConnectCommand | SSHCloseCommand | SSHResizeCommand; export async function dispatchCommand( dispatcher: Writable, diff --git a/app/server/agent/encoder.ts b/app/server/agent/encoder.ts index ab7e43b..73ac667 100644 --- a/app/server/agent/encoder.ts +++ b/app/server/agent/encoder.ts @@ -13,7 +13,7 @@ export type ChannelType = 0 | 1 | 2; interface SSHFrame { sessionId: string; channel: ChannelType; - payload: Blob | ArrayBufferLike | string; + payload: Buffer; } export async function encodeSSHFrame(frame: SSHFrame) { @@ -23,17 +23,9 @@ export async function encodeSSHFrame(frame: SSHFrame) { return; } - const payload = Buffer.isBuffer(frame.payload) - ? frame.payload - : typeof frame.payload === 'string' - ? Buffer.from(frame.payload, 'utf8') - : frame.payload instanceof Blob - ? Buffer.from(await frame.payload.arrayBuffer()) - : Buffer.from(frame.payload); - // Size can only hold 4 bytes - if (payload.length > 0xffffffff) { - log.error('agent', 'SSH payload too large: %d bytes', payload.length); + if (frame.payload.length > 0xffffffff) { + log.error('agent', 'SSH payload too large: %d bytes', frame.payload.length); return; } @@ -42,7 +34,7 @@ export async function encodeSSHFrame(frame: SSHFrame) { 1 + // Version 1 + // Channel Type (1 + sid.length) + // Session ID length + SID - (4 + payload.length); // Payload length + Payload + (4 + frame.payload.length); // Payload length + Payload const buf = Buffer.alloc(frameSize); buf.write(MAGIC, 0, 'utf8'); @@ -53,8 +45,8 @@ export async function encodeSSHFrame(frame: SSHFrame) { const offset = 7 + sid.length; sid.copy(buf, 7); - buf.writeUInt32BE(payload.length, offset); - payload.copy(buf, offset + 4); + buf.writeUInt32BE(frame.payload.length, offset); + frame.payload.copy(buf, offset + 4); return buf; } diff --git a/app/server/agent/ssh.ts b/app/server/agent/ssh.ts index e6bdc08..9777232 100644 --- a/app/server/agent/ssh.ts +++ b/app/server/agent/ssh.ts @@ -1,10 +1,17 @@ import { ChildProcess } from 'node:child_process'; import { randomUUID } from 'node:crypto'; import type { Readable, Writable } from 'node:stream'; +import { decode } from 'cborg'; import { Context } from 'hono'; import { WSContext, WSEvents } from 'hono/ws'; import log from '~/utils/log'; -import { dispatchCommand, dispatchWeb } from './dispatcher'; +import { + Command, + SSHDataCommand, + SSHResizeCommand, + dispatchCommand, + dispatchWeb, +} from './dispatcher'; import { decodeSSHFrame, encodeSSHFrame } from './encoder'; interface SSHConnection { @@ -134,13 +141,48 @@ export class SSHMultiplexer { return; } - const encodedFrame = await encodeSSHFrame({ - sessionId, - channel: 0, // stdin - payload: event.data, - }); + const wsData = Buffer.isBuffer(event.data) + ? event.data + : typeof event.data === 'string' + ? Buffer.from(event.data, 'utf8') + : event.data instanceof Blob + ? Buffer.from(await event.data.arrayBuffer()) + : Buffer.from(event.data); - this.sshInput.write(encodedFrame); + const obj = decode(wsData) as Command; + if (obj.op === 'ssh_data') { + const data = obj as SSHDataCommand; + if (data.payload.sessionId !== sessionId) { + log.warn( + 'agent', + 'Received data for mismatched SSH session %s', + data.payload.sessionId, + ); + return; + } + + const encodedFrame = await encodeSSHFrame({ + sessionId, + channel: 0, // stdin + payload: Buffer.from(data.payload.data), + }); + + this.sshInput.write(encodedFrame); + } + + if (obj.op === 'ssh_resize') { + const resize = obj as SSHResizeCommand; + if (resize.payload.sessionId !== sessionId) { + log.warn( + 'agent', + 'Received resize for mismatched SSH session %s', + resize.payload.sessionId, + ); + return; + } + + await dispatchCommand(this.control, resize); + } }, onClose: async (_, ws) => { diff --git a/package.json b/package.json index 52ab534..a706153 100644 --- a/package.json +++ b/package.json @@ -28,6 +28,11 @@ "@uiw/codemirror-theme-github": "^4.23.12", "@uiw/codemirror-theme-xcode": "^4.23.12", "@uiw/react-codemirror": "^4.23.12", + "@xterm/addon-clipboard": "^0.1.0", + "@xterm/addon-fit": "^0.10.0", + "@xterm/addon-unicode11": "^0.8.0", + "@xterm/addon-web-links": "^0.11.0", + "@xterm/addon-webgl": "^0.18.0", "@xterm/xterm": "^5.5.0", "arktype": "^2.1.20", "cborg": "^4.2.11", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index f7a9b14..629e533 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -61,6 +61,21 @@ importers: '@uiw/react-codemirror': specifier: ^4.23.12 version: 4.23.12(@babel/runtime@7.27.3)(@codemirror/autocomplete@6.18.2(@codemirror/language@6.11.0)(@codemirror/state@6.5.2)(@codemirror/view@6.36.8)(@lezer/common@1.2.3))(@codemirror/language@6.11.0)(@codemirror/lint@6.8.2)(@codemirror/search@6.5.7)(@codemirror/state@6.5.2)(@codemirror/theme-one-dark@6.1.2)(@codemirror/view@6.36.8)(codemirror@6.0.1(@lezer/common@1.2.3))(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@xterm/addon-clipboard': + specifier: ^0.1.0 + version: 0.1.0(@xterm/xterm@5.5.0) + '@xterm/addon-fit': + specifier: ^0.10.0 + version: 0.10.0(@xterm/xterm@5.5.0) + '@xterm/addon-unicode11': + specifier: ^0.8.0 + version: 0.8.0(@xterm/xterm@5.5.0) + '@xterm/addon-web-links': + specifier: ^0.11.0 + version: 0.11.0(@xterm/xterm@5.5.0) + '@xterm/addon-webgl': + specifier: ^0.18.0 + version: 0.18.0(@xterm/xterm@5.5.0) '@xterm/xterm': specifier: ^5.5.0 version: 5.5.0 @@ -1594,6 +1609,31 @@ packages: react: '>=16.8.0' react-dom: '>=16.8.0' + '@xterm/addon-clipboard@0.1.0': + resolution: {integrity: sha512-zdoM7p53T5sv/HbRTyp4hY0kKmEQ3MZvAvEtiXqNIHc/JdpqwByCtsTaQF5DX2n4hYdXRPO4P/eOS0QEhX1nPw==} + peerDependencies: + '@xterm/xterm': ^5.4.0 + + '@xterm/addon-fit@0.10.0': + resolution: {integrity: sha512-UFYkDm4HUahf2lnEyHvio51TNGiLK66mqP2JoATy7hRZeXaGMRDr00JiSF7m63vR5WKATF605yEggJKsw0JpMQ==} + peerDependencies: + '@xterm/xterm': ^5.0.0 + + '@xterm/addon-unicode11@0.8.0': + resolution: {integrity: sha512-LxinXu8SC4OmVa6FhgwsVCBZbr8WoSGzBl2+vqe8WcQ6hb1r6Gj9P99qTNdPiFPh4Ceiu2pC8xukZ6+2nnh49Q==} + peerDependencies: + '@xterm/xterm': ^5.0.0 + + '@xterm/addon-web-links@0.11.0': + resolution: {integrity: sha512-nIHQ38pQI+a5kXnRaTgwqSHnX7KE6+4SVoceompgHL26unAxdfP6IPqUTSYPQgSwM56hsElfoNrrW5V7BUED/Q==} + peerDependencies: + '@xterm/xterm': ^5.0.0 + + '@xterm/addon-webgl@0.18.0': + resolution: {integrity: sha512-xCnfMBTI+/HKPdRnSOHaJDRqEpq2Ugy8LEj9GiY4J3zJObo3joylIFaMvzBwbYRg8zLtkO0KQaStCeSfoaI2/w==} + peerDependencies: + '@xterm/xterm': ^5.0.0 + '@xterm/xterm@5.5.0': resolution: {integrity: sha512-hqJHYaQb5OptNunnyAnkHyM8aCjZ1MEIDTQu1iIbbTD/xops91NB5yq1ZK/dC2JDbVWtF23zUtl9JE2NqwT87A==} @@ -1989,6 +2029,9 @@ packages: jose@6.0.11: resolution: {integrity: sha512-QxG7EaliDARm1O1S8BGakqncGT9s25bKL1WSf6/oa17Tkqwi8D2ZNglqCF+DsYF88/rV66Q/Q2mFAy697E1DUg==} + js-base64@3.7.7: + resolution: {integrity: sha512-7rCnleh0z2CkXhH67J8K1Ytz0b2Y+yxTPL+/KOJoa20hfnVQ/3/T6W/KflYI4bRHRagNeXeU2bkNGI3v1oS/lw==} + js-tokens@4.0.0: resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} @@ -4719,6 +4762,27 @@ snapshots: - '@codemirror/lint' - '@codemirror/search' + '@xterm/addon-clipboard@0.1.0(@xterm/xterm@5.5.0)': + dependencies: + '@xterm/xterm': 5.5.0 + js-base64: 3.7.7 + + '@xterm/addon-fit@0.10.0(@xterm/xterm@5.5.0)': + dependencies: + '@xterm/xterm': 5.5.0 + + '@xterm/addon-unicode11@0.8.0(@xterm/xterm@5.5.0)': + dependencies: + '@xterm/xterm': 5.5.0 + + '@xterm/addon-web-links@0.11.0(@xterm/xterm@5.5.0)': + dependencies: + '@xterm/xterm': 5.5.0 + + '@xterm/addon-webgl@0.18.0(@xterm/xterm@5.5.0)': + dependencies: + '@xterm/xterm': 5.5.0 + '@xterm/xterm@5.5.0': {} acorn@8.14.1: @@ -5093,6 +5157,8 @@ snapshots: jose@6.0.11: {} + js-base64@3.7.7: {} + js-tokens@4.0.0: {} js-yaml@4.1.0: From 46f1576b8f9fa6755e8908e0672f0cee5afa3ecf Mon Sep 17 00:00:00 2001 From: Aarnav Tale Date: Sat, 31 May 2025 16:47:31 -0400 Subject: [PATCH 06/97] chore: update nix go hash --- go.mod | 4 ++-- go.sum | 12 ------------ nix/agent.nix | 2 +- 3 files changed, 3 insertions(+), 15 deletions(-) diff --git a/go.mod b/go.mod index b6437af..42c013d 100644 --- a/go.mod +++ b/go.mod @@ -5,8 +5,10 @@ go 1.23.1 toolchain go1.23.4 require ( + github.com/fxamacker/cbor/v2 v2.8.0 github.com/joho/godotenv v1.5.1 go4.org/mem v0.0.0-20220726221520-4f986261bf13 + golang.org/x/crypto v0.38.0 tailscale.com v1.78.3 ) @@ -33,7 +35,6 @@ require ( github.com/coreos/go-iptables v0.7.1-0.20240112124308-65c67c9f46e6 // indirect github.com/dblohm7/wingoes v0.0.0-20240119213807-a09d6be7affa // indirect github.com/digitalocean/go-smbios v0.0.0-20180907143718-390a4f403a8e // indirect - github.com/fxamacker/cbor/v2 v2.8.0 // indirect github.com/gaissmai/bart v0.11.1 // indirect github.com/go-json-experiment/json v0.0.0-20231102232822-2e55bd4e08b0 // indirect github.com/go-ole/go-ole v1.3.0 // indirect @@ -76,7 +77,6 @@ require ( github.com/vishvananda/netns v0.0.4 // indirect github.com/x448/float16 v0.8.4 // indirect go4.org/netipx v0.0.0-20231129151722-fdeea329fbba // indirect - golang.org/x/crypto v0.38.0 // indirect golang.org/x/exp v0.0.0-20240119083558-1b970713d09a // indirect golang.org/x/mod v0.19.0 // indirect golang.org/x/net v0.27.0 // indirect diff --git a/go.sum b/go.sum index 56d6ef3..e6f85ad 100644 --- a/go.sum +++ b/go.sum @@ -61,8 +61,6 @@ github.com/dsnet/try v0.0.3 h1:ptR59SsrcFUYbT/FhAbKTV6iLkeD6O18qfIWRml2fqI= github.com/dsnet/try v0.0.3/go.mod h1:WBM8tRpUmnXXhY1U6/S8dt6UWdHTQ7y8A5YSkRCkq40= github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= -github.com/fxamacker/cbor/v2 v2.6.0 h1:sU6J2usfADwWlYDAFhZBQ6TnLFBHxgesMrQfQgk1tWA= -github.com/fxamacker/cbor/v2 v2.6.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ= github.com/fxamacker/cbor/v2 v2.8.0 h1:fFtUGXUzXPHTIUdne5+zzMPTfffl3RD5qYnkY40vtxU= github.com/fxamacker/cbor/v2 v2.8.0/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ= github.com/gaissmai/bart v0.11.1 h1:5Uv5XwsaFBRo4E5VBcb9TzY8B7zxFf+U7isDxqOrRfc= @@ -194,8 +192,6 @@ go4.org/mem v0.0.0-20220726221520-4f986261bf13 h1:CbZeCBZ0aZj8EfVgnqQcYZgf0lpZ3H go4.org/mem v0.0.0-20220726221520-4f986261bf13/go.mod h1:reUoABIJ9ikfM5sgtSF3Wushcza7+WeD01VB9Lirh3g= go4.org/netipx v0.0.0-20231129151722-fdeea329fbba h1:0b9z3AuHCjxk0x/opv64kcgZLBseWJUpBw5I82+2U4M= go4.org/netipx v0.0.0-20231129151722-fdeea329fbba/go.mod h1:PLyyIXexvUFg3Owu6p/WfdlivPbZJsZdgWZlrGope/Y= -golang.org/x/crypto v0.25.0 h1:ypSNr+bnYL2YhwoMt2zPxHFmbAN1KZs/njMG3hxUp30= -golang.org/x/crypto v0.25.0/go.mod h1:T+wALwcMOSE0kXgUAnPAHqTLW+XHgcELELW8VaDgm/M= golang.org/x/crypto v0.38.0 h1:jt+WWG8IZlBnVbomuhg2Mdq0+BBQaHbtqHEFEigjUV8= golang.org/x/crypto v0.38.0/go.mod h1:MvrbAqul58NNYPKnOra203SB9vpuZW0e+RRZV+Ggqjw= golang.org/x/exp v0.0.0-20240119083558-1b970713d09a h1:Q8/wZp0KX97QFTc2ywcOE0YRjZPVIx+MXInMzdvQqcA= @@ -209,8 +205,6 @@ golang.org/x/mod v0.19.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/net v0.27.0 h1:5K3Njcw06/l2y9vpGCSdcxWOYHOUk3dVNGDXN+FvAys= golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.9.0 h1:fEo0HyrW1GIgZdpbhCRO0PkJajUS5H9IFUztCgEo2jQ= -golang.org/x/sync v0.9.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sync v0.14.0 h1:woo0S4Yywslg6hp4eUFjTVOyKt0RookbpAHG4c1HmhQ= golang.org/x/sync v0.14.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= golang.org/x/sys v0.0.0-20200217220822-9197077df867/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -220,16 +214,10 @@ golang.org/x/sys v0.0.0-20220817070843-5a390386f1f2/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.4.1-0.20230131160137-e7d7f63158de/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.27.0 h1:wBqf8DvsY9Y/2P8gAfPDEYNuS30J4lPHJxXSb/nJZ+s= -golang.org/x/sys v0.27.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw= golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= -golang.org/x/term v0.22.0 h1:BbsgPEJULsl2fV/AT3v15Mjva5yXKQDyKf+TbDz7QJk= -golang.org/x/term v0.22.0/go.mod h1:F3qCibpT5AMpCRfhfT53vVJwhLtIVHhB9XDjfFvnMI4= golang.org/x/term v0.32.0 h1:DR4lr0TjUs3epypdhTOkMmuF5CDFJ/8pOnbzMZPQ7bg= golang.org/x/term v0.32.0/go.mod h1:uZG1FhGx848Sqfsq4/DlJr3xGGsYMu/L5GW4abiaEPQ= -golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4= -golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= golang.org/x/text v0.25.0 h1:qVyWApTSYLk/drJRO5mDlNYskwQznZmkpV2c8q9zls4= golang.org/x/text v0.25.0/go.mod h1:WEdwpYrmk1qmdHvhkSTNPm3app7v4rsT8F2UD6+VHIA= golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= diff --git a/nix/agent.nix b/nix/agent.nix index 2c7bc70..29f1125 100644 --- a/nix/agent.nix +++ b/nix/agent.nix @@ -3,7 +3,7 @@ buildGoModule { pname = "hp_agent"; version = (builtins.fromJSON (builtins.readFile ../package.json)).version; src = ../.; - vendorHash = "sha256-5TmX9ZUotNC3ZnNWRlyugAmzQG/WSZ66jFfGljql/ww="; + vendorHash = "sha256-qAB6Q19ymeom7126N7ASaFkcqDBRdbjgmG/OTpcX9sI="; ldflags = ["-s" "-w"]; env.CGO_ENABLED = 0; } From f9751f5ab2cbf2c8b9e2875c717047ef517b7b96 Mon Sep 17 00:00:00 2001 From: Aarnav Tale Date: Fri, 6 Jun 2025 16:42:30 -0400 Subject: [PATCH 07/97] chore: add tooling for go wasm --- .gitignore | 2 ++ Caddyfile | 10 ++++++++++ compose.yaml | 16 ++++++++++++++++ docs/CONTRIBUTING.md | 6 ++++++ mise.toml | 27 +++++++++++++++++++++++++++ 5 files changed, 61 insertions(+) create mode 100644 Caddyfile create mode 100644 mise.toml diff --git a/.gitignore b/.gitignore index 022a176..b7add2d 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,5 @@ node_modules /build /test .env +public/hp_ssh.wasm +public/wasm_exec.js diff --git a/Caddyfile b/Caddyfile new file mode 100644 index 0000000..a036a72 --- /dev/null +++ b/Caddyfile @@ -0,0 +1,10 @@ +https://localhost { + reverse_proxy headscale:8080 + tls /certs/localhost.pem /certs/localhost-key.pem + + header { + Access-Control-Allow-Origin * + Access-Control-Allow-Headers * + Access-Control-Allow-Methods GET, POST, OPTIONS + } +} diff --git a/compose.yaml b/compose.yaml index 439515b..b20d969 100644 --- a/compose.yaml +++ b/compose.yaml @@ -7,6 +7,22 @@ networks: name: "headplane-dev" driver: "bridge" services: + caddy: + image: "caddy:2" + container_name: "headplane-caddy" + restart: "unless-stopped" + depends_on: + - "headscale" + networks: + - "headplane-dev" + ports: + - "80:80" + - "443:443" + volumes: + - "./Caddyfile:/etc/caddy/Caddyfile" + - "./test/caddy/data:/data" + - "./test/caddy/config:/config" + - "./test/caddy/certs:/certs" headscale: image: "headscale/headscale:0.26.0-debug" container_name: "headscale" diff --git a/docs/CONTRIBUTING.md b/docs/CONTRIBUTING.md index 539b8d4..947a1a5 100644 --- a/docs/CONTRIBUTING.md +++ b/docs/CONTRIBUTING.md @@ -45,3 +45,9 @@ before commit to make these changes automatically. > All of these guidelines are fairly simple to follow and are flexible if needed. > I won't automatically close PRs or issues if they don't follow these rules, > but instead we can discuss them and see if we can come to a compromise. + +### Contribution Tooling +If you plan to work with the WASM SSH agent, you will need to install +`mkcert` or an equivalent to create certificates in `./test/caddy/certs`. +It expects a `localhost.pem` and `localhost-key.pem` file to be present in that +directory. PNPM has a script to do it with `pnpm mkcert` already already. diff --git a/mise.toml b/mise.toml new file mode 100644 index 0000000..f92272c --- /dev/null +++ b/mise.toml @@ -0,0 +1,27 @@ +[tools] +"go" = "1.24.4" +"node" = "22" + +[tasks.copy-wasm-shim] +alias = ["gojs"] +description = "Copies Go's wasm_exec.js to the public directory" +run = [ + "echo $(go version) > public/wasm_exec.js", + "cat $(go env GOROOT)/lib/wasm/wasm_exec.js >> public/wasm_exec.js", +] + +[tasks.build-go-wasm] +alias = ["wasm"] +depends = ["copy-wasm-shim"] +description = "Builds the Go WebAssembly module for Tailscale SSH" +env = { GOOS = "js", GOARCH = "wasm" } +run = "go build -o public/hp_ssh.wasm ./cmd/hp_ssh" + +[tasks.generate-caddy-certs] +alias = ["mkcert"] +dir = "{{cwd}}/test/caddy/certs" +run = [ + "mkdir -p {{cwd}}/test/caddy/certs", + "mkcert -install", + "mkcert localhost" +] From 7a6ad8d2d532ffc81fe0500d9a1a6261e86bfac3 Mon Sep 17 00:00:00 2001 From: Aarnav Tale Date: Fri, 6 Jun 2025 16:44:42 -0400 Subject: [PATCH 08/97] feat: add horrible barely-working go ipn wasm code --- cmd/hp_ssh/hp_ssh.go | 23 ++ go.mod | 70 ++--- go.sum | 172 ++++++----- internal/hp_ipn/ipn.go | 661 +++++++++++++++++++++++++++++++++++++++++ 4 files changed, 799 insertions(+), 127 deletions(-) create mode 100644 cmd/hp_ssh/hp_ssh.go create mode 100644 internal/hp_ipn/ipn.go diff --git a/cmd/hp_ssh/hp_ssh.go b/cmd/hp_ssh/hp_ssh.go new file mode 100644 index 0000000..0610b42 --- /dev/null +++ b/cmd/hp_ssh/hp_ssh.go @@ -0,0 +1,23 @@ +//go:build js && wasm + +package main + +import ( + "log" + "syscall/js" + + "github.com/tale/headplane/internal/hp_ipn" +) + +func main() { + js.Global().Set("newIPN", js.FuncOf(func(this js.Value, args []js.Value) any { + if len(args) != 1 { + log.Fatal("Usage: newIPN(config)") + return nil + } + + return hp_ipn.NewIPN(args[0]) + })) + + <-make(chan bool) +} diff --git a/go.mod b/go.mod index 42c013d..098494d 100644 --- a/go.mod +++ b/go.mod @@ -1,42 +1,41 @@ module github.com/tale/headplane -go 1.23.1 +go 1.24.0 -toolchain go1.23.4 +toolchain go1.24.4 require ( github.com/fxamacker/cbor/v2 v2.8.0 github.com/joho/godotenv v1.5.1 - go4.org/mem v0.0.0-20220726221520-4f986261bf13 + go4.org/mem v0.0.0-20240501181205-ae6ca9944745 golang.org/x/crypto v0.38.0 - tailscale.com v1.78.3 + tailscale.com v1.84.1 ) require ( filippo.io/edwards25519 v1.1.0 // indirect github.com/akutz/memconn v0.1.0 // indirect github.com/alexbrainman/sspi v0.0.0-20231016080023-1a75b4708caa // indirect - github.com/aws/aws-sdk-go-v2 v1.24.1 // indirect - github.com/aws/aws-sdk-go-v2/config v1.26.5 // indirect - github.com/aws/aws-sdk-go-v2/credentials v1.16.16 // indirect - github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.14.11 // indirect - github.com/aws/aws-sdk-go-v2/internal/configsources v1.2.10 // indirect - github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.5.10 // indirect - github.com/aws/aws-sdk-go-v2/internal/ini v1.7.2 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.10.4 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.10.10 // indirect + github.com/aws/aws-sdk-go-v2 v1.36.0 // indirect + github.com/aws/aws-sdk-go-v2/config v1.29.5 // indirect + github.com/aws/aws-sdk-go-v2/credentials v1.17.58 // indirect + github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.27 // indirect + github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.31 // indirect + github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.31 // indirect + github.com/aws/aws-sdk-go-v2/internal/ini v1.8.2 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.2 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.12 // indirect github.com/aws/aws-sdk-go-v2/service/ssm v1.44.7 // indirect - github.com/aws/aws-sdk-go-v2/service/sso v1.18.7 // indirect - github.com/aws/aws-sdk-go-v2/service/ssooidc v1.21.7 // indirect - github.com/aws/aws-sdk-go-v2/service/sts v1.26.7 // indirect - github.com/aws/smithy-go v1.19.0 // indirect - github.com/bits-and-blooms/bitset v1.13.0 // indirect + github.com/aws/aws-sdk-go-v2/service/sso v1.24.14 // indirect + github.com/aws/aws-sdk-go-v2/service/ssooidc v1.28.13 // indirect + github.com/aws/aws-sdk-go-v2/service/sts v1.33.13 // indirect + github.com/aws/smithy-go v1.22.2 // indirect github.com/coder/websocket v1.8.12 // indirect github.com/coreos/go-iptables v0.7.1-0.20240112124308-65c67c9f46e6 // indirect github.com/dblohm7/wingoes v0.0.0-20240119213807-a09d6be7affa // indirect github.com/digitalocean/go-smbios v0.0.0-20180907143718-390a4f403a8e // indirect - github.com/gaissmai/bart v0.11.1 // indirect - github.com/go-json-experiment/json v0.0.0-20231102232822-2e55bd4e08b0 // indirect + github.com/gaissmai/bart v0.18.0 // indirect + github.com/go-json-experiment/json v0.0.0-20250223041408-d3c622f1b874 // indirect github.com/go-ole/go-ole v1.3.0 // indirect github.com/godbus/dbus/v5 v5.1.1-0.20230522191255-76236955d466 // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect @@ -44,49 +43,42 @@ require ( github.com/google/go-cmp v0.6.0 // indirect github.com/google/nftables v0.2.1-0.20240414091927-5e242ec57806 // indirect github.com/google/uuid v1.6.0 // indirect - github.com/gorilla/csrf v1.7.2 // indirect + github.com/gorilla/csrf v1.7.3 // indirect github.com/gorilla/securecookie v1.1.2 // indirect github.com/hdevalence/ed25519consensus v0.2.0 // indirect - github.com/illarion/gonotify/v2 v2.0.3 // indirect - github.com/insomniacslk/dhcp v0.0.0-20231206064809-8c70d406f6d2 // indirect + github.com/illarion/gonotify/v3 v3.0.2 // indirect github.com/jmespath/go-jmespath v0.4.0 // indirect - github.com/josharian/native v1.1.1-0.20230202152459-5c7d0dd6ab86 // indirect github.com/jsimonetti/rtnetlink v1.4.0 // indirect github.com/klauspost/compress v1.17.11 // indirect - github.com/kortschak/wol v0.0.0-20200729010619-da482cc4850a // indirect github.com/mdlayher/genetlink v1.3.2 // indirect - github.com/mdlayher/netlink v1.7.2 // indirect + github.com/mdlayher/netlink v1.7.3-0.20250113171957-fbb4dce95f42 // indirect github.com/mdlayher/sdnotify v1.0.0 // indirect github.com/mdlayher/socket v0.5.0 // indirect github.com/miekg/dns v1.1.58 // indirect github.com/mitchellh/go-ps v1.0.0 // indirect - github.com/pierrec/lz4/v4 v4.1.21 // indirect github.com/prometheus-community/pro-bing v0.4.0 // indirect github.com/safchain/ethtool v0.3.0 // indirect github.com/tailscale/certstore v0.1.1-0.20231202035212-d3fa0460f47e // indirect github.com/tailscale/go-winio v0.0.0-20231025203758-c4f33415bf55 // indirect - github.com/tailscale/golang-x-crypto v0.0.0-20240604161659-3fde5e568aa4 // indirect github.com/tailscale/goupnp v1.0.1-0.20210804011211-c64d0f06ea05 // indirect github.com/tailscale/hujson v0.0.0-20221223112325-20486734a56a // indirect github.com/tailscale/netlink v1.1.1-0.20240822203006-4d49adab4de7 // indirect - github.com/tailscale/peercred v0.0.0-20240214030740-b535050b2aa4 // indirect - github.com/tailscale/web-client-prebuilt v0.0.0-20240226180453-5db17b287bf1 // indirect - github.com/tailscale/wireguard-go v0.0.0-20241113014420-4e883d38c8d3 // indirect - github.com/tcnksm/go-httpstat v0.2.0 // indirect - github.com/u-root/uio v0.0.0-20240118234441-a3c409a6018e // indirect + github.com/tailscale/peercred v0.0.0-20250107143737-35a0c7bd7edc // indirect + github.com/tailscale/web-client-prebuilt v0.0.0-20250124233751-d4cd19a26976 // indirect + github.com/tailscale/wireguard-go v0.0.0-20250304000100-91a0587fb251 // indirect github.com/vishvananda/netns v0.0.4 // indirect github.com/x448/float16 v0.8.4 // indirect go4.org/netipx v0.0.0-20231129151722-fdeea329fbba // indirect - golang.org/x/exp v0.0.0-20240119083558-1b970713d09a // indirect - golang.org/x/mod v0.19.0 // indirect - golang.org/x/net v0.27.0 // indirect + golang.org/x/exp v0.0.0-20250210185358-939b2ce775ac // indirect + golang.org/x/mod v0.23.0 // indirect + golang.org/x/net v0.36.0 // indirect golang.org/x/sync v0.14.0 // indirect golang.org/x/sys v0.33.0 // indirect golang.org/x/term v0.32.0 // indirect golang.org/x/text v0.25.0 // indirect - golang.org/x/time v0.5.0 // indirect - golang.org/x/tools v0.23.0 // indirect + golang.org/x/time v0.10.0 // indirect + golang.org/x/tools v0.30.0 // indirect golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 // indirect golang.zx2c4.com/wireguard/windows v0.5.3 // indirect - gvisor.dev/gvisor v0.0.0-20240722211153-64c016c92987 // indirect + gvisor.dev/gvisor v0.0.0-20250205023644-9414b50a5633 // indirect ) diff --git a/go.sum b/go.sum index e6f85ad..1d5fc1f 100644 --- a/go.sum +++ b/go.sum @@ -1,3 +1,5 @@ +9fans.net/go v0.0.8-0.20250307142834-96bdba94b63f h1:1C7nZuxUMNz7eiQALRfiqNOm04+m3edWlRff/BYHf0Q= +9fans.net/go v0.0.8-0.20250307142834-96bdba94b63f/go.mod h1:hHyrZRryGqVdqrknjq5OWDLGCTJ2NeEvtrpR96mjraM= filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= filippo.io/mkcert v1.4.4 h1:8eVbbwfVlaqUM7OwuftKc2nuYOoTDQWqsoXmzoXZdbc= @@ -10,42 +12,42 @@ github.com/alexbrainman/sspi v0.0.0-20231016080023-1a75b4708caa h1:LHTHcTQiSGT7V github.com/alexbrainman/sspi v0.0.0-20231016080023-1a75b4708caa/go.mod h1:cEWa1LVoE5KvSD9ONXsZrj0z6KqySlCCNKHlLzbqAt4= github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8= github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4= -github.com/aws/aws-sdk-go-v2 v1.24.1 h1:xAojnj+ktS95YZlDf0zxWBkbFtymPeDP+rvUQIH3uAU= -github.com/aws/aws-sdk-go-v2 v1.24.1/go.mod h1:LNh45Br1YAkEKaAqvmE1m8FUx6a5b/V0oAKV7of29b4= -github.com/aws/aws-sdk-go-v2/config v1.26.5 h1:lodGSevz7d+kkFJodfauThRxK9mdJbyutUxGq1NNhvw= -github.com/aws/aws-sdk-go-v2/config v1.26.5/go.mod h1:DxHrz6diQJOc9EwDslVRh84VjjrE17g+pVZXUeSxaDU= -github.com/aws/aws-sdk-go-v2/credentials v1.16.16 h1:8q6Rliyv0aUFAVtzaldUEcS+T5gbadPbWdV1WcAddK8= -github.com/aws/aws-sdk-go-v2/credentials v1.16.16/go.mod h1:UHVZrdUsv63hPXFo1H7c5fEneoVo9UXiz36QG1GEPi0= -github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.14.11 h1:c5I5iH+DZcH3xOIMlz3/tCKJDaHFwYEmxvlh2fAcFo8= -github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.14.11/go.mod h1:cRrYDYAMUohBJUtUnOhydaMHtiK/1NZ0Otc9lIb6O0Y= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.2.10 h1:vF+Zgd9s+H4vOXd5BMaPWykta2a6Ih0AKLq/X6NYKn4= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.2.10/go.mod h1:6BkRjejp/GR4411UGqkX8+wFMbFbqsUIimfK4XjOKR4= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.5.10 h1:nYPe006ktcqUji8S2mqXf9c/7NdiKriOwMvWQHgYztw= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.5.10/go.mod h1:6UV4SZkVvmODfXKql4LCbaZUpF7HO2BX38FgBf9ZOLw= -github.com/aws/aws-sdk-go-v2/internal/ini v1.7.2 h1:GrSw8s0Gs/5zZ0SX+gX4zQjRnRsMJDJ2sLur1gRBhEM= -github.com/aws/aws-sdk-go-v2/internal/ini v1.7.2/go.mod h1:6fQQgfuGmw8Al/3M2IgIllycxV7ZW7WCdVSqfBeUiCY= -github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.10.4 h1:/b31bi3YVNlkzkBrm9LfpaKoaYZUxIAj4sHfOTmLfqw= -github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.10.4/go.mod h1:2aGXHFmbInwgP9ZfpmdIfOELL79zhdNYNmReK8qDfdQ= -github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.10.10 h1:DBYTXwIGQSGs9w4jKm60F5dmCQ3EEruxdc0MFh+3EY4= -github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.10.10/go.mod h1:wohMUQiFdzo0NtxbBg0mSRGZ4vL3n0dKjLTINdcIino= +github.com/aws/aws-sdk-go-v2 v1.36.0 h1:b1wM5CcE65Ujwn565qcwgtOTT1aT4ADOHHgglKjG7fk= +github.com/aws/aws-sdk-go-v2 v1.36.0/go.mod h1:5PMILGVKiW32oDzjj6RU52yrNrDPUHcbZQYr1sM7qmM= +github.com/aws/aws-sdk-go-v2/config v1.29.5 h1:4lS2IB+wwkj5J43Tq/AwvnscBerBJtQQ6YS7puzCI1k= +github.com/aws/aws-sdk-go-v2/config v1.29.5/go.mod h1:SNzldMlDVbN6nWxM7XsUiNXPSa1LWlqiXtvh/1PrJGg= +github.com/aws/aws-sdk-go-v2/credentials v1.17.58 h1:/d7FUpAPU8Lf2KUdjniQvfNdlMID0Sd9pS23FJ3SS9Y= +github.com/aws/aws-sdk-go-v2/credentials v1.17.58/go.mod h1:aVYW33Ow10CyMQGFgC0ptMRIqJWvJ4nxZb0sUiuQT/A= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.27 h1:7lOW8NUwE9UZekS1DYoiPdVAqZ6A+LheHWb+mHbNOq8= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.27/go.mod h1:w1BASFIPOPUae7AgaH4SbjNbfdkxuggLyGfNFTn8ITY= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.31 h1:lWm9ucLSRFiI4dQQafLrEOmEDGry3Swrz0BIRdiHJqQ= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.31/go.mod h1:Huu6GG0YTfbPphQkDSo4dEGmQRTKb9k9G7RdtyQWxuI= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.31 h1:ACxDklUKKXb48+eg5ROZXi1vDgfMyfIA/WyvqHcHI0o= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.31/go.mod h1:yadnfsDwqXeVaohbGc/RaD287PuyRw2wugkh5ZL2J6k= +github.com/aws/aws-sdk-go-v2/internal/ini v1.8.2 h1:Pg9URiobXy85kgFev3og2CuOZ8JZUBENF+dcgWBaYNk= +github.com/aws/aws-sdk-go-v2/internal/ini v1.8.2/go.mod h1:FbtygfRFze9usAadmnGJNc8KsP346kEe+y2/oyhGAGc= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.2 h1:D4oz8/CzT9bAEYtVhSBmFj2dNOtaHOtMKc2vHBwYizA= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.2/go.mod h1:Za3IHqTQ+yNcRHxu1OFucBh0ACZT4j4VQFF0BqpZcLY= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.12 h1:O+8vD2rGjfihBewr5bT+QUfYUHIxCVgG61LHoT59shM= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.12/go.mod h1:usVdWJaosa66NMvmCrr08NcWDBRv4E6+YFG2pUdw1Lk= github.com/aws/aws-sdk-go-v2/service/ssm v1.44.7 h1:a8HvP/+ew3tKwSXqL3BCSjiuicr+XTU2eFYeogV9GJE= github.com/aws/aws-sdk-go-v2/service/ssm v1.44.7/go.mod h1:Q7XIWsMo0JcMpI/6TGD6XXcXcV1DbTj6e9BKNntIMIM= -github.com/aws/aws-sdk-go-v2/service/sso v1.18.7 h1:eajuO3nykDPdYicLlP3AGgOyVN3MOlFmZv7WGTuJPow= -github.com/aws/aws-sdk-go-v2/service/sso v1.18.7/go.mod h1:+mJNDdF+qiUlNKNC3fxn74WWNN+sOiGOEImje+3ScPM= -github.com/aws/aws-sdk-go-v2/service/ssooidc v1.21.7 h1:QPMJf+Jw8E1l7zqhZmMlFw6w1NmfkfiSK8mS4zOx3BA= -github.com/aws/aws-sdk-go-v2/service/ssooidc v1.21.7/go.mod h1:ykf3COxYI0UJmxcfcxcVuz7b6uADi1FkiUz6Eb7AgM8= -github.com/aws/aws-sdk-go-v2/service/sts v1.26.7 h1:NzO4Vrau795RkUdSHKEwiR01FaGzGOH1EETJ+5QHnm0= -github.com/aws/aws-sdk-go-v2/service/sts v1.26.7/go.mod h1:6h2YuIoxaMSCFf5fi1EgZAwdfkGMgDY+DVfa61uLe4U= -github.com/aws/smithy-go v1.19.0 h1:KWFKQV80DpP3vJrrA9sVAHQ5gc2z8i4EzrLhLlWXcBM= -github.com/aws/smithy-go v1.19.0/go.mod h1:NukqUGpCZIILqqiV0NIjeFh24kd/FAa4beRb6nbIUPE= -github.com/bits-and-blooms/bitset v1.13.0 h1:bAQ9OPNFYbGHV6Nez0tmNI0RiEu7/hxlYJRUA0wFAVE= -github.com/bits-and-blooms/bitset v1.13.0/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8= +github.com/aws/aws-sdk-go-v2/service/sso v1.24.14 h1:c5WJ3iHz7rLIgArznb3JCSQT3uUMiz9DLZhIX+1G8ok= +github.com/aws/aws-sdk-go-v2/service/sso v1.24.14/go.mod h1:+JJQTxB6N4niArC14YNtxcQtwEqzS3o9Z32n7q33Rfs= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.28.13 h1:f1L/JtUkVODD+k1+IiSJUUv8A++2qVr+Xvb3xWXETMU= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.28.13/go.mod h1:tvqlFoja8/s0o+UruA1Nrezo/df0PzdunMDDurUfg6U= +github.com/aws/aws-sdk-go-v2/service/sts v1.33.13 h1:3LXNnmtH3TURctC23hnC0p/39Q5gre3FI7BNOiDcVWc= +github.com/aws/aws-sdk-go-v2/service/sts v1.33.13/go.mod h1:7Yn+p66q/jt38qMoVfNvjbm3D89mGBnkwDcijgtih8w= +github.com/aws/smithy-go v1.22.2 h1:6D9hW43xKFrRx/tXXfAlIZc4JI+yQe6snnWcQyxSyLQ= +github.com/aws/smithy-go v1.22.2/go.mod h1:irrKGvNn1InZwb2d7fkIRNucdfwR8R+Ts3wxYa/cJHg= github.com/cilium/ebpf v0.15.0 h1:7NxJhNiBT3NG8pZJ3c+yfrVdHY8ScgKD27sScgjLMMk= github.com/cilium/ebpf v0.15.0/go.mod h1:DHp1WyrLeiBh19Cf/tfiSMhqheEiK8fXFZ4No0P1Hso= github.com/coder/websocket v1.8.12 h1:5bUXkEPPIbewrnkU8LTCLVaxi4N4J8ahufH2vlo4NAo= github.com/coder/websocket v1.8.12/go.mod h1:LNVeNrXQZfe5qhS9ALED3uA+l5pPqvwXg3CKoDBB2gs= github.com/coreos/go-iptables v0.7.1-0.20240112124308-65c67c9f46e6 h1:8h5+bWd7R6AYUslN6c6iuZWTKsKxUFDlpnmilO6R2n0= github.com/coreos/go-iptables v0.7.1-0.20240112124308-65c67c9f46e6/go.mod h1:Qe8Bv2Xik5FyTXwgIbLAnv2sWSBmvWdFETJConOQ//Q= +github.com/creachadair/taskgroup v0.13.2 h1:3KyqakBuFsm3KkXi/9XIb0QcA8tEzLHLgaoidf0MdVc= +github.com/creachadair/taskgroup v0.13.2/go.mod h1:i3V1Zx7H8RjwljUEeUWYT30Lmb9poewSb2XI1yTwD0g= github.com/creack/pty v1.1.23 h1:4M6+isWdcStXEf15G/RbrMPOQj1dZ7HPZCGwE4kOeP0= github.com/creack/pty v1.1.23/go.mod h1:08sCNb52WyoAwi2QDyzUCTgcvVFhUzewun7wtTfvcwE= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -63,14 +65,16 @@ github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHk github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= github.com/fxamacker/cbor/v2 v2.8.0 h1:fFtUGXUzXPHTIUdne5+zzMPTfffl3RD5qYnkY40vtxU= github.com/fxamacker/cbor/v2 v2.8.0/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ= -github.com/gaissmai/bart v0.11.1 h1:5Uv5XwsaFBRo4E5VBcb9TzY8B7zxFf+U7isDxqOrRfc= -github.com/gaissmai/bart v0.11.1/go.mod h1:KHeYECXQiBjTzQz/om2tqn3sZF1J7hw9m6z41ftj3fg= +github.com/gaissmai/bart v0.18.0 h1:jQLBT/RduJu0pv/tLwXE+xKPgtWJejbxuXAR+wLJafo= +github.com/gaissmai/bart v0.18.0/go.mod h1:JJzMAhNF5Rjo4SF4jWBrANuJfqY+FvsFhW7t1UZJ+XY= github.com/github/fakeca v0.1.0 h1:Km/MVOFvclqxPM9dZBC4+QE564nU4gz4iZ0D9pMw28I= github.com/github/fakeca v0.1.0/go.mod h1:+bormgoGMMuamOscx7N91aOuUST7wdaJ2rNjeohylyo= -github.com/go-json-experiment/json v0.0.0-20231102232822-2e55bd4e08b0 h1:ymLjT4f35nQbASLnvxEde4XOBL+Sn7rFuV+FOJqkljg= -github.com/go-json-experiment/json v0.0.0-20231102232822-2e55bd4e08b0/go.mod h1:6daplAwHHGbUGib4990V3Il26O0OC4aRyvewaaAihaA= +github.com/go-json-experiment/json v0.0.0-20250223041408-d3c622f1b874 h1:F8d1AJ6M9UQCavhwmO6ZsrYLfG8zVFWfEfMS2MXPkSY= +github.com/go-json-experiment/json v0.0.0-20250223041408-d3c622f1b874/go.mod h1:TiCD2a1pcmjd7YnhGH0f/zKNcCD06B029pHhzV23c2M= github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE= github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78= +github.com/go4org/plan9netshell v0.0.0-20250324183649-788daa080737 h1:cf60tHxREO3g1nroKr2osU3JWZsJzkfi7rEg+oAB0Lo= +github.com/go4org/plan9netshell v0.0.0-20250324183649-788daa080737/go.mod h1:MIS0jDzbU/vuM9MC4YnBITCv+RYuTRq8dJzmCrFsK9g= github.com/godbus/dbus/v5 v5.1.1-0.20230522191255-76236955d466 h1:sQspH8M4niEijh3PFscJRLDnkL547IeP7kpPe3uUhEg= github.com/godbus/dbus/v5 v5.1.1-0.20230522191255-76236955d466/go.mod h1:ZiQxhyQ+bbbfxUKVvjfO498oPYvtYhZzycal3G/NHmU= github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= @@ -79,20 +83,22 @@ github.com/google/btree v1.1.2 h1:xf4v41cLI2Z6FxbKm+8Bu+m8ifhj15JuZ9sa0jZCMUU= github.com/google/btree v1.1.2/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-tpm v0.9.4 h1:awZRf9FwOeTunQmHoDYSHJps3ie6f1UlhS1fOdPEt1I= +github.com/google/go-tpm v0.9.4/go.mod h1:h9jEsEECg7gtLis0upRBQU+GhYVH6jMjrFxI8u6bVUY= github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/nftables v0.2.1-0.20240414091927-5e242ec57806 h1:wG8RYIyctLhdFk6Vl1yPGtSRtwGpVkWyZww1OCil2MI= github.com/google/nftables v0.2.1-0.20240414091927-5e242ec57806/go.mod h1:Beg6V6zZ3oEn0JuiUQ4wqwuyqqzasOltcoXPtgLbFp4= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/gorilla/csrf v1.7.2 h1:oTUjx0vyf2T+wkrx09Trsev1TE+/EbDAeHtSTbtC2eI= -github.com/gorilla/csrf v1.7.2/go.mod h1:F1Fj3KG23WYHE6gozCmBAezKookxbIvUJT+121wTuLk= +github.com/gorilla/csrf v1.7.3 h1:BHWt6FTLZAb2HtWT5KDBf6qgpZzvtbp9QWDRKZMXJC0= +github.com/gorilla/csrf v1.7.3/go.mod h1:F1Fj3KG23WYHE6gozCmBAezKookxbIvUJT+121wTuLk= github.com/gorilla/securecookie v1.1.2 h1:YCIWL56dvtr73r6715mJs5ZvhtnY73hBvEF8kXD8ePA= github.com/gorilla/securecookie v1.1.2/go.mod h1:NfCASbcHqRSY+3a8tlWJwsQap2VX5pwzwo4h3eOamfo= github.com/hdevalence/ed25519consensus v0.2.0 h1:37ICyZqdyj0lAZ8P4D1d1id3HqbbG1N3iBb1Tb4rdcU= github.com/hdevalence/ed25519consensus v0.2.0/go.mod h1:w3BHWjwJbFU29IRHL1Iqkw3sus+7FctEyM4RqDxYNzo= -github.com/illarion/gonotify/v2 v2.0.3 h1:B6+SKPo/0Sw8cRJh1aLzNEeNVFfzE3c6N+o+vyxM+9A= -github.com/illarion/gonotify/v2 v2.0.3/go.mod h1:38oIJTgFqupkEydkkClkbL6i5lXV/bxdH9do5TALPEE= +github.com/illarion/gonotify/v3 v3.0.2 h1:O7S6vcopHexutmpObkeWsnzMJt/r1hONIEogeVNmJMk= +github.com/illarion/gonotify/v3 v3.0.2/go.mod h1:HWGPdPe817GfvY3w7cx6zkbzNZfi3QjcBm/wgVvEL1U= github.com/insomniacslk/dhcp v0.0.0-20231206064809-8c70d406f6d2 h1:9K06NfxkBh25x56yVhWWlKFE8YpicaSfHwoV8SFbueA= github.com/insomniacslk/dhcp v0.0.0-20231206064809-8c70d406f6d2/go.mod h1:3A9PQ1cunSDF/1rbTq99Ts4pVnycWg+vlPkfeD2NLFI= github.com/jellydator/ttlcache/v3 v3.1.0 h1:0gPFG0IHHP6xyUyXq+JaD8fwkDCqgqwohXNJBcYE71g= @@ -103,9 +109,6 @@ github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGw github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= -github.com/josharian/native v1.0.1-0.20221213033349-c1e37c09b531/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w= -github.com/josharian/native v1.1.1-0.20230202152459-5c7d0dd6ab86 h1:elKwZS1OcdQ0WwEDBeqxKwb7WB62QX8bvZ/FJnVXIfk= -github.com/josharian/native v1.1.1-0.20230202152459-5c7d0dd6ab86/go.mod h1:aFAMtuldEgx/4q7iSGazk22+IcgvtiC+HIimFO9XlS8= github.com/jsimonetti/rtnetlink v1.4.0 h1:Z1BF0fRgcETPEa0Kt0MRk3yV5+kF1FWTni6KUFKrq2I= github.com/jsimonetti/rtnetlink v1.4.0/go.mod h1:5W1jDvWdnthFJ7fxYX1GMK07BUpI4oskfOqvPteYS6E= github.com/klauspost/compress v1.17.11 h1:In6xLpyWOi1+C7tXUUWv2ot1QvBjxevKAaI6IXrJmUc= @@ -120,8 +123,8 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/mdlayher/genetlink v1.3.2 h1:KdrNKe+CTu+IbZnm/GVUMXSqBBLqcGpRDa0xkQy56gw= github.com/mdlayher/genetlink v1.3.2/go.mod h1:tcC3pkCrPUGIKKsCsp0B3AdaaKuHtaxoJRz3cc+528o= -github.com/mdlayher/netlink v1.7.2 h1:/UtM3ofJap7Vl4QWCPDGXY8d3GIY2UGSDbK+QWmY8/g= -github.com/mdlayher/netlink v1.7.2/go.mod h1:xraEF7uJbxLhc5fpHL4cPe221LI2bdttWlU+ZGLfQSw= +github.com/mdlayher/netlink v1.7.3-0.20250113171957-fbb4dce95f42 h1:A1Cq6Ysb0GM0tpKMbdCXCIfBclan4oHk1Jb+Hrejirg= +github.com/mdlayher/netlink v1.7.3-0.20250113171957-fbb4dce95f42/go.mod h1:BB4YCPDOzfy7FniQ/lxuYQ3dgmM2cZumHbK8RpTjN2o= github.com/mdlayher/sdnotify v1.0.0 h1:Ma9XeLVN/l0qpyx1tNeMSeTjCPH6NtuD6/N9XdTlQ3c= github.com/mdlayher/sdnotify v1.0.0/go.mod h1:HQUmpM4XgYkhDLtd+Uad8ZFK1T9D5+pNxnXQjCeJlGE= github.com/mdlayher/socket v0.5.0 h1:ilICZmJcQz70vrWVes1MFera4jGiWNocSkykwwoy3XI= @@ -130,9 +133,10 @@ github.com/miekg/dns v1.1.58 h1:ca2Hdkz+cDg/7eNF6V56jjzuZ4aCAE+DbVkILdQWG/4= github.com/miekg/dns v1.1.58/go.mod h1:Ypv+3b/KadlvW9vJfXOTf300O4UqaHFzFCuHz+rPkBY= github.com/mitchellh/go-ps v1.0.0 h1:i6ampVEEF4wQFF+bkYfwYgY+F/uYJDktmvLPf7qIgjc= github.com/mitchellh/go-ps v1.0.0/go.mod h1:J4lOc8z8yJs6vUwklHw2XEIiT4z4C40KtWVN3nvg8Pg= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 h1:zYyBkD/k9seD2A7fsi6Oo2LfFZAehjjQMERAvZLEDnQ= github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646/go.mod h1:jpp1/29i3P1S/RLdc7JQKbRpFeM1dOBd8T9ki5s+AY8= -github.com/pierrec/lz4/v4 v4.1.14/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= github.com/pierrec/lz4/v4 v4.1.21 h1:yOVMLb6qSIDP67pl/5F7RepeKYu/VmTyEXvuMI5d9mQ= github.com/pierrec/lz4/v4 v4.1.21/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= github.com/pkg/sftp v1.13.6 h1:JFZT4XbOU7l77xGSpOdW+pwIMqP044IyjXX6FGyEKFo= @@ -142,77 +146,71 @@ github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRI github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/prometheus-community/pro-bing v0.4.0 h1:YMbv+i08gQz97OZZBwLyvmmQEEzyfyrrjEaAchdy3R4= github.com/prometheus-community/pro-bing v0.4.0/go.mod h1:b7wRYZtCcPmt4Sz319BykUU241rWLe1VFXyiyWK/dH4= -github.com/prometheus/client_model v0.5.0 h1:VQw1hfvPvk3Uv6Qf29VrPF32JB6rtbgI6cYPYQjL0Qw= -github.com/prometheus/client_model v0.5.0/go.mod h1:dTiFglRmd66nLR9Pv9f0mZi7B7fk5Pm3gvsjB5tr+kI= -github.com/prometheus/common v0.48.0 h1:QO8U2CdOzSn1BBsmXJXduaaW+dY/5QLjfB8svtSzKKE= -github.com/prometheus/common v0.48.0/go.mod h1:0/KsvlIEfPQCQ5I2iNSAWKPZziNCvRs5EC6ILDTlAPc= -github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= -github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= +github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E= +github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY= +github.com/prometheus/common v0.55.0 h1:KEi6DK7lXW/m7Ig5i47x0vRzuBsHuvJdi5ee6Y3G1dc= +github.com/prometheus/common v0.55.0/go.mod h1:2SECS4xJG1kd8XF9IcM1gMX6510RAEL65zxzNImwdc8= +github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= +github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= github.com/safchain/ethtool v0.3.0 h1:gimQJpsI6sc1yIqP/y8GYgiXn/NjgvpM0RNoWLVVmP0= github.com/safchain/ethtool v0.3.0/go.mod h1:SA9BwrgyAqNo7M+uaL6IYbxpm5wk3L7Mm6ocLW+CJUs= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= -github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/tailscale/certstore v0.1.1-0.20231202035212-d3fa0460f47e h1:PtWT87weP5LWHEY//SWsYkSO3RWRZo4OSWagh3YD2vQ= github.com/tailscale/certstore v0.1.1-0.20231202035212-d3fa0460f47e/go.mod h1:XrBNfAFN+pwoWuksbFS9Ccxnopa15zJGgXRFN90l3K4= github.com/tailscale/go-winio v0.0.0-20231025203758-c4f33415bf55 h1:Gzfnfk2TWrk8Jj4P4c1a3CtQyMaTVCznlkLZI++hok4= github.com/tailscale/go-winio v0.0.0-20231025203758-c4f33415bf55/go.mod h1:4k4QO+dQ3R5FofL+SanAUZe+/QfeK0+OIuwDIRu2vSg= -github.com/tailscale/golang-x-crypto v0.0.0-20240604161659-3fde5e568aa4 h1:rXZGgEa+k2vJM8xT0PoSKfVXwFGPQ3z3CJfmnHJkZZw= -github.com/tailscale/golang-x-crypto v0.0.0-20240604161659-3fde5e568aa4/go.mod h1:ikbF+YT089eInTp9f2vmvy4+ZVnW5hzX1q2WknxSprQ= +github.com/tailscale/golang-x-crypto v0.0.0-20250404221719-a5573b049869 h1:SRL6irQkKGQKKLzvQP/ke/2ZuB7Py5+XuqtOgSj+iMM= +github.com/tailscale/golang-x-crypto v0.0.0-20250404221719-a5573b049869/go.mod h1:ikbF+YT089eInTp9f2vmvy4+ZVnW5hzX1q2WknxSprQ= github.com/tailscale/goupnp v1.0.1-0.20210804011211-c64d0f06ea05 h1:4chzWmimtJPxRs2O36yuGRW3f9SYV+bMTTvMBI0EKio= github.com/tailscale/goupnp v1.0.1-0.20210804011211-c64d0f06ea05/go.mod h1:PdCqy9JzfWMJf1H5UJW2ip33/d4YkoKN0r67yKH1mG8= github.com/tailscale/hujson v0.0.0-20221223112325-20486734a56a h1:SJy1Pu0eH1C29XwJucQo73FrleVK6t4kYz4NVhp34Yw= github.com/tailscale/hujson v0.0.0-20221223112325-20486734a56a/go.mod h1:DFSS3NAGHthKo1gTlmEcSBiZrRJXi28rLNd/1udP1c8= github.com/tailscale/netlink v1.1.1-0.20240822203006-4d49adab4de7 h1:uFsXVBE9Qr4ZoF094vE6iYTLDl0qCiKzYXlL6UeWObU= github.com/tailscale/netlink v1.1.1-0.20240822203006-4d49adab4de7/go.mod h1:NzVQi3Mleb+qzq8VmcWpSkcSYxXIg0DkI6XDzpVkhJ0= -github.com/tailscale/peercred v0.0.0-20240214030740-b535050b2aa4 h1:Gz0rz40FvFVLTBk/K8UNAenb36EbDSnh+q7Z9ldcC8w= -github.com/tailscale/peercred v0.0.0-20240214030740-b535050b2aa4/go.mod h1:phI29ccmHQBc+wvroosENp1IF9195449VDnFDhJ4rJU= -github.com/tailscale/web-client-prebuilt v0.0.0-20240226180453-5db17b287bf1 h1:tdUdyPqJ0C97SJfjB9tW6EylTtreyee9C44de+UBG0g= -github.com/tailscale/web-client-prebuilt v0.0.0-20240226180453-5db17b287bf1/go.mod h1:agQPE6y6ldqCOui2gkIh7ZMztTkIQKH049tv8siLuNQ= +github.com/tailscale/peercred v0.0.0-20250107143737-35a0c7bd7edc h1:24heQPtnFR+yfntqhI3oAu9i27nEojcQ4NuBQOo5ZFA= +github.com/tailscale/peercred v0.0.0-20250107143737-35a0c7bd7edc/go.mod h1:f93CXfllFsO9ZQVq+Zocb1Gp4G5Fz0b0rXHLOzt/Djc= +github.com/tailscale/web-client-prebuilt v0.0.0-20250124233751-d4cd19a26976 h1:UBPHPtv8+nEAy2PD8RyAhOYvau1ek0HDJqLS/Pysi14= +github.com/tailscale/web-client-prebuilt v0.0.0-20250124233751-d4cd19a26976/go.mod h1:agQPE6y6ldqCOui2gkIh7ZMztTkIQKH049tv8siLuNQ= github.com/tailscale/wf v0.0.0-20240214030419-6fbb0a674ee6 h1:l10Gi6w9jxvinoiq15g8OToDdASBni4CyJOdHY1Hr8M= github.com/tailscale/wf v0.0.0-20240214030419-6fbb0a674ee6/go.mod h1:ZXRML051h7o4OcI0d3AaILDIad/Xw0IkXaHM17dic1Y= -github.com/tailscale/wireguard-go v0.0.0-20241113014420-4e883d38c8d3 h1:dmoPb3dG27tZgMtrvqfD/LW4w7gA6BSWl8prCPNmkCQ= -github.com/tailscale/wireguard-go v0.0.0-20241113014420-4e883d38c8d3/go.mod h1:BOm5fXUBFM+m9woLNBoxI9TaBXXhGNP50LX/TGIvGb4= +github.com/tailscale/wireguard-go v0.0.0-20250304000100-91a0587fb251 h1:h/41LFTrwMxB9Xvvug0kRdQCU5TlV1+pAMQw0ZtDE3U= +github.com/tailscale/wireguard-go v0.0.0-20250304000100-91a0587fb251/go.mod h1:BOm5fXUBFM+m9woLNBoxI9TaBXXhGNP50LX/TGIvGb4= github.com/tailscale/xnet v0.0.0-20240729143630-8497ac4dab2e h1:zOGKqN5D5hHhiYUp091JqK7DPCqSARyUfduhGUY8Bek= github.com/tailscale/xnet v0.0.0-20240729143630-8497ac4dab2e/go.mod h1:orPd6JZXXRyuDusYilywte7k094d7dycXXU5YnWsrwg= github.com/tc-hib/winres v0.2.1 h1:YDE0FiP0VmtRaDn7+aaChp1KiF4owBiJa5l964l5ujA= github.com/tc-hib/winres v0.2.1/go.mod h1:C/JaNhH3KBvhNKVbvdlDWkbMDO9H4fKKDaN7/07SSuk= -github.com/tcnksm/go-httpstat v0.2.0 h1:rP7T5e5U2HfmOBmZzGgGZjBQ5/GluWUylujl0tJ04I0= -github.com/tcnksm/go-httpstat v0.2.0/go.mod h1:s3JVJFtQxtBEBC9dwcdTTXS9xFnM3SXAZwPG41aurT8= -github.com/u-root/u-root v0.12.0 h1:K0AuBFriwr0w/PGS3HawiAw89e3+MU7ks80GpghAsNs= -github.com/u-root/u-root v0.12.0/go.mod h1:FYjTOh4IkIZHhjsd17lb8nYW6udgXdJhG1c0r6u0arI= -github.com/u-root/uio v0.0.0-20240118234441-a3c409a6018e h1:BA9O3BmlTmpjbvajAwzWx4Wo2TRVdpPXZEeemGQcajw= -github.com/u-root/uio v0.0.0-20240118234441-a3c409a6018e/go.mod h1:eLL9Nub3yfAho7qB0MzZizFhTU2QkLeoVsWdHtDW264= +github.com/u-root/u-root v0.14.0 h1:Ka4T10EEML7dQ5XDvO9c3MBN8z4nuSnGjcd1jmU2ivg= +github.com/u-root/u-root v0.14.0/go.mod h1:hAyZorapJe4qzbLWlAkmSVCJGbfoU9Pu4jpJ1WMluqE= +github.com/u-root/uio v0.0.0-20240224005618-d2acac8f3701 h1:pyC9PaHYZFgEKFdlp3G8RaCKgVpHZnecvArXvPXcFkM= +github.com/u-root/uio v0.0.0-20240224005618-d2acac8f3701/go.mod h1:P3a5rG4X7tI17Nn3aOIAYr5HbIMukwXG0urG0WuL8OA= github.com/vishvananda/netns v0.0.0-20200728191858-db3c7e526aae/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0= github.com/vishvananda/netns v0.0.4 h1:Oeaw1EM2JMxD51g9uhtC0D7erkIjgmj8+JZc26m1YX8= github.com/vishvananda/netns v0.0.4/go.mod h1:SpkAiCQRtJ6TvvxPnOSyH3BMl6unz3xZlaprSwhNNJM= github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= -go4.org/mem v0.0.0-20220726221520-4f986261bf13 h1:CbZeCBZ0aZj8EfVgnqQcYZgf0lpZ3H9rmp5nkDTAst8= -go4.org/mem v0.0.0-20220726221520-4f986261bf13/go.mod h1:reUoABIJ9ikfM5sgtSF3Wushcza7+WeD01VB9Lirh3g= +go4.org/mem v0.0.0-20240501181205-ae6ca9944745 h1:Tl++JLUCe4sxGu8cTpDzRLd3tN7US4hOxG5YpKCzkek= +go4.org/mem v0.0.0-20240501181205-ae6ca9944745/go.mod h1:reUoABIJ9ikfM5sgtSF3Wushcza7+WeD01VB9Lirh3g= go4.org/netipx v0.0.0-20231129151722-fdeea329fbba h1:0b9z3AuHCjxk0x/opv64kcgZLBseWJUpBw5I82+2U4M= go4.org/netipx v0.0.0-20231129151722-fdeea329fbba/go.mod h1:PLyyIXexvUFg3Owu6p/WfdlivPbZJsZdgWZlrGope/Y= golang.org/x/crypto v0.38.0 h1:jt+WWG8IZlBnVbomuhg2Mdq0+BBQaHbtqHEFEigjUV8= golang.org/x/crypto v0.38.0/go.mod h1:MvrbAqul58NNYPKnOra203SB9vpuZW0e+RRZV+Ggqjw= -golang.org/x/exp v0.0.0-20240119083558-1b970713d09a h1:Q8/wZp0KX97QFTc2ywcOE0YRjZPVIx+MXInMzdvQqcA= -golang.org/x/exp v0.0.0-20240119083558-1b970713d09a/go.mod h1:idGWGoKP1toJGkd5/ig9ZLuPcZBC3ewk7SzmH0uou08= +golang.org/x/exp v0.0.0-20250210185358-939b2ce775ac h1:l5+whBCLH3iH2ZNHYLbAe58bo7yrN4mVcnkHDYz5vvs= +golang.org/x/exp v0.0.0-20250210185358-939b2ce775ac/go.mod h1:hH+7mtFmImwwcMvScyxUhjuVHR3HGaDPMn9rMSUUbxo= golang.org/x/exp/typeparams v0.0.0-20240314144324-c7f7c6466f7f h1:phY1HzDcf18Aq9A8KkmRtY9WvOFIxN8wgfvy6Zm1DV8= golang.org/x/exp/typeparams v0.0.0-20240314144324-c7f7c6466f7f/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk= -golang.org/x/image v0.18.0 h1:jGzIakQa/ZXI1I0Fxvaa9W7yP25TqT6cHIHn+6CqvSQ= -golang.org/x/image v0.18.0/go.mod h1:4yyo5vMFQjVjUcVk4jEQcU9MGy/rulF5WvUILseCM2E= -golang.org/x/mod v0.19.0 h1:fEdghXQSo20giMthA7cd28ZC+jts4amQ3YMXiP5oMQ8= -golang.org/x/mod v0.19.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= -golang.org/x/net v0.27.0 h1:5K3Njcw06/l2y9vpGCSdcxWOYHOUk3dVNGDXN+FvAys= -golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE= +golang.org/x/image v0.24.0 h1:AN7zRgVsbvmTfNyqIbbOraYL8mSwcKncEj8ofjgzcMQ= +golang.org/x/image v0.24.0/go.mod h1:4b/ITuLfqYq1hqZcjofwctIhi7sZh2WaCjvsBNjjya8= +golang.org/x/mod v0.23.0 h1:Zb7khfcRGKk+kqfxFaP5tZqCnDZMjC5VtUBs87Hr6QM= +golang.org/x/mod v0.23.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= +golang.org/x/net v0.36.0 h1:vWF2fRbw4qslQsQzgFqZff+BItCvGFQqKzKIzx1rmoA= +golang.org/x/net v0.36.0/go.mod h1:bFmbeoIPfrw4sMHNhb4J9f6+tPziuGjq7Jk/38fxi1I= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.14.0 h1:woo0S4Yywslg6hp4eUFjTVOyKt0RookbpAHG4c1HmhQ= golang.org/x/sync v0.14.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= golang.org/x/sys v0.0.0-20200217220822-9197077df867/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200728102440-3e129f6d46b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20220622161953-175b2fd9d664/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220817070843-5a390386f1f2/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.4.1-0.20230131160137-e7d7f63158de/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw= golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= @@ -220,29 +218,27 @@ golang.org/x/term v0.32.0 h1:DR4lr0TjUs3epypdhTOkMmuF5CDFJ/8pOnbzMZPQ7bg= golang.org/x/term v0.32.0/go.mod h1:uZG1FhGx848Sqfsq4/DlJr3xGGsYMu/L5GW4abiaEPQ= golang.org/x/text v0.25.0 h1:qVyWApTSYLk/drJRO5mDlNYskwQznZmkpV2c8q9zls4= golang.org/x/text v0.25.0/go.mod h1:WEdwpYrmk1qmdHvhkSTNPm3app7v4rsT8F2UD6+VHIA= -golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= -golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= -golang.org/x/tools v0.23.0 h1:SGsXPZ+2l4JsgaCKkx+FQ9YZ5XEtA1GZYuoDjenLjvg= -golang.org/x/tools v0.23.0/go.mod h1:pnu6ufv6vQkll6szChhK3C3L/ruaIv5eBeztNG8wtsI= +golang.org/x/time v0.10.0 h1:3usCWA8tQn0L8+hFJQNgzpWbd89begxN66o1Ojdn5L4= +golang.org/x/time v0.10.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= +golang.org/x/tools v0.30.0 h1:BgcpHewrV5AUp2G9MebG4XPFI1E2W41zU1SaqVA9vJY= +golang.org/x/tools v0.30.0/go.mod h1:c347cR/OJfw5TI+GfX7RUPNMdDRRbjvYTS0jPyvsVtY= golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 h1:B82qJJgjvYKsXS9jeunTOisW56dUokqW/FOteYJJ/yg= golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2/go.mod h1:deeaetjYA+DHMHg+sMSMI58GrEteJUUzzw7en6TJQcI= golang.zx2c4.com/wireguard/windows v0.5.3 h1:On6j2Rpn3OEMXqBq00QEDC7bWSZrPIHKIus8eIuExIE= golang.zx2c4.com/wireguard/windows v0.5.3/go.mod h1:9TEe8TJmtwyQebdFwAkEWOPr3prrtqm+REGFifP60hI= -google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= -google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= +google.golang.org/protobuf v1.35.1 h1:m3LfL6/Ca+fqnjnlqQXNpFPABW1UD7mjh8KO2mKFytA= +google.golang.org/protobuf v1.35.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= -gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= -gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gvisor.dev/gvisor v0.0.0-20240722211153-64c016c92987 h1:TU8z2Lh3Bbq77w0t1eG8yRlLcNHzZu3x6mhoH2Mk0c8= -gvisor.dev/gvisor v0.0.0-20240722211153-64c016c92987/go.mod h1:sxc3Uvk/vHcd3tj7/DHVBoR5wvWT/MmRq2pj7HRJnwU= +gvisor.dev/gvisor v0.0.0-20250205023644-9414b50a5633 h1:2gap+Kh/3F47cO6hAu3idFvsJ0ue6TRcEi2IUkv/F8k= +gvisor.dev/gvisor v0.0.0-20250205023644-9414b50a5633/go.mod h1:5DMfjtclAbTIjbXqO1qCe2K5GKKxWz2JHvCChuTcJEM= honnef.co/go/tools v0.5.1 h1:4bH5o3b5ZULQ4UrBmP+63W9r7qIkqJClEA9ko5YKx+I= honnef.co/go/tools v0.5.1/go.mod h1:e9irvo83WDG9/irijV44wr3tbhcFeRnfpVlRqVwpzMs= howett.net/plist v1.0.0 h1:7CrbWYbPPO/PyNy38b2EB/+gYbjCe2DXBxgtOOZbSQM= howett.net/plist v1.0.0/go.mod h1:lqaXoTrLY4hg8tnEzNru53gicrbv7rrk+2xJA/7hw9g= software.sslmate.com/src/go-pkcs12 v0.4.0 h1:H2g08FrTvSFKUj+D309j1DPfk5APnIdAQAB8aEykJ5k= software.sslmate.com/src/go-pkcs12 v0.4.0/go.mod h1:Qiz0EyvDRJjjxGyUQa2cCNZn/wMyzrRJ/qcDXOQazLI= -tailscale.com v1.78.3 h1:2BJepIEYA0ba0ZXn2rOuZjYzIV4Az+X9RS5XJF007Ug= -tailscale.com v1.78.3/go.mod h1:gT7ALbLFCr2YIu0kgc9Q3tBVaTlod65D2N6jMLH11Bk= +tailscale.com v1.84.1 h1:xtuiYeAIUR+dRztPzzqUsjj+Fv/06vz28zoFaP1k/Os= +tailscale.com v1.84.1/go.mod h1:6/S63NMAhmncYT/1zIPDJkvCuZwMw+JnUuOfSPNazpo= diff --git a/internal/hp_ipn/ipn.go b/internal/hp_ipn/ipn.go new file mode 100644 index 0000000..4e97ad9 --- /dev/null +++ b/internal/hp_ipn/ipn.go @@ -0,0 +1,661 @@ +//go:build js && wasm + +package hp_ipn + +import ( + "bytes" + "context" + "encoding/hex" + "errors" + "fmt" + "log" + "math/rand/v2" + "net" + "net/http" + "net/netip" + "strings" + "syscall/js" + "time" + + "golang.org/x/crypto/ssh" + "tailscale.com/control/controlclient" + "tailscale.com/ipn" + "tailscale.com/ipn/ipnlocal" + "tailscale.com/ipn/ipnserver" + "tailscale.com/ipn/store/mem" + "tailscale.com/logpolicy" + "tailscale.com/logtail" + "tailscale.com/net/netmon" + "tailscale.com/net/netns" + "tailscale.com/net/tsdial" + "tailscale.com/safesocket" + "tailscale.com/tailcfg" + "tailscale.com/tsd" + "tailscale.com/types/views" + "tailscale.com/wgengine" + "tailscale.com/wgengine/netstack" + "tailscale.com/words" +) + +// ControlURL defines the URL to be used for connection to Control. +var ControlURL = ipn.DefaultControlURL + +func NewIPN(jsConfig js.Value) map[string]any { + netns.SetEnabled(false) + + store := &mem.Store{} + + controlURL := ControlURL + if jsControlURL := jsConfig.Get("controlURL"); jsControlURL.Type() == js.TypeString { + controlURL = jsControlURL.String() + } + + var authKey string + if jsAuthKey := jsConfig.Get("authKey"); jsAuthKey.Type() == js.TypeString { + authKey = jsAuthKey.String() + } + + var hostname string + if jsHostname := jsConfig.Get("hostname"); jsHostname.Type() == js.TypeString { + hostname = jsHostname.String() + } else { + hostname = generateHostname() + } + + lpc := getOrCreateLogPolicyConfig(store) + c := logtail.Config{ + Collection: lpc.Collection, + PrivateID: lpc.PrivateID, + + // Compressed requests set HTTP headers that are not supported by the + // no-cors fetching mode: + CompressLogs: false, + + HTTPC: &http.Client{Transport: &noCORSTransport{http.DefaultTransport}}, + } + + logtail := logtail.NewLogger(c, log.Printf) + logf := logtail.Logf + + // Instead of new(tsd.System), we use tsd.NewSystem() to ensure that + // the bus is initialized correctly and that the system is set up + sys := tsd.NewSystem() + sys.Set(store) + + dialer := &tsdial.Dialer{Logf: logf} + netmon, err := netmon.New(sys.Bus.Get(), logf) + + if err != nil { + log.Fatalf("netmon.New: %v", err) + return nil + } + + eng, err := wgengine.NewUserspaceEngine(logf, wgengine.Config{ + Dialer: dialer, + SetSubsystem: sys.Set, + ControlKnobs: sys.ControlKnobs(), + HealthTracker: sys.HealthTracker(), + NetMon: netmon, + Metrics: sys.UserMetricsRegistry(), + }) + + if err != nil { + log.Fatal(err) + } + + sys.Set(eng) + + ns, err := netstack.Create(logf, sys.Tun.Get(), eng, sys.MagicSock.Get(), dialer, sys.DNSManager.Get(), sys.ProxyMapper()) + if err != nil { + log.Fatalf("netstack.Create: %v", err) + } + sys.Set(ns) + ns.ProcessLocalIPs = true + ns.ProcessSubnets = true + + dialer.UseNetstackForIP = func(ip netip.Addr) bool { + return true + } + dialer.NetstackDialTCP = func(ctx context.Context, dst netip.AddrPort) (net.Conn, error) { + return ns.DialContextTCP(ctx, dst) + } + dialer.NetstackDialUDP = func(ctx context.Context, dst netip.AddrPort) (net.Conn, error) { + return ns.DialContextUDP(ctx, dst) + } + + sys.NetstackRouter.Set(true) + sys.Tun.Get().Start() + + logid := lpc.PublicID + srv := ipnserver.New(logf, logid, sys.NetMon.Get()) + lb, err := ipnlocal.NewLocalBackend(logf, logid, sys, controlclient.LoginDefault) + if err != nil { + log.Fatalf("ipnlocal.NewLocalBackend: %v", err) + } + if err := ns.Start(lb); err != nil { + log.Fatalf("failed to start netstack: %v", err) + } + + srv.SetLocalBackend(lb) + + log.Printf("Using control URL and auth key: %q, %q", controlURL, authKey) + jsIPN := &jsIPN{ + dialer: dialer, + srv: srv, + lb: lb, + controlURL: controlURL, + authKey: authKey, + hostname: hostname, + } + + ipnO := ipn.Options{ + AuthKey: authKey, + UpdatePrefs: &ipn.Prefs{ + ControlURL: controlURL, + Hostname: hostname, + WantRunning: true, + LoggedOut: false, + }, + } + + state := lb.State() + log.Printf("State: %+v", state) + + lb.SetNotifyCallback(func(n ipn.Notify) { + log.Printf("NOTIFY: %+v", n) + + st := n.State + if st != nil { + log.Printf("State changed: %s", *st) + if *st == ipn.NeedsLogin { + go func() { + if err := lb.StartLoginInteractive(context.Background()); err != nil { + log.Printf("StartLoginInteractive failed: %v", err) + } else { + log.Printf("Started login interactive") + } + }() + } + } + }) + + // Start safesocket listener before lb.Start + ln, err := safesocket.Listen("") + if err != nil { + log.Fatalf("safesocket.Listen: %v", err) + } + + // Start server loop before backend Start + go func() { + if err := srv.Run(context.Background(), ln); err != nil && !errors.Is(err, context.Canceled) { + log.Fatalf("ipnserver.Run exited: %v", err) + } + }() + + // Now start the backend (this can trigger login) + log.Printf("Starting local backend with options: %+v", ipnO) + if err := lb.Start(ipnO); err != nil { + log.Fatalf("Failed to start local backend: %v", err) + } + + return map[string]any{ + // "run": js.FuncOf(func(this js.Value, args []js.Value) any { + // if len(args) != 1 { + // log.Fatal(`Usage: run({ + // notifyState(state: int): void, + // notifyNetMap(netMap: object): void, + // notifyBrowseToURL(url: string): void, + // notifyPanicRecover(err: string): void, + // })`) + // return nil + // } + // jsIPN.run(args[0]) + // return nil + // }), + "ssh": js.FuncOf(func(this js.Value, args []js.Value) any { + if len(args) != 3 { + log.Printf("Usage: ssh(hostname, userName, termConfig)") + return nil + } + return jsIPN.ssh( + args[0].String(), + args[1].String(), + args[2]) + }), + "login": js.FuncOf(func(this js.Value, args []js.Value) any { + if len(args) != 0 { + log.Printf("Usage: login()") + return nil + } + jsIPN.login() + return nil + }), + } +} + +func (i *jsIPN) login() { + go i.lb.StartLoginInteractive(context.Background()) +} + +type jsIPN struct { + dialer *tsdial.Dialer + srv *ipnserver.Server + lb *ipnlocal.LocalBackend + controlURL string + authKey string + hostname string +} + +var jsIPNState = map[ipn.State]string{ + ipn.NoState: "NoState", + ipn.InUseOtherUser: "InUseOtherUser", + ipn.NeedsLogin: "NeedsLogin", + ipn.NeedsMachineAuth: "NeedsMachineAuth", + ipn.Stopped: "Stopped", + ipn.Starting: "Starting", + ipn.Running: "Running", +} + +var jsMachineStatus = map[tailcfg.MachineStatus]string{ + tailcfg.MachineUnknown: "MachineUnknown", + tailcfg.MachineUnauthorized: "MachineUnauthorized", + tailcfg.MachineAuthorized: "MachineAuthorized", + tailcfg.MachineInvalid: "MachineInvalid", +} + +func (i *jsIPN) Run(jsCallbacks js.Value) { + notifyState := func(state ipn.State) { + jsCallbacks.Call("notifyState", jsIPNState[state]) + } + + notifyState(ipn.NoState) + + i.lb.SetNotifyCallback(func(n ipn.Notify) { + // Panics in the notify callback are likely due to be due to bugs in + // this bridging module (as opposed to actual bugs in Tailscale) and + // thus may be recoverable. Let the UI know, and allow the user to + // choose if they want to reload the page. + defer func() { + if r := recover(); r != nil { + fmt.Println("Panic recovered:", r) + jsCallbacks.Call("notifyPanicRecover", fmt.Sprint(r)) + } + }() + log.Printf("NOTIFY: %+v", n) + + if n.Health != nil { + log.Printf("NOTIFY HEALTH: %+v", n.Health) + for warning := range n.Health.Warnings { + log.Printf("Health warning: %s", warning) + } + } + + if n.State != nil { + notifyState(*n.State) + } + + log.Printf("NOTIFY NETMAP: %+v", n.NetMap) + + // if nm := n.NetMap; nm != nil { + // jsNetMap := jsNetMap{ + // Self: jsNetMapSelfNode{ + // jsNetMapNode: jsNetMapNode{ + // Name: nm.Name, + // Addresses: mapSliceView(nm.GetAddresses(), func(a netip.Prefix) string { return a.Addr().String() }), + // NodeKey: nm.NodeKey.String(), + // MachineKey: nm.MachineKey.String(), + // }, + // MachineStatus: jsMachineStatus[nm.GetMachineStatus()], + // }, + // Peers: mapSlice(nm.Peers, func(p tailcfg.NodeView) jsNetMapPeerNode { + // name := p.Name() + // if name == "" { + // // In practice this should only happen for Hello. + // name = p.Hostinfo().Hostname() + // } + // addrs := make([]string, p.Addresses().Len()) + // for i, ap := range p.Addresses().All() { + // addrs[i] = ap.Addr().String() + // } + // return jsNetMapPeerNode{ + // jsNetMapNode: jsNetMapNode{ + // Name: name, + // Addresses: addrs, + // MachineKey: p.Machine().String(), + // NodeKey: p.Key().String(), + // }, + // Online: p.Online().Clone(), + // TailscaleSSHEnabled: p.Hostinfo().TailscaleSSHEnabled(), + // } + // }), + // LockedOut: nm.TKAEnabled && nm.SelfNode.KeySignature().Len() == 0, + // } + // if jsonNetMap, err := json.Marshal(jsNetMap); err == nil { + // jsCallbacks.Call("notifyNetMap", string(jsonNetMap)) + // } else { + // log.Printf("Could not generate JSON netmap: %v", err) + // } + // } + // if n.BrowseToURL != nil { + // jsCallbacks.Call("notifyBrowseToURL", *n.BrowseToURL) + // } + }) + + go func() { + o := ipn.Options{ + UpdatePrefs: &ipn.Prefs{ + ControlURL: i.controlURL, + RouteAll: false, + WantRunning: true, + Hostname: i.hostname, + }, + AuthKey: i.authKey, + } + + log.Printf("Fucking options: %+v", o) + err := i.lb.Start(o) + if err != nil { + log.Printf("Start error: %v", err) + } + }() + + // go func() { + // ln, err := safesocket.Listen("") + // if err != nil { + // log.Fatalf("safesocket.Listen: %v", err) + // } + + // err = i.srv.Run(context.Background(), ln) + // log.Fatalf("ipnserver.Run exited: %v", err) + // }() +} + +func (i *jsIPN) ssh(host, username string, termConfig js.Value) map[string]any { + jsSSHSession := &jsSSHSession{ + jsIPN: i, + host: host, + username: username, + termConfig: termConfig, + } + + go jsSSHSession.Run() + + return map[string]any{ + "close": js.FuncOf(func(this js.Value, args []js.Value) any { + return jsSSHSession.Close() != nil + }), + "resize": js.FuncOf(func(this js.Value, args []js.Value) any { + rows := args[0].Int() + cols := args[1].Int() + return jsSSHSession.Resize(rows, cols) != nil + }), + } +} + +type jsSSHSession struct { + jsIPN *jsIPN + host string + username string + termConfig js.Value + session *ssh.Session + + pendingResizeRows int + pendingResizeCols int +} + +func (s *jsSSHSession) Run() { + writeFn := s.termConfig.Get("writeFn") + writeErrorFn := s.termConfig.Get("writeErrorFn") + setReadFn := s.termConfig.Get("setReadFn") + rows := s.termConfig.Get("rows").Int() + cols := s.termConfig.Get("cols").Int() + timeoutSeconds := 5.0 + if jsTimeoutSeconds := s.termConfig.Get("timeoutSeconds"); jsTimeoutSeconds.Type() == js.TypeNumber { + timeoutSeconds = jsTimeoutSeconds.Float() + } + onConnectionProgress := s.termConfig.Get("onConnectionProgress") + onConnected := s.termConfig.Get("onConnected") + onDone := s.termConfig.Get("onDone") + defer onDone.Invoke() + + writeError := func(label string, err error) { + writeErrorFn.Invoke(fmt.Sprintf("%s Error: %v\r\n", label, err)) + } + reportProgress := func(message string) { + onConnectionProgress.Invoke(message) + } + + ctx, cancel := context.WithTimeout(context.Background(), time.Duration(timeoutSeconds*float64(time.Second))) + defer cancel() + reportProgress(fmt.Sprintf("Connecting to %s…", strings.Split(s.host, ".")[0])) + c, err := s.jsIPN.dialer.UserDial(ctx, "tcp", net.JoinHostPort(s.host, "22")) + if err != nil { + writeError("Dial", err) + return + } + defer c.Close() + + config := &ssh.ClientConfig{ + HostKeyCallback: func(hostname string, remote net.Addr, key ssh.PublicKey) error { + // Host keys are not used with Tailscale SSH, but we can use this + // callback to know that the connection has been established. + reportProgress("SSH connection established…") + return nil + }, + User: s.username, + } + + reportProgress("Starting SSH client…") + sshConn, _, _, err := ssh.NewClientConn(c, s.host, config) + if err != nil { + writeError("SSH Connection", err) + return + } + defer sshConn.Close() + + sshClient := ssh.NewClient(sshConn, nil, nil) + defer sshClient.Close() + + session, err := sshClient.NewSession() + if err != nil { + writeError("SSH Session", err) + return + } + s.session = session + defer session.Close() + + stdin, err := session.StdinPipe() + if err != nil { + writeError("SSH Stdin", err) + return + } + + session.Stdout = termWriter{writeFn} + session.Stderr = termWriter{writeFn} + + setReadFn.Invoke(js.FuncOf(func(this js.Value, args []js.Value) any { + input := args[0].String() + _, err := stdin.Write([]byte(input)) + if err != nil { + writeError("Write Input", err) + } + return nil + })) + + // We might have gotten a resize notification since we started opening the + // session, pick up the latest size. + if s.pendingResizeRows != 0 { + rows = s.pendingResizeRows + } + if s.pendingResizeCols != 0 { + cols = s.pendingResizeCols + } + err = session.RequestPty("xterm", rows, cols, ssh.TerminalModes{}) + + if err != nil { + writeError("Pseudo Terminal", err) + return + } + + err = session.Shell() + if err != nil { + writeError("Shell", err) + return + } + + onConnected.Invoke() + err = session.Wait() + if err != nil { + writeError("Wait", err) + return + } +} + +func (s *jsSSHSession) Close() error { + if s.session == nil { + // We never had a chance to open the session, ignore the close request. + return nil + } + return s.session.Close() +} + +func (s *jsSSHSession) Resize(rows, cols int) error { + if s.session == nil { + s.pendingResizeRows = rows + s.pendingResizeCols = cols + return nil + } + return s.session.WindowChange(rows, cols) +} + +type termWriter struct { + f js.Value +} + +func (w termWriter) Write(p []byte) (n int, err error) { + r := bytes.Replace(p, []byte("\n"), []byte("\n\r"), -1) + w.f.Invoke(string(r)) + return len(p), nil +} + +type jsNetMap struct { + Self jsNetMapSelfNode `json:"self"` + Peers []jsNetMapPeerNode `json:"peers"` + LockedOut bool `json:"lockedOut"` +} + +type jsNetMapNode struct { + Name string `json:"name"` + Addresses []string `json:"addresses"` + MachineKey string `json:"machineKey"` + NodeKey string `json:"nodeKey"` +} + +type jsNetMapSelfNode struct { + jsNetMapNode + MachineStatus string `json:"machineStatus"` +} + +type jsNetMapPeerNode struct { + jsNetMapNode + Online *bool `json:"online,omitempty"` + TailscaleSSHEnabled bool `json:"tailscaleSSHEnabled"` +} + +type jsStateStore struct { + jsStateStorage js.Value +} + +func (s *jsStateStore) ReadState(id ipn.StateKey) ([]byte, error) { + jsValue := s.jsStateStorage.Call("getState", string(id)) + if jsValue.String() == "" { + return nil, ipn.ErrStateNotExist + } + return hex.DecodeString(jsValue.String()) +} + +func (s *jsStateStore) WriteState(id ipn.StateKey, bs []byte) error { + s.jsStateStorage.Call("setState", string(id), hex.EncodeToString(bs)) + return nil +} + +func mapSlice[T any, M any](a []T, f func(T) M) []M { + n := make([]M, len(a)) + for i, e := range a { + n[i] = f(e) + } + return n +} + +func mapSliceView[T any, M any](a views.Slice[T], f func(T) M) []M { + n := make([]M, a.Len()) + for i, v := range a.All() { + n[i] = f(v) + } + return n +} + +func filterSlice[T any](a []T, f func(T) bool) []T { + n := make([]T, 0, len(a)) + for _, e := range a { + if f(e) { + n = append(n, e) + } + } + return n +} + +func generateHostname() string { + tails := words.Tails() + scales := words.Scales() + if rand.IntN(2) == 0 { + // JavaScript + tails = filterSlice(tails, func(s string) bool { return strings.HasPrefix(s, "j") }) + scales = filterSlice(scales, func(s string) bool { return strings.HasPrefix(s, "s") }) + } else { + // WebAssembly + tails = filterSlice(tails, func(s string) bool { return strings.HasPrefix(s, "w") }) + scales = filterSlice(scales, func(s string) bool { return strings.HasPrefix(s, "a") }) + } + + tail := tails[rand.IntN(len(tails))] + scale := scales[rand.IntN(len(scales))] + return fmt.Sprintf("%s-%s", tail, scale) +} + +const logPolicyStateKey = "log-policy" + +func getOrCreateLogPolicyConfig(state ipn.StateStore) *logpolicy.Config { + if configBytes, err := state.ReadState(logPolicyStateKey); err == nil { + if config, err := logpolicy.ConfigFromBytes(configBytes); err == nil { + return config + } else { + log.Printf("Could not parse log policy config: %v", err) + } + } else if err != ipn.ErrStateNotExist { + log.Printf("Could not get log policy config from state store: %v", err) + } + config := logpolicy.NewConfig(logtail.CollectionNode) + if err := state.WriteState(logPolicyStateKey, config.ToBytes()); err != nil { + log.Printf("Could not save log policy config to state store: %v", err) + } + return config +} + +// noCORSTransport wraps a RoundTripper and forces the no-cors mode on requests, +// so that we can use it with non-CORS-aware servers. +type noCORSTransport struct { + http.RoundTripper +} + +func (t *noCORSTransport) RoundTrip(req *http.Request) (*http.Response, error) { + req.Header.Set("js.fetch:mode", "no-cors") + resp, err := t.RoundTripper.RoundTrip(req) + if err == nil { + // In no-cors mode no response properties are returned. Populate just + // the status so that callers do not think this was an error. + resp.StatusCode = http.StatusOK + resp.Status = http.StatusText(http.StatusOK) + } + return resp, err +} From 0f9bf73b82309e4c626e89409874be25a2f1d475 Mon Sep 17 00:00:00 2001 From: Aarnav Tale Date: Sun, 8 Jun 2025 08:59:33 -0400 Subject: [PATCH 09/97] chore: reorganize go code --- .gitignore | 4 +- app/routes.ts | 2 +- app/routes/ssh/console.tsx | 166 +++++++++++++++++ app/routes/ssh/hp_ssh.d.ts | 48 +++++ app/routes/ssh/overview.tsx | 145 --------------- app/routes/ssh/wasm_exec.d.ts | 7 + app/routes/ssh/xterm.client.tsx | 3 + {agent/cmd => cmd}/hp_agent/hp_agent.go | 0 cmd/hp_ssh/hp_ssh.go | 34 +++- {agent/internal/config => internal}/config.go | 0 internal/hp_ipn/ipn.go | 36 +--- internal/hp_ipn/ipnserver.go | 172 ++++++++++++++++++ internal/hp_ipn/jsfunc.go | 100 ++++++++++ internal/hp_ipn/jsmap.go | 55 ++++++ internal/hp_ipn/notify.go | 95 ++++++++++ .../internal => internal}/hpagent/handler.go | 50 ++--- .../internal/config => internal}/preflight.go | 0 .../internal => internal}/sshutil/connect.go | 0 .../internal => internal}/sshutil/encoder.go | 0 internal/sshutil/framebatcher.go | 61 +++++++ .../internal => internal}/sshutil/handler.go | 22 ++- .../internal => internal}/sshutil/sessions.go | 0 {agent/internal => internal}/tsnet/peers.go | 0 {agent/internal => internal}/tsnet/server.go | 0 {agent/internal => internal}/util/logger.go | 0 mise.toml | 6 +- package.json | 1 + pnpm-lock.yaml | 9 + 28 files changed, 796 insertions(+), 220 deletions(-) create mode 100644 app/routes/ssh/console.tsx create mode 100644 app/routes/ssh/hp_ssh.d.ts delete mode 100644 app/routes/ssh/overview.tsx create mode 100644 app/routes/ssh/wasm_exec.d.ts rename {agent/cmd => cmd}/hp_agent/hp_agent.go (100%) rename {agent/internal/config => internal}/config.go (100%) create mode 100644 internal/hp_ipn/ipnserver.go create mode 100644 internal/hp_ipn/jsfunc.go create mode 100644 internal/hp_ipn/jsmap.go create mode 100644 internal/hp_ipn/notify.go rename {agent/internal => internal}/hpagent/handler.go (79%) rename {agent/internal/config => internal}/preflight.go (100%) rename {agent/internal => internal}/sshutil/connect.go (100%) rename {agent/internal => internal}/sshutil/encoder.go (100%) create mode 100644 internal/sshutil/framebatcher.go rename {agent/internal => internal}/sshutil/handler.go (89%) rename {agent/internal => internal}/sshutil/sessions.go (100%) rename {agent/internal => internal}/tsnet/peers.go (100%) rename {agent/internal => internal}/tsnet/server.go (100%) rename {agent/internal => internal}/util/logger.go (100%) diff --git a/.gitignore b/.gitignore index b7add2d..0e2cf27 100644 --- a/.gitignore +++ b/.gitignore @@ -4,5 +4,5 @@ node_modules /build /test .env -public/hp_ssh.wasm -public/wasm_exec.js +app/hp_ssh.wasm +app/wasm_exec.js diff --git a/app/routes.ts b/app/routes.ts index 9a80c50..e2abb9d 100644 --- a/app/routes.ts +++ b/app/routes.ts @@ -10,6 +10,7 @@ export default [ route('/logout', 'routes/auth/logout.ts'), route('/oidc/callback', 'routes/auth/oidc-callback.ts'), route('/oidc/start', 'routes/auth/oidc-start.ts'), + route('/ssh', 'routes/ssh/console.tsx'), // All the main logged-in dashboard routes // Double nested to separate error propagations @@ -25,7 +26,6 @@ export default [ route('/users', 'routes/users/overview.tsx'), route('/acls', 'routes/acls/overview.tsx'), route('/dns', 'routes/dns/overview.tsx'), - route('/ssh', 'routes/ssh/overview.tsx'), ...prefix('/settings', [ index('routes/settings/overview.tsx'), diff --git a/app/routes/ssh/console.tsx b/app/routes/ssh/console.tsx new file mode 100644 index 0000000..bdbc51e --- /dev/null +++ b/app/routes/ssh/console.tsx @@ -0,0 +1,166 @@ +import { faker } from '@faker-js/faker'; +import { useState } from 'react'; +import { LoaderFunctionArgs } from 'react-router'; +import { LoadContext } from '~/server'; + +import { Loader2 } from 'lucide-react'; +import '~/wasm_exec'; + +export async function loader({ + request, + context, +}: LoaderFunctionArgs) { + // const session = await context.sessions.auth(request); + // const user = session.get('user'); + // if (!user) { + // throw data('Unauthorized', 401); + // } + // if (user.subject === 'unknown-non-oauth') { + // throw data('Only OAuth users are allowed to use WebSSH', 403); + // } + // const { users } = await context.client.get<{ users: User[] }>( + // 'v1/user', + // session.get('api_key')!, + // ); + // // MARK: This assumes that a user has authenticated with Headscale first + // // Since the only way to enforce permissions via ACLs is to generate a + // // pre-authkey which REQUIRES a user ID, meaning the user has to have + // // authenticated with Headscale first. + // const lookup = users.find((u) => { + // const subject = u.providerId?.split('/').pop(); + // if (!subject) { + // return false; + // } + // return subject === user.subject; + // }); + // if (!lookup) { + // throw data( + // `User with subject ${user.subject} not found within Headscale`, + // 404, + // ); + // } + // const { preAuthKey } = await context.client.post<{ preAuthKey: PreAuthKey }>( + // 'v1/preauthkey', + // session.get('api_key')!, + // { + // user: lookup.id, + // reusable: false, + // ephemeral: true, + // expiration: new Date(Date.now() + 60 * 60 * 1000).toISOString(), // 1 hour + // }, + // ); + // // TODO: Enable config to enforce generate_authkeys capability + // // For now, any user is capable of WebSSH connections + // // const check = await context.sessions.check( + // // request, + // // Capabilities.generate_authkeys, + // // ); + // const qp = new URL(request.url).searchParams; + // const username = qp.get('username') || undefined; + // const hostname = qp.get('hostname') || undefined; + // const port = qp.get('port') + // ? Number.parseInt(qp.get('port')!, 10) + // : undefined; + // if (!username || !hostname || !port) { + // throw data('Missing required parameters: username, hostname, port', 400); + // } + // // TODO: Verify the Host headers to ensure CORS friendly + // // TODO: Check if the URL actually resolves correctly + // // TODO: Keep track of hostname since ephemeral nodes are broken atm + // return { + // PreAuthKey: preAuthKey.key, + // ControlURL: + // context.config.headscale.public_url ?? context.config.headscale.url, + // Hostname: generateHostname(username), + // ssh: { + // username, + // hostname, + // }, + // }; +} + +function generateHostname(username: string) { + const adjective = faker.word.adjective({ + length: { + min: 3, + max: 6, + }, + }); + + const noun = faker.word.noun({ + length: { + min: 3, + max: 6, + }, + }); + + return `ssh-${adjective}-${noun}-${username}`; +} + +export default function Page() { + // const { pause } = useLiveData(); + const [ipn, setIpn] = useState(null); + // const { PreAuthKey, ControlURL, Hostname, ssh } = + // useLoaderData(); + + // useEffect(() => { + // pause(); + // const go = new Go(); // Go is defined by wasm_exec.js + // WebAssembly.instantiateStreaming(fetch(wasm), go.importObject).then( + // (value) => { + // go.run(value.instance); + // const handle = TsWasmNet( + // { + // PreAuthKey, + // ControlURL, + // Hostname, + // }, + // { + // NotifyState: (state) => { + // console.log('State changed:', state); + // if (state === 'Running') { + // setIpn(handle); + // } + // }, + // NotifyNetMap: (netmap) => { + // console.log('NetMap updated:', netmap); + // }, + // NotifyBrowseToURL: (url) => { + // console.log('Browse to URL:', url); + // }, + // NotifyPanicRecover: (message) => { + // console.error('Panic recover:', message); + // }, + // }, + // ); + + // handle.Start(); + // }, + // ); + // }, []); + + return ( +
+ {ipn === null ? ( +
+ +
+ ) : ( +
+ {/*

Session ID: {sessionId}

+ {queue.current.length > 0 && ( +

+ {queue.current.length} frames queued +

+ )} */} + + {/* */} +
+ {/* Render your terminal component here */} + {/* */} +
+
+ )} +
+ ); +} diff --git a/app/routes/ssh/hp_ssh.d.ts b/app/routes/ssh/hp_ssh.d.ts new file mode 100644 index 0000000..4cf22e7 --- /dev/null +++ b/app/routes/ssh/hp_ssh.d.ts @@ -0,0 +1,48 @@ +declare function newIPN(config: NewIPNConfig): IPNHandle; +declare function TsWasmNet( + options: TsWasmNetOptions, + callbacks: TsWasmNetCallbacks, +): TsWasmNet; + +interface TsWasmNetOptions { + ControlURL: string; + PreAuthKey: string; + Hostname: string; +} + +interface TsWasmNetCallbacks { + NotifyState: (state: IPNState) => void; + NotifyNetMap: (netMapJson: string) => void; + NotifyBrowseToURL: (url: string) => void; + NotifyPanicRecover: (err: string) => void; +} + +interface TsWasmNet { + Start: () => void; +} + +type IPNState = + | 'NoState' + | 'InUseOtherUser' + | 'NeedsLogin' + | 'NeedsMachineAuth' + | 'Stopped' + | 'Starting' + | 'Running'; + +interface SSHTerminalConfig { + writeFn: (data: string) => void; + writeErrorFn: (error: string) => void; + setReadFn: (cb: (input: string) => void) => void; + rows: number; + cols: number; + timeoutSeconds?: number; + onConnectionProgress: (message: string) => void; + onConnected: () => void; + onDone: () => void; +} + +interface SSHSession { + close(): boolean; + resize(rows: number, cols: number): boolean; +} diff --git a/app/routes/ssh/overview.tsx b/app/routes/ssh/overview.tsx deleted file mode 100644 index 3cab350..0000000 --- a/app/routes/ssh/overview.tsx +++ /dev/null @@ -1,145 +0,0 @@ -import { decode } from 'cborg'; -import { Loader2 } from 'lucide-react'; -import { useEffect, useRef, useState } from 'react'; -import { LoaderFunctionArgs, data, useLoaderData } from 'react-router'; -import { ClientOnly } from 'remix-utils/client-only'; -import { - Command, - SSHConnectData, - SSHConnectFailedData, -} from '~/server/agent/dispatcher'; -import { useLiveData } from '~/utils/live-data'; -import toast from '~/utils/toast'; -import XTerm from './xterm.client'; - -export async function loader({ request }: LoaderFunctionArgs) { - const qp = new URL(request.url).searchParams; - const username = qp.get('username') || undefined; - const hostname = qp.get('hostname') || undefined; - const port = qp.get('port') - ? Number.parseInt(qp.get('port')!, 10) - : undefined; - - if (!username || !hostname || !port) { - throw data('Missing required parameters: username, hostname, port', 400); - } - - const baseUrl = new URL(request.url).origin; - const wsUrl = new URL('/_ssh_plexer', baseUrl); - wsUrl.protocol = wsUrl.protocol === 'https:' ? 'wss:' : 'ws:'; - wsUrl.searchParams.set('username', username); - wsUrl.searchParams.set('hostname', hostname); - wsUrl.searchParams.set('port', port.toString()); - - return { - socketUrl: wsUrl.toString(), - }; -} -type SessionStatus = 'loading' | 'connected' | 'error'; - -export default function Page() { - const { pause } = useLiveData(); - const { socketUrl } = useLoaderData(); - const [socket, setSocket] = useState(null); - const [status, setStatus] = useState('loading'); - const [sessionId, setSessionId] = useState(null); - - const queue = useRef>([]); - const validated = useRef(false); - - useEffect(() => { - // SSH connections should not use stale while revalidate logic. - pause(); - - const ws = new WebSocket(socketUrl); - ws.binaryType = 'arraybuffer'; - - ws.onopen = () => { - setSocket(ws); - setStatus('loading'); - }; - - // We need to wait for the WebSocket to open and respond with the - // connection ID. Without a session ID, we do not have a mux. - const messageHandler = (event: MessageEvent) => { - if (!(event.data instanceof ArrayBuffer)) { - toast('Invalid message received from server'); - return; - } - - const data = new Uint8Array(event.data); - const obj = decode(data) as Command; - - if (obj.op === 'ssh_conn_successful') { - const data = obj as SSHConnectData; - if (!validated.current) { - validated.current = true; - toast( - `SSH connection established with session ID: ${data.payload.sessionId}`, - ); - - setStatus('connected'); - setSessionId(data.payload.sessionId); - } - - return; - } - - if (obj.op === 'ssh_conn_failed') { - const data = obj as SSHConnectFailedData; - if (!validated.current) { - validated.current = true; - toast(`SSH connection failed: ${data.payload.reason}`); - setStatus('error'); - } - - return; - } - - if (obj.op === 'ssh_frame') { - queue.current.push(new Uint8Array(event.data)); - return; - } - }; - - ws.addEventListener('message', messageHandler); - - ws.onerror = (error) => { - setStatus('error'); - toast(`WebSocket error: ${error}`); - }; - - ws.onclose = () => { - if (status !== 'error') { - toast('SSH connection closed'); - } - - setSocket(null); - setStatus('error'); - }; - - return () => { - ws.removeEventListener('message', messageHandler); - ws.close(); - }; - }, [socketUrl]); - - if (socket === null || !sessionId || status === 'loading') { - return ( - - ); - } - - return ( -
-

Session ID: {sessionId}

- {queue.current.length > 0 && ( -

- {queue.current.length} frames queued -

- )} - - -
- ); -} diff --git a/app/routes/ssh/wasm_exec.d.ts b/app/routes/ssh/wasm_exec.d.ts new file mode 100644 index 0000000..25b56fd --- /dev/null +++ b/app/routes/ssh/wasm_exec.d.ts @@ -0,0 +1,7 @@ +declare class Go { + importObject: WebAssembly.Imports; + run(instance: WebAssembly.Instance): Promise; + argv?: string[]; + env?: Record; + exit?: (code: number) => void; +} diff --git a/app/routes/ssh/xterm.client.tsx b/app/routes/ssh/xterm.client.tsx index 1fb8d99..daa9a7e 100644 --- a/app/routes/ssh/xterm.client.tsx +++ b/app/routes/ssh/xterm.client.tsx @@ -114,6 +114,9 @@ export default function XTerm({ ws, sessionId, queue }: XTermProps) { return; } + const size = event.data.byteLength; + console.log(`[WS] Received ${size} bytes at ${Date.now()}`); + const data = new Uint8Array(event.data); handleFrame(data); }; diff --git a/agent/cmd/hp_agent/hp_agent.go b/cmd/hp_agent/hp_agent.go similarity index 100% rename from agent/cmd/hp_agent/hp_agent.go rename to cmd/hp_agent/hp_agent.go diff --git a/cmd/hp_ssh/hp_ssh.go b/cmd/hp_ssh/hp_ssh.go index 0610b42..e243daa 100644 --- a/cmd/hp_ssh/hp_ssh.go +++ b/cmd/hp_ssh/hp_ssh.go @@ -3,6 +3,7 @@ package main import ( + "context" "log" "syscall/js" @@ -10,14 +11,39 @@ import ( ) func main() { - js.Global().Set("newIPN", js.FuncOf(func(this js.Value, args []js.Value) any { - if len(args) != 1 { - log.Fatal("Usage: newIPN(config)") + log.Printf("Loading WASM Headplane SSH module") + js.Global().Set("TsWasmNet", js.FuncOf(func(this js.Value, args []js.Value) any { + if len(args) != 2 { + log.Fatal("Usage: TsWasmNet(config, callbacks)") return nil } - return hp_ipn.NewIPN(args[0]) + options, err := hp_ipn.ParseTsWasmNetOptions(args[0]) + if err != nil { + log.Fatal("Error parsing options:", err) + return nil + } + + callbacks, err := hp_ipn.ParseTsWasmNetCallbacks(args[1]) + if err != nil { + log.Fatal("Error parsing callbacks:", err) + return nil + } + + ipn, err := hp_ipn.NewTsWasmIpn(options, callbacks) + if err != nil { + log.Fatal("Error creating TsWasmIpn:", err) + return nil + } + + return map[string]any{ + "Start": js.FuncOf(func(this js.Value, args []js.Value) any { + ipn.Start(context.Background()) + return nil + }), + } })) + log.Printf("WASM Headplane SSH module loaded successfully") <-make(chan bool) } diff --git a/agent/internal/config/config.go b/internal/config.go similarity index 100% rename from agent/internal/config/config.go rename to internal/config.go diff --git a/internal/hp_ipn/ipn.go b/internal/hp_ipn/ipn.go index 4e97ad9..4819705 100644 --- a/internal/hp_ipn/ipn.go +++ b/internal/hp_ipn/ipn.go @@ -9,7 +9,6 @@ import ( "errors" "fmt" "log" - "math/rand/v2" "net" "net/http" "net/netip" @@ -31,10 +30,10 @@ import ( "tailscale.com/safesocket" "tailscale.com/tailcfg" "tailscale.com/tsd" + "tailscale.com/types/logid" "tailscale.com/types/views" "tailscale.com/wgengine" "tailscale.com/wgengine/netstack" - "tailscale.com/words" ) // ControlURL defines the URL to be used for connection to Control. @@ -59,7 +58,7 @@ func NewIPN(jsConfig js.Value) map[string]any { if jsHostname := jsConfig.Get("hostname"); jsHostname.Type() == js.TypeString { hostname = jsHostname.String() } else { - hostname = generateHostname() + hostname = "blah" } lpc := getOrCreateLogPolicyConfig(store) @@ -126,7 +125,8 @@ func NewIPN(jsConfig js.Value) map[string]any { sys.NetstackRouter.Set(true) sys.Tun.Get().Start() - logid := lpc.PublicID + logid := logid.PublicID{} + srv := ipnserver.New(logf, logid, sys.NetMon.Get()) lb, err := ipnlocal.NewLocalBackend(logf, logid, sys, controlclient.LoginDefault) if err != nil { @@ -595,34 +595,6 @@ func mapSliceView[T any, M any](a views.Slice[T], f func(T) M) []M { return n } -func filterSlice[T any](a []T, f func(T) bool) []T { - n := make([]T, 0, len(a)) - for _, e := range a { - if f(e) { - n = append(n, e) - } - } - return n -} - -func generateHostname() string { - tails := words.Tails() - scales := words.Scales() - if rand.IntN(2) == 0 { - // JavaScript - tails = filterSlice(tails, func(s string) bool { return strings.HasPrefix(s, "j") }) - scales = filterSlice(scales, func(s string) bool { return strings.HasPrefix(s, "s") }) - } else { - // WebAssembly - tails = filterSlice(tails, func(s string) bool { return strings.HasPrefix(s, "w") }) - scales = filterSlice(scales, func(s string) bool { return strings.HasPrefix(s, "a") }) - } - - tail := tails[rand.IntN(len(tails))] - scale := scales[rand.IntN(len(scales))] - return fmt.Sprintf("%s-%s", tail, scale) -} - const logPolicyStateKey = "log-policy" func getOrCreateLogPolicyConfig(state ipn.StateStore) *logpolicy.Config { diff --git a/internal/hp_ipn/ipnserver.go b/internal/hp_ipn/ipnserver.go new file mode 100644 index 0000000..4db9e55 --- /dev/null +++ b/internal/hp_ipn/ipnserver.go @@ -0,0 +1,172 @@ +//go:build js && wasm + +package hp_ipn + +import ( + "context" + "fmt" + "log" + "net" + "net/netip" + + "tailscale.com/control/controlclient" + "tailscale.com/ipn" + "tailscale.com/ipn/ipnlocal" + "tailscale.com/ipn/ipnserver" + "tailscale.com/ipn/store/mem" + "tailscale.com/net/netmon" + "tailscale.com/net/netns" + "tailscale.com/net/tsdial" + "tailscale.com/safesocket" + "tailscale.com/tsd" + "tailscale.com/types/logid" + "tailscale.com/wgengine" + "tailscale.com/wgengine/netstack" +) + +// Represents an in-state Tailscale backend that is WASM friendly. +// The bare minimum to have userspace Wireguard networking is a dialer, +// a server, and a backend. +type TsWasmIpn struct { + // The options used to initialize the TsWasmNet module. + options *TsWasmNetOptions + + // The Tailscale dialer, which is used to establish connections. + dialer *tsdial.Dialer + + // The Tailscale server, which handles incoming connections and requests. + server *ipnserver.Server + + // The Tailscale backend, which manages the local state and operations. + backend *ipnlocal.LocalBackend +} + +// NewTsWasmIpn initializes a new TsWasmIpn instance with the provided options. +// This intentionally does not initialize Logtail, as it is only available in +// the Tailscale SaaS and not on self-hosted instances. +func NewTsWasmIpn(options *TsWasmNetOptions, callbacks *TsWasmNetCallbacks) (*TsWasmIpn, error) { + logf := log.Printf // TODO: Update + + netns.SetEnabled(false) // netns is a separate process (not WASM friendly) + + // Base system (NewSystem() creates a bus automatically) + // We supply an in-memory store + sys := tsd.NewSystem() + bus := sys.Bus.Get() + sys.Set(new(mem.Store)) + + dialer := &tsdial.Dialer{Logf: logf} + netmon, err := netmon.New(bus, logf) + if err != nil { + return nil, err + } + + // Userspace Wireguard engine + engine, err := wgengine.NewUserspaceEngine(logf, wgengine.Config{ + Dialer: dialer, + NetMon: netmon, + SetSubsystem: sys.Set, + ControlKnobs: sys.ControlKnobs(), + HealthTracker: sys.HealthTracker(), + Metrics: sys.UserMetricsRegistry(), + }) + + sys.Set(engine) + if err != nil { + return nil, err + } + + tun := sys.Tun.Get() + msock := sys.MagicSock.Get() + dnsman := sys.DNSManager.Get() + proxymap := sys.ProxyMapper() + wgstack, err := netstack.Create(logf, tun, engine, msock, dialer, dnsman, proxymap) + + sys.Set(wgstack) + if err != nil { + return nil, err + } + + // Configure the local Netstack and Dialer + wgstack.ProcessLocalIPs = true + wgstack.ProcessSubnets = true + sys.NetstackRouter.Set(true) + + dialer.UseNetstackForIP = func(ip netip.Addr) bool { + return true + } + + dialer.NetstackDialTCP = func(ctx context.Context, dst netip.AddrPort) (net.Conn, error) { + return wgstack.DialContextTCP(ctx, dst) + } + + dialer.NetstackDialUDP = func(ctx context.Context, dst netip.AddrPort) (net.Conn, error) { + return wgstack.DialContextUDP(ctx, dst) + } + + // Dummy logid for the Tailscale backend + logid := logid.PublicID{} + tun.Start() + + server := ipnserver.New(logf, logid, sys.NetMon.Get()) + flags := controlclient.LoginDefault | controlclient.LoginEphemeral | controlclient.LocalBackendStartKeyOSNeutral + + backend, err := ipnlocal.NewLocalBackend(logf, logid, sys, flags) + if err != nil { + return nil, err + } + + err = wgstack.Start(backend) + if err != nil { + return nil, err + } + + server.SetLocalBackend(backend) + registerNotifyCallback(callbacks, backend) + + return &TsWasmIpn{ + options: options, + dialer: dialer, + server: server, + backend: backend, + }, nil +} + +// Starts the WASM backend which will connect to the Tailscale tailnet and +// register an ephemeral node viewable in the Tailscale admin console. +func (t *TsWasmIpn) Start(ctx context.Context) error { + // Blank "socket" is a requirement for WASM + // This NEEDS to happen before the LocalBackend is started, + listener, err := safesocket.Listen("") + if err != nil { + return fmt.Errorf("failed to create safesocket listener: %w", err) + } + + // Start the server BEFORE the LocalBackend is started + go func() { + err := t.server.Run(ctx, listener) + if err != nil { + // TODO: Handle this dispatch using a chan + log.Printf("Failed to run Tailscale server: %v", err) + } + }() + + // Start the LocalBackend + err = t.backend.Start(ipn.Options{ + AuthKey: t.options.PreAuthKey, + UpdatePrefs: &ipn.Prefs{ + ControlURL: t.options.ControlURL, + Hostname: t.options.Hostname, + WantRunning: true, + RunWebClient: false, + LoggedOut: false, + }, + }) + + if err != nil { + return fmt.Errorf("failed to start Tailscale backend: %w", err) + } + + log.Printf("Tailscale backend started successfully with hostname: %s", t.options.Hostname) + return nil +} diff --git a/internal/hp_ipn/jsfunc.go b/internal/hp_ipn/jsfunc.go new file mode 100644 index 0000000..70bb838 --- /dev/null +++ b/internal/hp_ipn/jsfunc.go @@ -0,0 +1,100 @@ +//go:build js && wasm + +package hp_ipn + +import ( + "errors" + "fmt" + "syscall/js" + + "tailscale.com/ipn" + "tailscale.com/types/netmap" +) + +// Maps ipn.State values to their string representations for the frontend. +var BackendState = map[ipn.State]string{ + ipn.NoState: "NoState", + ipn.Stopped: "Stopped", + ipn.Starting: "Starting", + ipn.Running: "Running", + ipn.InUseOtherUser: "InUseOtherUser", + ipn.NeedsMachineAuth: "NeedsMachineAuth", + ipn.NeedsLogin: "NeedsLogin", +} + +// Represents the callbacks that the TsWasmNet module can invoke to register +// data retrieval and notifications on the frontend. +type TsWasmNetCallbacks struct { + // Changes in the backend state. + NotifyState func(ipn.State) + + // Updates to the backend's network map. + NotifyNetMap func(*netmap.NetworkMap) + + // If interactive login is required, this passes a login URL. + NotifyBrowseToURL func(string) + + // If the process panics, this function is called in go.recover. + NotifyPanicRecover func(string) +} + +// Parses a JavaScript object containing the necessary callbacks for the +// TsWasmNet module to properly interact with the frontend. +func ParseTsWasmNetCallbacks(obj js.Value) (*TsWasmNetCallbacks, error) { + if obj.IsUndefined() || obj.IsNull() { + return nil, errors.New("callbacks object is undefined or null") + } + + state, err := validateCallback("NotifyState", obj) + if err != nil { + return nil, fmt.Errorf("invalid callback NotifyState: %w", err) + } + + // TODO: This is complicated, as the NetworkMap is a complex type. + _, err = validateCallback("NotifyNetMap", obj) + if err != nil { + return nil, fmt.Errorf("invalid callback NotifyNetMap: %w", err) + } + + browseURL, err := validateCallback("NotifyBrowseToURL", obj) + if err != nil { + return nil, fmt.Errorf("invalid callback NotifyBrowseToURL: %w", err) + } + + panicRecover, err := validateCallback("NotifyPanicRecover", obj) + if err != nil { + return nil, fmt.Errorf("invalid callback NotifyPanicRecover: %w", err) + } + + return &TsWasmNetCallbacks{ + NotifyState: func(ipnState ipn.State) { + state.Invoke(BackendState[ipnState]) + }, + + NotifyNetMap: func(nm *netmap.NetworkMap) { + // TODO: This is complicated + }, + + NotifyBrowseToURL: func(url string) { + browseURL.Invoke(url) + }, + + NotifyPanicRecover: func(msg string) { + panicRecover.Invoke(msg) + }, + }, nil +} + +// Validates the specified key is a JS function and returns it. +func validateCallback(key string, obj js.Value) (*js.Value, error) { + val := obj.Get(key) + if val.IsUndefined() || val.IsNull() { + return nil, errors.New("callback is undefined or null") + } + + if val.Type() != js.TypeFunction { + return nil, errors.New("callback is not a function") + } + + return &val, nil +} diff --git a/internal/hp_ipn/jsmap.go b/internal/hp_ipn/jsmap.go new file mode 100644 index 0000000..f0ab34f --- /dev/null +++ b/internal/hp_ipn/jsmap.go @@ -0,0 +1,55 @@ +//go:build js && wasm + +package hp_ipn + +import ( + "errors" + "syscall/js" +) + +// Represents the options needed to initialize the TsWasmNet module. +type TsWasmNetOptions struct { + // Tailscale control URL, e.g., "https://controlplane.tailscale.com" + ControlURL string + + // Pre-authentication key for the Tailnet + PreAuthKey string + + // Optional hostname, autogenerated if not provided. + Hostname string +} + +// Parses the provided JS object to validate and extract the TsWasmNetOptions. +func ParseTsWasmNetOptions(obj js.Value) (*TsWasmNetOptions, error) { + if obj.IsUndefined() || obj.IsNull() { + return nil, errors.New("TsWasmNetOptions cannot be undefined or null") + } + + cUrl := safeString("ControlURL", obj) + preAuthKey := safeString("PreAuthKey", obj) + hostname := safeString("Hostname", obj) + + if cUrl == "" || preAuthKey == "" || hostname == "" { + return nil, errors.New("missing required fields in TsWasmNetOptions") + } + + return &TsWasmNetOptions{ + ControlURL: cUrl, + PreAuthKey: preAuthKey, + Hostname: hostname, + }, nil +} + +// Retrieves a string value from a JS object safely. +func safeString(key string, obj js.Value) string { + if obj.IsUndefined() || obj.IsNull() { + return "" + } + + val := obj.Get(key) + if val.IsUndefined() || val.IsNull() { + return "" + } + + return val.String() +} diff --git a/internal/hp_ipn/notify.go b/internal/hp_ipn/notify.go new file mode 100644 index 0000000..4c66de2 --- /dev/null +++ b/internal/hp_ipn/notify.go @@ -0,0 +1,95 @@ +//go:build js && wasm + +package hp_ipn + +import ( + "context" + "fmt" + "log" + + "tailscale.com/ipn" + "tailscale.com/ipn/ipnlocal" +) + +func registerNotifyCallback(callbacks *TsWasmNetCallbacks, lb *ipnlocal.LocalBackend) { + lb.SetNotifyCallback(func(n ipn.Notify) { + // Panics should be treated with care in a JS/wasm environment. + // If a panic occurs, notify the user and either automatically reload + // or give the option to reload. + + defer func() { + rec := recover() + if rec != nil { + callbacks.NotifyPanicRecover(fmt.Sprint(rec)) + } + }() + + if n.State != nil { + callbacks.NotifyState(*n.State) + + if *n.State == ipn.NeedsLogin { + // If the state is NeedsLogin, we need to force an interactive login. + go forceInteractiveLogin(lb) + } + } + + if n.BrowseToURL != nil { + callbacks.NotifyBrowseToURL(*n.BrowseToURL) + } + + log.Printf("NOTIFY: %+v", n) + + // if nm := n.NetMap; nm != nil { + // jsNetMap := jsNetMap{ + // Self: jsNetMapSelfNode{ + // jsNetMapNode: jsNetMapNode{ + // Name: nm.Name, + // Addresses: mapSliceView(nm.GetAddresses(), func(a netip.Prefix) string { return a.Addr().String() }), + // NodeKey: nm.NodeKey.String(), + // MachineKey: nm.MachineKey.String(), + // }, + // MachineStatus: jsMachineStatus[nm.GetMachineStatus()], + // }, + // Peers: mapSlice(nm.Peers, func(p tailcfg.NodeView) jsNetMapPeerNode { + // name := p.Name() + // if name == "" { + // // In practice this should only happen for Hello. + // name = p.Hostinfo().Hostname() + // } + // addrs := make([]string, p.Addresses().Len()) + // for i, ap := range p.Addresses().All() { + // addrs[i] = ap.Addr().String() + // } + // return jsNetMapPeerNode{ + // jsNetMapNode: jsNetMapNode{ + // Name: name, + // Addresses: addrs, + // MachineKey: p.Machine().String(), + // NodeKey: p.Key().String(), + // }, + // Online: p.Online().Clone(), + // TailscaleSSHEnabled: p.Hostinfo().TailscaleSSHEnabled(), + // } + // }), + // LockedOut: nm.TKAEnabled && nm.SelfNode.KeySignature().Len() == 0, + // } + // if jsonNetMap, err := json.Marshal(jsNetMap); err == nil { + // jsCallbacks.Call("notifyNetMap", string(jsonNetMap)) + // } else { + // log.Printf("Could not generate JSON netmap: %v", err) + // } + // } + // if n.BrowseToURL != nil { + // jsCallbacks.Call("notifyBrowseToURL", *n.BrowseToURL) + // } + }) +} + +// To get auth to work, even with a pre-auth key, we need to +// force an interactive login on the NeedsLogin state. +func forceInteractiveLogin(lb *ipnlocal.LocalBackend) { + err := lb.StartLoginInteractive(context.Background()) + if err != nil { + fmt.Printf("Error starting interactive login: %v\n", err) + } +} diff --git a/agent/internal/hpagent/handler.go b/internal/hpagent/handler.go similarity index 79% rename from agent/internal/hpagent/handler.go rename to internal/hpagent/handler.go index 61667d6..7bb48e7 100644 --- a/agent/internal/hpagent/handler.go +++ b/internal/hpagent/handler.go @@ -49,38 +49,38 @@ func FollowMaster(agent *tsnet.TSAgent) { log.Debug("Received message from master: %s", msg) switch msg.Op { - case "ssh_conn": - var sshPayload sshutil.SSHConnectPayload - err = cbor.Unmarshal(msg.Payload, &sshPayload) - if err != nil { - log.Error("Unable to unmarshal SSH connect payload: %s", err) - continue - } - - sshutil.StartWebSSH(agent, sshPayload) + case "ssh_conn": + var sshPayload sshutil.SSHConnectPayload + err = cbor.Unmarshal(msg.Payload, &sshPayload) + if err != nil { + log.Error("Unable to unmarshal SSH connect payload: %s", err) continue + } - case "ssh_term": - var sshPayload sshutil.SSHClosePayload - err = cbor.Unmarshal(msg.Payload, &sshPayload) - if err != nil { - log.Error("Unable to unmarshal SSH close payload: %s", err) - continue - } + sshutil.StartWebSSH(agent, sshPayload) + continue - sshutil.CloseWebSSH(agent, sshPayload) + case "ssh_term": + var sshPayload sshutil.SSHClosePayload + err = cbor.Unmarshal(msg.Payload, &sshPayload) + if err != nil { + log.Error("Unable to unmarshal SSH close payload: %s", err) continue + } - case "ssh_resize": - var sshPayload sshutil.SSHResizePayload - err = cbor.Unmarshal(msg.Payload, &sshPayload) - if err != nil { - log.Error("Unable to unmarshal SSH resize payload: %s", err) - continue - } + sshutil.CloseWebSSH(agent, sshPayload) + continue - sshutil.ResizeWebSSH(agent, sshPayload) + case "ssh_resize": + var sshPayload sshutil.SSHResizePayload + err = cbor.Unmarshal(msg.Payload, &sshPayload) + if err != nil { + log.Error("Unable to unmarshal SSH resize payload: %s", err) continue + } + + sshutil.ResizeWebSSH(agent, sshPayload) + continue } } diff --git a/agent/internal/config/preflight.go b/internal/preflight.go similarity index 100% rename from agent/internal/config/preflight.go rename to internal/preflight.go diff --git a/agent/internal/sshutil/connect.go b/internal/sshutil/connect.go similarity index 100% rename from agent/internal/sshutil/connect.go rename to internal/sshutil/connect.go diff --git a/agent/internal/sshutil/encoder.go b/internal/sshutil/encoder.go similarity index 100% rename from agent/internal/sshutil/encoder.go rename to internal/sshutil/encoder.go diff --git a/internal/sshutil/framebatcher.go b/internal/sshutil/framebatcher.go new file mode 100644 index 0000000..6653452 --- /dev/null +++ b/internal/sshutil/framebatcher.go @@ -0,0 +1,61 @@ +package sshutil + +import ( + "io" + "sync" + "time" + + "github.com/tale/headplane/agent/internal/util" +) + +type FrameBatcher struct { + mu sync.Mutex + buffer []byte + writer io.Writer + timer *time.Timer + interval time.Duration + done chan struct{} +} + +func NewFrameBatcher(writer io.Writer, interval time.Duration) *FrameBatcher { + return &FrameBatcher{ + writer: writer, + interval: interval, + done: make(chan struct{}), + } +} + +func (b *FrameBatcher) QueueMsg(msg []byte) { + b.mu.Lock() + defer b.mu.Unlock() + + b.buffer = append(b.buffer, msg...) + if b.timer != nil { + b.timer.Stop() + } + + b.timer = time.AfterFunc(b.interval, b.flush) +} + +func (b *FrameBatcher) flush() { + log := util.GetLogger() + + b.mu.Lock() + defer b.mu.Unlock() + + if len(b.buffer) == 0 { + return + } + + _, err := b.writer.Write(b.buffer) + if err != nil { + log.Error("Failed to write batched message: %v", err) + } + + b.buffer = nil +} + +func (b *FrameBatcher) Close() { + close(b.done) + b.flush() +} diff --git a/agent/internal/sshutil/handler.go b/internal/sshutil/handler.go similarity index 89% rename from agent/internal/sshutil/handler.go rename to internal/sshutil/handler.go index e9b64ff..63b90c9 100644 --- a/agent/internal/sshutil/handler.go +++ b/internal/sshutil/handler.go @@ -3,6 +3,7 @@ package sshutil import ( "io" "os" + "time" "github.com/tale/headplane/agent/internal/util" ) @@ -92,17 +93,19 @@ func dispatchSSHStdout(id string, stdout io.Reader, stderr io.Reader) { return } + batcher := NewFrameBatcher(fd, 10*time.Millisecond) // Roughly 60fps + go readerStreamRoutine(StreamRoutine{ SessionID: id, Reader: stdout, - Writer: fd, + Writer: batcher, ChannelType: ChannelTypeStdout, }) go readerStreamRoutine(StreamRoutine{ SessionID: id, Reader: stderr, - Writer: fd, + Writer: batcher, ChannelType: ChannelTypeStderr, }) } @@ -110,13 +113,13 @@ func dispatchSSHStdout(id string, stdout io.Reader, stderr io.Reader) { type StreamRoutine struct { SessionID string Reader io.Reader - Writer io.Writer + Writer *FrameBatcher ChannelType ChannelType } func readerStreamRoutine(routine StreamRoutine) { hpls1 := HPLSFrame1{} - buf := make([]byte, 1024) + buf := make([]byte, 16384) // 16 KiB buffer for { byteCount, err := routine.Reader.Read(buf) if err != nil { @@ -138,9 +141,12 @@ func readerStreamRoutine(routine StreamRoutine) { continue } - if _, err := routine.Writer.Write(frame); err != nil { - util.GetLogger().Error("Failed to write frame to writer: %v", err) - break - } + // if _, err := routine.Writer.Write(frame); err != nil { + // util.GetLogger().Error("Failed to write frame to writer: %v", err) + // break + // } + // + + routine.Writer.QueueMsg(frame) } } diff --git a/agent/internal/sshutil/sessions.go b/internal/sshutil/sessions.go similarity index 100% rename from agent/internal/sshutil/sessions.go rename to internal/sshutil/sessions.go diff --git a/agent/internal/tsnet/peers.go b/internal/tsnet/peers.go similarity index 100% rename from agent/internal/tsnet/peers.go rename to internal/tsnet/peers.go diff --git a/agent/internal/tsnet/server.go b/internal/tsnet/server.go similarity index 100% rename from agent/internal/tsnet/server.go rename to internal/tsnet/server.go diff --git a/agent/internal/util/logger.go b/internal/util/logger.go similarity index 100% rename from agent/internal/util/logger.go rename to internal/util/logger.go diff --git a/mise.toml b/mise.toml index f92272c..f69d05e 100644 --- a/mise.toml +++ b/mise.toml @@ -6,8 +6,8 @@ alias = ["gojs"] description = "Copies Go's wasm_exec.js to the public directory" run = [ - "echo $(go version) > public/wasm_exec.js", - "cat $(go env GOROOT)/lib/wasm/wasm_exec.js >> public/wasm_exec.js", + "echo // $(go version) > app/wasm_exec.js", + "cat $(go env GOROOT)/lib/wasm/wasm_exec.js >> app/wasm_exec.js", ] [tasks.build-go-wasm] @@ -15,7 +15,7 @@ alias = ["wasm"] depends = ["copy-wasm-shim"] description = "Builds the Go WebAssembly module for Tailscale SSH" env = { GOOS = "js", GOARCH = "wasm" } -run = "go build -o public/hp_ssh.wasm ./cmd/hp_ssh" +run = "go build -o app/hp_ssh.wasm ./cmd/hp_ssh" [tasks.generate-caddy-certs] alias = ["mkcert"] diff --git a/package.json b/package.json index a706153..80c079f 100644 --- a/package.json +++ b/package.json @@ -16,6 +16,7 @@ "@dnd-kit/modifiers": "^7.0.0", "@dnd-kit/sortable": "^8.0.0", "@dnd-kit/utilities": "^3.2.2", + "@faker-js/faker": "^9.8.0", "@fontsource-variable/inter": "^5.2.5", "@kubernetes/client-node": "^1.3.0", "@primer/octicons-react": "^19.15.2", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 629e533..7da93f0 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -25,6 +25,9 @@ importers: '@dnd-kit/utilities': specifier: ^3.2.2 version: 3.2.2(react@19.1.0) + '@faker-js/faker': + specifier: ^9.8.0 + version: 9.8.0 '@fontsource-variable/inter': specifier: ^5.2.5 version: 5.2.5 @@ -611,6 +614,10 @@ packages: cpu: [x64] os: [win32] + '@faker-js/faker@9.8.0': + resolution: {integrity: sha512-U9wpuSrJC93jZBxx/Qq2wPjCuYISBueyVUGK7qqdmj7r/nxaxwW8AQDCLeRO7wZnjj94sh3p246cAYjUKuqgfg==} + engines: {node: '>=18.0.0', npm: '>=9.0.0'} + '@fontsource-variable/inter@5.2.5': resolution: {integrity: sha512-TrWffUAFOnT8zroE9YmGybagoOgM/HjRqMQ8k9R0vVgXlnUh/vnpbGPAS/Caz1KIlOPnPGh6fvJbb7DHbFCncA==} @@ -3306,6 +3313,8 @@ snapshots: '@esbuild/win32-x64@0.25.5': optional: true + '@faker-js/faker@9.8.0': {} + '@fontsource-variable/inter@5.2.5': {} '@formatjs/ecma402-abstract@2.3.4': From f4af5b920dfd18533c7e862b7fd7c937ddfe9b9e Mon Sep 17 00:00:00 2001 From: Aarnav Tale Date: Mon, 9 Jun 2025 17:46:09 -0400 Subject: [PATCH 10/97] feat: cleanup and streamline webssh capability --- app/routes/ssh/console.tsx | 240 ++++++------ app/routes/ssh/hp_ssh.d.ts | 42 ++- app/routes/ssh/xterm.client.tsx | 152 +++----- cmd/hp_ssh/hp_ssh.go | 59 +++ internal/hp_ipn/ipn.go | 633 -------------------------------- internal/hp_ipn/ipnserver.go | 4 +- internal/hp_ipn/jsmap.go | 137 +++++++ internal/hp_ipn/ssh.go | 184 ++++++++++ 8 files changed, 589 insertions(+), 862 deletions(-) delete mode 100644 internal/hp_ipn/ipn.go create mode 100644 internal/hp_ipn/ssh.go diff --git a/app/routes/ssh/console.tsx b/app/routes/ssh/console.tsx index bdbc51e..010bdbc 100644 --- a/app/routes/ssh/console.tsx +++ b/app/routes/ssh/console.tsx @@ -1,82 +1,95 @@ import { faker } from '@faker-js/faker'; -import { useState } from 'react'; -import { LoaderFunctionArgs } from 'react-router'; -import { LoadContext } from '~/server'; - import { Loader2 } from 'lucide-react'; +import { useEffect, useState } from 'react'; +import { + LoaderFunctionArgs, + ShouldRevalidateFunction, + data, + useLoaderData, +} from 'react-router'; +import { LoadContext } from '~/server'; +import { PreAuthKey, User } from '~/types'; +import { useLiveData } from '~/utils/live-data'; +import XTerm from './xterm.client'; + +import wasm from '~/hp_ssh.wasm?url'; import '~/wasm_exec'; +export const shouldRevalidate: ShouldRevalidateFunction = () => { + return false; +}; + export async function loader({ request, context, }: LoaderFunctionArgs) { - // const session = await context.sessions.auth(request); - // const user = session.get('user'); - // if (!user) { - // throw data('Unauthorized', 401); - // } - // if (user.subject === 'unknown-non-oauth') { - // throw data('Only OAuth users are allowed to use WebSSH', 403); - // } - // const { users } = await context.client.get<{ users: User[] }>( - // 'v1/user', - // session.get('api_key')!, + const session = await context.sessions.auth(request); + const user = session.get('user'); + if (!user) { + throw data('Unauthorized', 401); + } + if (user.subject === 'unknown-non-oauth') { + throw data('Only OAuth users are allowed to use WebSSH', 403); + } + const { users } = await context.client.get<{ users: User[] }>( + 'v1/user', + session.get('api_key')!, + ); + // MARK: This assumes that a user has authenticated with Headscale first + // Since the only way to enforce permissions via ACLs is to generate a + // pre-authkey which REQUIRES a user ID, meaning the user has to have + // authenticated with Headscale first. + const lookup = users.find((u) => { + const subject = u.providerId?.split('/').pop(); + if (!subject) { + return false; + } + return subject === user.subject; + }); + if (!lookup) { + throw data( + `User with subject ${user.subject} not found within Headscale`, + 404, + ); + } + const { preAuthKey } = await context.client.post<{ preAuthKey: PreAuthKey }>( + 'v1/preauthkey', + session.get('api_key')!, + { + user: lookup.id, + reusable: false, + ephemeral: true, + expiration: new Date(Date.now() + 60 * 60 * 1000).toISOString(), // 1 hour + }, + ); + // TODO: Enable config to enforce generate_authkeys capability + // For now, any user is capable of WebSSH connections + // const check = await context.sessions.check( + // request, + // Capabilities.generate_authkeys, // ); - // // MARK: This assumes that a user has authenticated with Headscale first - // // Since the only way to enforce permissions via ACLs is to generate a - // // pre-authkey which REQUIRES a user ID, meaning the user has to have - // // authenticated with Headscale first. - // const lookup = users.find((u) => { - // const subject = u.providerId?.split('/').pop(); - // if (!subject) { - // return false; - // } - // return subject === user.subject; - // }); - // if (!lookup) { - // throw data( - // `User with subject ${user.subject} not found within Headscale`, - // 404, - // ); - // } - // const { preAuthKey } = await context.client.post<{ preAuthKey: PreAuthKey }>( - // 'v1/preauthkey', - // session.get('api_key')!, - // { - // user: lookup.id, - // reusable: false, - // ephemeral: true, - // expiration: new Date(Date.now() + 60 * 60 * 1000).toISOString(), // 1 hour - // }, - // ); - // // TODO: Enable config to enforce generate_authkeys capability - // // For now, any user is capable of WebSSH connections - // // const check = await context.sessions.check( - // // request, - // // Capabilities.generate_authkeys, - // // ); - // const qp = new URL(request.url).searchParams; - // const username = qp.get('username') || undefined; - // const hostname = qp.get('hostname') || undefined; - // const port = qp.get('port') - // ? Number.parseInt(qp.get('port')!, 10) - // : undefined; - // if (!username || !hostname || !port) { - // throw data('Missing required parameters: username, hostname, port', 400); - // } - // // TODO: Verify the Host headers to ensure CORS friendly - // // TODO: Check if the URL actually resolves correctly - // // TODO: Keep track of hostname since ephemeral nodes are broken atm - // return { - // PreAuthKey: preAuthKey.key, - // ControlURL: - // context.config.headscale.public_url ?? context.config.headscale.url, - // Hostname: generateHostname(username), - // ssh: { - // username, - // hostname, - // }, - // }; + const qp = new URL(request.url).searchParams; + const username = qp.get('username') || undefined; + const hostname = qp.get('hostname') || undefined; + const port = qp.get('port') + ? Number.parseInt(qp.get('port')!, 10) + : undefined; + if (!username || !hostname || !port) { + throw data('Missing required parameters: username, hostname, port', 400); + } + // TODO: Verify the Host headers to ensure CORS friendly + // TODO: Check if the URL actually resolves correctly + // TODO: Keep track of hostname since ephemeral nodes are broken atm + return { + PreAuthKey: preAuthKey.key, + ControlURL: + context.config.headscale.public_url ?? context.config.headscale.url, + Hostname: generateHostname(username), + ssh: { + username, + hostname, + }, + }; } function generateHostname(username: string) { @@ -98,46 +111,46 @@ function generateHostname(username: string) { } export default function Page() { - // const { pause } = useLiveData(); + const { pause } = useLiveData(); const [ipn, setIpn] = useState(null); - // const { PreAuthKey, ControlURL, Hostname, ssh } = - // useLoaderData(); + const { PreAuthKey, ControlURL, Hostname, ssh } = + useLoaderData(); - // useEffect(() => { - // pause(); - // const go = new Go(); // Go is defined by wasm_exec.js - // WebAssembly.instantiateStreaming(fetch(wasm), go.importObject).then( - // (value) => { - // go.run(value.instance); - // const handle = TsWasmNet( - // { - // PreAuthKey, - // ControlURL, - // Hostname, - // }, - // { - // NotifyState: (state) => { - // console.log('State changed:', state); - // if (state === 'Running') { - // setIpn(handle); - // } - // }, - // NotifyNetMap: (netmap) => { - // console.log('NetMap updated:', netmap); - // }, - // NotifyBrowseToURL: (url) => { - // console.log('Browse to URL:', url); - // }, - // NotifyPanicRecover: (message) => { - // console.error('Panic recover:', message); - // }, - // }, - // ); + useEffect(() => { + pause(); + const go = new Go(); // Go is defined by wasm_exec.js + WebAssembly.instantiateStreaming(fetch(wasm), go.importObject).then( + (value) => { + go.run(value.instance); + const handle = TsWasmNet( + { + PreAuthKey, + ControlURL, + Hostname, + }, + { + NotifyState: (state) => { + console.log('State changed:', state); + if (state === 'Running') { + setIpn(handle); + } + }, + NotifyNetMap: (netmap) => { + console.log('NetMap updated:', netmap); + }, + NotifyBrowseToURL: (url) => { + console.log('Browse to URL:', url); + }, + NotifyPanicRecover: (message) => { + console.error('Panic recover:', message); + }, + }, + ); - // handle.Start(); - // }, - // ); - // }, []); + handle.Start(); + }, + ); + }, []); return (
@@ -147,18 +160,7 @@ export default function Page() {
) : (
- {/*

Session ID: {sessionId}

- {queue.current.length > 0 && ( -

- {queue.current.length} frames queued -

- )} */} - - {/* */} -
- {/* Render your terminal component here */} - {/* */} -
+
)}
diff --git a/app/routes/ssh/hp_ssh.d.ts b/app/routes/ssh/hp_ssh.d.ts index 4cf22e7..46b7df9 100644 --- a/app/routes/ssh/hp_ssh.d.ts +++ b/app/routes/ssh/hp_ssh.d.ts @@ -1,4 +1,3 @@ -declare function newIPN(config: NewIPNConfig): IPNHandle; declare function TsWasmNet( options: TsWasmNetOptions, callbacks: TsWasmNetCallbacks, @@ -19,6 +18,11 @@ interface TsWasmNetCallbacks { interface TsWasmNet { Start: () => void; + OpenSSH: ( + hostname: string, + username: string, + options: XtermConfig, + ) => SSHSession; } type IPNState = @@ -30,19 +34,31 @@ type IPNState = | 'Starting' | 'Running'; -interface SSHTerminalConfig { - writeFn: (data: string) => void; - writeErrorFn: (error: string) => void; - setReadFn: (cb: (input: string) => void) => void; - rows: number; - cols: number; - timeoutSeconds?: number; - onConnectionProgress: (message: string) => void; - onConnected: () => void; - onDone: () => void; +interface XtermConfig { + Rows: number; + Cols: number; + + OnStdout: (data: string) => void; + OnStderr: (data: string) => void; + OnStdin: (func: (input: string) => void) => void; + + OnConnect: () => void; + OnDisconnect: () => void; } +// interface SSHTerminalConfig { +// writeFn: (data: string) => void; +// writeErrorFn: (error: string) => void; +// setReadFn: (cb: (input: string) => void) => void; +// rows: number; +// cols: number; +// timeoutSeconds?: number; +// onConnectionProgress: (message: string) => void; +// onConnected: () => void; +// onDone: () => void; +// } + interface SSHSession { - close(): boolean; - resize(rows: number, cols: number): boolean; + Close(): boolean; + Resize(rows: number, cols: number): boolean; } diff --git a/app/routes/ssh/xterm.client.tsx b/app/routes/ssh/xterm.client.tsx index daa9a7e..d726fed 100644 --- a/app/routes/ssh/xterm.client.tsx +++ b/app/routes/ssh/xterm.client.tsx @@ -4,36 +4,30 @@ import { Unicode11Addon } from '@xterm/addon-unicode11'; import { WebLinksAddon } from '@xterm/addon-web-links'; import { WebglAddon } from '@xterm/addon-webgl'; import * as xterm from '@xterm/xterm'; -import { useEffect, useRef, useState } from 'react'; import '@xterm/xterm/css/xterm.css'; -import { decode, encode } from 'cborg'; -import type { - SSHDataCommand, - SSHFrameData, - SSHResizeCommand, -} from '~/server/agent/dispatcher'; +import { useEffect, useRef, useState } from 'react'; import cn from '~/utils/cn'; import { useLiveData } from '~/utils/live-data'; interface XTermProps { - ws: WebSocket; - sessionId: string; - queue: Array; + ipn: TsWasmNet; + username: string; + hostname: string; } const RED = new TextEncoder().encode('\x1b[31m'); const RESET = new TextEncoder().encode('\x1b[0m'); -export default function XTerm({ ws, sessionId, queue }: XTermProps) { +export default function XTerm({ ipn, username, hostname }: XTermProps) { const { pause } = useLiveData(); const container = useRef(null); const term = useRef(null); const [isResizing, setIsResizing] = useState(false); + const inputRef = useRef<((input: string) => void) | null>(null); useEffect(() => { pause(); - const terminal = new xterm.Terminal({ allowProposedApi: true, cursorBlink: true, @@ -49,9 +43,13 @@ export default function XTerm({ ws, sessionId, queue }: XTermProps) { terminal.loadAddon(new Unicode11Addon()); terminal.loadAddon(new ClipboardAddon()); - terminal.loadAddon(new WebLinksAddon()); - terminal.unicode.activeVersion = '11'; + terminal.loadAddon( + new WebLinksAddon((event, uri) => { + event.view?.open(uri, '_blank', 'noopener noreferrer'); + }), + ); + terminal.unicode.activeVersion = '11'; const gl = new WebglAddon(); terminal.loadAddon(gl); @@ -67,97 +65,61 @@ export default function XTerm({ ws, sessionId, queue }: XTermProps) { terminal.focus(); term.current = terminal; - const handleFrame = (data: Uint8Array) => { - try { - const frame: SSHFrameData = decode(data); - if (frame.op !== 'ssh_frame') { - console.warn('Received unexpected frame type:', frame.op); - return; + let ro: ResizeObserver | null = null; + let onUnload: ((e: Event) => void) | null = null; + + const session = ipn.OpenSSH(hostname, username, { + Rows: terminal.rows, + Cols: terminal.cols, + + OnStdout: (data) => terminal.write(data), + OnStderr: (data) => { + terminal.write(data); + console.log('SSH stderr:', data); + }, + OnStdin: (func) => { + inputRef.current = func; + }, + OnConnect: () => { + console.log('SSH session connected'); + }, + + OnDisconnect: () => { + ro?.disconnect(); + terminal.dispose(); + if (onUnload) { + parent.removeEventListener('unload', onUnload); } - // If this is stderr, color it red - if (frame.payload.channel === 2) { - terminal.write( - new Uint8Array([...RED, ...frame.payload.frame, ...RESET]), - ); - } else { - terminal.write(frame.payload.frame); - } - } catch (err) { - console.error('Failed to decode CBOR frame:', err); + console.log('SSH session disconnected'); + term.current = null; + }, + }); + + const parent = container.current?.ownerDocument.defaultView ?? window; + ro = new parent.ResizeObserver(() => { + if (term.current) { + setIsResizing(true); + fit.fit(); + setTimeout(() => setIsResizing(false), 100); } - }; + }); - for (const buffer of queue) { - handleFrame(buffer); + if (container.current) { + ro.observe(container.current); } - terminal.onData((input) => { - if (ws.readyState === WebSocket.OPEN) { - ws.send( - encode({ - op: 'ssh_data', - payload: { - sessionId, - data: new TextEncoder().encode(input), - }, - } satisfies SSHDataCommand), - ); - } else { - console.warn('WebSocket is not open, cannot send data'); - } + terminal.onResize(({ cols, rows }) => { + session.Resize(rows, cols); }); - const onMessage = (event: MessageEvent) => { - if (!(event.data instanceof ArrayBuffer)) { - console.warn('Received non-binary message from WebSocket'); - return; - } - - const size = event.data.byteLength; - console.log(`[WS] Received ${size} bytes at ${Date.now()}`); - - const data = new Uint8Array(event.data); - handleFrame(data); - }; - - ws.addEventListener('message', onMessage); - const ro = new ResizeObserver(() => { - const before = { - cols: terminal.cols, - rows: terminal.rows, - }; - - fit.fit(); - if (before.cols !== terminal.cols || before.rows !== terminal.rows) { - console.log( - `Resized terminal to ${terminal.cols} cols and ${terminal.rows} rows`, - ); - ws.send( - encode({ - op: 'ssh_resize', - payload: { - sessionId, - width: terminal.cols, - height: terminal.rows, - }, - } satisfies SSHResizeCommand), - ); - - setIsResizing(true); - setTimeout(() => { - setIsResizing(false); - }, 1000); - } + terminal.onData((data) => { + inputRef.current?.(data); }); - ro.observe(container.current!); - return () => { - ws.removeEventListener('message', onMessage); - term.current?.dispose(); - ro.disconnect(); - }; - }, [ws, queue]); + onUnload = (_) => session.Close(); + parent.addEventListener('unload', onUnload); + }, []); return (
diff --git a/cmd/hp_ssh/hp_ssh.go b/cmd/hp_ssh/hp_ssh.go index e243daa..bee7ce1 100644 --- a/cmd/hp_ssh/hp_ssh.go +++ b/cmd/hp_ssh/hp_ssh.go @@ -41,6 +41,65 @@ func main() { ipn.Start(context.Background()) return nil }), + "OpenSSH": js.FuncOf(func(this js.Value, args []js.Value) any { + if len(args) != 3 { + log.Fatal("Usage: OpenSSH(host, user, options)") + return nil + } + + hostname := args[0] + if hostname.IsNull() || hostname.IsUndefined() { + log.Fatal("Hostname must be a non-null, non-undefined string") + return nil + } + + if hostname.Type() != js.TypeString { + log.Fatal("Hostname must be a string") + return nil + } + + username := args[1] + if username.IsNull() || username.IsUndefined() { + log.Fatal("Username must be a non-null, non-undefined string") + return nil + } + + if username.Type() != js.TypeString { + log.Fatal("Username must be a string") + return nil + } + + sshOptions, err := hp_ipn.ParseSSHXtermConfig(args[2]) + if err != nil { + log.Fatal("Error parsing SSH options:", err) + return nil + } + + session := ipn.NewSSHSession(hostname.String(), username.String(), sshOptions) + go session.ConnectAndRun() + + return map[string]any{ + "Close": js.FuncOf(func(this js.Value, args []js.Value) any { + return session.Close() != nil + }), + + "Resize": js.FuncOf(func(this js.Value, args []js.Value) any { + if len(args) != 2 { + log.Fatal("Usage: Resize(cols, rows)") + return nil + } + + rows := args[0].Int() + cols := args[1].Int() + if cols <= 0 || rows <= 0 { + log.Fatal("Columns and rows must be positive integers") + return nil + } + + return session.Resize(cols, rows) == nil + }), + } + }), } })) diff --git a/internal/hp_ipn/ipn.go b/internal/hp_ipn/ipn.go deleted file mode 100644 index 4819705..0000000 --- a/internal/hp_ipn/ipn.go +++ /dev/null @@ -1,633 +0,0 @@ -//go:build js && wasm - -package hp_ipn - -import ( - "bytes" - "context" - "encoding/hex" - "errors" - "fmt" - "log" - "net" - "net/http" - "net/netip" - "strings" - "syscall/js" - "time" - - "golang.org/x/crypto/ssh" - "tailscale.com/control/controlclient" - "tailscale.com/ipn" - "tailscale.com/ipn/ipnlocal" - "tailscale.com/ipn/ipnserver" - "tailscale.com/ipn/store/mem" - "tailscale.com/logpolicy" - "tailscale.com/logtail" - "tailscale.com/net/netmon" - "tailscale.com/net/netns" - "tailscale.com/net/tsdial" - "tailscale.com/safesocket" - "tailscale.com/tailcfg" - "tailscale.com/tsd" - "tailscale.com/types/logid" - "tailscale.com/types/views" - "tailscale.com/wgengine" - "tailscale.com/wgengine/netstack" -) - -// ControlURL defines the URL to be used for connection to Control. -var ControlURL = ipn.DefaultControlURL - -func NewIPN(jsConfig js.Value) map[string]any { - netns.SetEnabled(false) - - store := &mem.Store{} - - controlURL := ControlURL - if jsControlURL := jsConfig.Get("controlURL"); jsControlURL.Type() == js.TypeString { - controlURL = jsControlURL.String() - } - - var authKey string - if jsAuthKey := jsConfig.Get("authKey"); jsAuthKey.Type() == js.TypeString { - authKey = jsAuthKey.String() - } - - var hostname string - if jsHostname := jsConfig.Get("hostname"); jsHostname.Type() == js.TypeString { - hostname = jsHostname.String() - } else { - hostname = "blah" - } - - lpc := getOrCreateLogPolicyConfig(store) - c := logtail.Config{ - Collection: lpc.Collection, - PrivateID: lpc.PrivateID, - - // Compressed requests set HTTP headers that are not supported by the - // no-cors fetching mode: - CompressLogs: false, - - HTTPC: &http.Client{Transport: &noCORSTransport{http.DefaultTransport}}, - } - - logtail := logtail.NewLogger(c, log.Printf) - logf := logtail.Logf - - // Instead of new(tsd.System), we use tsd.NewSystem() to ensure that - // the bus is initialized correctly and that the system is set up - sys := tsd.NewSystem() - sys.Set(store) - - dialer := &tsdial.Dialer{Logf: logf} - netmon, err := netmon.New(sys.Bus.Get(), logf) - - if err != nil { - log.Fatalf("netmon.New: %v", err) - return nil - } - - eng, err := wgengine.NewUserspaceEngine(logf, wgengine.Config{ - Dialer: dialer, - SetSubsystem: sys.Set, - ControlKnobs: sys.ControlKnobs(), - HealthTracker: sys.HealthTracker(), - NetMon: netmon, - Metrics: sys.UserMetricsRegistry(), - }) - - if err != nil { - log.Fatal(err) - } - - sys.Set(eng) - - ns, err := netstack.Create(logf, sys.Tun.Get(), eng, sys.MagicSock.Get(), dialer, sys.DNSManager.Get(), sys.ProxyMapper()) - if err != nil { - log.Fatalf("netstack.Create: %v", err) - } - sys.Set(ns) - ns.ProcessLocalIPs = true - ns.ProcessSubnets = true - - dialer.UseNetstackForIP = func(ip netip.Addr) bool { - return true - } - dialer.NetstackDialTCP = func(ctx context.Context, dst netip.AddrPort) (net.Conn, error) { - return ns.DialContextTCP(ctx, dst) - } - dialer.NetstackDialUDP = func(ctx context.Context, dst netip.AddrPort) (net.Conn, error) { - return ns.DialContextUDP(ctx, dst) - } - - sys.NetstackRouter.Set(true) - sys.Tun.Get().Start() - - logid := logid.PublicID{} - - srv := ipnserver.New(logf, logid, sys.NetMon.Get()) - lb, err := ipnlocal.NewLocalBackend(logf, logid, sys, controlclient.LoginDefault) - if err != nil { - log.Fatalf("ipnlocal.NewLocalBackend: %v", err) - } - if err := ns.Start(lb); err != nil { - log.Fatalf("failed to start netstack: %v", err) - } - - srv.SetLocalBackend(lb) - - log.Printf("Using control URL and auth key: %q, %q", controlURL, authKey) - jsIPN := &jsIPN{ - dialer: dialer, - srv: srv, - lb: lb, - controlURL: controlURL, - authKey: authKey, - hostname: hostname, - } - - ipnO := ipn.Options{ - AuthKey: authKey, - UpdatePrefs: &ipn.Prefs{ - ControlURL: controlURL, - Hostname: hostname, - WantRunning: true, - LoggedOut: false, - }, - } - - state := lb.State() - log.Printf("State: %+v", state) - - lb.SetNotifyCallback(func(n ipn.Notify) { - log.Printf("NOTIFY: %+v", n) - - st := n.State - if st != nil { - log.Printf("State changed: %s", *st) - if *st == ipn.NeedsLogin { - go func() { - if err := lb.StartLoginInteractive(context.Background()); err != nil { - log.Printf("StartLoginInteractive failed: %v", err) - } else { - log.Printf("Started login interactive") - } - }() - } - } - }) - - // Start safesocket listener before lb.Start - ln, err := safesocket.Listen("") - if err != nil { - log.Fatalf("safesocket.Listen: %v", err) - } - - // Start server loop before backend Start - go func() { - if err := srv.Run(context.Background(), ln); err != nil && !errors.Is(err, context.Canceled) { - log.Fatalf("ipnserver.Run exited: %v", err) - } - }() - - // Now start the backend (this can trigger login) - log.Printf("Starting local backend with options: %+v", ipnO) - if err := lb.Start(ipnO); err != nil { - log.Fatalf("Failed to start local backend: %v", err) - } - - return map[string]any{ - // "run": js.FuncOf(func(this js.Value, args []js.Value) any { - // if len(args) != 1 { - // log.Fatal(`Usage: run({ - // notifyState(state: int): void, - // notifyNetMap(netMap: object): void, - // notifyBrowseToURL(url: string): void, - // notifyPanicRecover(err: string): void, - // })`) - // return nil - // } - // jsIPN.run(args[0]) - // return nil - // }), - "ssh": js.FuncOf(func(this js.Value, args []js.Value) any { - if len(args) != 3 { - log.Printf("Usage: ssh(hostname, userName, termConfig)") - return nil - } - return jsIPN.ssh( - args[0].String(), - args[1].String(), - args[2]) - }), - "login": js.FuncOf(func(this js.Value, args []js.Value) any { - if len(args) != 0 { - log.Printf("Usage: login()") - return nil - } - jsIPN.login() - return nil - }), - } -} - -func (i *jsIPN) login() { - go i.lb.StartLoginInteractive(context.Background()) -} - -type jsIPN struct { - dialer *tsdial.Dialer - srv *ipnserver.Server - lb *ipnlocal.LocalBackend - controlURL string - authKey string - hostname string -} - -var jsIPNState = map[ipn.State]string{ - ipn.NoState: "NoState", - ipn.InUseOtherUser: "InUseOtherUser", - ipn.NeedsLogin: "NeedsLogin", - ipn.NeedsMachineAuth: "NeedsMachineAuth", - ipn.Stopped: "Stopped", - ipn.Starting: "Starting", - ipn.Running: "Running", -} - -var jsMachineStatus = map[tailcfg.MachineStatus]string{ - tailcfg.MachineUnknown: "MachineUnknown", - tailcfg.MachineUnauthorized: "MachineUnauthorized", - tailcfg.MachineAuthorized: "MachineAuthorized", - tailcfg.MachineInvalid: "MachineInvalid", -} - -func (i *jsIPN) Run(jsCallbacks js.Value) { - notifyState := func(state ipn.State) { - jsCallbacks.Call("notifyState", jsIPNState[state]) - } - - notifyState(ipn.NoState) - - i.lb.SetNotifyCallback(func(n ipn.Notify) { - // Panics in the notify callback are likely due to be due to bugs in - // this bridging module (as opposed to actual bugs in Tailscale) and - // thus may be recoverable. Let the UI know, and allow the user to - // choose if they want to reload the page. - defer func() { - if r := recover(); r != nil { - fmt.Println("Panic recovered:", r) - jsCallbacks.Call("notifyPanicRecover", fmt.Sprint(r)) - } - }() - log.Printf("NOTIFY: %+v", n) - - if n.Health != nil { - log.Printf("NOTIFY HEALTH: %+v", n.Health) - for warning := range n.Health.Warnings { - log.Printf("Health warning: %s", warning) - } - } - - if n.State != nil { - notifyState(*n.State) - } - - log.Printf("NOTIFY NETMAP: %+v", n.NetMap) - - // if nm := n.NetMap; nm != nil { - // jsNetMap := jsNetMap{ - // Self: jsNetMapSelfNode{ - // jsNetMapNode: jsNetMapNode{ - // Name: nm.Name, - // Addresses: mapSliceView(nm.GetAddresses(), func(a netip.Prefix) string { return a.Addr().String() }), - // NodeKey: nm.NodeKey.String(), - // MachineKey: nm.MachineKey.String(), - // }, - // MachineStatus: jsMachineStatus[nm.GetMachineStatus()], - // }, - // Peers: mapSlice(nm.Peers, func(p tailcfg.NodeView) jsNetMapPeerNode { - // name := p.Name() - // if name == "" { - // // In practice this should only happen for Hello. - // name = p.Hostinfo().Hostname() - // } - // addrs := make([]string, p.Addresses().Len()) - // for i, ap := range p.Addresses().All() { - // addrs[i] = ap.Addr().String() - // } - // return jsNetMapPeerNode{ - // jsNetMapNode: jsNetMapNode{ - // Name: name, - // Addresses: addrs, - // MachineKey: p.Machine().String(), - // NodeKey: p.Key().String(), - // }, - // Online: p.Online().Clone(), - // TailscaleSSHEnabled: p.Hostinfo().TailscaleSSHEnabled(), - // } - // }), - // LockedOut: nm.TKAEnabled && nm.SelfNode.KeySignature().Len() == 0, - // } - // if jsonNetMap, err := json.Marshal(jsNetMap); err == nil { - // jsCallbacks.Call("notifyNetMap", string(jsonNetMap)) - // } else { - // log.Printf("Could not generate JSON netmap: %v", err) - // } - // } - // if n.BrowseToURL != nil { - // jsCallbacks.Call("notifyBrowseToURL", *n.BrowseToURL) - // } - }) - - go func() { - o := ipn.Options{ - UpdatePrefs: &ipn.Prefs{ - ControlURL: i.controlURL, - RouteAll: false, - WantRunning: true, - Hostname: i.hostname, - }, - AuthKey: i.authKey, - } - - log.Printf("Fucking options: %+v", o) - err := i.lb.Start(o) - if err != nil { - log.Printf("Start error: %v", err) - } - }() - - // go func() { - // ln, err := safesocket.Listen("") - // if err != nil { - // log.Fatalf("safesocket.Listen: %v", err) - // } - - // err = i.srv.Run(context.Background(), ln) - // log.Fatalf("ipnserver.Run exited: %v", err) - // }() -} - -func (i *jsIPN) ssh(host, username string, termConfig js.Value) map[string]any { - jsSSHSession := &jsSSHSession{ - jsIPN: i, - host: host, - username: username, - termConfig: termConfig, - } - - go jsSSHSession.Run() - - return map[string]any{ - "close": js.FuncOf(func(this js.Value, args []js.Value) any { - return jsSSHSession.Close() != nil - }), - "resize": js.FuncOf(func(this js.Value, args []js.Value) any { - rows := args[0].Int() - cols := args[1].Int() - return jsSSHSession.Resize(rows, cols) != nil - }), - } -} - -type jsSSHSession struct { - jsIPN *jsIPN - host string - username string - termConfig js.Value - session *ssh.Session - - pendingResizeRows int - pendingResizeCols int -} - -func (s *jsSSHSession) Run() { - writeFn := s.termConfig.Get("writeFn") - writeErrorFn := s.termConfig.Get("writeErrorFn") - setReadFn := s.termConfig.Get("setReadFn") - rows := s.termConfig.Get("rows").Int() - cols := s.termConfig.Get("cols").Int() - timeoutSeconds := 5.0 - if jsTimeoutSeconds := s.termConfig.Get("timeoutSeconds"); jsTimeoutSeconds.Type() == js.TypeNumber { - timeoutSeconds = jsTimeoutSeconds.Float() - } - onConnectionProgress := s.termConfig.Get("onConnectionProgress") - onConnected := s.termConfig.Get("onConnected") - onDone := s.termConfig.Get("onDone") - defer onDone.Invoke() - - writeError := func(label string, err error) { - writeErrorFn.Invoke(fmt.Sprintf("%s Error: %v\r\n", label, err)) - } - reportProgress := func(message string) { - onConnectionProgress.Invoke(message) - } - - ctx, cancel := context.WithTimeout(context.Background(), time.Duration(timeoutSeconds*float64(time.Second))) - defer cancel() - reportProgress(fmt.Sprintf("Connecting to %s…", strings.Split(s.host, ".")[0])) - c, err := s.jsIPN.dialer.UserDial(ctx, "tcp", net.JoinHostPort(s.host, "22")) - if err != nil { - writeError("Dial", err) - return - } - defer c.Close() - - config := &ssh.ClientConfig{ - HostKeyCallback: func(hostname string, remote net.Addr, key ssh.PublicKey) error { - // Host keys are not used with Tailscale SSH, but we can use this - // callback to know that the connection has been established. - reportProgress("SSH connection established…") - return nil - }, - User: s.username, - } - - reportProgress("Starting SSH client…") - sshConn, _, _, err := ssh.NewClientConn(c, s.host, config) - if err != nil { - writeError("SSH Connection", err) - return - } - defer sshConn.Close() - - sshClient := ssh.NewClient(sshConn, nil, nil) - defer sshClient.Close() - - session, err := sshClient.NewSession() - if err != nil { - writeError("SSH Session", err) - return - } - s.session = session - defer session.Close() - - stdin, err := session.StdinPipe() - if err != nil { - writeError("SSH Stdin", err) - return - } - - session.Stdout = termWriter{writeFn} - session.Stderr = termWriter{writeFn} - - setReadFn.Invoke(js.FuncOf(func(this js.Value, args []js.Value) any { - input := args[0].String() - _, err := stdin.Write([]byte(input)) - if err != nil { - writeError("Write Input", err) - } - return nil - })) - - // We might have gotten a resize notification since we started opening the - // session, pick up the latest size. - if s.pendingResizeRows != 0 { - rows = s.pendingResizeRows - } - if s.pendingResizeCols != 0 { - cols = s.pendingResizeCols - } - err = session.RequestPty("xterm", rows, cols, ssh.TerminalModes{}) - - if err != nil { - writeError("Pseudo Terminal", err) - return - } - - err = session.Shell() - if err != nil { - writeError("Shell", err) - return - } - - onConnected.Invoke() - err = session.Wait() - if err != nil { - writeError("Wait", err) - return - } -} - -func (s *jsSSHSession) Close() error { - if s.session == nil { - // We never had a chance to open the session, ignore the close request. - return nil - } - return s.session.Close() -} - -func (s *jsSSHSession) Resize(rows, cols int) error { - if s.session == nil { - s.pendingResizeRows = rows - s.pendingResizeCols = cols - return nil - } - return s.session.WindowChange(rows, cols) -} - -type termWriter struct { - f js.Value -} - -func (w termWriter) Write(p []byte) (n int, err error) { - r := bytes.Replace(p, []byte("\n"), []byte("\n\r"), -1) - w.f.Invoke(string(r)) - return len(p), nil -} - -type jsNetMap struct { - Self jsNetMapSelfNode `json:"self"` - Peers []jsNetMapPeerNode `json:"peers"` - LockedOut bool `json:"lockedOut"` -} - -type jsNetMapNode struct { - Name string `json:"name"` - Addresses []string `json:"addresses"` - MachineKey string `json:"machineKey"` - NodeKey string `json:"nodeKey"` -} - -type jsNetMapSelfNode struct { - jsNetMapNode - MachineStatus string `json:"machineStatus"` -} - -type jsNetMapPeerNode struct { - jsNetMapNode - Online *bool `json:"online,omitempty"` - TailscaleSSHEnabled bool `json:"tailscaleSSHEnabled"` -} - -type jsStateStore struct { - jsStateStorage js.Value -} - -func (s *jsStateStore) ReadState(id ipn.StateKey) ([]byte, error) { - jsValue := s.jsStateStorage.Call("getState", string(id)) - if jsValue.String() == "" { - return nil, ipn.ErrStateNotExist - } - return hex.DecodeString(jsValue.String()) -} - -func (s *jsStateStore) WriteState(id ipn.StateKey, bs []byte) error { - s.jsStateStorage.Call("setState", string(id), hex.EncodeToString(bs)) - return nil -} - -func mapSlice[T any, M any](a []T, f func(T) M) []M { - n := make([]M, len(a)) - for i, e := range a { - n[i] = f(e) - } - return n -} - -func mapSliceView[T any, M any](a views.Slice[T], f func(T) M) []M { - n := make([]M, a.Len()) - for i, v := range a.All() { - n[i] = f(v) - } - return n -} - -const logPolicyStateKey = "log-policy" - -func getOrCreateLogPolicyConfig(state ipn.StateStore) *logpolicy.Config { - if configBytes, err := state.ReadState(logPolicyStateKey); err == nil { - if config, err := logpolicy.ConfigFromBytes(configBytes); err == nil { - return config - } else { - log.Printf("Could not parse log policy config: %v", err) - } - } else if err != ipn.ErrStateNotExist { - log.Printf("Could not get log policy config from state store: %v", err) - } - config := logpolicy.NewConfig(logtail.CollectionNode) - if err := state.WriteState(logPolicyStateKey, config.ToBytes()); err != nil { - log.Printf("Could not save log policy config to state store: %v", err) - } - return config -} - -// noCORSTransport wraps a RoundTripper and forces the no-cors mode on requests, -// so that we can use it with non-CORS-aware servers. -type noCORSTransport struct { - http.RoundTripper -} - -func (t *noCORSTransport) RoundTrip(req *http.Request) (*http.Response, error) { - req.Header.Set("js.fetch:mode", "no-cors") - resp, err := t.RoundTripper.RoundTrip(req) - if err == nil { - // In no-cors mode no response properties are returned. Populate just - // the status so that callers do not think this was an error. - resp.StatusCode = http.StatusOK - resp.Status = http.StatusText(http.StatusOK) - } - return resp, err -} diff --git a/internal/hp_ipn/ipnserver.go b/internal/hp_ipn/ipnserver.go index 4db9e55..d77f290 100644 --- a/internal/hp_ipn/ipnserver.go +++ b/internal/hp_ipn/ipnserver.go @@ -90,7 +90,6 @@ func NewTsWasmIpn(options *TsWasmNetOptions, callbacks *TsWasmNetCallbacks) (*Ts // Configure the local Netstack and Dialer wgstack.ProcessLocalIPs = true wgstack.ProcessSubnets = true - sys.NetstackRouter.Set(true) dialer.UseNetstackForIP = func(ip netip.Addr) bool { return true @@ -106,7 +105,8 @@ func NewTsWasmIpn(options *TsWasmNetOptions, callbacks *TsWasmNetCallbacks) (*Ts // Dummy logid for the Tailscale backend logid := logid.PublicID{} - tun.Start() + sys.NetstackRouter.Set(true) + sys.Tun.Get().Start() server := ipnserver.New(logf, logid, sys.NetMon.Get()) flags := controlclient.LoginDefault | controlclient.LoginEphemeral | controlclient.LocalBackendStartKeyOSNeutral diff --git a/internal/hp_ipn/jsmap.go b/internal/hp_ipn/jsmap.go index f0ab34f..bc47a7c 100644 --- a/internal/hp_ipn/jsmap.go +++ b/internal/hp_ipn/jsmap.go @@ -4,6 +4,7 @@ package hp_ipn import ( "errors" + "log" "syscall/js" ) @@ -40,6 +41,128 @@ func ParseTsWasmNetOptions(obj js.Value) (*TsWasmNetOptions, error) { }, nil } +// Options passed from the JS side to pass data to xterm.js. +type SSHXtermConfig struct { + // Number of rows in the PTY. + Rows int + + // Number of columns in the PTY. + Cols int + + // Fires when the PTY has output. + OnStdout func(data string) + + // Fires when the PTY has an error. + OnStderr func(error string) + + // Passes a function to the JS side to provide input. + OnStdin js.Value + + // Fires when the PTY is opened. + OnConnect func() + + // Fires when the PTY is closed. + OnDisconnect func() +} + +// Parses the provided JS object to validate and extract SSHXtermConfig. +func ParseSSHXtermConfig(obj js.Value) (*SSHXtermConfig, error) { + if obj.IsUndefined() || obj.IsNull() { + return nil, errors.New("SSHXtermConfig cannot be undefined or null") + } + + rows := safeInt("Rows", obj) + cols := safeInt("Cols", obj) + + if rows <= 0 || cols <= 0 { + return nil, errors.New("Rows and Cols must be positive integers") + } + + config := &SSHXtermConfig{ + Rows: rows, + Cols: cols, + } + + onStdout := obj.Get("OnStdout") + if onStdout.IsUndefined() || onStdout.IsNull() { + return nil, errors.New("OnStdout must be a function") + } + + if onStdout.Type() != js.TypeFunction { + return nil, errors.New("OnStdout must be a function") + } + + config.OnStdout = func(data string) { + onStdout.Invoke(data) + } + + onStderr := obj.Get("OnStderr") + if onStderr.IsUndefined() || onStderr.IsNull() { + return nil, errors.New("OnStderr must be a function") + } + + if onStderr.Type() != js.TypeFunction { + return nil, errors.New("OnStderr must be a function") + } + + config.OnStderr = func(error string) { + onStderr.Invoke(error) + } + + onStdin := obj.Get("OnStdin") + if onStdin.IsUndefined() || onStdin.IsNull() { + return nil, errors.New("OnStdin must be a function") + } + + if onStdin.Type() != js.TypeFunction { + return nil, errors.New("OnStdin must be a function") + } + + config.OnStdin = onStdin + + onConnect := obj.Get("OnConnect") + if onConnect.IsUndefined() || onConnect.IsNull() { + return nil, errors.New("OnConnect must be a function") + } + + if onConnect.Type() != js.TypeFunction { + return nil, errors.New("OnConnect must be a function") + } + + config.OnConnect = func() { + onConnect.Invoke() + } + + onDisconnect := obj.Get("OnDisconnect") + if onDisconnect.IsUndefined() || onDisconnect.IsNull() { + return nil, errors.New("OnDisconnect must be a function") + } + + if onDisconnect.Type() != js.TypeFunction { + return nil, errors.New("OnDisconnect must be a function") + } + + config.OnDisconnect = func() { + onDisconnect.Invoke() + } + + return config, nil +} + +func (t *SSHXtermConfig) PassStdinHandler(fn func(string)) { + handler := js.FuncOf(func(this js.Value, args []js.Value) any { + if len(args) != 1 || args[0].Type() != js.TypeString { + return nil + } + + fn(args[0].String()) + return nil + }) + + log.Printf("Passing stdin handler to JS: %v", handler) + t.OnStdin.Invoke(handler) +} + // Retrieves a string value from a JS object safely. func safeString(key string, obj js.Value) string { if obj.IsUndefined() || obj.IsNull() { @@ -53,3 +176,17 @@ func safeString(key string, obj js.Value) string { return val.String() } + +// Retrieves an integer value from a JS object safely. +func safeInt(key string, obj js.Value) int { + if obj.IsUndefined() || obj.IsNull() { + return 0 + } + + val := obj.Get(key) + if val.IsUndefined() || val.IsNull() { + return 0 + } + + return val.Int() +} diff --git a/internal/hp_ipn/ssh.go b/internal/hp_ipn/ssh.go new file mode 100644 index 0000000..8db62b2 --- /dev/null +++ b/internal/hp_ipn/ssh.go @@ -0,0 +1,184 @@ +//go:build js && wasm + +package hp_ipn + +import ( + "bytes" + "context" + "fmt" + "log" + "net" + "time" + + "golang.org/x/crypto/ssh" +) + +// Represents an SSH session over the Tailnet. +type SSHSession struct { + // Hostname on the Tailnet. + Hostname string + + // Username for the SSH connection. + Username string + + // Xterm configuration for the SSH session. + TermConfig *SSHXtermConfig + + // Handle to the current IPN connection. + Ipn *TsWasmIpn + + // Handle to the current SSH session. + Pty *ssh.Session + + // Tracks resize notifications for rows. + ResizeRows int + + // Tracks resize notifications for columns. + ResizeCols int +} + +// Creates a new SSH session given a hostname and username. +func (i *TsWasmIpn) NewSSHSession(hostname, username string, termConfig *SSHXtermConfig) *SSHSession { + return &SSHSession{ + Hostname: hostname, + Username: username, + TermConfig: termConfig, + Ipn: i, + } +} + +func (s *SSHSession) ConnectAndRun() { + defer s.TermConfig.OnDisconnect() + + // Default to a 5 second timeout for the connection AFTER dial. + ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second) + defer cancel() + + // TODO: Log here + log.Printf("Attempting SSH dial to host: %s", net.JoinHostPort(s.Hostname, "22")) + conn, err := s.Ipn.dialer.UserDial(ctx, "tcp", net.JoinHostPort(s.Hostname, "22")) + if err != nil { + log.Printf("SSH dial error: %v", err) + s.writeError("Dial", err) + return + } + + defer conn.Close() + sshConf := &ssh.ClientConfig{ + User: s.Username, + HostKeyCallback: func(hostname string, remote net.Addr, key ssh.PublicKey) error { + // Tailscale SSH doesn't use host keys + // TODO: Log that the connection was established + return nil + }, + } + + // TODO: LOG: Starting SSH Client + sshConn, _, _, err := ssh.NewClientConn(conn, s.Hostname, sshConf) + if err != nil { + s.writeError("SSH", err) + return + } + + defer sshConn.Close() + sshClient := ssh.NewClient(sshConn, nil, nil) + defer sshClient.Close() + + pty, err := sshClient.NewSession() + if err != nil { + s.writeError("SSH", err) + return + } + + defer pty.Close() + s.Pty = pty + + pty.Stdout = XtermPipe{s.TermConfig.OnStdout} + pty.Stderr = XtermPipe{s.TermConfig.OnStdout} + + // TODO: Set Stdin func + stdin, err := pty.StdinPipe() + if err != nil { + s.writeError("SSH", err) + return + } + + s.TermConfig.PassStdinHandler(func(input string) { + _, err := stdin.Write([]byte(input)) + if err != nil { + s.writeError("SSH", err) + return + } + }) + + rows := s.TermConfig.Rows + if s.ResizeRows != 0 { + rows = s.ResizeRows + } + + cols := s.TermConfig.Cols + if s.ResizeCols != 0 { + cols = s.ResizeCols + } + + err = pty.RequestPty("xterm", rows, cols, ssh.TerminalModes{}) + if err != nil { + s.writeError("SSH", err) + return + } + + err = pty.Shell() + if err != nil { + s.writeError("SSH", err) + return + } + + s.TermConfig.OnConnect() + err = pty.Wait() + if err != nil { + s.writeError("SSH", err) + return + } +} + +// Resize resizes the terminal for the SSH session. +// TODO: This does NOT work correctly from Xterm.js +func (s *SSHSession) Resize(rows, cols int) error { + // Used to handle resizes while still connecting. + if s.Pty == nil { + s.ResizeRows = rows + s.ResizeCols = cols + return nil + } + + return s.Pty.WindowChange(rows, cols) +} + +// Closes the SSH session. +func (s *SSHSession) Close() error { + if s.Pty == nil { + return nil + } + + return s.Pty.Close() +} + +// Quick easy formatter for writing errors to the terminal. +func (s *SSHSession) writeError(label string, err error) { + o := fmt.Sprintf("%s error: %v\r\n", label, err) + s.TermConfig.OnStderr(o) +} + +// io.Writer "emulator" to pass to the ssh module. +type XtermPipe struct { + // Function to call when data is written. + Send func(data string) +} + +// Write implements the io.Writer interface for XtermPipe. +func (x XtermPipe) Write(data []byte) (int, error) { + // Tailscale's webSSH does this to fix issues in xterm.js + res := bytes.Replace(data, []byte("\n"), []byte("\n\r"), -1) + x.Send(string(res)) + return len(data), nil +} From 721439868f8e44fb45e3e4af88f9b8311639af78 Mon Sep 17 00:00:00 2001 From: Aarnav Tale Date: Mon, 9 Jun 2025 17:48:35 -0400 Subject: [PATCH 11/97] style: format go code on precommit --- .zed/settings.json | 10 +++++++++- lefthook.yml | 4 ++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/.zed/settings.json b/.zed/settings.json index fc38e77..aad9e35 100644 --- a/.zed/settings.json +++ b/.zed/settings.json @@ -6,12 +6,20 @@ }, "code_actions_on_format": { "source.fixAll.biome": true, - "source.organizeImports.biome": true + "source.organizeImports.biome": true, + "source.organizeImports": true }, "languages": { "YAML": { "tab_size": 2, "hard_tabs": false + }, + "Go": { + "formatter": { + "language_server": { + "name": "gopls" + } + } } } } diff --git a/lefthook.yml b/lefthook.yml index 1596122..6f1be6e 100644 --- a/lefthook.yml +++ b/lefthook.yml @@ -4,3 +4,7 @@ pre-commit: glob: "*.{js,ts,cjs,mjs,d.cts,d.mts,jsx,tsx,json,jsonc}" run: pnpm biome check --write --no-errors-on-unmatched --files-ignore-unknown=true --colors=off {staged_files} stage_fixed: true + check_go: + glob: "*.go" + run: gofmt -w {staged_files} + stage_fixed: true From 6a0e097412d0b9208bbb90c088266e1cd35d400a Mon Sep 17 00:00:00 2001 From: Aarnav Tale Date: Mon, 9 Jun 2025 17:48:55 -0400 Subject: [PATCH 12/97] chore: fix tiny nits --- app/routes/users/overview.tsx | 3 +-- app/types/Machine.ts | 3 ++- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/app/routes/users/overview.tsx b/app/routes/users/overview.tsx index f930f77..a0fcf99 100644 --- a/app/routes/users/overview.tsx +++ b/app/routes/users/overview.tsx @@ -105,8 +105,7 @@ export default function Page() { <>

Users

- Manage the users in your network and their permissions. Tip: You can - drag machines between users to change ownership. + Manage the users in your network and their permissions.

diff --git a/app/types/Machine.ts b/app/types/Machine.ts index de9b929..5d3e27d 100644 --- a/app/types/Machine.ts +++ b/app/types/Machine.ts @@ -1,3 +1,4 @@ +import type { PreAuthKey } from './PreAuthKey'; import type { User } from './User'; export interface Machine { @@ -12,7 +13,7 @@ export interface Machine { lastSeen: string; expiry: string | null; - preAuthKey?: unknown; // TODO + preAuthKey?: PreAuthKey; createdAt: string; registerMethod: From 7f376c3f709b9642d2b633078c4913fb2ac6df5a Mon Sep 17 00:00:00 2001 From: Aarnav Tale Date: Mon, 16 Jun 2025 11:23:44 -0400 Subject: [PATCH 13/97] fix: better ssh resilience and customizable timeout for future backoff --- app/routes/ssh/console.tsx | 2 +- app/routes/ssh/xterm.client.tsx | 36 +++++++++++++++++++++++++------ internal/hp_ipn/jsmap.go | 38 ++++++++++++++------------------- internal/hp_ipn/ssh.go | 3 +-- 4 files changed, 48 insertions(+), 31 deletions(-) diff --git a/app/routes/ssh/console.tsx b/app/routes/ssh/console.tsx index 010bdbc..0741224 100644 --- a/app/routes/ssh/console.tsx +++ b/app/routes/ssh/console.tsx @@ -156,7 +156,7 @@ export default function Page() {
{ipn === null ? (
- +
) : (
diff --git a/app/routes/ssh/xterm.client.tsx b/app/routes/ssh/xterm.client.tsx index d726fed..566b9d8 100644 --- a/app/routes/ssh/xterm.client.tsx +++ b/app/routes/ssh/xterm.client.tsx @@ -5,9 +5,11 @@ import { WebLinksAddon } from '@xterm/addon-web-links'; import { WebglAddon } from '@xterm/addon-webgl'; import * as xterm from '@xterm/xterm'; import '@xterm/xterm/css/xterm.css'; +import { Loader2 } from 'lucide-react'; import { useEffect, useRef, useState } from 'react'; import cn from '~/utils/cn'; import { useLiveData } from '~/utils/live-data'; +import toast from '~/utils/toast'; interface XTermProps { ipn: TsWasmNet; @@ -15,17 +17,17 @@ interface XTermProps { hostname: string; } -const RED = new TextEncoder().encode('\x1b[31m'); -const RESET = new TextEncoder().encode('\x1b[0m'); - export default function XTerm({ ipn, username, hostname }: XTermProps) { const { pause } = useLiveData(); const container = useRef(null); const term = useRef(null); - const [isResizing, setIsResizing] = useState(false); const inputRef = useRef<((input: string) => void) | null>(null); + const [isResizing, setIsResizing] = useState(false); + const [isLoading, setIsLoading] = useState(true); + const [isFailed, setIsFailed] = useState(false); + useEffect(() => { pause(); const terminal = new xterm.Terminal({ @@ -76,12 +78,14 @@ export default function XTerm({ ipn, username, hostname }: XTermProps) { OnStderr: (data) => { terminal.write(data); console.log('SSH stderr:', data); + toast(data); }, OnStdin: (func) => { inputRef.current = func; }, OnConnect: () => { console.log('SSH session connected'); + setIsLoading(false); }, OnDisconnect: () => { @@ -92,6 +96,9 @@ export default function XTerm({ ipn, username, hostname }: XTermProps) { } console.log('SSH session disconnected'); + terminal.writeln('Disconnected from SSH session'); + setIsLoading(false); + setIsFailed(true); term.current = null; }, }); @@ -123,8 +130,15 @@ export default function XTerm({ ipn, username, hostname }: XTermProps) { return (
-
- + {isLoading ? ( +
+ +
+ ) : undefined} +
{term.current && isResizing ? (
) : undefined} + {isFailed ? ( +
+ Failed to connect to SSH session +
+ ) : undefined}
); } diff --git a/internal/hp_ipn/jsmap.go b/internal/hp_ipn/jsmap.go index bc47a7c..d0c8940 100644 --- a/internal/hp_ipn/jsmap.go +++ b/internal/hp_ipn/jsmap.go @@ -43,26 +43,14 @@ func ParseTsWasmNetOptions(obj js.Value) (*TsWasmNetOptions, error) { // Options passed from the JS side to pass data to xterm.js. type SSHXtermConfig struct { - // Number of rows in the PTY. - Rows int - - // Number of columns in the PTY. - Cols int - - // Fires when the PTY has output. - OnStdout func(data string) - - // Fires when the PTY has an error. - OnStderr func(error string) - - // Passes a function to the JS side to provide input. - OnStdin js.Value - - // Fires when the PTY is opened. - OnConnect func() - - // Fires when the PTY is closed. - OnDisconnect func() + Timeout int // Timeout in seconds for the PTY connection. + Rows int // Number of rows in the PTY. + Cols int // Number of columns in the PTY. + OnStdout func(data string) // Fires when the PTY has output. + OnStderr func(error string) // Fires when the PTY has an error. + OnStdin js.Value // Passes a function to the JS side to provide input. + OnConnect func() // Fires when the PTY is opened. + OnDisconnect func() // Fires when the PTY is closed. } // Parses the provided JS object to validate and extract SSHXtermConfig. @@ -71,6 +59,7 @@ func ParseSSHXtermConfig(obj js.Value) (*SSHXtermConfig, error) { return nil, errors.New("SSHXtermConfig cannot be undefined or null") } + timeout := safeInt("Timeout", obj) rows := safeInt("Rows", obj) cols := safeInt("Cols", obj) @@ -78,9 +67,14 @@ func ParseSSHXtermConfig(obj js.Value) (*SSHXtermConfig, error) { return nil, errors.New("Rows and Cols must be positive integers") } + if timeout <= 0 { + timeout = 30 // Default timeout to 30 seconds if not specified + } + config := &SSHXtermConfig{ - Rows: rows, - Cols: cols, + Timeout: timeout, + Rows: rows, + Cols: cols, } onStdout := obj.Get("OnStdout") diff --git a/internal/hp_ipn/ssh.go b/internal/hp_ipn/ssh.go index 8db62b2..a03e3cb 100644 --- a/internal/hp_ipn/ssh.go +++ b/internal/hp_ipn/ssh.go @@ -50,8 +50,7 @@ func (i *TsWasmIpn) NewSSHSession(hostname, username string, termConfig *SSHXter func (s *SSHSession) ConnectAndRun() { defer s.TermConfig.OnDisconnect() - // Default to a 5 second timeout for the connection AFTER dial. - ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second) + ctx, cancel := context.WithTimeout(context.Background(), time.Duration(s.TermConfig.Timeout)*time.Second) defer cancel() // TODO: Log here From 6d15c411f1fd6a93f1377e754e1a9d9509a9b15e Mon Sep 17 00:00:00 2001 From: Aarnav Tale Date: Mon, 16 Jun 2025 11:27:47 -0400 Subject: [PATCH 14/97] chore: use mise for ci --- .github/workflows/build.yaml | 18 ++++-------------- mise.toml | 8 ++++++++ 2 files changed, 12 insertions(+), 14 deletions(-) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index e03834d..fb73c13 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -28,15 +28,8 @@ jobs: - name: Check out the repo uses: actions/checkout@v4 - - name: Install node.js - uses: actions/setup-node@v4 - with: - node-version: 22 - - - uses: pnpm/action-setup@v4 - name: Install pnpm - with: - run_install: false + - name: Setup Mise + uses: jdx/mise-action@v2 - name: Get pnpm store directory shell: bash @@ -51,11 +44,8 @@ jobs: restore-keys: | ${{ runner.os }}-pnpm-store- - - name: Install dependencies - run: pnpm install - - - name: Build - run: pnpm build + - name: CI pipeline + run: mise run ci nix: name: nix diff --git a/mise.toml b/mise.toml index f69d05e..bc3e84b 100644 --- a/mise.toml +++ b/mise.toml @@ -25,3 +25,11 @@ run = [ "mkcert -install", "mkcert localhost" ] + +[tasks.ci] +description = "Runs the CI pipeline" +depends = ["build-go-wasm"] +run = [ + "pnpm install", + "pnpm run build" +] From ab7587e7a9352e5fc17fcc674d3e5ddfca4b802f Mon Sep 17 00:00:00 2001 From: Aarnav Tale Date: Mon, 16 Jun 2025 11:31:23 -0400 Subject: [PATCH 15/97] chore: cache go build deps --- .github/workflows/build.yaml | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index fb73c13..47610b5 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -31,10 +31,12 @@ jobs: - name: Setup Mise uses: jdx/mise-action@v2 - - name: Get pnpm store directory + - name: Set caching paths shell: bash run: | echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV + echo "GO_CACHE=$(go env GOCACHE)" >> $GITHUB_ENV + echo "GO_MODCACHE=$(go env GOMODCACHE)" >> $GITHUB_ENV - uses: actions/cache@v4 name: Setup pnpm cache @@ -44,6 +46,14 @@ jobs: restore-keys: | ${{ runner.os }}-pnpm-store- + - name: Setup Go cache + uses: actions/cache@v4 + with: + path: | + ${{ env.GO_CACHE }} + ${{ env.GO_MODCACHE }} + key: ${{ runner.os }}-go-${{ hashFiles('**/go.mod', '**/go.sum') }} + - name: CI pipeline run: mise run ci From 4961e83a9c50c55dbd78610564af5ebab5335807 Mon Sep 17 00:00:00 2001 From: Aarnav Tale Date: Mon, 16 Jun 2025 11:38:13 -0400 Subject: [PATCH 16/97] fix: cleanup agent and add to ci --- cmd/hp_agent/hp_agent.go | 10 +- go.mod | 2 +- internal/config/config.go | 45 +++++++ internal/config/preflight.go | 48 ++++++++ internal/hpagent/handler.go | 175 ++++++++------------------- internal/sshutil/connect.go | 201 ------------------------------- internal/sshutil/encoder.go | 125 ------------------- internal/sshutil/framebatcher.go | 61 ---------- internal/sshutil/handler.go | 152 ----------------------- internal/sshutil/sessions.go | 83 ------------- internal/tsnet/peers.go | 2 +- internal/tsnet/server.go | 8 +- mise.toml | 6 + 13 files changed, 160 insertions(+), 758 deletions(-) create mode 100644 internal/config/config.go create mode 100644 internal/config/preflight.go delete mode 100644 internal/sshutil/connect.go delete mode 100644 internal/sshutil/encoder.go delete mode 100644 internal/sshutil/framebatcher.go delete mode 100644 internal/sshutil/handler.go delete mode 100644 internal/sshutil/sessions.go diff --git a/cmd/hp_agent/hp_agent.go b/cmd/hp_agent/hp_agent.go index 03d90bb..b20c34d 100644 --- a/cmd/hp_agent/hp_agent.go +++ b/cmd/hp_agent/hp_agent.go @@ -2,11 +2,10 @@ package main import ( _ "github.com/joho/godotenv/autoload" - "github.com/tale/headplane/agent/internal/config" - "github.com/tale/headplane/agent/internal/hpagent" - "github.com/tale/headplane/agent/internal/sshutil" - "github.com/tale/headplane/agent/internal/tsnet" - "github.com/tale/headplane/agent/internal/util" + "github.com/tale/headplane/internal/config" + "github.com/tale/headplane/internal/hpagent" + "github.com/tale/headplane/internal/tsnet" + "github.com/tale/headplane/internal/util" ) type Register struct { @@ -32,6 +31,5 @@ func main() { ID: agent.ID, }) - sshutil.DispatchSSHStdin() hpagent.FollowMaster(agent) } diff --git a/go.mod b/go.mod index 098494d..b06e64f 100644 --- a/go.mod +++ b/go.mod @@ -5,7 +5,6 @@ go 1.24.0 toolchain go1.24.4 require ( - github.com/fxamacker/cbor/v2 v2.8.0 github.com/joho/godotenv v1.5.1 go4.org/mem v0.0.0-20240501181205-ae6ca9944745 golang.org/x/crypto v0.38.0 @@ -34,6 +33,7 @@ require ( github.com/coreos/go-iptables v0.7.1-0.20240112124308-65c67c9f46e6 // indirect github.com/dblohm7/wingoes v0.0.0-20240119213807-a09d6be7affa // indirect github.com/digitalocean/go-smbios v0.0.0-20180907143718-390a4f403a8e // indirect + github.com/fxamacker/cbor/v2 v2.8.0 // indirect github.com/gaissmai/bart v0.18.0 // indirect github.com/go-json-experiment/json v0.0.0-20250223041408-d3c622f1b874 // indirect github.com/go-ole/go-ole v1.3.0 // indirect diff --git a/internal/config/config.go b/internal/config/config.go new file mode 100644 index 0000000..d33c014 --- /dev/null +++ b/internal/config/config.go @@ -0,0 +1,45 @@ +package config + +import "os" + +// Config represents the configuration for the agent. +type Config struct { + Debug bool + Hostname string + TSControlURL string + TSAuthKey string + WorkDir string +} + +const ( + DebugEnv = "HEADPLANE_AGENT_DEBUG" + HostnameEnv = "HEADPLANE_AGENT_HOSTNAME" + TSControlURLEnv = "HEADPLANE_AGENT_TS_SERVER" + TSAuthKeyEnv = "HEADPLANE_AGENT_TS_AUTHKEY" + WorkDirEnv = "HEADPLANE_AGENT_WORK_DIR" +) + +// Load reads the agent configuration from environment variables. +func Load() (*Config, error) { + c := &Config{ + Debug: false, + Hostname: os.Getenv(HostnameEnv), + TSControlURL: os.Getenv(TSControlURLEnv), + TSAuthKey: os.Getenv(TSAuthKeyEnv), + WorkDir: os.Getenv(WorkDirEnv), + } + + if os.Getenv(DebugEnv) == "true" { + c.Debug = true + } + + if err := validateRequired(c); err != nil { + return nil, err + } + + if err := validateTSReady(c); err != nil { + return nil, err + } + + return c, nil +} diff --git a/internal/config/preflight.go b/internal/config/preflight.go new file mode 100644 index 0000000..ee06d9f --- /dev/null +++ b/internal/config/preflight.go @@ -0,0 +1,48 @@ +package config + +import ( + "fmt" + "net/http" + "strings" +) + +// Checks to make sure all required environment variables are set +func validateRequired(config *Config) error { + if config.Hostname == "" { + return fmt.Errorf("%s is required", HostnameEnv) + } + + if config.TSControlURL == "" { + return fmt.Errorf("%s is required", TSControlURLEnv) + } + + if config.TSAuthKey == "" { + return fmt.Errorf("%s is required", TSAuthKeyEnv) + } + + if config.WorkDir == "" { + return fmt.Errorf("%s is required", WorkDirEnv) + } + + return nil +} + +// Pings the Tailscale control server to make sure it's up and running +func validateTSReady(config *Config) error { + testURL := config.TSControlURL + if strings.HasSuffix(testURL, "/") { + testURL = testURL[:len(testURL)-1] + } + + testURL = fmt.Sprintf("%s/key?v=116", testURL) + resp, err := http.Get(testURL) + if err != nil { + return fmt.Errorf("Failed to connect to TS control server: %s", err) + } + + if resp.StatusCode != 200 { + return fmt.Errorf("Failed to connect to TS control server: %s", resp.Status) + } + + return nil +} diff --git a/internal/hpagent/handler.go b/internal/hpagent/handler.go index 7bb48e7..826b74f 100644 --- a/internal/hpagent/handler.go +++ b/internal/hpagent/handler.go @@ -2,16 +2,14 @@ package hpagent import ( "bufio" - "bytes" - // "encoding/json" - "os" - // "sync" - "github.com/fxamacker/cbor/v2" - "github.com/tale/headplane/agent/internal/sshutil" - "github.com/tale/headplane/agent/internal/tsnet" - "github.com/tale/headplane/agent/internal/util" - // "tailscale.com/tailcfg" + "encoding/json" + "os" + "sync" + + "github.com/tale/headplane/internal/tsnet" + "github.com/tale/headplane/internal/util" + "tailscale.com/tailcfg" ) // Represents messages from the Headplane master @@ -19,11 +17,6 @@ type RecvMessage struct { NodeIDs []string } -type CborMessage struct { - Op string `cbor:"op"` - Payload cbor.RawMessage `cbor:"payload"` -} - type SendMessage struct { Type string Data any @@ -37,124 +30,58 @@ func FollowMaster(agent *tsnet.TSAgent) { for scanner.Scan() { line := scanner.Bytes() - - var msg CborMessage - decoder := cbor.NewDecoder(bytes.NewReader(line)) - err := decoder.Decode(&msg) - + var msg RecvMessage + err := json.Unmarshal(line, &msg) if err != nil { log.Error("Unable to decode message from master: %s", err) continue } - log.Debug("Received message from master: %s", msg) - switch msg.Op { - case "ssh_conn": - var sshPayload sshutil.SSHConnectPayload - err = cbor.Unmarshal(msg.Payload, &sshPayload) - if err != nil { - log.Error("Unable to unmarshal SSH connect payload: %s", err) - continue - } + log.Debug("Recieved message from master: %v", line) - sshutil.StartWebSSH(agent, sshPayload) - continue - - case "ssh_term": - var sshPayload sshutil.SSHClosePayload - err = cbor.Unmarshal(msg.Payload, &sshPayload) - if err != nil { - log.Error("Unable to unmarshal SSH close payload: %s", err) - continue - } - - sshutil.CloseWebSSH(agent, sshPayload) - continue - - case "ssh_resize": - var sshPayload sshutil.SSHResizePayload - err = cbor.Unmarshal(msg.Payload, &sshPayload) - if err != nil { - log.Error("Unable to unmarshal SSH resize payload: %s", err) - continue - } - - sshutil.ResizeWebSSH(agent, sshPayload) + if len(msg.NodeIDs) == 0 { + log.Debug("Message recieved had no node IDs") + log.Debug("Full message: %s", line) continue } + + // Accumulate the results since we invoke via gofunc + results := make(map[string]*tailcfg.HostinfoView) + mu := sync.Mutex{} + wg := sync.WaitGroup{} + + for _, nodeID := range msg.NodeIDs { + wg.Add(1) + go func(nodeID string) { + defer wg.Done() + result, err := agent.GetStatusForPeer(nodeID) + if err != nil { + log.Error("Unable to get status for node %s: %s", nodeID, err) + return + } + + if result == nil { + log.Debug("No status for node %s", nodeID) + return + } + + mu.Lock() + results[nodeID] = result + mu.Unlock() + }(nodeID) + } + + wg.Wait() + + // Send the results back to the Headplane master + log.Debug("Sending status back to master: %v", results) + log.Msg(&SendMessage{ + Type: "status", + Data: results, + }) } - // var msg RecvMessage - // err := json.Unmarshal(line, &msg) - // if err != nil { - // var cborMsg CborMessage - // dec := cbor.NewDecoder(bytes.NewReader(line)) - // err := dec.Decode(&cborMsg) - - // if err == nil { - // log.Info("Unmarshalled CBOR message: %s", cborMsg) - // var sshPayload SSHConnect - // err = cbor.Unmarshal(cborMsg.Payload, &sshPayload) - // sshutil.OpenSshPty(agent, sshutil.SshConnectParams{ - // Hostname: sshPayload.Hostname, - // Port: sshPayload.Port, - // Username: sshPayload.Username, - // Id: sshPayload.SessionId, - // }) - - // return; - // } - - // log.Error("Unable to unmarshal message: %s", err) - // log.Debug("Full Error: %v", err) - // continue - // } - - // log.Debug("Recieved message from master: %v", line) - - // if len(msg.NodeIDs) == 0 { - // log.Debug("Message recieved had no node IDs") - // log.Debug("Full message: %s", line) - // continue - // } - - // // Accumulate the results since we invoke via gofunc - // results := make(map[string]*tailcfg.HostinfoView) - // mu := sync.Mutex{} - // wg := sync.WaitGroup{} - - // for _, nodeID := range msg.NodeIDs { - // wg.Add(1) - // go func(nodeID string) { - // defer wg.Done() - // result, err := agent.GetStatusForPeer(nodeID) - // if err != nil { - // log.Error("Unable to get status for node %s: %s", nodeID, err) - // return - // } - - // if result == nil { - // log.Debug("No status for node %s", nodeID) - // return - // } - - // mu.Lock() - // results[nodeID] = result - // mu.Unlock() - // }(nodeID) - // } - - // wg.Wait() - - // // Send the results back to the Headplane master - // log.Debug("Sending status back to master: %v", results) - // log.Msg(&SendMessage{ - // Type: "status", - // Data: results, - // }) - // } - - // if err := scanner.Err(); err != nil { - // log.Fatal("Error reading from stdin: %s", err) - // } + if err := scanner.Err(); err != nil { + log.Fatal("Error reading from stdin: %s", err) + } } diff --git a/internal/sshutil/connect.go b/internal/sshutil/connect.go deleted file mode 100644 index c4e23cf..0000000 --- a/internal/sshutil/connect.go +++ /dev/null @@ -1,201 +0,0 @@ -package sshutil - -import ( - "context" - "errors" - "strconv" - "strings" - - "github.com/tale/headplane/agent/internal/tsnet" - "github.com/tale/headplane/agent/internal/util" - "golang.org/x/crypto/ssh" -) - -type SSHConnectPayload struct { - SessionId string `cbor:"sessionId"` - Username string `cbor:"username"` - Hostname string `cbor:"hostname"` - Port int `cbor:"port"` -} - -type SSHClosePayload struct { - SessionId string `cbor:"sessionId"` -} - -type SSHResizePayload struct { - SessionId string `cbor:"sessionId"` - Width int `cbor:"width"` - Height int `cbor:"height"` -} - -func connectToTailscaleSSH(agent *tsnet.TSAgent, params SSHConnectPayload) (*ssh.Client, error) { - log := util.GetLogger() - addr := strings.Join([]string{params.Hostname, ":", strconv.Itoa(params.Port)}, "") - - log.Debug("Initiating Tailscale SSH connection to %s@%s", params.Username, addr) - tailnetConn, err := agent.Dial(context.Background(), "tcp", addr) - if err != nil { - return nil, err - } - - log.Debug("Routed connection via tsnet to %s", addr) - config := &ssh.ClientConfig{ - User: params.Username, - // This isn't a concern because we are only dialing within the Tailnet - // and every device is trusted and *should* be ACL accessible. - HostKeyCallback: ssh.InsecureIgnoreHostKey(), - } - - conn, chans, reqs, err := ssh.NewClientConn(tailnetConn, addr, config) - if err != nil { - conn.Close() - return nil, err - } - - // At this point we have successfully connected to the node - sshClient := ssh.NewClient(conn, chans, reqs) - version := string(sshClient.ServerVersion()) - - if !strings.Contains(version, "Tailscale") { - conn.Close() - return nil, errors.New("server is not running Tailscale SSH") - } - - log.Info("Connected to %s@%s:%d via Tailscale SSH (%s)", params.Username, params.Hostname, params.Port, version) - return sshClient, nil -} - -func StartWebSSH(agent *tsnet.TSAgent, params SSHConnectPayload) { - log := util.GetLogger() - - if agent == nil { - log.Error("tsnet.TSAgent is not initialized correctly") - return - } - - if params.Hostname == "" || params.Port <= 0 || params.Username == "" || params.SessionId == "" { - log.Error("Invalid SSH connection parameters: %v", params) - return - } - - client, err := connectToTailscaleSSH(agent, params) - if err != nil { - log.Error("Failed to connect to Tailscale SSH for (%s): %s", params.SessionId, err) - return - } - - // Everything in the func is related to the SSH session. - // Each session runs in its own goroutine, allowing concurrency. - go func() { - log.Debug("Creating SSH session for session ID: %s", params.SessionId) - sess, err := client.NewSession() - if err != nil { - log.Error("Failed to create new SSH session: %s", err) - client.Close() - return - } - - modes := ssh.TerminalModes{ - ssh.ECHO: 1, - ssh.TTY_OP_ISPEED: 14400, - ssh.TTY_OP_OSPEED: 14400, - } - - // Resize event is possible via the control channel later - err = sess.RequestPty("xterm-256color", 24, 80, modes) - if err != nil { - log.Error("Failed to request PTY for (%s): %s", params.SessionId, err) - return - } - - ctx, err := registerSessionChans(params.SessionId, sess) - if err != nil { - log.Error("Failed to register session channels for (%s): %s", params.SessionId, err) - client.Close() - return - } - - // Input buffer handler - go func() { - for data := range ctx.InputCh { - _, err := ctx.Stdin.Write(data) - if err != nil { - log.Error("Failed to write to SSH stdin: %s", err) - return - } - } - }() - - // Spin up a shell and wait for the pty to terminate - err = sess.Shell() - if err != nil { - log.Error("Failed to start shell for (%s): %s", params.SessionId, err) - client.Close() - return - } - - // This spawns 2 goroutins for stdout and stderr - dispatchSSHStdout(params.SessionId, ctx.Stdout, ctx.Stderr) - - log.Info("Opened an SSH PTY for %s", params.SessionId) - sess.Wait() - sess.Close() - client.Close() - - log.Info("SSH session for %s closed", params.SessionId) - RemoveSession(params.SessionId) - }() -} - -func CloseWebSSH(agent *tsnet.TSAgent, params SSHClosePayload) { - log := util.GetLogger() - - if agent == nil { - log.Error("tsnet.TSAgent is not initialized correctly") - return - } - - if params.SessionId == "" { - log.Error("Invalid SSH close parameters: %v", params) - return - } - - log.Debug("Closing SSH session for session ID: %s", params.SessionId) - ctx, ok := lookupSession(params.SessionId) - if !ok { - log.Info("No active SSH session found for session ID: %s", params.SessionId) - return - } - - RemoveSession(ctx.ID) - log.Info("SSH session for %s closed", params.SessionId) -} - -func ResizeWebSSH(agent *tsnet.TSAgent, params SSHResizePayload) { - log := util.GetLogger() - - if agent == nil { - log.Error("tsnet.TSAgent is not initialized correctly") - return - } - - if params.SessionId == "" || params.Width <= 0 || params.Height <= 0 { - log.Error("Invalid SSH resize parameters: %v", params) - return - } - - log.Debug("Resizing SSH session for session ID: %s to %dx%d", params.SessionId, params.Width, params.Height) - ctx, ok := lookupSession(params.SessionId) - if !ok { - log.Info("No active SSH session found for session ID: %s", params.SessionId) - return - } - - err := ctx.Session.WindowChange(params.Height, params.Width) - if err != nil { - log.Error("Failed to resize SSH session for (%s): %s", params.SessionId, err) - return - } - - log.Info("Resized SSH session for %s to %dx%d", params.SessionId, params.Width, params.Height) -} diff --git a/internal/sshutil/encoder.go b/internal/sshutil/encoder.go deleted file mode 100644 index f90e400..0000000 --- a/internal/sshutil/encoder.go +++ /dev/null @@ -1,125 +0,0 @@ -package sshutil - -import ( - "encoding/binary" - "fmt" -) - -// An SSH frame is used to wrap raw binary data to and from an SSH session -// in order to allow multiplexing connections over a single file descriptor. -// -// In practice, this is how we can easily support multiple SSH connections -// through the 2 file descriptors created by the parent node process. -// -// This is the format of an SSH frame: -// - Magic: The first 4 bytes are HPLS (0x48504C53) to identify the frame. -// - Version Byte: The first byte is the version of the frame format. -// - Channel Type: The second byte indicates the type of channel. -// - Session ID: The next bytes are the length and actual session ID. -// - Payload: The remaining bytes are the payload length and actual data. -// -// +---------+----------+--------------+-------------+----------+ -// | Magic | Version | Channel Type | SID Length | SID | -// | 4 bytes | 1 byte | 1 byte | 1 byte (S) | S bytes | -// +---------+----------+--------------+------------------------+ -// | Payload Length | Payload | -// | 4 bytes (u32, P) | P bytes | -// +--------------------+---------------------------------------+ - -const ( - MagicString = "HPLS" - VersionByte = 1 -) - -type ChannelType int - -const ( - ChannelTypeStdin ChannelType = iota - ChannelTypeStdout - ChannelTypeStderr -) - -type SSHFrame struct { - ChannelType ChannelType - SessionID string - Payload []byte - - Length func() int -} - -type HPLSFrame1 struct{} - -func (t HPLSFrame1) Encode(frame SSHFrame) ([]byte, error) { - frameChan := frame.ChannelType - switch frameChan { - case ChannelTypeStdin, ChannelTypeStdout, ChannelTypeStderr: - default: - return nil, fmt.Errorf("invalid channel type: %d", frameChan) - } - - if len(frame.SessionID) == 0 { - return nil, fmt.Errorf("session ID cannot be empty") - } - - if len(frame.Payload) == 0 { - return nil, fmt.Errorf("payload cannot be empty") - } - - sid := []byte(frame.SessionID) - if len(sid) > 255 { - return nil, fmt.Errorf("session ID exceeds 255 byte limit") - } - - if len(frame.Payload) > 0xFFFFFFFF { - return nil, fmt.Errorf("payload exceeds 4GB limit") - } - - frameLen := 4 // Magic - frameLen += 1 // Version byte - frameLen += 1 // Channel type - frameLen += 1 + len(sid) // Session ID length + SID - frameLen += 4 + len(frame.Payload) // Payload length + Payload - - buf := make([]byte, frameLen) - copy(buf[0:4], []byte(MagicString)) - buf[4] = VersionByte - buf[5] = byte(frameChan) - buf[6] = byte(len(sid)) - - offset := 7 + len(sid) - copy(buf[7:offset], sid) - - binary.BigEndian.PutUint32(buf[offset:offset+4], uint32(len(frame.Payload))) - copy(buf[offset+4:], frame.Payload) - return buf, nil -} - -func (t HPLSFrame1) Decode(buf []byte) (SSHFrame, error) { - frame := SSHFrame{} - if len(buf) < 5 || string(buf[0:4]) != MagicString || buf[4] != VersionByte { - return frame, fmt.Errorf("illegal HPLS1 frame format") - } - - frame.ChannelType = ChannelType(buf[5]) - if frame.ChannelType < ChannelTypeStdin || frame.ChannelType > ChannelTypeStderr { - return frame, fmt.Errorf("invalid channel type: %d", frame.ChannelType) - } - - sidLen := int(buf[6]) - if len(buf) < 7+sidLen+4 { - return frame, fmt.Errorf("buffer too short for session ID and payload length") - } - - frame.SessionID = string(buf[7 : 7+sidLen]) - payloadLen := int(binary.BigEndian.Uint32(buf[7+sidLen:])) - if len(buf) < 7+sidLen+4+payloadLen { - return frame, fmt.Errorf("buffer too short for payload") - } - - frame.Payload = buf[7+sidLen+4 : 7+sidLen+4+payloadLen] - frame.Length = func() int { - return 7 + sidLen + 4 + payloadLen - } - - return frame, nil -} diff --git a/internal/sshutil/framebatcher.go b/internal/sshutil/framebatcher.go deleted file mode 100644 index 6653452..0000000 --- a/internal/sshutil/framebatcher.go +++ /dev/null @@ -1,61 +0,0 @@ -package sshutil - -import ( - "io" - "sync" - "time" - - "github.com/tale/headplane/agent/internal/util" -) - -type FrameBatcher struct { - mu sync.Mutex - buffer []byte - writer io.Writer - timer *time.Timer - interval time.Duration - done chan struct{} -} - -func NewFrameBatcher(writer io.Writer, interval time.Duration) *FrameBatcher { - return &FrameBatcher{ - writer: writer, - interval: interval, - done: make(chan struct{}), - } -} - -func (b *FrameBatcher) QueueMsg(msg []byte) { - b.mu.Lock() - defer b.mu.Unlock() - - b.buffer = append(b.buffer, msg...) - if b.timer != nil { - b.timer.Stop() - } - - b.timer = time.AfterFunc(b.interval, b.flush) -} - -func (b *FrameBatcher) flush() { - log := util.GetLogger() - - b.mu.Lock() - defer b.mu.Unlock() - - if len(b.buffer) == 0 { - return - } - - _, err := b.writer.Write(b.buffer) - if err != nil { - log.Error("Failed to write batched message: %v", err) - } - - b.buffer = nil -} - -func (b *FrameBatcher) Close() { - close(b.done) - b.flush() -} diff --git a/internal/sshutil/handler.go b/internal/sshutil/handler.go deleted file mode 100644 index 63b90c9..0000000 --- a/internal/sshutil/handler.go +++ /dev/null @@ -1,152 +0,0 @@ -package sshutil - -import ( - "io" - "os" - "time" - - "github.com/tale/headplane/agent/internal/util" -) - -// The file descriptors attached by the parent node process -const ( - InputFd = 3 - OutputFd = 4 -) - -// DispatchSSHStdin listens for SSH stdin frames on the InputFd file descriptor -// and writes the payload to the appropriate session's stdin. -// -// This function runs in a goroutine in main and is responsible for dispatching -// to ALL connections, not just its own like dispatchSSHStdout does. -func DispatchSSHStdin() { - log := util.GetLogger() - - log.Debug("Opening file descriptor: %d for SSH stdin", InputFd) - fd := os.NewFile(InputFd, "ssh_stdin") - if fd == nil { - log.Error("Failed to open file descriptor %d for SSH stdin", InputFd) - return - } - - log.Info("Listening for SSH stdin on fd %d", InputFd) - go func() { - buffer := make([]byte, 8192) - hpls1 := HPLSFrame1{} - - for { - // This is the only check where we can detect if the descriptor - // was closed so we can return and exit the goroutine. - bufCount, err := fd.Read(buffer) - if err != nil { - log.Error("Failed to read from SSH stdin: %v", err) - return - } - - // Check if we have 0 EOF, which means the descriptor was closed. - if bufCount == 0 { - log.Info("SSH stdin closed, stopping listener") - return - } - - offset := 0 - for offset < bufCount { - frame, err := hpls1.Decode(buffer[offset:bufCount]) - if err != nil { - // We need to wait for more data to decode the frame - break - } - - if frame.ChannelType != ChannelTypeStdin { - log.Error("Received invalid channel type: %d, expected %d", frame.ChannelType, ChannelTypeStdin) - continue - } - - offset += frame.Length() - log.Debug("Received SSH stdin frame: %s", frame.SessionID) - sess, ok := lookupSession(frame.SessionID) - if !ok { - log.Error("Invalid session ID: %s", frame.SessionID) - continue - } - - // Write the payload to the session's stdin - writeCount, err := sess.Stdin.Write(frame.Payload) - if err != nil { - log.Error("Failed to write to session stdin: %v", err) - continue - } - - log.Debug("Wrote %d bytes to session %s stdin", writeCount, frame.SessionID) - } - } - }() -} - -func dispatchSSHStdout(id string, stdout io.Reader, stderr io.Reader) { - log := util.GetLogger() - - log.Debug("Opening file descriptor: %d for SSH stdout", OutputFd) - fd := os.NewFile(OutputFd, "ssh_stdout") - if fd == nil { - log.Error("Failed to open file descriptor %d for SSH stdout", OutputFd) - return - } - - batcher := NewFrameBatcher(fd, 10*time.Millisecond) // Roughly 60fps - - go readerStreamRoutine(StreamRoutine{ - SessionID: id, - Reader: stdout, - Writer: batcher, - ChannelType: ChannelTypeStdout, - }) - - go readerStreamRoutine(StreamRoutine{ - SessionID: id, - Reader: stderr, - Writer: batcher, - ChannelType: ChannelTypeStderr, - }) -} - -type StreamRoutine struct { - SessionID string - Reader io.Reader - Writer *FrameBatcher - ChannelType ChannelType -} - -func readerStreamRoutine(routine StreamRoutine) { - hpls1 := HPLSFrame1{} - buf := make([]byte, 16384) // 16 KiB buffer - for { - byteCount, err := routine.Reader.Read(buf) - if err != nil { - if err != io.EOF { - util.GetLogger().Error("Failed to read from reader: %v", err) - } - - break - } - - frame, err := hpls1.Encode(SSHFrame{ - ChannelType: routine.ChannelType, - SessionID: routine.SessionID, - Payload: buf[:byteCount], - }) - - if err != nil { - util.GetLogger().Error("Failed to encode frame: %v", err) - continue - } - - // if _, err := routine.Writer.Write(frame); err != nil { - // util.GetLogger().Error("Failed to write frame to writer: %v", err) - // break - // } - // - - routine.Writer.QueueMsg(frame) - } -} diff --git a/internal/sshutil/sessions.go b/internal/sshutil/sessions.go deleted file mode 100644 index 3bfb04d..0000000 --- a/internal/sshutil/sessions.go +++ /dev/null @@ -1,83 +0,0 @@ -package sshutil - -import ( - "errors" - "io" - "sync" - - "github.com/tale/headplane/agent/internal/util" - "golang.org/x/crypto/ssh" -) - -type SessionContext struct { - ID string - Session *ssh.Session - Stdin io.WriteCloser - Stdout io.Reader - Stderr io.Reader - InputCh chan []byte -} - -var sessions = make(map[string]*SessionContext) -var sessionsLock sync.RWMutex - -func registerSessionChans(id string, session *ssh.Session) (*SessionContext, error) { - log := util.GetLogger() - - sessionsLock.Lock() - defer sessionsLock.Unlock() - - if _, exists := sessions[id]; exists { - return sessions[id], nil - } - - stdin, err := session.StdinPipe() - if err != nil { - return nil, errors.New("failed to create stdin pipe: " + err.Error()) - } - - stdout, err := session.StdoutPipe() - if err != nil { - stdin.Close() - return nil, errors.New("failed to create stdout pipe: " + err.Error()) - } - - stderr, err := session.StderrPipe() - if err != nil { - stdin.Close() - return nil, errors.New("failed to create stderr pipe: " + err.Error()) - } - - ctx := &SessionContext{ - ID: id, - Session: session, - Stdin: stdin, - Stdout: stdout, - Stderr: stderr, - // Buffered channel to queue input data - InputCh: make(chan []byte, 256), - } - - sessions[id] = ctx - log.Debug("Registered session %s with stdin, stdout, and stderr pipes", id) - return ctx, nil -} - -func lookupSession(id string) (*SessionContext, bool) { - sessionsLock.RLock() - defer sessionsLock.RUnlock() - - sessionContext, exists := sessions[id] - return sessionContext, exists -} - -func RemoveSession(id string) { - sessionsLock.Lock() - defer sessionsLock.Unlock() - - if sessionContext, exists := sessions[id]; exists { - sessionContext.Stdin.Close() // Close the stdin pipe - sessionContext.Session.Close() // Close the SSH session - delete(sessions, id) // Remove from the map - } -} diff --git a/internal/tsnet/peers.go b/internal/tsnet/peers.go index 9628dca..1ce2c2b 100644 --- a/internal/tsnet/peers.go +++ b/internal/tsnet/peers.go @@ -6,7 +6,7 @@ import ( "fmt" "strings" - "github.com/tale/headplane/agent/internal/util" + "github.com/tale/headplane/internal/util" "tailscale.com/tailcfg" "tailscale.com/types/key" diff --git a/internal/tsnet/server.go b/internal/tsnet/server.go index eb4ffe8..2909911 100644 --- a/internal/tsnet/server.go +++ b/internal/tsnet/server.go @@ -5,16 +5,16 @@ import ( "os" "path/filepath" - "github.com/tale/headplane/agent/internal/config" - "github.com/tale/headplane/agent/internal/util" - "tailscale.com/client/tailscale" + "github.com/tale/headplane/internal/config" + "github.com/tale/headplane/internal/util" + "tailscale.com/client/local" "tailscale.com/tsnet" ) // Wrapper type so we can add methods to the server. type TSAgent struct { *tsnet.Server - Lc *tailscale.LocalClient + Lc *local.Client ID string } diff --git a/mise.toml b/mise.toml index bc3e84b..cc54c04 100644 --- a/mise.toml +++ b/mise.toml @@ -17,6 +17,11 @@ description = "Builds the Go WebAssembly module for Tailscale SSH" env = { GOOS = "js", GOARCH = "wasm" } run = "go build -o app/hp_ssh.wasm ./cmd/hp_ssh" +[tasks.build-go-agent] +alias = ["agent"] +description = "Builds the Go agent for HostInfo" +run = "go build -o build/hp_agent ./cmd/hp_agent" + [tasks.generate-caddy-certs] alias = ["mkcert"] dir = "{{cwd}}/test/caddy/certs" @@ -29,6 +34,7 @@ run = [ [tasks.ci] description = "Runs the CI pipeline" depends = ["build-go-wasm"] +depends_post = ["build-go-agent"] run = [ "pnpm install", "pnpm run build" From 799d7b5728d5d5618c6ad078a7dea52e8d898fa4 Mon Sep 17 00:00:00 2001 From: Aarnav Tale Date: Mon, 16 Jun 2025 11:41:04 -0400 Subject: [PATCH 17/97] chore: update go deps nix hash --- nix/agent.nix | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nix/agent.nix b/nix/agent.nix index 29f1125..bde9927 100644 --- a/nix/agent.nix +++ b/nix/agent.nix @@ -3,7 +3,7 @@ buildGoModule { pname = "hp_agent"; version = (builtins.fromJSON (builtins.readFile ../package.json)).version; src = ../.; - vendorHash = "sha256-qAB6Q19ymeom7126N7ASaFkcqDBRdbjgmG/OTpcX9sI="; + vendorHash = "sha256-3hZzDORAH+D4FW6SkOv3Enddd+q36ZALryvCPD9E5Ac="; ldflags = ["-s" "-w"]; env.CGO_ENABLED = 0; } From b34a3f0ca1e11b68602b094e6c27684222f0ec35 Mon Sep 17 00:00:00 2001 From: Aarnav Tale Date: Mon, 16 Jun 2025 11:43:29 -0400 Subject: [PATCH 18/97] chore: sigh update pnpm deps hash --- nix/package.nix | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nix/package.nix b/nix/package.nix index 11349c4..4afff51 100644 --- a/nix/package.nix +++ b/nix/package.nix @@ -23,7 +23,7 @@ stdenv.mkDerivation (finalAttrs: { pnpmDeps = pnpm_10.fetchDeps { inherit (finalAttrs) pname version src; - hash = "sha256-xjjkqbgjYaAGYAmlTFE+Lq3Hp6myZKaW3br0YTDNhQA="; + hash = "sha256-owncSijanuu+So2LZqAy3XEz12hfTgcjCd3cVsgT4a8="; }; buildPhase = '' From 1e86b0e95b92464d265c54c4eb85048d2eb9c47f Mon Sep 17 00:00:00 2001 From: Aarnav Tale Date: Wed, 18 Jun 2025 11:56:27 -0400 Subject: [PATCH 19/97] feat: add ssh buttons in the ui using hostinfo checks --- app/components/tags/TailscaleSSH.tsx | 21 +++++ .../machines/components/machine-row.tsx | 8 ++ app/routes/machines/components/menu.tsx | 82 +++++++++++++++---- app/routes/machines/machine.tsx | 1 - app/routes/ssh/console.tsx | 7 +- 5 files changed, 97 insertions(+), 22 deletions(-) create mode 100644 app/components/tags/TailscaleSSH.tsx diff --git a/app/components/tags/TailscaleSSH.tsx b/app/components/tags/TailscaleSSH.tsx new file mode 100644 index 0000000..96262b9 --- /dev/null +++ b/app/components/tags/TailscaleSSH.tsx @@ -0,0 +1,21 @@ +import cn from '~/utils/cn'; +import Chip from '../Chip'; +import Tooltip from '../Tooltip'; + +export function TailscaleSSHTag() { + return ( + + + + This machine advertises Tailscale SSH, which allows you to authenticate + SSH credentials using your Tailscale account and via the Headplane web + UI. + + + ); +} diff --git a/app/routes/machines/components/machine-row.tsx b/app/routes/machines/components/machine-row.tsx index 285e7fb..0dba2d9 100644 --- a/app/routes/machines/components/machine-row.tsx +++ b/app/routes/machines/components/machine-row.tsx @@ -12,6 +12,7 @@ import { ExitNodeTag } from '~/components/tags/ExitNode'; import { ExpiryTag } from '~/components/tags/Expiry'; import { HeadplaneAgentTag } from '~/components/tags/HeadplaneAgent'; import { SubnetTag } from '~/components/tags/Subnet'; +import { TailscaleSSHTag } from '~/components/tags/TailscaleSSH'; import { PopulatedNode } from '~/utils/node-info'; import toast from '~/utils/toast'; import MenuOptions from './menu'; @@ -177,6 +178,10 @@ export function uiTagsForNode(node: PopulatedNode, isAgent?: boolean) { uiTags.push('subnet-approved'); } + if (node.hostInfo?.sshHostKeys && node.hostInfo?.sshHostKeys.length > 0) { + uiTags.push('tailscale-ssh'); + } + if (isAgent === true) { uiTags.push('headplane-agent'); } @@ -205,6 +210,9 @@ export function mapTagsToComponents(node: PopulatedNode, uiTags: string[]) { /> ); + case 'tailscale-ssh': + return ; + case 'headplane-agent': return ; diff --git a/app/routes/machines/components/menu.tsx b/app/routes/machines/components/menu.tsx index 167f92d..c3acf16 100644 --- a/app/routes/machines/components/menu.tsx +++ b/app/routes/machines/components/menu.tsx @@ -1,4 +1,4 @@ -import { Cog, Ellipsis } from 'lucide-react'; +import { Cog, Ellipsis, SquareTerminal } from 'lucide-react'; import { useState } from 'react'; import Menu from '~/components/Menu'; import type { User } from '~/types'; @@ -10,7 +10,6 @@ import Move from '../dialogs/move'; import Rename from '../dialogs/rename'; import Routes from '../dialogs/routes'; import Tags from '../dialogs/tags'; - interface MenuProps { node: PopulatedNode; users: User[]; @@ -29,6 +28,9 @@ export default function MachineMenu({ isDisabled, }: MenuProps) { const [modal, setModal] = useState(null); + const supportsTailscaleSSH = + node.hostInfo?.sshHostKeys && node.hostInfo?.sshHostKeys.length > 0; + return ( <> {modal === 'remove' && ( @@ -90,21 +92,69 @@ export default function MachineMenu({ {isFullButton ? ( - - -

Machine Settings

-
+
+ {supportsTailscaleSSH ? ( + { + // We need to use JS to open the SSH URL + // in a new WINDOW since href can only + // do a new TAB. + window.open( + // TODO: Use the actual real username lol + `${__PREFIX__}/ssh?hostname=${node.name}&username=tale`, + '_blank', + 'noopener,noreferrer,width=800,height=600', + ); + }} + > + +

SSH

+
+ ) : undefined} + + +

Machine Settings

+
+
) : ( - - - +
+ {supportsTailscaleSSH ? ( + { + // We need to use JS to open the SSH URL + // in a new WINDOW since href can only + // do a new TAB. + window.open( + // TODO: Use the actual real username lol + `${__PREFIX__}/ssh?hostname=${node.name}&username=tale`, + '_blank', + 'noopener,noreferrer,width=800,height=600', + ); + }} + className={cn( + 'py-0.5 w-fit bg-transparent border-transparent', + 'border group-hover:border-headplane-200', + 'dark:group-hover:border-headplane-700', + 'opacity-0 pointer-events-none group-hover:opacity-100', + 'group-hover:pointer-events-auto', + )} + > + SSH + + ) : undefined} + + + +
)} setModal(key as Modal)} diff --git a/app/routes/machines/machine.tsx b/app/routes/machines/machine.tsx index ae3f892..962ff45 100644 --- a/app/routes/machines/machine.tsx +++ b/app/routes/machines/machine.tsx @@ -2,7 +2,6 @@ import { CheckCircle, CircleSlash, Info, UserCircle } from 'lucide-react'; import { useMemo, useState } from 'react'; import type { ActionFunctionArgs, LoaderFunctionArgs } from 'react-router'; import { Link as RemixLink, useLoaderData } from 'react-router'; -import { mapTag } from 'yaml/util'; import Attribute from '~/components/Attribute'; import Button from '~/components/Button'; import Card from '~/components/Card'; diff --git a/app/routes/ssh/console.tsx b/app/routes/ssh/console.tsx index 0741224..9588f9d 100644 --- a/app/routes/ssh/console.tsx +++ b/app/routes/ssh/console.tsx @@ -71,11 +71,8 @@ export async function loader({ const qp = new URL(request.url).searchParams; const username = qp.get('username') || undefined; const hostname = qp.get('hostname') || undefined; - const port = qp.get('port') - ? Number.parseInt(qp.get('port')!, 10) - : undefined; - if (!username || !hostname || !port) { - throw data('Missing required parameters: username, hostname, port', 400); + if (!username || !hostname) { + throw data('Missing required parameters: username, hostname', 400); } // TODO: Verify the Host headers to ensure CORS friendly // TODO: Check if the URL actually resolves correctly From bf1d75a27ab38e37c2e389cafaf5c78dcd509c1e Mon Sep 17 00:00:00 2001 From: Aarnav Tale Date: Wed, 18 Jun 2025 13:24:34 -0400 Subject: [PATCH 20/97] fix: resize cols and rows, not the other way around *sigh* --- app/routes/ssh/console.tsx | 8 +++++ app/routes/ssh/hp_ssh.d.ts | 12 -------- app/routes/ssh/xterm.client.tsx | 52 +++++++++++++++++++++++---------- internal/hp_ipn/ssh.go | 2 +- 4 files changed, 45 insertions(+), 29 deletions(-) diff --git a/app/routes/ssh/console.tsx b/app/routes/ssh/console.tsx index 9588f9d..a5ad6e4 100644 --- a/app/routes/ssh/console.tsx +++ b/app/routes/ssh/console.tsx @@ -23,6 +23,13 @@ export async function loader({ request, context, }: LoaderFunctionArgs) { + if (!context.agents?.agentID()) { + throw data( + 'WebSSH is only available with the Headplane agent integration', + 400, + ); + } + const session = await context.sessions.auth(request); const user = session.get('user'); if (!user) { @@ -35,6 +42,7 @@ export async function loader({ 'v1/user', session.get('api_key')!, ); + // MARK: This assumes that a user has authenticated with Headscale first // Since the only way to enforce permissions via ACLs is to generate a // pre-authkey which REQUIRES a user ID, meaning the user has to have diff --git a/app/routes/ssh/hp_ssh.d.ts b/app/routes/ssh/hp_ssh.d.ts index 46b7df9..72bcb33 100644 --- a/app/routes/ssh/hp_ssh.d.ts +++ b/app/routes/ssh/hp_ssh.d.ts @@ -46,18 +46,6 @@ interface XtermConfig { OnDisconnect: () => void; } -// interface SSHTerminalConfig { -// writeFn: (data: string) => void; -// writeErrorFn: (error: string) => void; -// setReadFn: (cb: (input: string) => void) => void; -// rows: number; -// cols: number; -// timeoutSeconds?: number; -// onConnectionProgress: (message: string) => void; -// onConnected: () => void; -// onDone: () => void; -// } - interface SSHSession { Close(): boolean; Resize(rows: number, cols: number): boolean; diff --git a/app/routes/ssh/xterm.client.tsx b/app/routes/ssh/xterm.client.tsx index 566b9d8..3c152e9 100644 --- a/app/routes/ssh/xterm.client.tsx +++ b/app/routes/ssh/xterm.client.tsx @@ -30,6 +30,17 @@ export default function XTerm({ ipn, username, hostname }: XTermProps) { useEffect(() => { pause(); + if (!container.current) { + console.error('Container ref is not set'); + return; + } + + // Don't create a new terminal if one already exists + if (term.current) { + console.warn('Terminal already exists, skipping initialization'); + return; + } + const terminal = new xterm.Terminal({ allowProposedApi: true, cursorBlink: true, @@ -52,24 +63,23 @@ export default function XTerm({ ipn, username, hostname }: XTermProps) { ); terminal.unicode.activeVersion = '11'; - const gl = new WebglAddon(); - terminal.loadAddon(gl); - const fit = new FitAddon(); terminal.loadAddon(fit); + const gl = new WebglAddon(); + terminal.loadAddon(gl); + gl.onContextLoss(() => { console.warn('WebGL context lost, falling back to canvas rendering'); gl.dispose(); }); + fit.fit(); + term.current = terminal; terminal.open(container.current!); terminal.focus(); - term.current = terminal; - let ro: ResizeObserver | null = null; let onUnload: ((e: Event) => void) | null = null; - const session = ipn.OpenSSH(hostname, username, { Rows: terminal.rows, Cols: terminal.cols, @@ -103,8 +113,7 @@ export default function XTerm({ ipn, username, hostname }: XTermProps) { }, }); - const parent = container.current?.ownerDocument.defaultView ?? window; - ro = new parent.ResizeObserver(() => { + const ro = new ResizeObserver(() => { if (term.current) { setIsResizing(true); fit.fit(); @@ -112,12 +121,10 @@ export default function XTerm({ ipn, username, hostname }: XTermProps) { } }); - if (container.current) { - ro.observe(container.current); - } - + ro.observe(container.current); terminal.onResize(({ cols, rows }) => { - session.Resize(rows, cols); + console.log(`Terminal resized to ${cols}x${rows}`); + session.Resize(cols, rows); }); terminal.onData((data) => { @@ -126,12 +133,25 @@ export default function XTerm({ ipn, username, hostname }: XTermProps) { onUnload = (_) => session.Close(); parent.addEventListener('unload', onUnload); + + return () => { + if (onUnload) { + parent.removeEventListener('unload', onUnload); + } + + session.Close(); + ro?.disconnect(); + terminal.dispose(); + term.current = null; + inputRef.current = null; + console.log('SSH session closed and terminal disposed'); + }; }, []); return ( -
+ <> {isLoading ? ( -
+
) : undefined} @@ -159,6 +179,6 @@ export default function XTerm({ ipn, username, hostname }: XTermProps) { Failed to connect to SSH session
) : undefined} -
+ ); } diff --git a/internal/hp_ipn/ssh.go b/internal/hp_ipn/ssh.go index a03e3cb..1728799 100644 --- a/internal/hp_ipn/ssh.go +++ b/internal/hp_ipn/ssh.go @@ -150,7 +150,7 @@ func (s *SSHSession) Resize(rows, cols int) error { return nil } - return s.Pty.WindowChange(rows, cols) + return s.Pty.WindowChange(cols, rows) } // Closes the SSH session. From b18147fa82768608047819d78f19d2fb255fe79f Mon Sep 17 00:00:00 2001 From: Aarnav Tale Date: Fri, 20 Jun 2025 00:14:00 -0400 Subject: [PATCH 21/97] feat: cleanup removal of old ssh plexer and logic we also have added the necessary logic to auto prune ephemeral nodes because headscale doesn't seem to automatically remove them. this change made use of a database which is now stored in the persistent headplane directory. --- CHANGELOG.md | 5 + app/layouts/dashboard.tsx | 17 +- app/routes/acls/overview.tsx | 4 +- app/routes/ssh/console.tsx | 166 ++++++-- app/routes/ssh/hp_ssh.d.ts | 6 +- app/server/agent/dispatcher.ts | 89 ---- app/server/agent/encoder.ts | 91 ----- app/server/agent/ssh.ts | 255 ------------ app/server/config/schema.ts | 1 + app/server/db/client.ts | 26 ++ app/server/db/pruner.ts | 58 +++ app/server/db/schema.ts | 9 + app/server/index.ts | 38 +- app/server/web/agent.ts | 12 - biome.json | 2 +- config.example.yaml | 7 + drizzle.config.ts | 8 + drizzle/0000_spicy_bloodscream.sql | 4 + drizzle/meta/0000_snapshot.json | 42 ++ drizzle/meta/_journal.json | 13 + internal/hp_ipn/jsfunc.go | 8 +- internal/hp_ipn/notify.go | 4 + package.json | 13 +- pnpm-lock.yaml | 632 ++++++++++++++++++++++++++++- 24 files changed, 963 insertions(+), 547 deletions(-) delete mode 100644 app/server/agent/dispatcher.ts delete mode 100644 app/server/agent/encoder.ts delete mode 100644 app/server/agent/ssh.ts create mode 100644 app/server/db/client.ts create mode 100644 app/server/db/pruner.ts create mode 100644 app/server/db/schema.ts create mode 100644 drizzle.config.ts create mode 100644 drizzle/0000_spicy_bloodscream.sql create mode 100644 drizzle/meta/0000_snapshot.json create mode 100644 drizzle/meta/_journal.json diff --git a/CHANGELOG.md b/CHANGELOG.md index 9bd38be..e6f45e3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,8 @@ +### 0.6.1 (Next) +- **Headplane now supports connecting to machines via SSH in the web browser.** + - This is an experimental feature and requires the `integration.agent` section to be set up in the config file. + - This is built on top of a Go binary that runs in WebAssembly, using Xterm.js for the terminal interface. + ### 0.6.0 (May 25, 2025) - Headplane 0.6.0 now requires **Headscale 0.26.0** or newer. - Breaking API changes with routes and pre auth keys are now supported (closes [#204](https://github.com/tale/headplane/issues/204)). diff --git a/app/layouts/dashboard.tsx b/app/layouts/dashboard.tsx index ca53a7e..5e93233 100644 --- a/app/layouts/dashboard.tsx +++ b/app/layouts/dashboard.tsx @@ -1,25 +1,18 @@ -import { XCircleFillIcon } from '@primer/octicons-react'; -import { ServerCrash } from 'lucide-react'; -import { - type LoaderFunctionArgs, - isRouteErrorResponse, - redirect, - useRouteError, -} from 'react-router'; -import { Outlet, useLoaderData } from 'react-router'; -import Card from '~/components/Card'; -import { ErrorPopup, getErrorMessage } from '~/components/Error'; +import { type LoaderFunctionArgs, Outlet, redirect } from 'react-router'; +import { ErrorPopup } from '~/components/Error'; import type { LoadContext } from '~/server'; +import { pruneEphemeralNodes } from '~/server/db/pruner'; import ResponseError from '~/server/headscale/api-error'; -import cn from '~/utils/cn'; import log from '~/utils/log'; export async function loader({ request, context, + ...rest }: LoaderFunctionArgs) { const healthy = await context.client.healthcheck(); const session = await context.sessions.auth(request); + await pruneEphemeralNodes({ context, request, ...rest }); // We shouldn't session invalidate if Headscale is down // TODO: Notify in the logs or the UI that OIDC auth key is wrong if enabled diff --git a/app/routes/acls/overview.tsx b/app/routes/acls/overview.tsx index b1a4667..d4c0f33 100644 --- a/app/routes/acls/overview.tsx +++ b/app/routes/acls/overview.tsx @@ -66,8 +66,8 @@ export default function Page() { The ACL policy mode is most likely set to file in your Headscale configuration. This means that the ACL file cannot be edited through the web interface. In order to resolve this, you'll need to - set policy.mode to database in your Headscale - configuration. + set policy.mode to database in your + Headscale configuration. ) : undefined}

Access Control List (ACL)

diff --git a/app/routes/ssh/console.tsx b/app/routes/ssh/console.tsx index a5ad6e4..e5ccf00 100644 --- a/app/routes/ssh/console.tsx +++ b/app/routes/ssh/console.tsx @@ -2,17 +2,22 @@ import { faker } from '@faker-js/faker'; import { Loader2 } from 'lucide-react'; import { useEffect, useState } from 'react'; import { + ActionFunctionArgs, LoaderFunctionArgs, ShouldRevalidateFunction, data, useLoaderData, + useSubmit, } from 'react-router'; +import { useEventSource } from 'remix-utils/sse/react'; import { LoadContext } from '~/server'; -import { PreAuthKey, User } from '~/types'; +import { Machine, PreAuthKey, User } from '~/types'; import { useLiveData } from '~/utils/live-data'; import XTerm from './xterm.client'; +import { eq } from 'drizzle-orm'; import wasm from '~/hp_ssh.wasm?url'; +import { EphemeralNodeInsert, ephemeralNodes } from '~/server/db/schema'; import '~/wasm_exec'; export const shouldRevalidate: ShouldRevalidateFunction = () => { @@ -54,12 +59,14 @@ export async function loader({ } return subject === user.subject; }); + if (!lookup) { throw data( `User with subject ${user.subject} not found within Headscale`, 404, ); } + const { preAuthKey } = await context.client.post<{ preAuthKey: PreAuthKey }>( 'v1/preauthkey', session.get('api_key')!, @@ -67,30 +74,71 @@ export async function loader({ user: lookup.id, reusable: false, ephemeral: true, - expiration: new Date(Date.now() + 60 * 60 * 1000).toISOString(), // 1 hour + expiration: new Date(Date.now() + 60 * 1000).toISOString(), // 1 minute }, ); + // TODO: Enable config to enforce generate_authkeys capability // For now, any user is capable of WebSSH connections // const check = await context.sessions.check( // request, // Capabilities.generate_authkeys, // ); + const qp = new URL(request.url).searchParams; const username = qp.get('username') || undefined; const hostname = qp.get('hostname') || undefined; if (!username || !hostname) { throw data('Missing required parameters: username, hostname', 400); } - // TODO: Verify the Host headers to ensure CORS friendly - // TODO: Check if the URL actually resolves correctly - // TODO: Keep track of hostname since ephemeral nodes are broken atm + + // We're making a request to /key?v=116 to check the CORS headers + const u = context.config.headscale.public_url ?? context.config.headscale.url; + // const res = await fetch(`${u}/key?v=116`, { + // method: 'GET', + // }); + + // const corsOrigin = res.headers.get('Access-Control-Allow-Origin'); + // const corsMethods = res.headers.get('Access-Control-Allow-Methods'); + // const corsHeaders = res.headers.get('Access-Control-Allow-Headers'); + // console.log(corsOrigin, corsMethods, corsHeaders); + + // if (!corsOrigin || !corsMethods || !corsHeaders) { + // throw data( + // 'Headscale server does not have the required CORS headers for WebSSH', + // 500, + // ); + // } + + const { nodes } = await context.client.get<{ nodes: Machine[] }>( + 'v1/node', + session.get('api_key')!, + ); + + // node.name is the hostname, given_name is the set name + const lookupNode = nodes.find((n) => n.name === hostname); + if (!lookupNode) { + throw data(`Node with hostname ${hostname} not found`, 404); + } + + // Last thing is keeping track of the ephemeral node in the database + // because Headscale doesn't automatically delete ephemeral nodes??? + const [ephemeralNode] = await context.db + .insert(ephemeralNodes) + .values({ + auth_key: preAuthKey.key, + } satisfies EphemeralNodeInsert) + .returning(); + console.log('Created ephemeral node:', ephemeralNode); + return { - PreAuthKey: preAuthKey.key, - ControlURL: - context.config.headscale.public_url ?? context.config.headscale.url, - Hostname: generateHostname(username), - ssh: { + ipnDetails: { + PreAuthKey: preAuthKey.key, + Hostname: generateHostname(username), + ControlURL: u, + }, + + sshDetails: { username, hostname, }, @@ -115,11 +163,46 @@ function generateHostname(username: string) { return `ssh-${adjective}-${noun}-${username}`; } +export async function action({ + request, + context, +}: ActionFunctionArgs) { + const session = await context.sessions.auth(request); + if (!context.agents?.agentID()) { + throw data( + 'WebSSH is only available with the Headplane agent integration', + 400, + ); + } + + const form = await request.formData(); + const nodeKey = form.get('node_key'); + const authKey = form.get('auth_key'); + + if (nodeKey === null || typeof nodeKey !== 'string') { + throw data('Missing node_key', 400); + } + + if (authKey === null || typeof authKey !== 'string') { + throw data('Missing auth_key', 400); + } + + await context.db + .update(ephemeralNodes) + .set({ + node_key: nodeKey, + }) + .where(eq(ephemeralNodes.auth_key, authKey)); +} + export default function Page() { + const submit = useSubmit(); const { pause } = useLiveData(); + const [ipn, setIpn] = useState(null); - const { PreAuthKey, ControlURL, Hostname, ssh } = - useLoaderData(); + const [nodeKey, setNodeKey] = useState(null); + const { ipnDetails, sshDetails } = useLoaderData(); + const ping = useEventSource('/ssh/ping', { event: 'ping' }); useEffect(() => { pause(); @@ -127,30 +210,39 @@ export default function Page() { WebAssembly.instantiateStreaming(fetch(wasm), go.importObject).then( (value) => { go.run(value.instance); - const handle = TsWasmNet( - { - PreAuthKey, - ControlURL, - Hostname, + const handle = TsWasmNet(ipnDetails, { + NotifyState: (state) => { + console.log('State changed:', state); + if (state === 'Running') { + setIpn(handle); + } }, - { - NotifyState: (state) => { - console.log('State changed:', state); - if (state === 'Running') { - setIpn(handle); - } - }, - NotifyNetMap: (netmap) => { - console.log('NetMap updated:', netmap); - }, - NotifyBrowseToURL: (url) => { - console.log('Browse to URL:', url); - }, - NotifyPanicRecover: (message) => { - console.error('Panic recover:', message); - }, + NotifyNetMap: (netmap) => { + // Only set NodeKey if it is not already set and then + // also dispatch that to the backend to track the + // ephemeral node. + // + // We open an SSE connection to the backend + // so that when the connection is closed, + // the backend can delete the ephemeral node. + if (nodeKey === null) { + setNodeKey(netmap.NodeKey); + submit( + { + node_key: netmap.NodeKey, + auth_key: ipnDetails.PreAuthKey, + }, + { method: 'POST' }, + ); + } }, - ); + NotifyBrowseToURL: (url) => { + console.log('Browse to URL:', url); + }, + NotifyPanicRecover: (message) => { + console.error('Panic recover:', message); + }, + }); handle.Start(); }, @@ -165,7 +257,11 @@ export default function Page() {
) : (
- +
)}
diff --git a/app/routes/ssh/hp_ssh.d.ts b/app/routes/ssh/hp_ssh.d.ts index 72bcb33..8b9784b 100644 --- a/app/routes/ssh/hp_ssh.d.ts +++ b/app/routes/ssh/hp_ssh.d.ts @@ -11,11 +11,15 @@ interface TsWasmNetOptions { interface TsWasmNetCallbacks { NotifyState: (state: IPNState) => void; - NotifyNetMap: (netMapJson: string) => void; + NotifyNetMap: (netmap: TsWasmNetMap) => void; NotifyBrowseToURL: (url: string) => void; NotifyPanicRecover: (err: string) => void; } +interface TsWasmNetMap { + NodeKey: string; +} + interface TsWasmNet { Start: () => void; OpenSSH: ( diff --git a/app/server/agent/dispatcher.ts b/app/server/agent/dispatcher.ts deleted file mode 100644 index 87585c3..0000000 --- a/app/server/agent/dispatcher.ts +++ /dev/null @@ -1,89 +0,0 @@ -import type { Writable } from 'node:stream'; -import { encode } from 'cborg'; -import { WSContext } from 'hono/ws'; -import { ChannelType } from './encoder'; - -export interface Command { - op: string; - payload: unknown; -} - -export interface SSHConnectCommand extends Command { - op: 'ssh_conn'; - payload: { - sessionId: string; - username: string; - hostname: string; - port: number; - }; -} - -export interface SSHCloseCommand extends Command { - op: 'ssh_term'; - payload: { - sessionId: string; - }; -} - -export interface SSHResizeCommand extends Command { - op: 'ssh_resize'; - payload: { - sessionId: string; - width: number; - height: number; - }; -} - -export interface SSHDataCommand extends Command { - op: 'ssh_data'; - payload: { - sessionId: string; - data: Uint8Array; - }; -} - -type AgentCommand = SSHConnectCommand | SSHCloseCommand | SSHResizeCommand; - -export async function dispatchCommand( - dispatcher: Writable, - command: AgentCommand, -) { - return new Promise((resolve, reject) => { - const encodedCommand = Buffer.concat([encode(command), Buffer.from('\n')]); - dispatcher.write(encodedCommand, (err) => { - if (err) { - reject(err); - } else { - resolve(); - } - }); - }); -} - -export interface SSHConnectData extends Command { - op: 'ssh_conn_successful'; - payload: { - sessionId: string; - }; -} - -export interface SSHConnectFailedData extends Command { - op: 'ssh_conn_failed'; - payload: { - reason: string; - }; -} - -export interface SSHFrameData extends Command { - op: 'ssh_frame'; - payload: { - channel: ChannelType; - frame: Buffer; - }; -} - -type WebData = SSHConnectData | SSHConnectFailedData | SSHFrameData; - -export function dispatchWeb(dispatcher: WSContext, data: WebData) { - return dispatcher.send(encode(data)); -} diff --git a/app/server/agent/encoder.ts b/app/server/agent/encoder.ts deleted file mode 100644 index 73ac667..0000000 --- a/app/server/agent/encoder.ts +++ /dev/null @@ -1,91 +0,0 @@ -// Refer to agent/internal/sshutil/encoder.go for more details -// This is the Node.js implementation of the SSH encoder -import log from '~/utils/log'; - -const MAGIC = 'HPLS'; -const VERSION = 1; - -// 0 -> Stdin -// 1 -> Stdout -// 2 -> Stderr -export type ChannelType = 0 | 1 | 2; - -interface SSHFrame { - sessionId: string; - channel: ChannelType; - payload: Buffer; -} - -export async function encodeSSHFrame(frame: SSHFrame) { - const sid = Buffer.from(frame.sessionId, 'utf8'); - if (sid.length > 255) { - log.error('agent', 'SSH session ID too long: %s', frame.sessionId); - return; - } - - // Size can only hold 4 bytes - if (frame.payload.length > 0xffffffff) { - log.error('agent', 'SSH payload too large: %d bytes', frame.payload.length); - return; - } - - const frameSize = - 4 + // Magic - 1 + // Version - 1 + // Channel Type - (1 + sid.length) + // Session ID length + SID - (4 + frame.payload.length); // Payload length + Payload - - const buf = Buffer.alloc(frameSize); - buf.write(MAGIC, 0, 'utf8'); - buf.writeUInt8(VERSION, 4); - buf.writeUInt8(frame.channel, 5); - buf.writeUInt8(sid.length, 6); - - const offset = 7 + sid.length; - sid.copy(buf, 7); - - buf.writeUInt32BE(frame.payload.length, offset); - frame.payload.copy(buf, offset + 4); - return buf; -} - -export function decodeSSHFrame(data: Buffer) { - if (data.length < 5) { - log.error('agent', 'SSH frame too short: %d bytes', data.length); - return; - } - - const magic = data.toString('utf8', 0, 4); - const version = data.readUInt8(4); - - if (magic !== MAGIC || version !== VERSION) { - log.error('agent', 'Invalid SSH frame magic or version'); - return; - } - - const channel = data.readUInt8(5) as ChannelType; - const sidLength = data.readUInt8(6); - if (data.length < 7 + sidLength + 4) { - log.error('agent', 'SSH frame too short for session ID and payload'); - return; - } - - const sessionId = data.toString('utf8', 7, 7 + sidLength); - const payloadLength = data.readUInt32BE(7 + sidLength); - if (data.length < 7 + sidLength + 4 + payloadLength) { - log.error('agent', 'SSH frame too short for payload'); - return; - } - - const payload = data.subarray( - 7 + sidLength + 4, - 7 + sidLength + 4 + payloadLength, - ); - - return { - sessionId, - channel, - payload, - }; -} diff --git a/app/server/agent/ssh.ts b/app/server/agent/ssh.ts deleted file mode 100644 index 9777232..0000000 --- a/app/server/agent/ssh.ts +++ /dev/null @@ -1,255 +0,0 @@ -import { ChildProcess } from 'node:child_process'; -import { randomUUID } from 'node:crypto'; -import type { Readable, Writable } from 'node:stream'; -import { decode } from 'cborg'; -import { Context } from 'hono'; -import { WSContext, WSEvents } from 'hono/ws'; -import log from '~/utils/log'; -import { - Command, - SSHDataCommand, - SSHResizeCommand, - dispatchCommand, - dispatchWeb, -} from './dispatcher'; -import { decodeSSHFrame, encodeSSHFrame } from './encoder'; - -interface SSHConnection { - username: string; - hostname: string; - port: number; -} - -interface SSHSession { - connectionDetails: SSHConnection; - connected: boolean; - sessionId: string; - ws: WSContext; -} - -interface FrameDecodeSuccess { - id: string; - data: Buffer; -} - -interface FrameDecodeFailure { - id: undefined; - data: undefined; -} - -export function createSSHMultiplexer(proc: ChildProcess): SSHMultiplexer { - const control = proc.stdin; - const sshInput = proc.stdio[3]; - const sshOutput = proc.stdio[4]; - - if (!control || !sshInput || !sshOutput) { - throw new Error('Invalid SSH multiplexer process: missing stdio streams'); - } - - return new SSHMultiplexer( - control, - sshInput as Writable, - sshOutput as Readable, - ); -} - -export class SSHMultiplexer { - private connections: Map; - private control: Writable; - private sshInput: Writable; - private sshOutput: Readable; - - constructor(control: Writable, sshInput: Writable, sshOutput: Readable) { - this.connections = new Map(); - this.control = control; - this.sshInput = sshInput; - this.sshOutput = sshOutput; - this.configureStdout(); - } - - // TODO: Determine if we want to allow multiple connections for the same - // target or attempt to reuse the existing connection (sounds stupid) - private async connect(conn: SSHConnection, ws: WSContext) { - const sessionId = randomUUID(); - const session: SSHSession = { - connectionDetails: conn, - connected: true, - sessionId, - ws, - }; - - log.debug('agent', 'Dispatching SSH connection for %s', sessionId); - await dispatchCommand(this.control, { - op: 'ssh_conn', - payload: { - sessionId, - ...conn, - }, - }); - - this.connections.set(sessionId, session); - return sessionId; - } - - websocketHandler(c: Context): WSEvents { - return { - onOpen: async (_, ws) => { - const { username, hostname, port } = c.req.query(); - if (!username || !hostname || !port) { - ws.close(1008, 'Missing connection parameters'); - return; - } - - const conn: SSHConnection = { - username, - hostname, - port: Number.parseInt(port, 10), - }; - - try { - const sessionId = await this.connect(conn, ws); - ws.raw = sessionId; - dispatchWeb(ws, { - op: 'ssh_conn_successful', - payload: { sessionId }, - }); - } catch (error) { - const errorMessage = - error instanceof Error ? error.message : 'Unknown error'; - - dispatchWeb(ws, { - op: 'ssh_conn_failed', - payload: { - reason: errorMessage, - }, - }); - - ws.close(1011, 'Connection failed'); - } - }, - - onMessage: async (event, ws) => { - const sessionId = ws.raw; - if (!sessionId || !this.connections.has(sessionId)) { - ws.close(1008, 'Invalid session ID'); - return; - } - - const session = this.connections.get(sessionId); - if (!session || !session.connected) { - ws.close(1008, 'Session not connected'); - return; - } - - const wsData = Buffer.isBuffer(event.data) - ? event.data - : typeof event.data === 'string' - ? Buffer.from(event.data, 'utf8') - : event.data instanceof Blob - ? Buffer.from(await event.data.arrayBuffer()) - : Buffer.from(event.data); - - const obj = decode(wsData) as Command; - if (obj.op === 'ssh_data') { - const data = obj as SSHDataCommand; - if (data.payload.sessionId !== sessionId) { - log.warn( - 'agent', - 'Received data for mismatched SSH session %s', - data.payload.sessionId, - ); - return; - } - - const encodedFrame = await encodeSSHFrame({ - sessionId, - channel: 0, // stdin - payload: Buffer.from(data.payload.data), - }); - - this.sshInput.write(encodedFrame); - } - - if (obj.op === 'ssh_resize') { - const resize = obj as SSHResizeCommand; - if (resize.payload.sessionId !== sessionId) { - log.warn( - 'agent', - 'Received resize for mismatched SSH session %s', - resize.payload.sessionId, - ); - return; - } - - await dispatchCommand(this.control, resize); - } - }, - - onClose: async (_, ws) => { - const sessionId = ws.raw; - if (sessionId && this.connections.has(sessionId)) { - const session = this.connections.get(sessionId); - if (session) { - await dispatchCommand(this.control, { - op: 'ssh_term', - payload: { - sessionId, - }, - }); - - session.connected = false; - this.connections.delete(sessionId); - } - } - }, - - onError: async (event, ws) => { - const sessionId = ws.raw; - if (sessionId && this.connections.has(sessionId)) { - const session = this.connections.get(sessionId); - if (session) { - await dispatchCommand(this.control, { - op: 'ssh_term', - payload: { - sessionId, - }, - }); - - session.connected = false; - this.connections.delete(sessionId); - } - } - - log.error('agent', 'SSH WebSocket Error with %s', sessionId); - log.debug('agent', 'Error details: %o', event); - }, - }; - } - - private configureStdout() { - this.sshOutput.on('data', (bytes) => { - const frame = decodeSSHFrame(bytes); - if (!frame) { - return; - } - - const session = this.connections.get(frame.sessionId); - if (!session || !session.connected) { - log.warn( - 'agent', - 'Received data for invalid SSH session %s', - frame.sessionId, - ); - return; - } - - dispatchWeb(session.ws, { - op: 'ssh_frame', - payload: { - channel: frame.channel, - frame: frame.payload, - }, - }); - }); - } -} diff --git a/app/server/config/schema.ts b/app/server/config/schema.ts index ccb1743..deb6b50 100644 --- a/app/server/config/schema.ts +++ b/app/server/config/schema.ts @@ -19,6 +19,7 @@ const stringToBool = type('string | boolean').pipe((v) => { const serverConfig = type({ host: 'string.ip', port: type('string | number.integer').pipe((v) => Number(v)), + data_path: 'string = "/var/lib/headplane/"', cookie_secret: '32 <= string <= 32', cookie_secure: stringToBool, }); diff --git a/app/server/db/client.ts b/app/server/db/client.ts new file mode 100644 index 0000000..da1cbee --- /dev/null +++ b/app/server/db/client.ts @@ -0,0 +1,26 @@ +import { mkdir } from 'node:fs/promises'; +import { dirname } from 'node:path'; +import { drizzle } from 'drizzle-orm/better-sqlite3'; +import { migrate } from 'drizzle-orm/better-sqlite3/migrator'; +import log from '~/utils/log'; + +export async function createDbClient(path: string) { + try { + await mkdir(dirname(path), { recursive: true }); + } catch (error) { + log.error( + 'server', + 'Failed to create directory for database at %s: %s', + path, + error instanceof Error ? error.message : String(error), + ); + throw new Error(`Could not create directory for database at ${path}`); + } + + const db = drizzle(path); + migrate(db, { + migrationsFolder: './drizzle', + }); + + return db; +} diff --git a/app/server/db/pruner.ts b/app/server/db/pruner.ts new file mode 100644 index 0000000..57adafd --- /dev/null +++ b/app/server/db/pruner.ts @@ -0,0 +1,58 @@ +import { eq, isNotNull } from 'drizzle-orm'; +import { LoaderFunctionArgs } from 'react-router'; +import { Machine } from '~/types'; +import log from '~/utils/log'; +import { LoadContext } from '..'; +import { ephemeralNodes } from './schema'; + +export async function pruneEphemeralNodes({ + context, + request, +}: LoaderFunctionArgs) { + const session = await context.sessions.auth(request); + const ephemerals = await context.db + .select() + .from(ephemeralNodes) + .where(isNotNull(ephemeralNodes.node_key)); + + if (ephemerals.length === 0) { + log.debug('api', 'No ephemeral nodes to prune'); + return; + } + + const { nodes } = await context.client.get<{ nodes: Machine[] }>( + 'v1/node', + session.get('api_key')!, + ); + + const toPrune = nodes.filter((node) => { + if (node.online) { + return false; + } + + return ephemerals.some((ephemeral) => node.nodeKey === ephemeral.node_key); + }); + + if (toPrune.length === 0) { + log.debug('api', 'No SSH nodes to prune'); + return; + } + + // Delete from the Headscale nodes list and then from the database + const promises = toPrune.map((node) => { + return async () => { + log.info('api', `Pruning node ${node.name}`); + await context.client.delete( + `v1/node/${node.id}`, + session.get('api_key')!, + ); + + await context.db + .delete(ephemeralNodes) + .where(eq(ephemeralNodes.node_key, node.nodeKey)); + log.info('api', `Node ${node.name} pruned successfully`); + }; + }); + + await Promise.all(promises.map((p) => p())); +} diff --git a/app/server/db/schema.ts b/app/server/db/schema.ts new file mode 100644 index 0000000..b0034fe --- /dev/null +++ b/app/server/db/schema.ts @@ -0,0 +1,9 @@ +import { sqliteTable, text } from 'drizzle-orm/sqlite-core'; + +export const ephemeralNodes = sqliteTable('ephemeral_nodes', { + auth_key: text('auth_key').primaryKey(), + node_key: text('node_key'), +}); + +export type EphemeralNode = typeof ephemeralNodes.$inferSelect; +export type EphemeralNodeInsert = typeof ephemeralNodes.$inferInsert; diff --git a/app/server/index.ts b/app/server/index.ts index f8c15ea..ed4fea6 100644 --- a/app/server/index.ts +++ b/app/server/index.ts @@ -1,10 +1,11 @@ +import { join } from 'node:path'; import { env, versions } from 'node:process'; -import type { WSEvents } from 'hono/ws'; import { createHonoServer } from 'react-router-hono-server/node'; import log from '~/utils/log'; import { configureConfig, configureLogger, envVariables } from './config/env'; import { loadIntegration } from './config/integration'; import { loadConfig } from './config/loader'; +import { createDbClient } from './db/client'; import { createApiClient } from './headscale/api-client'; import { loadHeadscaleConfig } from './headscale/config-loader'; import { loadAgentSocket } from './web/agent'; @@ -33,6 +34,8 @@ const agentManager = await loadAgentSocket( config.headscale.url, ); +const db = await createDbClient(join(config.server.data_path, 'hp_persist.db')); + // We also use this file to load anything needed by the react router code. // These are usually per-request things that we need access to, like the // helper that can issue and revoke cookies. @@ -64,6 +67,7 @@ const appLoadContext = { agents: agentManager, integration: await loadIntegration(config.integration), oidc: config.oidc ? await createOidcClient(config.oidc) : undefined, + db, }; declare module 'react-router' { @@ -87,36 +91,4 @@ export default createHonoServer({ listeningListener(info) { log.info('server', 'Running on %s:%s', info.address, info.port); }, - - useWebSocket: true, - configure: (app, { upgradeWebSocket }) => { - if (agentManager === undefined) { - return; - } - - app.get( - '/_ssh_plexer', - upgradeWebSocket((c) => { - // MARK: This is a limitation of the hono NPM module we use - const wsHandler = agentManager.multiplexer?.websocketHandler( - c, - ) as WSEvents; - - return { - onOpen: wsHandler - ? wsHandler.onOpen - : (_, ws) => ws.close(1000, 'Multiplexer not available'), - onClose: wsHandler - ? wsHandler.onClose - : (_, ws) => ws.close(1000, 'Multiplexer not available'), - onMessage: wsHandler - ? wsHandler.onMessage - : (_, ws) => ws.close(1000, 'Multiplexer not available'), - onError: wsHandler - ? wsHandler.onError - : (_, ws) => ws.close(1000, 'Multiplexer error'), - }; - }), - ); - }, }); diff --git a/app/server/web/agent.ts b/app/server/web/agent.ts index a17e5c7..0e399e9 100644 --- a/app/server/web/agent.ts +++ b/app/server/web/agent.ts @@ -10,12 +10,10 @@ import { } from 'node:fs/promises'; import { exit } from 'node:process'; import { createInterface } from 'node:readline'; -import { Readable, Writable } from 'node:stream'; import { setTimeout } from 'node:timers/promises'; import { type } from 'arktype'; import { HostInfo } from '~/types'; import log from '~/utils/log'; -import { SSHMultiplexer, createSSHMultiplexer } from '../agent/ssh'; import type { HeadplaneConfig } from '../config/schema'; interface LogResponse { @@ -115,7 +113,6 @@ class AgentManager { >; private spawnProcess: ChildProcess | null; - multiplexer: SSHMultiplexer | null; private agentId: string | null; constructor( @@ -127,7 +124,6 @@ class AgentManager { this.config = config; this.headscaleUrl = headscaleUrl; this.spawnProcess = null; - this.multiplexer = null; this.agentId = null; this.startAgent(); @@ -214,14 +210,6 @@ class AgentManager { return; } - const sshInput = this.spawnProcess.stdio[3]; - const sshOutput = this.spawnProcess.stdio[4]; - - if (sshInput && sshOutput) { - log.info('agent', 'Using SSH multiplexer manager'); - this.multiplexer = createSSHMultiplexer(this.spawnProcess); - } - const rlStdout = createInterface({ input: this.spawnProcess.stdout, crlfDelay: Number.POSITIVE_INFINITY, diff --git a/biome.json b/biome.json index 2092ced..302a432 100644 --- a/biome.json +++ b/biome.json @@ -7,7 +7,7 @@ }, "files": { "ignoreUnknown": false, - "ignore": [] + "ignore": ["app/wasm_exec.js"] }, "formatter": { "enabled": true, diff --git a/config.example.yaml b/config.example.yaml index 14df23d..0c827bb 100644 --- a/config.example.yaml +++ b/config.example.yaml @@ -12,6 +12,13 @@ server: # (I recommend this is true in production) cookie_secure: true + # The path to persist Headplane specific data. All data going forward + # is stored in this directory, including the internal database and + # any cache related files. + # + # Data formats prior to 0.6.1 will automatically be migrated. + data_path: "/var/lib/headplane" + # Headscale specific settings to allow Headplane to talk # to Headscale and access deep integration features headscale: diff --git a/drizzle.config.ts b/drizzle.config.ts new file mode 100644 index 0000000..4b0e848 --- /dev/null +++ b/drizzle.config.ts @@ -0,0 +1,8 @@ +import { defineConfig } from 'drizzle-kit'; +export default defineConfig({ + dialect: 'sqlite', + schema: './app/server/db/schema.ts', + dbCredentials: { + url: 'file:test/hp_persist.db', + }, +}); diff --git a/drizzle/0000_spicy_bloodscream.sql b/drizzle/0000_spicy_bloodscream.sql new file mode 100644 index 0000000..7876f33 --- /dev/null +++ b/drizzle/0000_spicy_bloodscream.sql @@ -0,0 +1,4 @@ +CREATE TABLE `ephemeral_nodes` ( + `auth_key` text PRIMARY KEY NOT NULL, + `node_key` text +); diff --git a/drizzle/meta/0000_snapshot.json b/drizzle/meta/0000_snapshot.json new file mode 100644 index 0000000..034f563 --- /dev/null +++ b/drizzle/meta/0000_snapshot.json @@ -0,0 +1,42 @@ +{ + "version": "6", + "dialect": "sqlite", + "id": "ab03ffcd-9aa5-4b4b-9f38-322acc6899a3", + "prevId": "00000000-0000-0000-0000-000000000000", + "tables": { + "ephemeral_nodes": { + "name": "ephemeral_nodes", + "columns": { + "auth_key": { + "name": "auth_key", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "node_key": { + "name": "node_key", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + } + }, + "views": {}, + "enums": {}, + "_meta": { + "schemas": {}, + "tables": {}, + "columns": {} + }, + "internal": { + "indexes": {} + } +} diff --git a/drizzle/meta/_journal.json b/drizzle/meta/_journal.json new file mode 100644 index 0000000..dc3d0f8 --- /dev/null +++ b/drizzle/meta/_journal.json @@ -0,0 +1,13 @@ +{ + "version": "7", + "dialect": "sqlite", + "entries": [ + { + "idx": 0, + "version": "6", + "when": 1750355487927, + "tag": "0000_spicy_bloodscream", + "breakpoints": true + } + ] +} diff --git a/internal/hp_ipn/jsfunc.go b/internal/hp_ipn/jsfunc.go index 70bb838..0b63185 100644 --- a/internal/hp_ipn/jsfunc.go +++ b/internal/hp_ipn/jsfunc.go @@ -72,7 +72,13 @@ func ParseTsWasmNetCallbacks(obj js.Value) (*TsWasmNetCallbacks, error) { }, NotifyNetMap: func(nm *netmap.NetworkMap) { - // TODO: This is complicated + // We need to build a JSON representation of the NetworkMap + // For now we just pass the NodeKey since that's what we need. + jsObj := js.ValueOf(map[string]any{ + "NodeKey": nm.NodeKey.String(), + }) + + obj.Get("NotifyNetMap").Invoke(jsObj) }, NotifyBrowseToURL: func(url string) { diff --git a/internal/hp_ipn/notify.go b/internal/hp_ipn/notify.go index 4c66de2..c236654 100644 --- a/internal/hp_ipn/notify.go +++ b/internal/hp_ipn/notify.go @@ -37,6 +37,10 @@ func registerNotifyCallback(callbacks *TsWasmNetCallbacks, lb *ipnlocal.LocalBac callbacks.NotifyBrowseToURL(*n.BrowseToURL) } + if n.NetMap != nil { + callbacks.NotifyNetMap(n.NetMap) + } + log.Printf("NOTIFY: %+v", n) // if nm := n.NetMap; nm != nil { diff --git a/package.json b/package.json index 80c079f..9e9b31f 100644 --- a/package.json +++ b/package.json @@ -36,9 +36,10 @@ "@xterm/addon-webgl": "^0.18.0", "@xterm/xterm": "^5.5.0", "arktype": "^2.1.20", - "cborg": "^4.2.11", + "better-sqlite3": "^11.10.0", "clsx": "^2.1.1", "dotenv": "^16.5.0", + "drizzle-orm": "^0.44.2", "isbot": "^5.1.28", "lucide-react": "^0.511.0", "mime": "^4.0.7", @@ -62,8 +63,9 @@ "@biomejs/biome": "^1.9.4", "@react-router/dev": "^7.6.1", "@tailwindcss/vite": "^4.1.8", + "@types/better-sqlite3": "^7.6.13", "@types/websocket": "^1.0.10", - "hono": "^4.7.10", + "drizzle-kit": "^0.31.1", "lefthook": "^1.11.13", "postcss": "^8.5.4", "react-router-dom": "^7.6.1", @@ -84,6 +86,11 @@ "patchedDependencies": { "react-router-hono-server": "patches/react-router-hono-server.patch" }, - "onlyBuiltDependencies": ["@biomejs/biome", "esbuild", "lefthook"] + "onlyBuiltDependencies": [ + "@biomejs/biome", + "better-sqlite3", + "esbuild", + "lefthook" + ] } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 7da93f0..362b2d1 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -85,15 +85,18 @@ importers: arktype: specifier: ^2.1.20 version: 2.1.20 - cborg: - specifier: ^4.2.11 - version: 4.2.11 + better-sqlite3: + specifier: ^11.10.0 + version: 11.10.0 clsx: specifier: ^2.1.1 version: 2.1.1 dotenv: specifier: ^16.5.0 version: 16.5.0 + drizzle-orm: + specifier: ^0.44.2 + version: 0.44.2(@types/better-sqlite3@7.6.13)(better-sqlite3@11.10.0) isbot: specifier: ^5.1.28 version: 5.1.28 @@ -158,12 +161,15 @@ importers: '@tailwindcss/vite': specifier: ^4.1.8 version: 4.1.8(vite@6.3.5(@types/node@22.15.24)(jiti@2.4.2)(lightningcss@1.30.1)(terser@5.39.0)(tsx@4.19.4)(yaml@2.8.0)) + '@types/better-sqlite3': + specifier: ^7.6.13 + version: 7.6.13 '@types/websocket': specifier: ^1.0.10 version: 1.0.10 - hono: - specifier: ^4.7.10 - version: 4.7.10 + drizzle-kit: + specifier: ^0.31.1 + version: 0.31.1 lefthook: specifier: ^1.11.13 version: 1.11.13 @@ -461,105 +467,212 @@ packages: peerDependencies: react: '>=16.8.0' + '@drizzle-team/brocli@0.10.2': + resolution: {integrity: sha512-z33Il7l5dKjUgGULTqBsQBQwckHh5AbIuxhdsIxDDiZAzBOrZO6q9ogcWC65kU382AfynTfgNumVcNIjuIua6w==} + '@drizzle-team/brocli@0.11.0': resolution: {integrity: sha512-hD3pekGiPg0WPCCGAZmusBBJsDqGUR66Y452YgQsZOnkdQ7ViEPKuyP4huUGEZQefp8g34RRodXYmJ2TbCH+tg==} + '@esbuild-kit/core-utils@3.3.2': + resolution: {integrity: sha512-sPRAnw9CdSsRmEtnsl2WXWdyquogVpB3yZ3dgwJfe8zrOzTsV7cJvmwrKVa+0ma5BoiGJ+BoqkMvawbayKUsqQ==} + deprecated: 'Merged into tsx: https://tsx.is' + + '@esbuild-kit/esm-loader@2.6.5': + resolution: {integrity: sha512-FxEMIkJKnodyA1OaCUoEvbYRkoZlLZ4d/eXFu9Fh8CbBBgP5EmZxrfTRyN0qpXZ4vOvqnE5YdRdcrmUUXuU+dA==} + deprecated: 'Merged into tsx: https://tsx.is' + '@esbuild/aix-ppc64@0.25.5': resolution: {integrity: sha512-9o3TMmpmftaCMepOdA5k/yDw8SfInyzWWTjYTFCX3kPSDJMROQTb8jg+h9Cnwnmm1vOzvxN7gIfB5V2ewpjtGA==} engines: {node: '>=18'} cpu: [ppc64] os: [aix] + '@esbuild/android-arm64@0.18.20': + resolution: {integrity: sha512-Nz4rJcchGDtENV0eMKUNa6L12zz2zBDXuhj/Vjh18zGqB44Bi7MBMSXjgunJgjRhCmKOjnPuZp4Mb6OKqtMHLQ==} + engines: {node: '>=12'} + cpu: [arm64] + os: [android] + '@esbuild/android-arm64@0.25.5': resolution: {integrity: sha512-VGzGhj4lJO+TVGV1v8ntCZWJktV7SGCs3Pn1GRWI1SBFtRALoomm8k5E9Pmwg3HOAal2VDc2F9+PM/rEY6oIDg==} engines: {node: '>=18'} cpu: [arm64] os: [android] + '@esbuild/android-arm@0.18.20': + resolution: {integrity: sha512-fyi7TDI/ijKKNZTUJAQqiG5T7YjJXgnzkURqmGj13C6dCqckZBLdl4h7bkhHt/t0WP+zO9/zwroDvANaOqO5Sw==} + engines: {node: '>=12'} + cpu: [arm] + os: [android] + '@esbuild/android-arm@0.25.5': resolution: {integrity: sha512-AdJKSPeEHgi7/ZhuIPtcQKr5RQdo6OO2IL87JkianiMYMPbCtot9fxPbrMiBADOWWm3T2si9stAiVsGbTQFkbA==} engines: {node: '>=18'} cpu: [arm] os: [android] + '@esbuild/android-x64@0.18.20': + resolution: {integrity: sha512-8GDdlePJA8D6zlZYJV/jnrRAi6rOiNaCC/JclcXpB+KIuvfBN4owLtgzY2bsxnx666XjJx2kDPUmnTtR8qKQUg==} + engines: {node: '>=12'} + cpu: [x64] + os: [android] + '@esbuild/android-x64@0.25.5': resolution: {integrity: sha512-D2GyJT1kjvO//drbRT3Hib9XPwQeWd9vZoBJn+bu/lVsOZ13cqNdDeqIF/xQ5/VmWvMduP6AmXvylO/PIc2isw==} engines: {node: '>=18'} cpu: [x64] os: [android] + '@esbuild/darwin-arm64@0.18.20': + resolution: {integrity: sha512-bxRHW5kHU38zS2lPTPOyuyTm+S+eobPUnTNkdJEfAddYgEcll4xkT8DB9d2008DtTbl7uJag2HuE5NZAZgnNEA==} + engines: {node: '>=12'} + cpu: [arm64] + os: [darwin] + '@esbuild/darwin-arm64@0.25.5': resolution: {integrity: sha512-GtaBgammVvdF7aPIgH2jxMDdivezgFu6iKpmT+48+F8Hhg5J/sfnDieg0aeG/jfSvkYQU2/pceFPDKlqZzwnfQ==} engines: {node: '>=18'} cpu: [arm64] os: [darwin] + '@esbuild/darwin-x64@0.18.20': + resolution: {integrity: sha512-pc5gxlMDxzm513qPGbCbDukOdsGtKhfxD1zJKXjCCcU7ju50O7MeAZ8c4krSJcOIJGFR+qx21yMMVYwiQvyTyQ==} + engines: {node: '>=12'} + cpu: [x64] + os: [darwin] + '@esbuild/darwin-x64@0.25.5': resolution: {integrity: sha512-1iT4FVL0dJ76/q1wd7XDsXrSW+oLoquptvh4CLR4kITDtqi2e/xwXwdCVH8hVHU43wgJdsq7Gxuzcs6Iq/7bxQ==} engines: {node: '>=18'} cpu: [x64] os: [darwin] + '@esbuild/freebsd-arm64@0.18.20': + resolution: {integrity: sha512-yqDQHy4QHevpMAaxhhIwYPMv1NECwOvIpGCZkECn8w2WFHXjEwrBn3CeNIYsibZ/iZEUemj++M26W3cNR5h+Tw==} + engines: {node: '>=12'} + cpu: [arm64] + os: [freebsd] + '@esbuild/freebsd-arm64@0.25.5': resolution: {integrity: sha512-nk4tGP3JThz4La38Uy/gzyXtpkPW8zSAmoUhK9xKKXdBCzKODMc2adkB2+8om9BDYugz+uGV7sLmpTYzvmz6Sw==} engines: {node: '>=18'} cpu: [arm64] os: [freebsd] + '@esbuild/freebsd-x64@0.18.20': + resolution: {integrity: sha512-tgWRPPuQsd3RmBZwarGVHZQvtzfEBOreNuxEMKFcd5DaDn2PbBxfwLcj4+aenoh7ctXcbXmOQIn8HI6mCSw5MQ==} + engines: {node: '>=12'} + cpu: [x64] + os: [freebsd] + '@esbuild/freebsd-x64@0.25.5': resolution: {integrity: sha512-PrikaNjiXdR2laW6OIjlbeuCPrPaAl0IwPIaRv+SMV8CiM8i2LqVUHFC1+8eORgWyY7yhQY+2U2fA55mBzReaw==} engines: {node: '>=18'} cpu: [x64] os: [freebsd] + '@esbuild/linux-arm64@0.18.20': + resolution: {integrity: sha512-2YbscF+UL7SQAVIpnWvYwM+3LskyDmPhe31pE7/aoTMFKKzIc9lLbyGUpmmb8a8AixOL61sQ/mFh3jEjHYFvdA==} + engines: {node: '>=12'} + cpu: [arm64] + os: [linux] + '@esbuild/linux-arm64@0.25.5': resolution: {integrity: sha512-Z9kfb1v6ZlGbWj8EJk9T6czVEjjq2ntSYLY2cw6pAZl4oKtfgQuS4HOq41M/BcoLPzrUbNd+R4BXFyH//nHxVg==} engines: {node: '>=18'} cpu: [arm64] os: [linux] + '@esbuild/linux-arm@0.18.20': + resolution: {integrity: sha512-/5bHkMWnq1EgKr1V+Ybz3s1hWXok7mDFUMQ4cG10AfW3wL02PSZi5kFpYKrptDsgb2WAJIvRcDm+qIvXf/apvg==} + engines: {node: '>=12'} + cpu: [arm] + os: [linux] + '@esbuild/linux-arm@0.25.5': resolution: {integrity: sha512-cPzojwW2okgh7ZlRpcBEtsX7WBuqbLrNXqLU89GxWbNt6uIg78ET82qifUy3W6OVww6ZWobWub5oqZOVtwolfw==} engines: {node: '>=18'} cpu: [arm] os: [linux] + '@esbuild/linux-ia32@0.18.20': + resolution: {integrity: sha512-P4etWwq6IsReT0E1KHU40bOnzMHoH73aXp96Fs8TIT6z9Hu8G6+0SHSw9i2isWrD2nbx2qo5yUqACgdfVGx7TA==} + engines: {node: '>=12'} + cpu: [ia32] + os: [linux] + '@esbuild/linux-ia32@0.25.5': resolution: {integrity: sha512-sQ7l00M8bSv36GLV95BVAdhJ2QsIbCuCjh/uYrWiMQSUuV+LpXwIqhgJDcvMTj+VsQmqAHL2yYaasENvJ7CDKA==} engines: {node: '>=18'} cpu: [ia32] os: [linux] + '@esbuild/linux-loong64@0.18.20': + resolution: {integrity: sha512-nXW8nqBTrOpDLPgPY9uV+/1DjxoQ7DoB2N8eocyq8I9XuqJ7BiAMDMf9n1xZM9TgW0J8zrquIb/A7s3BJv7rjg==} + engines: {node: '>=12'} + cpu: [loong64] + os: [linux] + '@esbuild/linux-loong64@0.25.5': resolution: {integrity: sha512-0ur7ae16hDUC4OL5iEnDb0tZHDxYmuQyhKhsPBV8f99f6Z9KQM02g33f93rNH5A30agMS46u2HP6qTdEt6Q1kg==} engines: {node: '>=18'} cpu: [loong64] os: [linux] + '@esbuild/linux-mips64el@0.18.20': + resolution: {integrity: sha512-d5NeaXZcHp8PzYy5VnXV3VSd2D328Zb+9dEq5HE6bw6+N86JVPExrA6O68OPwobntbNJ0pzCpUFZTo3w0GyetQ==} + engines: {node: '>=12'} + cpu: [mips64el] + os: [linux] + '@esbuild/linux-mips64el@0.25.5': resolution: {integrity: sha512-kB/66P1OsHO5zLz0i6X0RxlQ+3cu0mkxS3TKFvkb5lin6uwZ/ttOkP3Z8lfR9mJOBk14ZwZ9182SIIWFGNmqmg==} engines: {node: '>=18'} cpu: [mips64el] os: [linux] + '@esbuild/linux-ppc64@0.18.20': + resolution: {integrity: sha512-WHPyeScRNcmANnLQkq6AfyXRFr5D6N2sKgkFo2FqguP44Nw2eyDlbTdZwd9GYk98DZG9QItIiTlFLHJHjxP3FA==} + engines: {node: '>=12'} + cpu: [ppc64] + os: [linux] + '@esbuild/linux-ppc64@0.25.5': resolution: {integrity: sha512-UZCmJ7r9X2fe2D6jBmkLBMQetXPXIsZjQJCjgwpVDz+YMcS6oFR27alkgGv3Oqkv07bxdvw7fyB71/olceJhkQ==} engines: {node: '>=18'} cpu: [ppc64] os: [linux] + '@esbuild/linux-riscv64@0.18.20': + resolution: {integrity: sha512-WSxo6h5ecI5XH34KC7w5veNnKkju3zBRLEQNY7mv5mtBmrP/MjNBCAlsM2u5hDBlS3NGcTQpoBvRzqBcRtpq1A==} + engines: {node: '>=12'} + cpu: [riscv64] + os: [linux] + '@esbuild/linux-riscv64@0.25.5': resolution: {integrity: sha512-kTxwu4mLyeOlsVIFPfQo+fQJAV9mh24xL+y+Bm6ej067sYANjyEw1dNHmvoqxJUCMnkBdKpvOn0Ahql6+4VyeA==} engines: {node: '>=18'} cpu: [riscv64] os: [linux] + '@esbuild/linux-s390x@0.18.20': + resolution: {integrity: sha512-+8231GMs3mAEth6Ja1iK0a1sQ3ohfcpzpRLH8uuc5/KVDFneH6jtAJLFGafpzpMRO6DzJ6AvXKze9LfFMrIHVQ==} + engines: {node: '>=12'} + cpu: [s390x] + os: [linux] + '@esbuild/linux-s390x@0.25.5': resolution: {integrity: sha512-K2dSKTKfmdh78uJ3NcWFiqyRrimfdinS5ErLSn3vluHNeHVnBAFWC8a4X5N+7FgVE1EjXS1QDZbpqZBjfrqMTQ==} engines: {node: '>=18'} cpu: [s390x] os: [linux] + '@esbuild/linux-x64@0.18.20': + resolution: {integrity: sha512-UYqiqemphJcNsFEskc73jQ7B9jgwjWrSayxawS6UVFZGWrAAtkzjxSqnoclCXxWtfwLdzU+vTpcNYhpn43uP1w==} + engines: {node: '>=12'} + cpu: [x64] + os: [linux] + '@esbuild/linux-x64@0.25.5': resolution: {integrity: sha512-uhj8N2obKTE6pSZ+aMUbqq+1nXxNjZIIjCjGLfsWvVpy7gKCOL6rsY1MhRh9zLtUtAI7vpgLMK6DxjO8Qm9lJw==} engines: {node: '>=18'} @@ -572,6 +685,12 @@ packages: cpu: [arm64] os: [netbsd] + '@esbuild/netbsd-x64@0.18.20': + resolution: {integrity: sha512-iO1c++VP6xUBUmltHZoMtCUdPlnPGdBom6IrO4gyKPFFVBKioIImVooR5I83nTew5UOYrk3gIJhbZh8X44y06A==} + engines: {node: '>=12'} + cpu: [x64] + os: [netbsd] + '@esbuild/netbsd-x64@0.25.5': resolution: {integrity: sha512-WOb5fKrvVTRMfWFNCroYWWklbnXH0Q5rZppjq0vQIdlsQKuw6mdSihwSo4RV/YdQ5UCKKvBy7/0ZZYLBZKIbwQ==} engines: {node: '>=18'} @@ -584,30 +703,60 @@ packages: cpu: [arm64] os: [openbsd] + '@esbuild/openbsd-x64@0.18.20': + resolution: {integrity: sha512-e5e4YSsuQfX4cxcygw/UCPIEP6wbIL+se3sxPdCiMbFLBWu0eiZOJ7WoD+ptCLrmjZBK1Wk7I6D/I3NglUGOxg==} + engines: {node: '>=12'} + cpu: [x64] + os: [openbsd] + '@esbuild/openbsd-x64@0.25.5': resolution: {integrity: sha512-G4hE405ErTWraiZ8UiSoesH8DaCsMm0Cay4fsFWOOUcz8b8rC6uCvnagr+gnioEjWn0wC+o1/TAHt+It+MpIMg==} engines: {node: '>=18'} cpu: [x64] os: [openbsd] + '@esbuild/sunos-x64@0.18.20': + resolution: {integrity: sha512-kDbFRFp0YpTQVVrqUd5FTYmWo45zGaXe0X8E1G/LKFC0v8x0vWrhOWSLITcCn63lmZIxfOMXtCfti/RxN/0wnQ==} + engines: {node: '>=12'} + cpu: [x64] + os: [sunos] + '@esbuild/sunos-x64@0.25.5': resolution: {integrity: sha512-l+azKShMy7FxzY0Rj4RCt5VD/q8mG/e+mDivgspo+yL8zW7qEwctQ6YqKX34DTEleFAvCIUviCFX1SDZRSyMQA==} engines: {node: '>=18'} cpu: [x64] os: [sunos] + '@esbuild/win32-arm64@0.18.20': + resolution: {integrity: sha512-ddYFR6ItYgoaq4v4JmQQaAI5s7npztfV4Ag6NrhiaW0RrnOXqBkgwZLofVTlq1daVTQNhtI5oieTvkRPfZrePg==} + engines: {node: '>=12'} + cpu: [arm64] + os: [win32] + '@esbuild/win32-arm64@0.25.5': resolution: {integrity: sha512-O2S7SNZzdcFG7eFKgvwUEZ2VG9D/sn/eIiz8XRZ1Q/DO5a3s76Xv0mdBzVM5j5R639lXQmPmSo0iRpHqUUrsxw==} engines: {node: '>=18'} cpu: [arm64] os: [win32] + '@esbuild/win32-ia32@0.18.20': + resolution: {integrity: sha512-Wv7QBi3ID/rROT08SABTS7eV4hX26sVduqDOTe1MvGMjNd3EjOz4b7zeexIR62GTIEKrfJXKL9LFxTYgkyeu7g==} + engines: {node: '>=12'} + cpu: [ia32] + os: [win32] + '@esbuild/win32-ia32@0.25.5': resolution: {integrity: sha512-onOJ02pqs9h1iMJ1PQphR+VZv8qBMQ77Klcsqv9CNW2w6yLqoURLcgERAIurY6QE63bbLuqgP9ATqajFLK5AMQ==} engines: {node: '>=18'} cpu: [ia32] os: [win32] + '@esbuild/win32-x64@0.18.20': + resolution: {integrity: sha512-kTdfRcSiDfQca/y9QIkng02avJ+NCaQvrMejlsB3RRv5sE9rRoeBPISaZpKxHELzRxZyLvNts1P27W3wV+8geQ==} + engines: {node: '>=12'} + cpu: [x64] + os: [win32] + '@esbuild/win32-x64@0.25.5': resolution: {integrity: sha512-TXv6YnJ8ZMVdX+SXWVBo/0p8LTcrUYngpWjvm91TMjjBQii7Oz11Lw5lbDV5Y0TzuhSJHwiH4hEtC1I42mMS0g==} engines: {node: '>=18'} @@ -1541,6 +1690,9 @@ packages: peerDependencies: vite: ^5.2.0 || ^6 + '@types/better-sqlite3@7.6.13': + resolution: {integrity: sha512-NMv9ASNARoKksWtsq/SHakpYAYnhBrQgGD8zkLYk/jaK8jUGn08CfEdTRgYhMypUQAfzSP8W6gNLe0q19/t4VA==} + '@types/estree@1.0.6': resolution: {integrity: sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==} @@ -1720,11 +1872,23 @@ packages: bare-events: optional: true + base64-js@1.5.1: + resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} + + better-sqlite3@11.10.0: + resolution: {integrity: sha512-EwhOpyXiOEL/lKzHz9AW1msWFNzGc/z+LzeB3/jnFJpxu+th2yqvzsSWas1v9jgs9+xiXJcD5A8CJxAG2TaghQ==} + + bindings@1.5.0: + resolution: {integrity: sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==} + bippy@0.3.14: resolution: {integrity: sha512-lg8i4YjVBcAsF8mKVaWjbgG/yo1fY9SUbGgilsnkOnLyU37Jj0XDLWTBeuZjFrrd6LNf1od8/tO/soIi2KUTrw==} peerDependencies: react: '>=17.0.1' + bl@4.1.0: + resolution: {integrity: sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==} + brace-expansion@2.0.1: resolution: {integrity: sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==} @@ -1736,6 +1900,9 @@ packages: buffer-from@1.1.2: resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==} + buffer@5.7.1: + resolution: {integrity: sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==} + bufferutil@4.0.9: resolution: {integrity: sha512-WDtdLmJvAuNNPzByAYpRo2rF1Mmradw6gvWsQKf63476DDXmomT9zUiGypLcG4ibIM67vhAj8jJRdbmEws2Aqw==} engines: {node: '>=6.14.2'} @@ -1751,14 +1918,13 @@ packages: caniuse-lite@1.0.30001718: resolution: {integrity: sha512-AflseV1ahcSunK53NfEs9gFWgOEmzr0f+kaMFA4xiLZlr9Hzt7HxcSpIFcnNCUkz6R6dWKa54rUz3HUmI3nVcw==} - cborg@4.2.11: - resolution: {integrity: sha512-7gs3iaqtsD9OHowgqzc6ixQGwSBONqosVR2co0Bg0pARgrLap+LCcEIXJuuIz2jHy0WWQeDMFPEsU2r17I2XPQ==} - hasBin: true - chokidar@4.0.3: resolution: {integrity: sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==} engines: {node: '>= 14.16.0'} + chownr@1.1.4: + resolution: {integrity: sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==} + chownr@3.0.0: resolution: {integrity: sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==} engines: {node: '>=18'} @@ -1822,6 +1988,10 @@ packages: decimal.js@10.5.0: resolution: {integrity: sha512-8vDa8Qxvr/+d94hSh5P3IJwI5t8/c0KsMp+g8bNw9cY2icONa5aPfvKeieW1WlG0WQYwwhJ7mjui2xtiePQSXw==} + decompress-response@6.0.0: + resolution: {integrity: sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==} + engines: {node: '>=10'} + dedent@1.6.0: resolution: {integrity: sha512-F1Z+5UCFpmQUzJa11agbyPVMbpgT/qA3/SKyJ1jyBgm7dUcUEa8v9JwDkerSQXfakBwFljIxhOJqGkjUwZ9FSA==} peerDependencies: @@ -1830,6 +2000,10 @@ packages: babel-plugin-macros: optional: true + deep-extend@0.6.0: + resolution: {integrity: sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==} + engines: {node: '>=4.0.0'} + delayed-stream@1.0.0: resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} engines: {node: '>=0.4.0'} @@ -1842,6 +2016,102 @@ packages: resolution: {integrity: sha512-m/C+AwOAr9/W1UOIZUo232ejMNnJAJtYQjUbHoNTBNTJSvqzzDh7vnrei3o3r3m9blf6ZoDkvcw0VmozNRFJxg==} engines: {node: '>=12'} + drizzle-kit@0.31.1: + resolution: {integrity: sha512-PUjYKWtzOzPtdtQlTHQG3qfv4Y0XT8+Eas6UbxCmxTj7qgMf+39dDujf1BP1I+qqZtw9uzwTh8jYtkMuCq+B0Q==} + hasBin: true + + drizzle-orm@0.44.2: + resolution: {integrity: sha512-zGAqBzWWkVSFjZpwPOrmCrgO++1kZ5H/rZ4qTGeGOe18iXGVJWf3WPfHOVwFIbmi8kHjfJstC6rJomzGx8g/dQ==} + peerDependencies: + '@aws-sdk/client-rds-data': '>=3' + '@cloudflare/workers-types': '>=4' + '@electric-sql/pglite': '>=0.2.0' + '@libsql/client': '>=0.10.0' + '@libsql/client-wasm': '>=0.10.0' + '@neondatabase/serverless': '>=0.10.0' + '@op-engineering/op-sqlite': '>=2' + '@opentelemetry/api': ^1.4.1 + '@planetscale/database': '>=1.13' + '@prisma/client': '*' + '@tidbcloud/serverless': '*' + '@types/better-sqlite3': '*' + '@types/pg': '*' + '@types/sql.js': '*' + '@upstash/redis': '>=1.34.7' + '@vercel/postgres': '>=0.8.0' + '@xata.io/client': '*' + better-sqlite3: '>=7' + bun-types: '*' + expo-sqlite: '>=14.0.0' + gel: '>=2' + knex: '*' + kysely: '*' + mysql2: '>=2' + pg: '>=8' + postgres: '>=3' + prisma: '*' + sql.js: '>=1' + sqlite3: '>=5' + peerDependenciesMeta: + '@aws-sdk/client-rds-data': + optional: true + '@cloudflare/workers-types': + optional: true + '@electric-sql/pglite': + optional: true + '@libsql/client': + optional: true + '@libsql/client-wasm': + optional: true + '@neondatabase/serverless': + optional: true + '@op-engineering/op-sqlite': + optional: true + '@opentelemetry/api': + optional: true + '@planetscale/database': + optional: true + '@prisma/client': + optional: true + '@tidbcloud/serverless': + optional: true + '@types/better-sqlite3': + optional: true + '@types/pg': + optional: true + '@types/sql.js': + optional: true + '@upstash/redis': + optional: true + '@vercel/postgres': + optional: true + '@xata.io/client': + optional: true + better-sqlite3: + optional: true + bun-types: + optional: true + expo-sqlite: + optional: true + gel: + optional: true + knex: + optional: true + kysely: + optional: true + mysql2: + optional: true + pg: + optional: true + postgres: + optional: true + prisma: + optional: true + sql.js: + optional: true + sqlite3: + optional: true + dunder-proto@1.0.1: resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} engines: {node: '>= 0.4'} @@ -1887,6 +2157,16 @@ packages: resolution: {integrity: sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==} engines: {node: '>= 0.4'} + esbuild-register@3.6.0: + resolution: {integrity: sha512-H2/S7Pm8a9CL1uhp9OvjwrBh5Pvx0H8qVOxNu8Wed9Y7qv56MPtq+GGM8RJpq6glYJn9Wspr8uw7l55uyinNeg==} + peerDependencies: + esbuild: '>=0.12 <1' + + esbuild@0.18.20: + resolution: {integrity: sha512-ceqxoedUrcayh7Y7ZX6NdbbDzGROiyVBgC4PriJThBKSVPWnnFHZAkfI1lJT8QFkOwH4qOS2SJkS4wvpGl8BpA==} + engines: {node: '>=12'} + hasBin: true + esbuild@0.25.5: resolution: {integrity: sha512-P8OtKZRv/5J5hhz0cUAdu/cLuPIKXpQl1R9pZtvmHWQvrAUVd0UNIPT4IB4W3rNOqVO0rlqHmCIbSwxh/c9yUQ==} engines: {node: '>=18'} @@ -1906,6 +2186,10 @@ packages: resolution: {integrity: sha512-eNTPlAD67BmP31LDINZ3U7HSF8l57TxOY2PmBJ1shpCvpnxBF93mWCE8YHBnXs8qiUZJc9WDcWIeC3a2HIAMfw==} engines: {node: '>=6'} + expand-template@2.0.3: + resolution: {integrity: sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==} + engines: {node: '>=6'} + fast-fifo@1.3.2: resolution: {integrity: sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==} @@ -1917,6 +2201,9 @@ packages: picomatch: optional: true + file-uri-to-path@1.0.0: + resolution: {integrity: sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==} + foreground-child@3.3.0: resolution: {integrity: sha512-Ld2g8rrAyMYFXBhEqMz8ZAHBi4J4uS1i/CxGMDnjyFWddMXLVcDp051DZfu+t7+ab7Wv6SMqpWmyFIj5UbfFvg==} engines: {node: '>=14'} @@ -1925,6 +2212,9 @@ packages: resolution: {integrity: sha512-hGfm/slu0ZabnNt4oaRZ6uREyfCj6P4fT/n6A1rGV+Z0VdGXjfOhVUpkn6qVQONHGIFwmveGXyDs75+nr6FM8w==} engines: {node: '>= 6'} + fs-constants@1.0.0: + resolution: {integrity: sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==} + fs-extra@10.1.0: resolution: {integrity: sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==} engines: {node: '>=12'} @@ -1957,6 +2247,9 @@ packages: get-tsconfig@4.10.1: resolution: {integrity: sha512-auHyJ4AgMz7vgS8Hp3N6HXSmlMdUyhSUrfBF16w153rxtLIEOE+HGqaBppczZvnHLqQJfiHotCYpNhl0lUROFQ==} + github-from-package@0.0.0: + resolution: {integrity: sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==} + glob@10.4.5: resolution: {integrity: sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==} hasBin: true @@ -1999,6 +2292,15 @@ packages: resolution: {integrity: sha512-A91dYTeIB6NoXG+PxTQpCCDDnfHsW9kc06Lvpu1TEe9gnd6ZFeiBoRO9JvzEv6xK7EX97/dUE8g/vBMTqTS3CA==} engines: {node: '>=14'} + ieee754@1.2.1: + resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==} + + inherits@2.0.4: + resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} + + ini@1.3.8: + resolution: {integrity: sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==} + intl-messageformat@10.7.16: resolution: {integrity: sha512-UmdmHUmp5CIKKjSoE10la5yfU+AYJAaiYLsodbjL4lji83JNvgOQUjGaGhGrpFCb0Uh7sl7qfP1IyILa8Z40ug==} @@ -2239,10 +2541,17 @@ packages: engines: {node: '>=16'} hasBin: true + mimic-response@3.1.0: + resolution: {integrity: sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==} + engines: {node: '>=10'} + minimatch@9.0.5: resolution: {integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==} engines: {node: '>=16 || 14 >=14.17'} + minimist@1.2.8: + resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} + minipass@7.1.2: resolution: {integrity: sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==} engines: {node: '>=16 || 14 >=14.17'} @@ -2251,6 +2560,9 @@ packages: resolution: {integrity: sha512-umcy022ILvb5/3Djuu8LWeqUa8D68JaBzlttKeMWen48SjabqS3iY5w/vzeMzMUNhLDifyhbOwKDSznB1vvrwg==} engines: {node: '>= 18'} + mkdirp-classic@0.5.3: + resolution: {integrity: sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==} + mkdirp@3.0.1: resolution: {integrity: sha512-+NsyUUAZDmo6YVHzL/stxSu3t9YS1iljliy3BSDrXJ/dkn1KYdmtZODGGjLcc9XLgVVpH4KshHB8XmZgMhaBXg==} engines: {node: '>=10'} @@ -2268,6 +2580,13 @@ packages: engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} hasBin: true + napi-build-utils@2.0.0: + resolution: {integrity: sha512-GEbrYkbfF7MoNaoh2iGG84Mnf/WZfB0GdGEsM8wz7Expx/LlWf5U8t9nvJKXSp3qr5IsEbK04cBGhol/KwOsWA==} + + node-abi@3.75.0: + resolution: {integrity: sha512-OhYaY5sDsIka7H7AtijtI9jwGYLyl29eQn/W623DiN/MIv5sUqc4g7BIDThX+gb7di9f6xK02nkp8sdfFWZLTg==} + engines: {node: '>=10'} + node-fetch@2.7.0: resolution: {integrity: sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==} engines: {node: 4.x || >=6.0.0} @@ -2351,6 +2670,11 @@ packages: preact@10.26.7: resolution: {integrity: sha512-43xS+QYc1X1IPbw03faSgY6I6OYWcLrJRv3hU0+qMOfh/XCHcP0MX2CVjNARYR2cC/guu975sta4OcjlczxD7g==} + prebuild-install@7.1.3: + resolution: {integrity: sha512-8Mf2cbV7x1cXPUILADGI3wuhfqWvtiLA1iclTDbFRZkgRQS0NqsPZphna9V+HyTEadheuPmjaJMsbzKQFOzLug==} + engines: {node: '>=10'} + hasBin: true + prettier@2.8.8: resolution: {integrity: sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==} engines: {node: '>=10.13.0'} @@ -2375,6 +2699,10 @@ packages: pump@3.0.2: resolution: {integrity: sha512-tUPXtzlGM8FE3P0ZL6DVs/3P58k9nk8/jZeQCurTJylQA8qFYzHFfhBJkuqyE0FifOsQ0uKWekiZ5g8wtr28cw==} + rc@1.2.8: + resolution: {integrity: sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==} + hasBin: true + react-aria@3.40.0: resolution: {integrity: sha512-pxZusRI1jCBIvJkORJnhAXey/5U/VJa1whCeP6ETzRKepJiXLRPjJerHHJw+3Q6kAJXADL9qds5xdq4nvmyLRA==} peerDependencies: @@ -2477,6 +2805,10 @@ packages: resolution: {integrity: sha512-FS+XFBNvn3GTAWq26joslQgWNoFu08F4kl0J4CgdNKADkdSGXQyTCnKteIAJy96Br6YbpEU1LSzV5dYtjMkMDg==} engines: {node: '>=0.10.0'} + readable-stream@3.6.2: + resolution: {integrity: sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==} + engines: {node: '>= 6'} + readdirp@4.1.2: resolution: {integrity: sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==} engines: {node: '>= 14.18.0'} @@ -2536,6 +2868,9 @@ packages: engines: {node: '>=18.0.0', npm: '>=8.0.0'} hasBin: true + safe-buffer@5.2.1: + resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} + scheduler@0.26.0: resolution: {integrity: sha512-NlHwttCI/l5gCPR3D1nNXtWABUmBwvZpEQiD4IXSbIDq8BzLIK/7Ir5gTFSGZDUu37K5cMNp0hFtzO38sC7gWA==} @@ -2563,6 +2898,12 @@ packages: resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} engines: {node: '>=14'} + simple-concat@1.0.1: + resolution: {integrity: sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==} + + simple-get@4.0.1: + resolution: {integrity: sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==} + sisteransi@1.0.5: resolution: {integrity: sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==} @@ -2622,6 +2963,9 @@ packages: resolution: {integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==} engines: {node: '>=12'} + string_decoder@1.3.0: + resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==} + strip-ansi@6.0.1: resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} engines: {node: '>=8'} @@ -2630,6 +2974,10 @@ packages: resolution: {integrity: sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==} engines: {node: '>=12'} + strip-json-comments@2.0.1: + resolution: {integrity: sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==} + engines: {node: '>=0.10.0'} + style-mod@4.1.2: resolution: {integrity: sha512-wnD1HyVqpJUI2+eKZ+eo1UwghftP6yuFheBqqe+bWCotBjC2K1YnteJILRMs3SM4V/0dLEW1SC27MWP5y+mwmw==} @@ -2653,9 +3001,16 @@ packages: resolution: {integrity: sha512-Re10+NauLTMCudc7T5WLFLAwDhQ0JWdrMK+9B2M8zR5hRExKmsRDCBA7/aV/pNJFltmBFO5BAMlQFi/vq3nKOg==} engines: {node: '>=6'} + tar-fs@2.1.3: + resolution: {integrity: sha512-090nwYJDmlhwFwEW3QQl+vaNnxsO2yVsd45eTKRBzSzu+hlb1w2K9inVq5b0ngXuLVqQ4ApvsUHHnu/zQNkWAg==} + tar-fs@3.0.9: resolution: {integrity: sha512-XF4w9Xp+ZQgifKakjZYmFdkLoSWd34VGKcsTCwlNWM7QG3ZbaxnTsaBwnjFZqHRf/rROxaR8rXnbtwdvaDI+lA==} + tar-stream@2.2.0: + resolution: {integrity: sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==} + engines: {node: '>=6'} + tar-stream@3.1.7: resolution: {integrity: sha512-qJj60CXt7IU1Ffyc3NJMjh6EkuCFej46zUqJ4J7pqYlThyd9bO0XBTmcOIhSzZJVWfsLks0+nle/j538YAW9RQ==} @@ -2699,6 +3054,9 @@ packages: engines: {node: '>=18.0.0'} hasBin: true + tunnel-agent@0.6.0: + resolution: {integrity: sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==} + type-fest@4.41.0: resolution: {integrity: sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==} engines: {node: '>=16'} @@ -2754,6 +3112,9 @@ packages: resolution: {integrity: sha512-Z6czzLq4u8fPOyx7TU6X3dvUZVvoJmxSQ+IcrlmagKhilxlhZgxPK6C5Jqbkw1IDUmFTM+cz9QDnnLTwDz/2gQ==} engines: {node: '>=6.14.2'} + util-deprecate@1.0.2: + resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} + valibot@0.41.0: resolution: {integrity: sha512-igDBb8CTYr8YTQlOKgaN9nSS0Be7z+WRuaeYqGf3Cjz3aKmSnqEmYnkfVjzIuumGqfHpa3fLIvMEAfhrpqN8ng==} peerDependencies: @@ -3236,80 +3597,158 @@ snapshots: react: 19.1.0 tslib: 2.6.2 + '@drizzle-team/brocli@0.10.2': {} + '@drizzle-team/brocli@0.11.0': {} + '@esbuild-kit/core-utils@3.3.2': + dependencies: + esbuild: 0.18.20 + source-map-support: 0.5.21 + + '@esbuild-kit/esm-loader@2.6.5': + dependencies: + '@esbuild-kit/core-utils': 3.3.2 + get-tsconfig: 4.10.1 + '@esbuild/aix-ppc64@0.25.5': optional: true + '@esbuild/android-arm64@0.18.20': + optional: true + '@esbuild/android-arm64@0.25.5': optional: true + '@esbuild/android-arm@0.18.20': + optional: true + '@esbuild/android-arm@0.25.5': optional: true + '@esbuild/android-x64@0.18.20': + optional: true + '@esbuild/android-x64@0.25.5': optional: true + '@esbuild/darwin-arm64@0.18.20': + optional: true + '@esbuild/darwin-arm64@0.25.5': optional: true + '@esbuild/darwin-x64@0.18.20': + optional: true + '@esbuild/darwin-x64@0.25.5': optional: true + '@esbuild/freebsd-arm64@0.18.20': + optional: true + '@esbuild/freebsd-arm64@0.25.5': optional: true + '@esbuild/freebsd-x64@0.18.20': + optional: true + '@esbuild/freebsd-x64@0.25.5': optional: true + '@esbuild/linux-arm64@0.18.20': + optional: true + '@esbuild/linux-arm64@0.25.5': optional: true + '@esbuild/linux-arm@0.18.20': + optional: true + '@esbuild/linux-arm@0.25.5': optional: true + '@esbuild/linux-ia32@0.18.20': + optional: true + '@esbuild/linux-ia32@0.25.5': optional: true + '@esbuild/linux-loong64@0.18.20': + optional: true + '@esbuild/linux-loong64@0.25.5': optional: true + '@esbuild/linux-mips64el@0.18.20': + optional: true + '@esbuild/linux-mips64el@0.25.5': optional: true + '@esbuild/linux-ppc64@0.18.20': + optional: true + '@esbuild/linux-ppc64@0.25.5': optional: true + '@esbuild/linux-riscv64@0.18.20': + optional: true + '@esbuild/linux-riscv64@0.25.5': optional: true + '@esbuild/linux-s390x@0.18.20': + optional: true + '@esbuild/linux-s390x@0.25.5': optional: true + '@esbuild/linux-x64@0.18.20': + optional: true + '@esbuild/linux-x64@0.25.5': optional: true '@esbuild/netbsd-arm64@0.25.5': optional: true + '@esbuild/netbsd-x64@0.18.20': + optional: true + '@esbuild/netbsd-x64@0.25.5': optional: true '@esbuild/openbsd-arm64@0.25.5': optional: true + '@esbuild/openbsd-x64@0.18.20': + optional: true + '@esbuild/openbsd-x64@0.25.5': optional: true + '@esbuild/sunos-x64@0.18.20': + optional: true + '@esbuild/sunos-x64@0.25.5': optional: true + '@esbuild/win32-arm64@0.18.20': + optional: true + '@esbuild/win32-arm64@0.25.5': optional: true + '@esbuild/win32-ia32@0.18.20': + optional: true + '@esbuild/win32-ia32@0.25.5': optional: true + '@esbuild/win32-x64@0.18.20': + optional: true + '@esbuild/win32-x64@0.25.5': optional: true @@ -4679,6 +5118,10 @@ snapshots: tailwindcss: 4.1.8 vite: 6.3.5(@types/node@22.15.24)(jiti@2.4.2)(lightningcss@1.30.1)(terser@5.39.0)(tsx@4.19.4)(yaml@2.8.0) + '@types/better-sqlite3@7.6.13': + dependencies: + '@types/node': 22.15.24 + '@types/estree@1.0.6': {} '@types/estree@1.0.7': {} @@ -4858,6 +5301,17 @@ snapshots: bare-events: 2.5.4 optional: true + base64-js@1.5.1: {} + + better-sqlite3@11.10.0: + dependencies: + bindings: 1.5.0 + prebuild-install: 7.1.3 + + bindings@1.5.0: + dependencies: + file-uri-to-path: 1.0.0 + bippy@0.3.14(@types/react@19.1.6)(react@19.1.0): dependencies: '@types/react-reconciler': 0.28.9(@types/react@19.1.6) @@ -4865,6 +5319,12 @@ snapshots: transitivePeerDependencies: - '@types/react' + bl@4.1.0: + dependencies: + buffer: 5.7.1 + inherits: 2.0.4 + readable-stream: 3.6.2 + brace-expansion@2.0.1: dependencies: balanced-match: 1.0.2 @@ -4878,6 +5338,11 @@ snapshots: buffer-from@1.1.2: {} + buffer@5.7.1: + dependencies: + base64-js: 1.5.1 + ieee754: 1.2.1 + bufferutil@4.0.9: dependencies: node-gyp-build: 4.8.4 @@ -4892,12 +5357,12 @@ snapshots: caniuse-lite@1.0.30001718: {} - cborg@4.2.11: {} - chokidar@4.0.3: dependencies: readdirp: 4.1.2 + chownr@1.1.4: {} + chownr@3.0.0: {} clsx@2.1.1: {} @@ -4951,14 +5416,34 @@ snapshots: decimal.js@10.5.0: {} + decompress-response@6.0.0: + dependencies: + mimic-response: 3.1.0 + dedent@1.6.0: {} + deep-extend@0.6.0: {} + delayed-stream@1.0.0: {} detect-libc@2.0.4: {} dotenv@16.5.0: {} + drizzle-kit@0.31.1: + dependencies: + '@drizzle-team/brocli': 0.10.2 + '@esbuild-kit/esm-loader': 2.6.5 + esbuild: 0.25.5 + esbuild-register: 3.6.0(esbuild@0.25.5) + transitivePeerDependencies: + - supports-color + + drizzle-orm@0.44.2(@types/better-sqlite3@7.6.13)(better-sqlite3@11.10.0): + optionalDependencies: + '@types/better-sqlite3': 7.6.13 + better-sqlite3: 11.10.0 + dunder-proto@1.0.1: dependencies: call-bind-apply-helpers: 1.0.2 @@ -5001,6 +5486,38 @@ snapshots: has-tostringtag: 1.0.2 hasown: 2.0.2 + esbuild-register@3.6.0(esbuild@0.25.5): + dependencies: + debug: 4.4.1 + esbuild: 0.25.5 + transitivePeerDependencies: + - supports-color + + esbuild@0.18.20: + optionalDependencies: + '@esbuild/android-arm': 0.18.20 + '@esbuild/android-arm64': 0.18.20 + '@esbuild/android-x64': 0.18.20 + '@esbuild/darwin-arm64': 0.18.20 + '@esbuild/darwin-x64': 0.18.20 + '@esbuild/freebsd-arm64': 0.18.20 + '@esbuild/freebsd-x64': 0.18.20 + '@esbuild/linux-arm': 0.18.20 + '@esbuild/linux-arm64': 0.18.20 + '@esbuild/linux-ia32': 0.18.20 + '@esbuild/linux-loong64': 0.18.20 + '@esbuild/linux-mips64el': 0.18.20 + '@esbuild/linux-ppc64': 0.18.20 + '@esbuild/linux-riscv64': 0.18.20 + '@esbuild/linux-s390x': 0.18.20 + '@esbuild/linux-x64': 0.18.20 + '@esbuild/netbsd-x64': 0.18.20 + '@esbuild/openbsd-x64': 0.18.20 + '@esbuild/sunos-x64': 0.18.20 + '@esbuild/win32-arm64': 0.18.20 + '@esbuild/win32-ia32': 0.18.20 + '@esbuild/win32-x64': 0.18.20 + esbuild@0.25.5: optionalDependencies: '@esbuild/aix-ppc64': 0.25.5 @@ -5039,12 +5556,16 @@ snapshots: exit-hook@2.2.1: {} + expand-template@2.0.3: {} + fast-fifo@1.3.2: {} fdir@6.4.5(picomatch@4.0.2): optionalDependencies: picomatch: 4.0.2 + file-uri-to-path@1.0.0: {} + foreground-child@3.3.0: dependencies: cross-spawn: 7.0.3 @@ -5057,6 +5578,8 @@ snapshots: es-set-tostringtag: 2.1.0 mime-types: 2.1.35 + fs-constants@1.0.0: {} + fs-extra@10.1.0: dependencies: graceful-fs: 4.2.11 @@ -5095,6 +5618,8 @@ snapshots: dependencies: resolve-pkg-maps: 1.0.0 + github-from-package@0.0.0: {} + glob@10.4.5: dependencies: foreground-child: 3.3.0 @@ -5130,6 +5655,12 @@ snapshots: hpagent@1.2.0: {} + ieee754@1.2.1: {} + + inherits@2.0.4: {} + + ini@1.3.8: {} + intl-messageformat@10.7.16: dependencies: '@formatjs/ecma402-abstract': 2.3.4 @@ -5316,10 +5847,14 @@ snapshots: mime@4.0.7: {} + mimic-response@3.1.0: {} + minimatch@9.0.5: dependencies: brace-expansion: 2.0.1 + minimist@1.2.8: {} + minipass@7.1.2: {} minizlib@3.0.1: @@ -5327,6 +5862,8 @@ snapshots: minipass: 7.1.2 rimraf: 5.0.10 + mkdirp-classic@0.5.3: {} + mkdirp@3.0.1: {} mri@1.2.0: {} @@ -5335,6 +5872,12 @@ snapshots: nanoid@3.3.11: {} + napi-build-utils@2.0.0: {} + + node-abi@3.75.0: + dependencies: + semver: 7.7.2 + node-fetch@2.7.0: dependencies: whatwg-url: 5.0.0 @@ -5413,6 +5956,21 @@ snapshots: preact@10.26.7: {} + prebuild-install@7.1.3: + dependencies: + detect-libc: 2.0.4 + expand-template: 2.0.3 + github-from-package: 0.0.0 + minimist: 1.2.8 + mkdirp-classic: 0.5.3 + napi-build-utils: 2.0.0 + node-abi: 3.75.0 + pump: 3.0.2 + rc: 1.2.8 + simple-get: 4.0.1 + tar-fs: 2.1.3 + tunnel-agent: 0.6.0 + prettier@2.8.8: {} proc-log@3.0.0: {} @@ -5429,6 +5987,13 @@ snapshots: end-of-stream: 1.4.4 once: 1.4.0 + rc@1.2.8: + dependencies: + deep-extend: 0.6.0 + ini: 1.3.8 + minimist: 1.2.8 + strip-json-comments: 2.0.1 + react-aria@3.40.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0): dependencies: '@internationalized/string': 3.2.6 @@ -5600,6 +6165,12 @@ snapshots: react@19.1.0: {} + readable-stream@3.6.2: + dependencies: + inherits: 2.0.4 + string_decoder: 1.3.0 + util-deprecate: 1.0.2 + readdirp@4.1.2: {} remix-utils@8.7.0(react-router@7.6.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react@19.1.0)(zod@3.25.33): @@ -5645,6 +6216,8 @@ snapshots: '@rollup/rollup-win32-x64-msvc': 4.36.0 fsevents: 2.3.3 + safe-buffer@5.2.1: {} + scheduler@0.26.0: {} semver@6.3.1: {} @@ -5661,6 +6234,14 @@ snapshots: signal-exit@4.1.0: {} + simple-concat@1.0.1: {} + + simple-get@4.0.1: + dependencies: + decompress-response: 6.0.0 + once: 1.4.0 + simple-concat: 1.0.1 + sisteransi@1.0.5: {} smart-buffer@4.2.0: {} @@ -5726,6 +6307,10 @@ snapshots: emoji-regex: 9.2.2 strip-ansi: 7.1.0 + string_decoder@1.3.0: + dependencies: + safe-buffer: 5.2.1 + strip-ansi@6.0.1: dependencies: ansi-regex: 5.0.1 @@ -5734,6 +6319,8 @@ snapshots: dependencies: ansi-regex: 6.0.1 + strip-json-comments@2.0.1: {} + style-mod@4.1.2: {} tailwind-merge@3.3.0: {} @@ -5750,6 +6337,13 @@ snapshots: tapable@2.2.2: {} + tar-fs@2.1.3: + dependencies: + chownr: 1.1.4 + mkdirp-classic: 0.5.3 + pump: 3.0.2 + tar-stream: 2.2.0 + tar-fs@3.0.9: dependencies: pump: 3.0.2 @@ -5760,6 +6354,14 @@ snapshots: transitivePeerDependencies: - bare-buffer + tar-stream@2.2.0: + dependencies: + bl: 4.1.0 + end-of-stream: 1.4.4 + fs-constants: 1.0.0 + inherits: 2.0.4 + readable-stream: 3.6.2 + tar-stream@3.1.7: dependencies: b4a: 1.6.7 @@ -5809,6 +6411,10 @@ snapshots: optionalDependencies: fsevents: 2.3.3 + tunnel-agent@0.6.0: + dependencies: + safe-buffer: 5.2.1 + type-fest@4.41.0: {} typescript@5.8.3: {} @@ -5851,6 +6457,8 @@ snapshots: node-gyp-build: 4.8.4 optional: true + util-deprecate@1.0.2: {} + valibot@0.41.0(typescript@5.8.3): optionalDependencies: typescript: 5.8.3 From c0af2aa9c8b7032b157ce0838d7c64a4d4790bc6 Mon Sep 17 00:00:00 2001 From: Aarnav Tale Date: Fri, 20 Jun 2025 00:20:27 -0400 Subject: [PATCH 22/97] chore: update changelog --- CHANGELOG.md | 3 +++ nix/package.nix | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e6f45e3..a0f5049 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,9 @@ - **Headplane now supports connecting to machines via SSH in the web browser.** - This is an experimental feature and requires the `integration.agent` section to be set up in the config file. - This is built on top of a Go binary that runs in WebAssembly, using Xterm.js for the terminal interface. +- Begin using a new SQLite database file in `/var/lib/headplane/hp_persist.db`. + - The database is created automatically if it does not exist. + - It currently stores SSH connection details and will migrate older data. ### 0.6.0 (May 25, 2025) - Headplane 0.6.0 now requires **Headscale 0.26.0** or newer. diff --git a/nix/package.nix b/nix/package.nix index 4afff51..b43a7cc 100644 --- a/nix/package.nix +++ b/nix/package.nix @@ -23,7 +23,7 @@ stdenv.mkDerivation (finalAttrs: { pnpmDeps = pnpm_10.fetchDeps { inherit (finalAttrs) pname version src; - hash = "sha256-owncSijanuu+So2LZqAy3XEz12hfTgcjCd3cVsgT4a8="; + hash = "sha256-hu3028V/EWimYB1TGn7g06kJRIpZA6cuOIjPMEc8ddw="; }; buildPhase = '' From 71f130ede5ec1c3b845ce536d7c51bfce313602f Mon Sep 17 00:00:00 2001 From: Aarnav Tale Date: Fri, 20 Jun 2025 10:36:29 -0400 Subject: [PATCH 23/97] feat: switch to libsql since its esm friendly --- app/server/db/client.ts | 16 ++- package.json | 3 +- pnpm-lock.yaml | 295 ++++++++++++++++++++++++++++++++++++---- 3 files changed, 279 insertions(+), 35 deletions(-) diff --git a/app/server/db/client.ts b/app/server/db/client.ts index da1cbee..f816a6b 100644 --- a/app/server/db/client.ts +++ b/app/server/db/client.ts @@ -1,23 +1,25 @@ import { mkdir } from 'node:fs/promises'; -import { dirname } from 'node:path'; -import { drizzle } from 'drizzle-orm/better-sqlite3'; -import { migrate } from 'drizzle-orm/better-sqlite3/migrator'; +import { dirname, resolve } from 'node:path'; +import { drizzle } from 'drizzle-orm/libsql'; +import { migrate } from 'drizzle-orm/libsql/migrator'; import log from '~/utils/log'; export async function createDbClient(path: string) { + const realPath = resolve(path); try { - await mkdir(dirname(path), { recursive: true }); + await mkdir(dirname(realPath), { recursive: true }); } catch (error) { log.error( 'server', 'Failed to create directory for database at %s: %s', - path, + realPath, error instanceof Error ? error.message : String(error), ); - throw new Error(`Could not create directory for database at ${path}`); + throw new Error(`Could not create directory for database at ${realPath}`); } - const db = drizzle(path); + // Turn the path into a URL with the file protocol + const db = drizzle(`file://${realPath}`); migrate(db, { migrationsFolder: './drizzle', }); diff --git a/package.json b/package.json index 9e9b31f..f58624f 100644 --- a/package.json +++ b/package.json @@ -19,6 +19,7 @@ "@faker-js/faker": "^9.8.0", "@fontsource-variable/inter": "^5.2.5", "@kubernetes/client-node": "^1.3.0", + "@libsql/client": "^0.15.9", "@primer/octicons-react": "^19.15.2", "@react-aria/toast": "3.0.3", "@react-router/node": "^7.6.1", @@ -36,7 +37,6 @@ "@xterm/addon-webgl": "^0.18.0", "@xterm/xterm": "^5.5.0", "arktype": "^2.1.20", - "better-sqlite3": "^11.10.0", "clsx": "^2.1.1", "dotenv": "^16.5.0", "drizzle-orm": "^0.44.2", @@ -63,7 +63,6 @@ "@biomejs/biome": "^1.9.4", "@react-router/dev": "^7.6.1", "@tailwindcss/vite": "^4.1.8", - "@types/better-sqlite3": "^7.6.13", "@types/websocket": "^1.0.10", "drizzle-kit": "^0.31.1", "lefthook": "^1.11.13", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 362b2d1..133e653 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -34,6 +34,9 @@ importers: '@kubernetes/client-node': specifier: ^1.3.0 version: 1.3.0(bufferutil@4.0.9)(utf-8-validate@5.0.10) + '@libsql/client': + specifier: ^0.15.9 + version: 0.15.9(bufferutil@4.0.9)(utf-8-validate@5.0.10) '@primer/octicons-react': specifier: ^19.15.2 version: 19.15.2(react@19.1.0) @@ -85,9 +88,6 @@ importers: arktype: specifier: ^2.1.20 version: 2.1.20 - better-sqlite3: - specifier: ^11.10.0 - version: 11.10.0 clsx: specifier: ^2.1.1 version: 2.1.1 @@ -96,7 +96,7 @@ importers: version: 16.5.0 drizzle-orm: specifier: ^0.44.2 - version: 0.44.2(@types/better-sqlite3@7.6.13)(better-sqlite3@11.10.0) + version: 0.44.2(@libsql/client@0.15.9(bufferutil@4.0.9)(utf-8-validate@5.0.10))(@types/better-sqlite3@7.6.13)(better-sqlite3@11.10.0) isbot: specifier: ^5.1.28 version: 5.1.28 @@ -161,9 +161,6 @@ importers: '@tailwindcss/vite': specifier: ^4.1.8 version: 4.1.8(vite@6.3.5(@types/node@22.15.24)(jiti@2.4.2)(lightningcss@1.30.1)(terser@5.39.0)(tsx@4.19.4)(yaml@2.8.0)) - '@types/better-sqlite3': - specifier: ^7.6.13 - version: 7.6.13 '@types/websocket': specifier: ^1.0.10 version: 1.0.10 @@ -879,12 +876,76 @@ packages: '@lezer/lr@1.4.2': resolution: {integrity: sha512-pu0K1jCIdnQ12aWNaAVU5bzi7Bd1w54J3ECgANPmYLtQKP0HBj2cE/5coBD66MT10xbtIuUr7tg0Shbsvk0mDA==} + '@libsql/client@0.15.9': + resolution: {integrity: sha512-VT3do0a0vwYVaNcp/y05ikkKS3OrFR5UeEf5SUuYZVgKVl1Nc1k9ajoYSsOid8AD/vlhLDB5yFQaV4HmT/OB9w==} + + '@libsql/core@0.15.9': + resolution: {integrity: sha512-4OVdeAmuaCUq5hYT8NNn0nxlO9AcA/eTjXfUZ+QK8MT3Dz7Z76m73x7KxjU6I64WyXX98dauVH2b9XM+d84npw==} + + '@libsql/darwin-arm64@0.5.13': + resolution: {integrity: sha512-ASz/EAMLDLx3oq9PVvZ4zBXXHbz2TxtxUwX2xpTRFR4V4uSHAN07+jpLu3aK5HUBLuv58z7+GjaL5w/cyjR28Q==} + cpu: [arm64] + os: [darwin] + + '@libsql/darwin-x64@0.5.13': + resolution: {integrity: sha512-kzglniv1difkq8opusSXM7u9H0WoEPeKxw0ixIfcGfvlCVMJ+t9UNtXmyNHW68ljdllje6a4C6c94iPmIYafYA==} + cpu: [x64] + os: [darwin] + + '@libsql/hrana-client@0.7.0': + resolution: {integrity: sha512-OF8fFQSkbL7vJY9rfuegK1R7sPgQ6kFMkDamiEccNUvieQ+3urzfDFI616oPl8V7T9zRmnTkSjMOImYCAVRVuw==} + + '@libsql/isomorphic-fetch@0.3.1': + resolution: {integrity: sha512-6kK3SUK5Uu56zPq/Las620n5aS9xJq+jMBcNSOmjhNf/MUvdyji4vrMTqD7ptY7/4/CAVEAYDeotUz60LNQHtw==} + engines: {node: '>=18.0.0'} + + '@libsql/isomorphic-ws@0.1.5': + resolution: {integrity: sha512-DtLWIH29onUYR00i0GlQ3UdcTRC6EP4u9w/h9LxpUZJWRMARk6dQwZ6Jkd+QdwVpuAOrdxt18v0K2uIYR3fwFg==} + + '@libsql/linux-arm-gnueabihf@0.5.13': + resolution: {integrity: sha512-UEW+VZN2r0mFkfztKOS7cqfS8IemuekbjUXbXCwULHtusww2QNCXvM5KU9eJCNE419SZCb0qaEWYytcfka8qeA==} + cpu: [arm] + os: [linux] + + '@libsql/linux-arm-musleabihf@0.5.13': + resolution: {integrity: sha512-NMDgLqryYBv4Sr3WoO/m++XDjR5KLlw9r/JK4Ym6A1XBv2bxQQNhH0Lxx3bjLW8qqhBD4+0xfms4d2cOlexPyA==} + cpu: [arm] + os: [linux] + + '@libsql/linux-arm64-gnu@0.5.13': + resolution: {integrity: sha512-/wCxVdrwl1ee6D6LEjwl+w4SxuLm5UL9Kb1LD5n0bBGs0q+49ChdPPh7tp175iRgkcrTgl23emymvt1yj3KxVQ==} + cpu: [arm64] + os: [linux] + + '@libsql/linux-arm64-musl@0.5.13': + resolution: {integrity: sha512-xnVAbZIanUgX57XqeI5sNaDnVilp0Di5syCLSEo+bRyBobe/1IAeehNZpyVbCy91U2N6rH1C/mZU7jicVI9x+A==} + cpu: [arm64] + os: [linux] + + '@libsql/linux-x64-gnu@0.5.13': + resolution: {integrity: sha512-/mfMRxcQAI9f8t7tU3QZyh25lXgXKzgin9B9TOSnchD73PWtsVhlyfA6qOCfjQl5kr4sHscdXD5Yb3KIoUgrpQ==} + cpu: [x64] + os: [linux] + + '@libsql/linux-x64-musl@0.5.13': + resolution: {integrity: sha512-rdefPTpQCVwUjIQYbDLMv3qpd5MdrT0IeD0UZPGqhT9AWU8nJSQoj2lfyIDAWEz7PPOVCY4jHuEn7FS2sw9kRA==} + cpu: [x64] + os: [linux] + + '@libsql/win32-x64-msvc@0.5.13': + resolution: {integrity: sha512-aNcmDrD1Ws+dNZIv9ECbxBQumqB9MlSVEykwfXJpqv/593nABb8Ttg5nAGUPtnADyaGDTrGvPPP81d/KsKho4Q==} + cpu: [x64] + os: [win32] + '@marijn/find-cluster-break@1.0.2': resolution: {integrity: sha512-l0h88YhZFyKdXIFNfSWpyjStDjGHwZ/U7iobcK1cQQD8sejsONdQtTVU+1wVN1PBw40PiiHB1vA5S7VTfQiP9g==} '@mjackson/node-fetch-server@0.2.0': resolution: {integrity: sha512-EMlH1e30yzmTpGLQjlFmaDAjyOeZhng1/XCd7DExR8PNAnG/G1tyruZxEoUe11ClnwGhGrtsdnyyUx1frSzjng==} + '@neon-rs/load@0.0.4': + resolution: {integrity: sha512-kTPhdZyTQxB+2wpiRcFWrDcejc4JI6tkPuS7UZCG4l6Zvc5kU/gGQ/ozvHTh1XR5tS+UlfAfGuPajjzQjCiHCw==} + '@npmcli/git@4.1.0': resolution: {integrity: sha512-9hwoB3gStVfa0N31ymBmrX+GuDGdVA/QWShZVqE0HK2Af+7QGGrCTbZia/SW0ImUTjTne7SP91qxDmtXvDHRPQ==} engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} @@ -1733,6 +1794,9 @@ packages: '@types/websocket@1.0.10': resolution: {integrity: sha512-svjGZvPB7EzuYS94cI7a+qhwgGU1y89wUgjT6E2wVUfmAGIvRfT7obBvRtnhXCSsoMdlG4gBFGE7MfkIXZLoww==} + '@types/ws@8.18.1': + resolution: {integrity: sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==} + '@uiw/codemirror-extensions-basic-setup@4.23.12': resolution: {integrity: sha512-l9vuiXOTFDBetYrRLDmz3jDxQHDsrVAZ2Y6dVfmrqi2AsulsDu+y7csW0JsvaMqo79rYkaIZg8yeqmDgMb7VyQ==} peerDependencies: @@ -1967,6 +2031,10 @@ packages: csstype@3.1.3: resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==} + data-uri-to-buffer@4.0.1: + resolution: {integrity: sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==} + engines: {node: '>= 12'} + debug@4.4.0: resolution: {integrity: sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==} engines: {node: '>=6.0'} @@ -2008,6 +2076,10 @@ packages: resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} engines: {node: '>=0.4.0'} + detect-libc@2.0.2: + resolution: {integrity: sha512-UX6sGumvvqSaXgdKGUsgZWqcUyIXZ/vZTrlRT/iobiKhGL0zL4d3osHj3uqllWJK+i+sixDS/3COVEOFbupFyw==} + engines: {node: '>=8'} + detect-libc@2.0.4: resolution: {integrity: sha512-3UDv+G9CsCKO1WKMGw9fwq/SWJYbI0c5Y7LU1AXYoDdbhE2AHQ6N6Nb34sG8Fj7T5APy8qXDCKuuIHd1BR0tVA==} engines: {node: '>=8'} @@ -2201,6 +2273,10 @@ packages: picomatch: optional: true + fetch-blob@3.2.0: + resolution: {integrity: sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==} + engines: {node: ^12.20 || >= 14.13} + file-uri-to-path@1.0.0: resolution: {integrity: sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==} @@ -2212,6 +2288,10 @@ packages: resolution: {integrity: sha512-hGfm/slu0ZabnNt4oaRZ6uREyfCj6P4fT/n6A1rGV+Z0VdGXjfOhVUpkn6qVQONHGIFwmveGXyDs75+nr6FM8w==} engines: {node: '>= 6'} + formdata-polyfill@4.0.10: + resolution: {integrity: sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==} + engines: {node: '>=12.20.0'} + fs-constants@1.0.0: resolution: {integrity: sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==} @@ -2435,6 +2515,11 @@ packages: resolution: {integrity: sha512-SDTk3D4nW1XRpR9u9fdYQ/qj1xeZVIwZmIFdJUnyq+w9ZLdCCvIrOmtD8SFiJowSevISjQDC+f9nqyFXUxc0SQ==} hasBin: true + libsql@0.5.13: + resolution: {integrity: sha512-5Bwoa/CqzgkTwySgqHA5TsaUDRrdLIbdM4egdPcaAnqO3aC+qAgS6BwdzuZwARA5digXwiskogZ8H7Yy4XfdOg==} + cpu: [x64, arm64, wasm32, arm] + os: [darwin, linux, win32] + lightningcss-darwin-arm64@1.30.1: resolution: {integrity: sha512-c8JK7hyE65X1MHMN+Viq9n11RRC7hgin3HhYKhrMyaXflk5GVplZ60IxyoVtzILeKr+xAJwg6zK6sjTBJ0FKYQ==} engines: {node: '>= 12.0.0'} @@ -2587,6 +2672,11 @@ packages: resolution: {integrity: sha512-OhYaY5sDsIka7H7AtijtI9jwGYLyl29eQn/W623DiN/MIv5sUqc4g7BIDThX+gb7di9f6xK02nkp8sdfFWZLTg==} engines: {node: '>=10'} + node-domexception@1.0.0: + resolution: {integrity: sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==} + engines: {node: '>=10.5.0'} + deprecated: Use your platform's native DOMException instead + node-fetch@2.7.0: resolution: {integrity: sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==} engines: {node: 4.x || >=6.0.0} @@ -2596,6 +2686,10 @@ packages: encoding: optional: true + node-fetch@3.3.2: + resolution: {integrity: sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + node-gyp-build@4.8.4: resolution: {integrity: sha512-LA4ZjwlnUblHVgq0oBF3Jl/6h/Nvs5fzBLwdEF4nuxnFdsfajde4WfxtJr3CaiH+F6ewcIB/q4jQ4UzPyid+CQ==} hasBin: true @@ -2692,6 +2786,9 @@ packages: bluebird: optional: true + promise-limit@2.7.0: + resolution: {integrity: sha512-7nJ6v5lnJsXwGprnGXga4wx6d1POjvi5Qmf1ivTRxTjH4Z/9Czja/UCMLVmB9N93GeWOU93XaFaEt6jbuoagNw==} + promise-retry@2.0.1: resolution: {integrity: sha512-y+WKFlBR8BGXnsNlIHFGPZmyDf3DFMoLhaflAnyZgV6rG6xu+JwesTo2Q9R6XwYmtmwAFCkAk3e35jEdoeh/3g==} engines: {node: '>=10'} @@ -3186,6 +3283,10 @@ packages: w3c-keyname@2.2.8: resolution: {integrity: sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ==} + web-streams-polyfill@3.3.3: + resolution: {integrity: sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==} + engines: {node: '>= 8'} + webidl-conversions@3.0.1: resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==} @@ -3899,10 +4000,74 @@ snapshots: dependencies: '@lezer/common': 1.2.3 + '@libsql/client@0.15.9(bufferutil@4.0.9)(utf-8-validate@5.0.10)': + dependencies: + '@libsql/core': 0.15.9 + '@libsql/hrana-client': 0.7.0(bufferutil@4.0.9)(utf-8-validate@5.0.10) + js-base64: 3.7.7 + libsql: 0.5.13 + promise-limit: 2.7.0 + transitivePeerDependencies: + - bufferutil + - utf-8-validate + + '@libsql/core@0.15.9': + dependencies: + js-base64: 3.7.7 + + '@libsql/darwin-arm64@0.5.13': + optional: true + + '@libsql/darwin-x64@0.5.13': + optional: true + + '@libsql/hrana-client@0.7.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)': + dependencies: + '@libsql/isomorphic-fetch': 0.3.1 + '@libsql/isomorphic-ws': 0.1.5(bufferutil@4.0.9)(utf-8-validate@5.0.10) + js-base64: 3.7.7 + node-fetch: 3.3.2 + transitivePeerDependencies: + - bufferutil + - utf-8-validate + + '@libsql/isomorphic-fetch@0.3.1': {} + + '@libsql/isomorphic-ws@0.1.5(bufferutil@4.0.9)(utf-8-validate@5.0.10)': + dependencies: + '@types/ws': 8.18.1 + ws: 8.18.2(bufferutil@4.0.9)(utf-8-validate@5.0.10) + transitivePeerDependencies: + - bufferutil + - utf-8-validate + + '@libsql/linux-arm-gnueabihf@0.5.13': + optional: true + + '@libsql/linux-arm-musleabihf@0.5.13': + optional: true + + '@libsql/linux-arm64-gnu@0.5.13': + optional: true + + '@libsql/linux-arm64-musl@0.5.13': + optional: true + + '@libsql/linux-x64-gnu@0.5.13': + optional: true + + '@libsql/linux-x64-musl@0.5.13': + optional: true + + '@libsql/win32-x64-msvc@0.5.13': + optional: true + '@marijn/find-cluster-break@1.0.2': {} '@mjackson/node-fetch-server@0.2.0': {} + '@neon-rs/load@0.0.4': {} + '@npmcli/git@4.1.0': dependencies: '@npmcli/promise-spawn': 6.0.2 @@ -5121,6 +5286,7 @@ snapshots: '@types/better-sqlite3@7.6.13': dependencies: '@types/node': 22.15.24 + optional: true '@types/estree@1.0.6': {} @@ -5165,6 +5331,10 @@ snapshots: dependencies: '@types/node': 22.10.7 + '@types/ws@8.18.1': + dependencies: + '@types/node': 22.15.24 + '@uiw/codemirror-extensions-basic-setup@4.23.12(@codemirror/autocomplete@6.18.2(@codemirror/language@6.11.0)(@codemirror/state@6.5.2)(@codemirror/view@6.36.8)(@lezer/common@1.2.3))(@codemirror/commands@6.8.1)(@codemirror/language@6.11.0)(@codemirror/lint@6.8.2)(@codemirror/search@6.5.7)(@codemirror/state@6.5.2)(@codemirror/view@6.36.8)': dependencies: '@codemirror/autocomplete': 6.18.2(@codemirror/language@6.11.0)(@codemirror/state@6.5.2)(@codemirror/view@6.36.8)(@lezer/common@1.2.3) @@ -5301,16 +5471,19 @@ snapshots: bare-events: 2.5.4 optional: true - base64-js@1.5.1: {} + base64-js@1.5.1: + optional: true better-sqlite3@11.10.0: dependencies: bindings: 1.5.0 prebuild-install: 7.1.3 + optional: true bindings@1.5.0: dependencies: file-uri-to-path: 1.0.0 + optional: true bippy@0.3.14(@types/react@19.1.6)(react@19.1.0): dependencies: @@ -5324,6 +5497,7 @@ snapshots: buffer: 5.7.1 inherits: 2.0.4 readable-stream: 3.6.2 + optional: true brace-expansion@2.0.1: dependencies: @@ -5342,6 +5516,7 @@ snapshots: dependencies: base64-js: 1.5.1 ieee754: 1.2.1 + optional: true bufferutil@4.0.9: dependencies: @@ -5361,7 +5536,8 @@ snapshots: dependencies: readdirp: 4.1.2 - chownr@1.1.4: {} + chownr@1.1.4: + optional: true chownr@3.0.0: {} @@ -5406,6 +5582,8 @@ snapshots: csstype@3.1.3: {} + data-uri-to-buffer@4.0.1: {} + debug@4.4.0: dependencies: ms: 2.1.3 @@ -5419,13 +5597,17 @@ snapshots: decompress-response@6.0.0: dependencies: mimic-response: 3.1.0 + optional: true dedent@1.6.0: {} - deep-extend@0.6.0: {} + deep-extend@0.6.0: + optional: true delayed-stream@1.0.0: {} + detect-libc@2.0.2: {} + detect-libc@2.0.4: {} dotenv@16.5.0: {} @@ -5439,8 +5621,9 @@ snapshots: transitivePeerDependencies: - supports-color - drizzle-orm@0.44.2(@types/better-sqlite3@7.6.13)(better-sqlite3@11.10.0): + drizzle-orm@0.44.2(@libsql/client@0.15.9(bufferutil@4.0.9)(utf-8-validate@5.0.10))(@types/better-sqlite3@7.6.13)(better-sqlite3@11.10.0): optionalDependencies: + '@libsql/client': 0.15.9(bufferutil@4.0.9)(utf-8-validate@5.0.10) '@types/better-sqlite3': 7.6.13 better-sqlite3: 11.10.0 @@ -5556,7 +5739,8 @@ snapshots: exit-hook@2.2.1: {} - expand-template@2.0.3: {} + expand-template@2.0.3: + optional: true fast-fifo@1.3.2: {} @@ -5564,7 +5748,13 @@ snapshots: optionalDependencies: picomatch: 4.0.2 - file-uri-to-path@1.0.0: {} + fetch-blob@3.2.0: + dependencies: + node-domexception: 1.0.0 + web-streams-polyfill: 3.3.3 + + file-uri-to-path@1.0.0: + optional: true foreground-child@3.3.0: dependencies: @@ -5578,7 +5768,12 @@ snapshots: es-set-tostringtag: 2.1.0 mime-types: 2.1.35 - fs-constants@1.0.0: {} + formdata-polyfill@4.0.10: + dependencies: + fetch-blob: 3.2.0 + + fs-constants@1.0.0: + optional: true fs-extra@10.1.0: dependencies: @@ -5618,7 +5813,8 @@ snapshots: dependencies: resolve-pkg-maps: 1.0.0 - github-from-package@0.0.0: {} + github-from-package@0.0.0: + optional: true glob@10.4.5: dependencies: @@ -5655,11 +5851,14 @@ snapshots: hpagent@1.2.0: {} - ieee754@1.2.1: {} + ieee754@1.2.1: + optional: true - inherits@2.0.4: {} + inherits@2.0.4: + optional: true - ini@1.3.8: {} + ini@1.3.8: + optional: true intl-messageformat@10.7.16: dependencies: @@ -5772,6 +5971,21 @@ snapshots: lefthook-windows-arm64: 1.11.13 lefthook-windows-x64: 1.11.13 + libsql@0.5.13: + dependencies: + '@neon-rs/load': 0.0.4 + detect-libc: 2.0.2 + optionalDependencies: + '@libsql/darwin-arm64': 0.5.13 + '@libsql/darwin-x64': 0.5.13 + '@libsql/linux-arm-gnueabihf': 0.5.13 + '@libsql/linux-arm-musleabihf': 0.5.13 + '@libsql/linux-arm64-gnu': 0.5.13 + '@libsql/linux-arm64-musl': 0.5.13 + '@libsql/linux-x64-gnu': 0.5.13 + '@libsql/linux-x64-musl': 0.5.13 + '@libsql/win32-x64-msvc': 0.5.13 + lightningcss-darwin-arm64@1.30.1: optional: true @@ -5847,13 +6061,15 @@ snapshots: mime@4.0.7: {} - mimic-response@3.1.0: {} + mimic-response@3.1.0: + optional: true minimatch@9.0.5: dependencies: brace-expansion: 2.0.1 - minimist@1.2.8: {} + minimist@1.2.8: + optional: true minipass@7.1.2: {} @@ -5862,7 +6078,8 @@ snapshots: minipass: 7.1.2 rimraf: 5.0.10 - mkdirp-classic@0.5.3: {} + mkdirp-classic@0.5.3: + optional: true mkdirp@3.0.1: {} @@ -5872,16 +6089,26 @@ snapshots: nanoid@3.3.11: {} - napi-build-utils@2.0.0: {} + napi-build-utils@2.0.0: + optional: true node-abi@3.75.0: dependencies: semver: 7.7.2 + optional: true + + node-domexception@1.0.0: {} node-fetch@2.7.0: dependencies: whatwg-url: 5.0.0 + node-fetch@3.3.2: + dependencies: + data-uri-to-buffer: 4.0.1 + fetch-blob: 3.2.0 + formdata-polyfill: 4.0.10 + node-gyp-build@4.8.4: optional: true @@ -5970,6 +6197,7 @@ snapshots: simple-get: 4.0.1 tar-fs: 2.1.3 tunnel-agent: 0.6.0 + optional: true prettier@2.8.8: {} @@ -5977,6 +6205,8 @@ snapshots: promise-inflight@1.0.1: {} + promise-limit@2.7.0: {} + promise-retry@2.0.1: dependencies: err-code: 2.0.3 @@ -5993,6 +6223,7 @@ snapshots: ini: 1.3.8 minimist: 1.2.8 strip-json-comments: 2.0.1 + optional: true react-aria@3.40.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0): dependencies: @@ -6170,6 +6401,7 @@ snapshots: inherits: 2.0.4 string_decoder: 1.3.0 util-deprecate: 1.0.2 + optional: true readdirp@4.1.2: {} @@ -6216,7 +6448,8 @@ snapshots: '@rollup/rollup-win32-x64-msvc': 4.36.0 fsevents: 2.3.3 - safe-buffer@5.2.1: {} + safe-buffer@5.2.1: + optional: true scheduler@0.26.0: {} @@ -6234,13 +6467,15 @@ snapshots: signal-exit@4.1.0: {} - simple-concat@1.0.1: {} + simple-concat@1.0.1: + optional: true simple-get@4.0.1: dependencies: decompress-response: 6.0.0 once: 1.4.0 simple-concat: 1.0.1 + optional: true sisteransi@1.0.5: {} @@ -6310,6 +6545,7 @@ snapshots: string_decoder@1.3.0: dependencies: safe-buffer: 5.2.1 + optional: true strip-ansi@6.0.1: dependencies: @@ -6319,7 +6555,8 @@ snapshots: dependencies: ansi-regex: 6.0.1 - strip-json-comments@2.0.1: {} + strip-json-comments@2.0.1: + optional: true style-mod@4.1.2: {} @@ -6343,6 +6580,7 @@ snapshots: mkdirp-classic: 0.5.3 pump: 3.0.2 tar-stream: 2.2.0 + optional: true tar-fs@3.0.9: dependencies: @@ -6361,6 +6599,7 @@ snapshots: fs-constants: 1.0.0 inherits: 2.0.4 readable-stream: 3.6.2 + optional: true tar-stream@3.1.7: dependencies: @@ -6414,6 +6653,7 @@ snapshots: tunnel-agent@0.6.0: dependencies: safe-buffer: 5.2.1 + optional: true type-fest@4.41.0: {} @@ -6457,7 +6697,8 @@ snapshots: node-gyp-build: 4.8.4 optional: true - util-deprecate@1.0.2: {} + util-deprecate@1.0.2: + optional: true valibot@0.41.0(typescript@5.8.3): optionalDependencies: @@ -6521,6 +6762,8 @@ snapshots: w3c-keyname@2.2.8: {} + web-streams-polyfill@3.3.3: {} + webidl-conversions@3.0.1: {} webpack-virtual-modules@0.6.2: From 73f0a0d2738ab319b805f505f303a3c2a27e0af8 Mon Sep 17 00:00:00 2001 From: Aarnav Tale Date: Fri, 20 Jun 2025 11:32:27 -0400 Subject: [PATCH 24/97] fix: use distroless and fix build --- CHANGELOG.md | 1 + Dockerfile | 48 +++++++++---------- app/server/db/{client.ts => client.server.ts} | 2 +- app/server/index.ts | 2 +- mise.toml | 1 + pnpm-lock.yaml | 1 - vite.config.ts | 5 +- 7 files changed, 32 insertions(+), 28 deletions(-) rename app/server/db/{client.ts => client.server.ts} (93%) diff --git a/CHANGELOG.md b/CHANGELOG.md index a0f5049..ae3b433 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ - Begin using a new SQLite database file in `/var/lib/headplane/hp_persist.db`. - The database is created automatically if it does not exist. - It currently stores SSH connection details and will migrate older data. +- The docker container now runs in a non-root, distroless image (closes [#255](https://github.com/tale/headplane/issues/255)) ### 0.6.0 (May 25, 2025) - Headplane 0.6.0 now requires **Headscale 0.26.0** or newer. diff --git a/Dockerfile b/Dockerfile index 30b76a0..9baccb9 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,35 +1,35 @@ -FROM golang:1.24 AS agent-build -WORKDIR /app +FROM jdxcode/mise:latest AS mise-context +COPY mise.toml . +RUN mise install + +FROM mise-context AS go-build +WORKDIR /build/ COPY go.mod go.sum ./ -RUN go mod download +COPY cmd/ ./cmd/ +COPY internal/ ./internal/ +RUN mkdir -p /build/app/ && mise run wasm ::: agent +RUN chmod +x /build/build/hp_agent -COPY agent/ ./agent -RUN CGO_ENABLED=0 GOOS=linux go build \ - -trimpath \ - -ldflags "-s -w" \ - -o /app/hp_agent ./agent/cmd/hp_agent - -FROM node:22-alpine AS build -WORKDIR /app - -RUN npm install -g pnpm@10 -RUN apk add --no-cache git -COPY package.json pnpm-lock.yaml ./ +FROM mise-context AS js-build +WORKDIR /build COPY patches ./patches +COPY package.json pnpm-lock.yaml ./ RUN pnpm install --frozen-lockfile COPY . . +RUN mise trust +COPY --from=go-build /build/app/hp_ssh.wasm /build/app/hp_ssh.wasm +COPY --from=go-build /build/app/wasm_exec.js /build/app/wasm_exec.js RUN pnpm run build - -FROM node:22-alpine -RUN apk add --no-cache ca-certificates -RUN mkdir -p /var/lib/headplane -RUN mkdir -p /usr/libexec/headplane RUN mkdir -p /var/lib/headplane/agent +FROM gcr.io/distroless/nodejs22-debian12:nonroot +COPY --from=js-build --chown=nonroot:nonroot /build/build/ /app/build/ +COPY --from=js-build --chown=nonroot:nonroot /build/drizzle /app/drizzle/ +COPY --from=js-build --chown=nonroot:nonroot /var/lib/headplane /var/lib/headplane +COPY --from=js-build --chown=nonroot:nonroot /build/node_modules/ /app/node_modules/ +COPY --from=go-build --chown=nonroot:nonroot /build/build/hp_agent /usr/libexec/headplane/agent + WORKDIR /app -COPY --from=build /app/build /app/build -COPY --from=agent-build /app/hp_agent /usr/libexec/headplane/agent -RUN chmod +x /usr/libexec/headplane/agent -CMD [ "node", "./build/server/index.js" ] +CMD [ "/app/build/server/index.js" ] diff --git a/app/server/db/client.ts b/app/server/db/client.server.ts similarity index 93% rename from app/server/db/client.ts rename to app/server/db/client.server.ts index f816a6b..c364b1d 100644 --- a/app/server/db/client.ts +++ b/app/server/db/client.server.ts @@ -1,7 +1,7 @@ import { mkdir } from 'node:fs/promises'; import { dirname, resolve } from 'node:path'; -import { drizzle } from 'drizzle-orm/libsql'; import { migrate } from 'drizzle-orm/libsql/migrator'; +import { drizzle } from 'drizzle-orm/libsql/sqlite3'; import log from '~/utils/log'; export async function createDbClient(path: string) { diff --git a/app/server/index.ts b/app/server/index.ts index ed4fea6..1235b12 100644 --- a/app/server/index.ts +++ b/app/server/index.ts @@ -5,7 +5,7 @@ import log from '~/utils/log'; import { configureConfig, configureLogger, envVariables } from './config/env'; import { loadIntegration } from './config/integration'; import { loadConfig } from './config/loader'; -import { createDbClient } from './db/client'; +import { createDbClient } from './db/client.server'; import { createApiClient } from './headscale/api-client'; import { loadHeadscaleConfig } from './headscale/config-loader'; import { loadAgentSocket } from './web/agent'; diff --git a/mise.toml b/mise.toml index cc54c04..71e70c3 100644 --- a/mise.toml +++ b/mise.toml @@ -1,6 +1,7 @@ [tools] "go" = "1.24.4" "node" = "22" +"pnpm" = "10" [tasks.copy-wasm-shim] alias = ["gojs"] diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 133e653..002e709 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -2517,7 +2517,6 @@ packages: libsql@0.5.13: resolution: {integrity: sha512-5Bwoa/CqzgkTwySgqHA5TsaUDRrdLIbdM4egdPcaAnqO3aC+qAgS6BwdzuZwARA5digXwiskogZ8H7Yy4XfdOg==} - cpu: [x64, arm64, wasm32, arm] os: [darwin, linux, win32] lightningcss-darwin-arm64@1.30.1: diff --git a/vite.config.ts b/vite.config.ts index 9a8899e..d10ecb5 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -36,7 +36,10 @@ export default defineConfig(({ isSsrBuild }) => ({ }, ssr: { target: 'node', - noExternal: isSsrBuild ? true : undefined, + noExternal: isSsrBuild ? ['@libsql/client'] : undefined, + }, + optimizeDeps: { + include: ['@libsql/client'], }, define: { __VERSION__: JSON.stringify(version), From cf55621b57e55f11cd77f63464c9cae4b14fa692 Mon Sep 17 00:00:00 2001 From: Aarnav Tale Date: Fri, 20 Jun 2025 19:46:20 -0400 Subject: [PATCH 25/97] docs: warn about permission change --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index ae3b433..4443af4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ - The database is created automatically if it does not exist. - It currently stores SSH connection details and will migrate older data. - The docker container now runs in a non-root, distroless image (closes [#255](https://github.com/tale/headplane/issues/255)) + - You may need to run `chown -R 65532:65532 ` on your data directory to ensure the container can write to it. ### 0.6.0 (May 25, 2025) - Headplane 0.6.0 now requires **Headscale 0.26.0** or newer. From dd287c0444ec57f4aefe7b24d5d18022d26b6d39 Mon Sep 17 00:00:00 2001 From: Aarnav Tale Date: Fri, 20 Jun 2025 19:55:02 -0400 Subject: [PATCH 26/97] fix: menu should work even with ssh target --- app/routes/machines/components/menu.tsx | 124 ++++++++++++------------ 1 file changed, 60 insertions(+), 64 deletions(-) diff --git a/app/routes/machines/components/menu.tsx b/app/routes/machines/components/menu.tsx index c3acf16..4c7a9ce 100644 --- a/app/routes/machines/components/menu.tsx +++ b/app/routes/machines/components/menu.tsx @@ -1,5 +1,6 @@ import { Cog, Ellipsis, SquareTerminal } from 'lucide-react'; import { useState } from 'react'; +import Button from '~/components/Button'; import Menu from '~/components/Menu'; import type { User } from '~/types'; import cn from '~/utils/cn'; @@ -32,7 +33,7 @@ export default function MachineMenu({ node.hostInfo?.sshHostKeys && node.hostInfo?.sshHostKeys.length > 0; return ( - <> +
{modal === 'remove' && ( )} + {supportsTailscaleSSH ? ( + isFullButton ? ( + + ) : ( + + ) + ) : undefined} {isFullButton ? ( -
- {supportsTailscaleSSH ? ( - { - // We need to use JS to open the SSH URL - // in a new WINDOW since href can only - // do a new TAB. - window.open( - // TODO: Use the actual real username lol - `${__PREFIX__}/ssh?hostname=${node.name}&username=tale`, - '_blank', - 'noopener,noreferrer,width=800,height=600', - ); - }} - > - -

SSH

-
- ) : undefined} - - -

Machine Settings

-
-
+ + +

Machine Settings

+
) : ( -
- {supportsTailscaleSSH ? ( - { - // We need to use JS to open the SSH URL - // in a new WINDOW since href can only - // do a new TAB. - window.open( - // TODO: Use the actual real username lol - `${__PREFIX__}/ssh?hostname=${node.name}&username=tale`, - '_blank', - 'noopener,noreferrer,width=800,height=600', - ); - }} - className={cn( - 'py-0.5 w-fit bg-transparent border-transparent', - 'border group-hover:border-headplane-200', - 'dark:group-hover:border-headplane-700', - 'opacity-0 pointer-events-none group-hover:opacity-100', - 'group-hover:pointer-events-auto', - )} - > - SSH - - ) : undefined} - - - -
+ + + )} setModal(key as Modal)} @@ -176,6 +172,6 @@ export default function MachineMenu({
- +
); } From 1150d1616e7de905ba77289120d8a58777a0ef91 Mon Sep 17 00:00:00 2001 From: Aarnav Tale Date: Fri, 20 Jun 2025 20:09:28 -0400 Subject: [PATCH 27/97] fix: support user input when not specified for ssh --- app/routes/ssh/console.tsx | 25 +++++++++++++++--- app/routes/ssh/user-prompt.tsx | 47 ++++++++++++++++++++++++++++++++++ app/server/db/pruner.ts | 4 +-- 3 files changed, 70 insertions(+), 6 deletions(-) create mode 100644 app/routes/ssh/user-prompt.tsx diff --git a/app/routes/ssh/console.tsx b/app/routes/ssh/console.tsx index e5ccf00..7e47379 100644 --- a/app/routes/ssh/console.tsx +++ b/app/routes/ssh/console.tsx @@ -9,7 +9,6 @@ import { useLoaderData, useSubmit, } from 'react-router'; -import { useEventSource } from 'remix-utils/sse/react'; import { LoadContext } from '~/server'; import { Machine, PreAuthKey, User } from '~/types'; import { useLiveData } from '~/utils/live-data'; @@ -19,6 +18,7 @@ import { eq } from 'drizzle-orm'; import wasm from '~/hp_ssh.wasm?url'; import { EphemeralNodeInsert, ephemeralNodes } from '~/server/db/schema'; import '~/wasm_exec'; +import UserPrompt from './user-prompt'; export const shouldRevalidate: ShouldRevalidateFunction = () => { return false; @@ -88,8 +88,18 @@ export async function loader({ const qp = new URL(request.url).searchParams; const username = qp.get('username') || undefined; const hostname = qp.get('hostname') || undefined; - if (!username || !hostname) { - throw data('Missing required parameters: username, hostname', 400); + if (!hostname) { + throw data('Missing required parameter: hostname', 400); + } + + if (!username) { + return { + ipnDetails: undefined, + sshDetails: { + username, + hostname, + }, + }; } // We're making a request to /key?v=116 to check the CORS headers @@ -202,9 +212,12 @@ export default function Page() { const [ipn, setIpn] = useState(null); const [nodeKey, setNodeKey] = useState(null); const { ipnDetails, sshDetails } = useLoaderData(); - const ping = useEventSource('/ssh/ping', { event: 'ping' }); useEffect(() => { + if (!ipnDetails) { + return; + } + pause(); const go = new Go(); // Go is defined by wasm_exec.js WebAssembly.instantiateStreaming(fetch(wasm), go.importObject).then( @@ -249,6 +262,10 @@ export default function Page() { ); }, []); + if (!sshDetails.username) { + return ; + } + return (
{ipn === null ? ( diff --git a/app/routes/ssh/user-prompt.tsx b/app/routes/ssh/user-prompt.tsx new file mode 100644 index 0000000..bbf160d --- /dev/null +++ b/app/routes/ssh/user-prompt.tsx @@ -0,0 +1,47 @@ +import { useState } from 'react'; +import Button from '~/components/Button'; +import Card from '~/components/Card'; +import Code from '~/components/Code'; +import Input from '~/components/Input'; + +interface UserPromptProps { + hostname: string; +} + +export default function UserPrompt({ hostname }: UserPromptProps) { + const [username, setUsername] = useState(''); + + return ( +
+ + Enter Username + + Enter the username you want to use to connect to{' '} + {hostname} + {'. '} + WebSSH follows the Headscale ACLs, so only permitted usernames will be + able to connect. + + + + +
+ ); +} diff --git a/app/server/db/pruner.ts b/app/server/db/pruner.ts index 57adafd..815e393 100644 --- a/app/server/db/pruner.ts +++ b/app/server/db/pruner.ts @@ -41,7 +41,7 @@ export async function pruneEphemeralNodes({ // Delete from the Headscale nodes list and then from the database const promises = toPrune.map((node) => { return async () => { - log.info('api', `Pruning node ${node.name}`); + log.debug('api', `Pruning node ${node.name}`); await context.client.delete( `v1/node/${node.id}`, session.get('api_key')!, @@ -50,7 +50,7 @@ export async function pruneEphemeralNodes({ await context.db .delete(ephemeralNodes) .where(eq(ephemeralNodes.node_key, node.nodeKey)); - log.info('api', `Node ${node.name} pruned successfully`); + log.debug('api', `Node ${node.name} pruned successfully`); }; }); From 8819af270d62fb86dfbf9a91d64c7fb064a413c5 Mon Sep 17 00:00:00 2001 From: Aarnav Tale Date: Sat, 21 Jun 2025 13:07:39 -0400 Subject: [PATCH 28/97] fix: properly handle removing the last split dns record Previously it would set the value to an empty array, breaking DNS resolution for that split completely. The correct behavior was to remove the key altogether! This fixes #231 --- CHANGELOG.md | 3 ++- app/routes/dns/dns-actions.ts | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4443af4..dd7be6d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,8 +5,9 @@ - Begin using a new SQLite database file in `/var/lib/headplane/hp_persist.db`. - The database is created automatically if it does not exist. - It currently stores SSH connection details and will migrate older data. -- The docker container now runs in a non-root, distroless image (closes [#255](https://github.com/tale/headplane/issues/255)) +- The docker container now runs in a non-root, distroless image (closes [#255](https://github.com/tale/headplane/issues/255)). - You may need to run `chown -R 65532:65532 ` on your data directory to ensure the container can write to it. +- Removing a Split DNS record will no longer make the split domain unresolvable by clients (closes [#231](https://github.com/tale/headplane/issues/231)). ### 0.6.0 (May 25, 2025) - Headplane 0.6.0 now requires **Headscale 0.26.0** or newer. diff --git a/app/routes/dns/dns-actions.ts b/app/routes/dns/dns-actions.ts index 654b8ff..d318737 100644 --- a/app/routes/dns/dns-actions.ts +++ b/app/routes/dns/dns-actions.ts @@ -104,7 +104,7 @@ async function removeNs(formData: FormData, context: LoadContext) { await context.hs.patch([ { path: `dns.nameservers.split."${splitName}"`, - value: servers, + value: servers.length > 0 ? servers : null, }, ]); } From 87b8d64bcc65142eb796770bb72e53121330dfbc Mon Sep 17 00:00:00 2001 From: Aarnav Tale Date: Sat, 21 Jun 2025 13:53:19 -0400 Subject: [PATCH 29/97] feat: reintroduce missing local dns override (fixes #236) --- CHANGELOG.md | 1 + app/components/Switch.tsx | 3 ++ app/routes/dns/components/manage-ns.tsx | 63 +++++++++++++++++++++++-- app/routes/dns/dns-actions.ts | 19 ++++++++ app/routes/dns/overview.tsx | 7 ++- app/server/headscale/config-schema.ts | 1 + 6 files changed, 88 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index dd7be6d..cd803da 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ - The docker container now runs in a non-root, distroless image (closes [#255](https://github.com/tale/headplane/issues/255)). - You may need to run `chown -R 65532:65532 ` on your data directory to ensure the container can write to it. - Removing a Split DNS record will no longer make the split domain unresolvable by clients (closes [#231](https://github.com/tale/headplane/issues/231)). +- Reintroduce the toggle for overriding local DNS settings in the Headscale config (closes [#236](https://github.com/tale/headplane/issues/236)). ### 0.6.0 (May 25, 2025) - Headplane 0.6.0 now requires **Headscale 0.26.0** or newer. diff --git a/app/components/Switch.tsx b/app/components/Switch.tsx index 2230774..6c73a39 100644 --- a/app/components/Switch.tsx +++ b/app/components/Switch.tsx @@ -11,6 +11,7 @@ import cn from '~/utils/cn'; export interface SwitchProps extends AriaSwitchProps { label: string; className?: string; + switchClassName?: string; } export default function Switch(props: SwitchProps) { @@ -45,6 +46,7 @@ export default function Switch(props: SwitchProps) { state.isSelected && 'bg-headplane-900 dark:bg-headplane-950', isFocusVisible && 'ring-2', props.isDisabled && 'opacity-50', + props.className, )} >
diff --git a/app/routes/dns/components/manage-ns.tsx b/app/routes/dns/components/manage-ns.tsx index 74a3948..285277f 100644 --- a/app/routes/dns/components/manage-ns.tsx +++ b/app/routes/dns/components/manage-ns.tsx @@ -1,16 +1,24 @@ -import { Form } from 'react-router'; +import { Info } from 'lucide-react'; +import { Form, useSubmit } from 'react-router'; import Button from '~/components/Button'; import Link from '~/components/Link'; +import Switch from '~/components/Switch'; import TableList from '~/components/TableList'; +import Tooltip from '~/components/Tooltip'; import cn from '~/utils/cn'; import AddNS from '../dialogs/add-ns'; interface Props { nameservers: Record; + overrideLocalDns: boolean; isDisabled: boolean; } -export default function ManageNS({ nameservers, isDisabled }: Props) { +export default function ManageNS({ + nameservers, + isDisabled, + overrideLocalDns, +}: Props) { return (

Nameservers

@@ -31,6 +39,7 @@ export default function ManageNS({ nameservers, isDisabled }: Props) { isGlobal={key === 'global'} isDisabled={isDisabled} nameservers={nameservers} + overrideLocalDns={overrideLocalDns} name={key} /> ))} @@ -45,6 +54,7 @@ interface ListProps { isGlobal: boolean; isDisabled: boolean; nameservers: Record; + overrideLocalDns: boolean; name: string; } @@ -52,6 +62,7 @@ function NameserverList({ isGlobal, isDisabled, nameservers, + overrideLocalDns, name, }: ListProps) { const list = isGlobal ? nameservers.global : nameservers[name]; @@ -59,12 +70,54 @@ function NameserverList({ return null; } + const submit = useSubmit(); return (
-

- {isGlobal ? 'Global Nameservers' : name} -

+ {isGlobal ? ( +
+

+ Global Nameservers +

+
+ + + + When enabled, use the DNS servers listed below to resolve + names outside the tailnet. When disabled (default), devices + will prefer their local DNS configuration. + + Learn More + + + +

Override DNS servers

+ { + submit( + { + action_id: 'override_dns', + override_dns: v ? 'true' : 'false', + }, + { + method: 'POST', + }, + ); + }} + /> +
+
+ ) : ( +

{name}

+ )}
{list.length > 0 diff --git a/app/routes/dns/dns-actions.ts b/app/routes/dns/dns-actions.ts index d318737..4d0e60e 100644 --- a/app/routes/dns/dns-actions.ts +++ b/app/routes/dns/dns-actions.ts @@ -42,6 +42,8 @@ export async function dnsAction({ return removeRecord(formData, context); case 'add_record': return addRecord(formData, context); + case 'override_dns': + return overrideDns(formData, context); default: return data({ success: false }, 400); } @@ -230,3 +232,20 @@ async function addRecord(formData: FormData, context: LoadContext) { await context.integration?.onConfigChange(context.client); } + +async function overrideDns(formData: FormData, context: LoadContext) { + const override = formData.get('override_dns')?.toString(); + if (!override) { + return data({ success: false }, 400); + } + + const overrideValue = override === 'true'; + await context.hs.patch([ + { + path: 'dns.override_local_dns', + value: overrideValue, + }, + ]); + + await context.integration?.onConfigChange(context.client); +} diff --git a/app/routes/dns/overview.tsx b/app/routes/dns/overview.tsx index a5794e2..5c4c42a 100644 --- a/app/routes/dns/overview.tsx +++ b/app/routes/dns/overview.tsx @@ -44,6 +44,7 @@ export async function loader({ nameservers: config.dns.nameservers.global, splitDns: config.dns.nameservers.split, searchDomains: config.dns.search_domains, + overrideDns: config.dns.override_local_dns, extraRecords: context.hs.d, }; @@ -84,7 +85,11 @@ export default function Page() { )} - + []), split: type('Record').default(() => ({})), From 2bf088b4a9e0bf76d83102433c3e2b5f512f7829 Mon Sep 17 00:00:00 2001 From: Aarnav Tale Date: Sun, 22 Jun 2025 16:59:21 -0400 Subject: [PATCH 30/97] feat: speed up docker build --- CHANGELOG.md | 1 + Dockerfile | 14 ++++++++++---- package.json | 4 ++++ 3 files changed, 15 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index cd803da..9829b30 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ - You may need to run `chown -R 65532:65532 ` on your data directory to ensure the container can write to it. - Removing a Split DNS record will no longer make the split domain unresolvable by clients (closes [#231](https://github.com/tale/headplane/issues/231)). - Reintroduce the toggle for overriding local DNS settings in the Headscale config (closes [#236](https://github.com/tale/headplane/issues/236)). +- Prefer cross-compiling in the Dockerfile to speed up builds while still supporting multiple architectures. ### 0.6.0 (May 25, 2025) - Headplane 0.6.0 now requires **Headscale 0.26.0** or newer. diff --git a/Dockerfile b/Dockerfile index 9baccb9..a224d81 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,17 +1,23 @@ -FROM jdxcode/mise:latest AS mise-context +FROM --platform=$BUILDPLATFORM jdxcode/mise:latest AS mise-context COPY mise.toml . RUN mise install -FROM mise-context AS go-build +FROM --platform=$BUILDPLATFORM mise-context AS go-build WORKDIR /build/ COPY go.mod go.sum ./ COPY cmd/ ./cmd/ COPY internal/ ./internal/ -RUN mkdir -p /build/app/ && mise run wasm ::: agent + +ARG TARGETOS +ARG TARGETARCH +RUN mkdir -p /build/app/ && \ + GOOS=$TARGETOS GOARCH=$TARGETARCH CGO_ENABLED=0 \ + mise run wasm ::: agent + RUN chmod +x /build/build/hp_agent -FROM mise-context AS js-build +FROM --platform=$BUILDPLATFORM mise-context AS js-build WORKDIR /build COPY patches ./patches COPY package.json pnpm-lock.yaml ./ diff --git a/package.json b/package.json index f58624f..592081b 100644 --- a/package.json +++ b/package.json @@ -85,6 +85,10 @@ "patchedDependencies": { "react-router-hono-server": "patches/react-router-hono-server.patch" }, + "supportedArchitectures": { + "os": ["current", "linux"], + "cpu": ["x64", "arm64"] + }, "onlyBuiltDependencies": [ "@biomejs/biome", "better-sqlite3", From 144a8b87cd5cf96eec902fbc1d2279dd8b27075a Mon Sep 17 00:00:00 2001 From: Aarnav Tale Date: Sun, 22 Jun 2025 17:17:56 -0400 Subject: [PATCH 31/97] feat: cache docker build cache using gha --- .github/workflows/next.yaml | 4 +++- .github/workflows/release.yaml | 4 +++- Dockerfile | 2 ++ 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/.github/workflows/next.yaml b/.github/workflows/next.yaml index f77a1f8..dbabedd 100644 --- a/.github/workflows/next.yaml +++ b/.github/workflows/next.yaml @@ -45,7 +45,7 @@ jobs: password: ${{ secrets.GITHUB_TOKEN }} - name: Build and push Docker image - uses: docker/build-push-action@v5 + uses: docker/build-push-action@v6 with: context: . file: ./Dockerfile @@ -53,3 +53,5 @@ jobs: tags: ${{ steps.meta.outputs.tags }} labels: ${{ steps.meta.outputs.labels }} platforms: linux/amd64, linux/arm64 + cache-from: type=gha + cache-to: type=gha,mode=max diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 04bff84..e21be79 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -43,7 +43,7 @@ jobs: password: ${{ secrets.GITHUB_TOKEN }} - name: Build and push Docker image - uses: docker/build-push-action@v5 + uses: docker/build-push-action@v6 with: context: . file: ./Dockerfile @@ -51,3 +51,5 @@ jobs: tags: ${{ steps.meta.outputs.tags }} labels: ${{ steps.meta.outputs.labels }} platforms: linux/amd64, linux/arm64 + cache-from: type=gha + cache-to: type=gha,mode=max diff --git a/Dockerfile b/Dockerfile index a224d81..b722f5c 100644 --- a/Dockerfile +++ b/Dockerfile @@ -6,6 +6,8 @@ FROM --platform=$BUILDPLATFORM mise-context AS go-build WORKDIR /build/ COPY go.mod go.sum ./ +RUN go mod download + COPY cmd/ ./cmd/ COPY internal/ ./internal/ From 9a81b763983ed9a16354be1d80062981a1b3ecb3 Mon Sep 17 00:00:00 2001 From: Aarnav Tale Date: Sun, 22 Jun 2025 17:28:19 -0400 Subject: [PATCH 32/97] feat: add provenance attestations --- .github/workflows/next.yaml | 8 ++++++++ .github/workflows/release.yaml | 8 ++++++++ 2 files changed, 16 insertions(+) diff --git a/.github/workflows/next.yaml b/.github/workflows/next.yaml index dbabedd..7129d52 100644 --- a/.github/workflows/next.yaml +++ b/.github/workflows/next.yaml @@ -46,6 +46,7 @@ jobs: - name: Build and push Docker image uses: docker/build-push-action@v6 + id: push with: context: . file: ./Dockerfile @@ -55,3 +56,10 @@ jobs: platforms: linux/amd64, linux/arm64 cache-from: type=gha cache-to: type=gha,mode=max + - name: Attest + uses: actions/attest-build-provenance@v2 + id: attest + with: + subject-name: ghcr.io/${{ github.repository }} + subject-digest: ${{ steps.push.outputs.digest }} + push-to-registry: true diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index e21be79..b7442cf 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -44,6 +44,7 @@ jobs: - name: Build and push Docker image uses: docker/build-push-action@v6 + id: push with: context: . file: ./Dockerfile @@ -53,3 +54,10 @@ jobs: platforms: linux/amd64, linux/arm64 cache-from: type=gha cache-to: type=gha,mode=max + - name: Attest + uses: actions/attest-build-provenance@v2 + id: attest + with: + subject-name: ghcr.io/${{ github.repository }} + subject-digest: ${{ steps.push.outputs.digest }} + push-to-registry: true From 8f1b577b5e228de0ebad6a9b72ed5d658da3a599 Mon Sep 17 00:00:00 2001 From: Aarnav Tale Date: Sun, 22 Jun 2025 17:32:39 -0400 Subject: [PATCH 33/97] fix: allow id-token write in gh actions --- .github/workflows/next.yaml | 3 ++- .github/workflows/release.yaml | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/.github/workflows/next.yaml b/.github/workflows/next.yaml index 7129d52..25db922 100644 --- a/.github/workflows/next.yaml +++ b/.github/workflows/next.yaml @@ -12,6 +12,7 @@ permissions: actions: write # Allow canceling in-progress runs contents: read # Read access to the repository packages: write # Write access to the container registry + id-token: write # For the attest action to push jobs: publish: @@ -56,7 +57,7 @@ jobs: platforms: linux/amd64, linux/arm64 cache-from: type=gha cache-to: type=gha,mode=max - - name: Attest + - name: Attestation Provenance uses: actions/attest-build-provenance@v2 id: attest with: diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index b7442cf..d36072a 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -12,6 +12,7 @@ permissions: actions: write # Allow canceling in-progress runs contents: read # Read access to the repository packages: write # Write access to the container registry + id-token: write # For the attest action to push jobs: docker: @@ -54,7 +55,7 @@ jobs: platforms: linux/amd64, linux/arm64 cache-from: type=gha cache-to: type=gha,mode=max - - name: Attest + - name: Attestation Provenance uses: actions/attest-build-provenance@v2 id: attest with: From f28bfd5f86a62fb3eb22f3fbe46c20c5bf06f6d5 Mon Sep 17 00:00:00 2001 From: Aarnav Tale Date: Sun, 22 Jun 2025 17:36:22 -0400 Subject: [PATCH 34/97] fix: add attestation perms to gha --- .github/workflows/next.yaml | 1 + .github/workflows/release.yaml | 1 + 2 files changed, 2 insertions(+) diff --git a/.github/workflows/next.yaml b/.github/workflows/next.yaml index 25db922..10a618a 100644 --- a/.github/workflows/next.yaml +++ b/.github/workflows/next.yaml @@ -13,6 +13,7 @@ permissions: contents: read # Read access to the repository packages: write # Write access to the container registry id-token: write # For the attest action to push + attestations: write # For the attest action to push jobs: publish: diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index d36072a..bb46be7 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -13,6 +13,7 @@ permissions: contents: read # Read access to the repository packages: write # Write access to the container registry id-token: write # For the attest action to push + attestations: write # For the attest action to push jobs: docker: From 2cc4339499bbe575c0cd242ac01d1f5e2e5816ec Mon Sep 17 00:00:00 2001 From: Aarnav Tale Date: Mon, 23 Jun 2025 00:35:50 -0400 Subject: [PATCH 35/97] chore: update changelog --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9829b30..87e995d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,9 @@ - Removing a Split DNS record will no longer make the split domain unresolvable by clients (closes [#231](https://github.com/tale/headplane/issues/231)). - Reintroduce the toggle for overriding local DNS settings in the Headscale config (closes [#236](https://github.com/tale/headplane/issues/236)). - Prefer cross-compiling in the Dockerfile to speed up builds while still supporting multiple architectures. +- Add a build attestation to validate SLSA provenance for the Docker image. +- Implement more accurate guessing on the PID with the `/proc` integration (via [#219](https://github.com/tale/headplane/pull/219)). +- Usernames will now correctly fall back to emails if not provided (via [#257](https://github.com/tale/headplane/pull/257)). ### 0.6.0 (May 25, 2025) - Headplane 0.6.0 now requires **Headscale 0.26.0** or newer. From 5a254b746bd6aa76a108aac34d74cbe9d9d0e9be Mon Sep 17 00:00:00 2001 From: Aarnav Tale Date: Tue, 24 Jun 2025 10:39:51 -0400 Subject: [PATCH 36/97] docs: enforce node lts 22.16 which has sqlite support unflagged --- .tool-versions | 3 ++- docs/Bare-Metal.md | 2 +- mise.toml | 5 ----- package.json | 2 +- 4 files changed, 4 insertions(+), 8 deletions(-) diff --git a/.tool-versions b/.tool-versions index e3ec780..e39e04d 100644 --- a/.tool-versions +++ b/.tool-versions @@ -1,2 +1,3 @@ pnpm 10.4.0 -node 22 +node 22.16 +go 1.24.4 diff --git a/docs/Bare-Metal.md b/docs/Bare-Metal.md index 4b83bcc..1283ab3 100644 --- a/docs/Bare-Metal.md +++ b/docs/Bare-Metal.md @@ -12,7 +12,7 @@ different needs. Requirements: - Headscale 0.26 or newer (already deployed) -- Node.js 22 LTS or newer +- Node.js **22.16** LTS or newer - [PNPM](https://pnpm.io/installation) 10.x - A finished configuration file (config.yaml) diff --git a/mise.toml b/mise.toml index 71e70c3..e5b2ab6 100644 --- a/mise.toml +++ b/mise.toml @@ -1,8 +1,3 @@ -[tools] -"go" = "1.24.4" -"node" = "22" -"pnpm" = "10" - [tasks.copy-wasm-shim] alias = ["gojs"] description = "Copies Go's wasm_exec.js to the public directory" diff --git a/package.json b/package.json index 592081b..48cf2ad 100644 --- a/package.json +++ b/package.json @@ -78,7 +78,7 @@ }, "packageManager": "pnpm@10.4.0", "engines": { - "node": ">=22", + "node": ">=22.16", "pnpm": ">=10.4 <11" }, "pnpm": { From eed12d348e42f323f24ebd13d42d999ddbeaf5a2 Mon Sep 17 00:00:00 2001 From: Aarnav Tale Date: Tue, 24 Jun 2025 10:56:43 -0400 Subject: [PATCH 37/97] feat: build a debug-shell and distroless container --- .github/workflows/next.yaml | 20 ++++++++++++++++---- .tool-versions | 1 + Dockerfile | 28 +++++++++++++++++++++++----- cmd/fake_sh/fake_sh.go | 19 +++++++++++++++++++ mise.toml | 14 ++++++++++++++ 5 files changed, 73 insertions(+), 9 deletions(-) create mode 100644 cmd/fake_sh/fake_sh.go diff --git a/.github/workflows/next.yaml b/.github/workflows/next.yaml index 10a618a..535a446 100644 --- a/.github/workflows/next.yaml +++ b/.github/workflows/next.yaml @@ -21,6 +21,14 @@ jobs: if: ${{ github.event_name == 'workflow_dispatch' || (github.event.pull_request && github.event.pull_request.head.repo.full_name == github.repository && github.event.pull_request.head.ref == 'next') }} name: Docker Pre-release runs-on: ubuntu-latest + strategy: + matrix: + include: + - target: final + tag: 'next' + - target: debug-shell + tag: 'next-shell' + steps: - name: Check out the repo uses: actions/checkout@v4 @@ -31,7 +39,7 @@ jobs: with: images: ghcr.io/${{ github.repository }} tags: | - type=raw,value=next + type=raw,value=${{ matrix.tag }} - name: Set up QEMU uses: docker/setup-qemu-action@v3 @@ -46,22 +54,26 @@ jobs: username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} - - name: Build and push Docker image + - name: Build and publish ghcr.io/${{ github.repository }}:${{ matrix.tag }} uses: docker/build-push-action@v6 id: push with: context: . file: ./Dockerfile + target: ${{ matrix.target }} push: true tags: ${{ steps.meta.outputs.tags }} labels: ${{ steps.meta.outputs.labels }} platforms: linux/amd64, linux/arm64 cache-from: type=gha cache-to: type=gha,mode=max - - name: Attestation Provenance + build-args: | + IMAGE_TAG=ghcr.io/${{ github.repository }}:${{ matrix.tag }} + + - name: Attestation Provenance for ghcr.io/${{ github.repository }}:${{ matrix.tag }} uses: actions/attest-build-provenance@v2 id: attest with: - subject-name: ghcr.io/${{ github.repository }} + subject-name: ghcr.io/${{ github.repository }}:${{ matrix.tag }} subject-digest: ${{ steps.push.outputs.digest }} push-to-registry: true diff --git a/.tool-versions b/.tool-versions index e39e04d..c06aa6a 100644 --- a/.tool-versions +++ b/.tool-versions @@ -1,3 +1,4 @@ +# REMEMBER TO UPDATE mise.toml TOO pnpm 10.4.0 node 22.16 go 1.24.4 diff --git a/Dockerfile b/Dockerfile index b722f5c..4e593e9 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,5 +1,5 @@ FROM --platform=$BUILDPLATFORM jdxcode/mise:latest AS mise-context -COPY mise.toml . +COPY mise.toml .tool-versions ./ RUN mise install FROM --platform=$BUILDPLATFORM mise-context AS go-build @@ -13,11 +13,13 @@ COPY internal/ ./internal/ ARG TARGETOS ARG TARGETARCH +ARG IMAGE_TAG RUN mkdir -p /build/app/ && \ - GOOS=$TARGETOS GOARCH=$TARGETARCH CGO_ENABLED=0 \ - mise run wasm ::: agent + GOOS=$TARGETOS GOARCH=$TARGETARCH CGO_ENABLED=0 IMAGE_TAG=$IMAGE_TAG \ + mise run wasm ::: agent ::: fake-shell RUN chmod +x /build/build/hp_agent +RUN chmod +x /build/build/sh FROM --platform=$BUILDPLATFORM mise-context AS js-build WORKDIR /build @@ -32,7 +34,23 @@ COPY --from=go-build /build/app/wasm_exec.js /build/app/wasm_exec.js RUN pnpm run build RUN mkdir -p /var/lib/headplane/agent -FROM gcr.io/distroless/nodejs22-debian12:nonroot +FROM gcr.io/distroless/nodejs22-debian12:nonroot AS final +COPY --from=js-build --chown=nonroot:nonroot /build/build/ /app/build/ +COPY --from=js-build --chown=nonroot:nonroot /build/drizzle /app/drizzle/ +COPY --from=js-build --chown=nonroot:nonroot /var/lib/headplane /var/lib/headplane +COPY --from=js-build --chown=nonroot:nonroot /build/node_modules/ /app/node_modules/ +COPY --from=go-build --chown=nonroot:nonroot /build/build/hp_agent /usr/libexec/headplane/agent + +# Fake shell to inform the user that they should use the debug image +COPY --from=go-build --chown=nonroot:nonroot /build/build/sh /bin/sh +COPY --from=go-build --chown=nonroot:nonroot /build/build/sh /bin/bash + +WORKDIR /app +CMD [ "/app/build/server/index.js" ] + +FROM node:22-alpine AS debug-shell +RUN apk add --no-cache bash curl git + COPY --from=js-build --chown=nonroot:nonroot /build/build/ /app/build/ COPY --from=js-build --chown=nonroot:nonroot /build/drizzle /app/drizzle/ COPY --from=js-build --chown=nonroot:nonroot /var/lib/headplane /var/lib/headplane @@ -40,4 +58,4 @@ COPY --from=js-build --chown=nonroot:nonroot /build/node_modules/ /app/node_modu COPY --from=go-build --chown=nonroot:nonroot /build/build/hp_agent /usr/libexec/headplane/agent WORKDIR /app -CMD [ "/app/build/server/index.js" ] +CMD [ "node", "/app/build/server/index.js" ] diff --git a/cmd/fake_sh/fake_sh.go b/cmd/fake_sh/fake_sh.go new file mode 100644 index 0000000..8057a33 --- /dev/null +++ b/cmd/fake_sh/fake_sh.go @@ -0,0 +1,19 @@ +package main + +import ( + "fmt" + "os" +) + +var imageTag string + +func main() { + if imageTag == "" { + os.Exit(1) + } + + fmt.Fprintln(os.Stderr, "Headplane containers do not contain a shell by default.") + fmt.Fprintln(os.Stderr, "If you need a non-production container with a shell and root access use:") + fmt.Fprintf(os.Stderr, "\n%s-shell\n\n", imageTag) + os.Exit(127) +} diff --git a/mise.toml b/mise.toml index e5b2ab6..559c243 100644 --- a/mise.toml +++ b/mise.toml @@ -1,3 +1,9 @@ +[tools] +# REMEMBER TO UPDATE .tool-versions TOO +go = "1.24.4" +pnpm = "10.4.0" +node = "22.16" + [tasks.copy-wasm-shim] alias = ["gojs"] description = "Copies Go's wasm_exec.js to the public directory" @@ -18,6 +24,14 @@ alias = ["agent"] description = "Builds the Go agent for HostInfo" run = "go build -o build/hp_agent ./cmd/hp_agent" +[tasks.build-fake-shell] +alias = ["fake-shell"] +description = "Builds the fake shell for Distroless docker images" +run = [ + 'test -n "$IMAGE_TAG" || (echo "IMAGE_TAG is not set" && exit 1)', + 'go build -ldflags="-s -w -X main.imageTag=$IMAGE_TAG" -o build/sh ./cmd/fake_sh' +] + [tasks.generate-caddy-certs] alias = ["mkcert"] dir = "{{cwd}}/test/caddy/certs" From ecc547601ff1fd1d972a990e65284f2b3297aea6 Mon Sep 17 00:00:00 2001 From: Aarnav Tale Date: Tue, 24 Jun 2025 11:01:16 -0400 Subject: [PATCH 38/97] fix: pass in github token for mise ratelimits --- .github/workflows/next.yaml | 1 + Dockerfile | 3 +++ 2 files changed, 4 insertions(+) diff --git a/.github/workflows/next.yaml b/.github/workflows/next.yaml index 535a446..d1bfa42 100644 --- a/.github/workflows/next.yaml +++ b/.github/workflows/next.yaml @@ -69,6 +69,7 @@ jobs: cache-to: type=gha,mode=max build-args: | IMAGE_TAG=ghcr.io/${{ github.repository }}:${{ matrix.tag }} + MISE_GITHUB_TOKEN=${{ secrets.GITHUB_TOKEN }} - name: Attestation Provenance for ghcr.io/${{ github.repository }}:${{ matrix.tag }} uses: actions/attest-build-provenance@v2 diff --git a/Dockerfile b/Dockerfile index 4e593e9..479ca40 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,5 +1,8 @@ FROM --platform=$BUILDPLATFORM jdxcode/mise:latest AS mise-context COPY mise.toml .tool-versions ./ + +ARG MISE_GITHUB_TOKEN +ENV MISE_GITHUB_TOKEN=${MISE_GITHUB_TOKEN:-} RUN mise install FROM --platform=$BUILDPLATFORM mise-context AS go-build From fc7bedc8b1d48f526de04ed5ec9563b1357502f0 Mon Sep 17 00:00:00 2001 From: Aarnav Tale Date: Tue, 24 Jun 2025 11:04:24 -0400 Subject: [PATCH 39/97] feat: append -next to pre-release versions in pnpm build --- Dockerfile | 4 +++- vite.config.ts | 3 ++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/Dockerfile b/Dockerfile index 479ca40..1c801ed 100644 --- a/Dockerfile +++ b/Dockerfile @@ -34,7 +34,9 @@ COPY . . RUN mise trust COPY --from=go-build /build/app/hp_ssh.wasm /build/app/hp_ssh.wasm COPY --from=go-build /build/app/wasm_exec.js /build/app/wasm_exec.js -RUN pnpm run build + +ARG IMAGE_TAG +RUN IMAGE_TAG=$IMAGE_TAG pnpm run build RUN mkdir -p /var/lib/headplane/agent FROM gcr.io/distroless/nodejs22-debian12:nonroot AS final diff --git a/vite.config.ts b/vite.config.ts index d10ecb5..48e4f03 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -13,6 +13,7 @@ if (prefix.endsWith('/')) { // Load the version via package.json const pkg = await readFile('package.json', 'utf-8'); +const isNext = process.env.IMAGE_TAG?.includes('next'); const { version } = JSON.parse(pkg); if (!version) { throw new Error('Unable to read version from package.json'); @@ -42,7 +43,7 @@ export default defineConfig(({ isSsrBuild }) => ({ include: ['@libsql/client'], }, define: { - __VERSION__: JSON.stringify(version), + __VERSION__: JSON.stringify(isNext ? `${version}-next` : version), __PREFIX__: JSON.stringify(prefix), }, })); From eacde3deb852437f5ce7bfeb848b6ba3159f7c3e Mon Sep 17 00:00:00 2001 From: Aarnav Tale Date: Tue, 24 Jun 2025 11:15:28 -0400 Subject: [PATCH 40/97] chore: attestation is sha256 based, we dont need tags --- .github/workflows/next.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/next.yaml b/.github/workflows/next.yaml index d1bfa42..c338328 100644 --- a/.github/workflows/next.yaml +++ b/.github/workflows/next.yaml @@ -75,6 +75,6 @@ jobs: uses: actions/attest-build-provenance@v2 id: attest with: - subject-name: ghcr.io/${{ github.repository }}:${{ matrix.tag }} + subject-name: ghcr.io/${{ github.repository }} subject-digest: ${{ steps.push.outputs.digest }} push-to-registry: true From d809eea56478132c18ad7e1cfef86726015283bf Mon Sep 17 00:00:00 2001 From: Aarnav Tale Date: Tue, 24 Jun 2025 11:28:55 -0400 Subject: [PATCH 41/97] feat: finalize split debug and distroless container (closes #255) --- .github/workflows/next.yaml | 1 + CHANGELOG.md | 1 + package.json | 2 +- 3 files changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/next.yaml b/.github/workflows/next.yaml index c338328..f85ad7a 100644 --- a/.github/workflows/next.yaml +++ b/.github/workflows/next.yaml @@ -69,6 +69,7 @@ jobs: cache-to: type=gha,mode=max build-args: | IMAGE_TAG=ghcr.io/${{ github.repository }}:${{ matrix.tag }} + secrets: | MISE_GITHUB_TOKEN=${{ secrets.GITHUB_TOKEN }} - name: Attestation Provenance for ghcr.io/${{ github.repository }}:${{ matrix.tag }} diff --git a/CHANGELOG.md b/CHANGELOG.md index 87e995d..885e6a0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ - It currently stores SSH connection details and will migrate older data. - The docker container now runs in a non-root, distroless image (closes [#255](https://github.com/tale/headplane/issues/255)). - You may need to run `chown -R 65532:65532 ` on your data directory to ensure the container can write to it. + - A debug version of the container that runs as root and has a shell is available as `ghcr.io/tale/headplane:-shell`. - Removing a Split DNS record will no longer make the split domain unresolvable by clients (closes [#231](https://github.com/tale/headplane/issues/231)). - Reintroduce the toggle for overriding local DNS settings in the Headscale config (closes [#236](https://github.com/tale/headplane/issues/236)). - Prefer cross-compiling in the Dockerfile to speed up builds while still supporting multiple architectures. diff --git a/package.json b/package.json index 48cf2ad..235775a 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "headplane", "private": true, "sideEffects": false, - "version": "0.6.0", + "version": "0.6.1", "type": "module", "scripts": { "preinstall": "npx only-allow pnpm", From 6d99aca0585e0669c48334be035fed3ae22d6650 Mon Sep 17 00:00:00 2001 From: Aarnav Tale Date: Tue, 24 Jun 2025 12:06:23 -0400 Subject: [PATCH 42/97] feat: add matrix build for release --- .github/workflows/next.yaml | 2 +- .github/workflows/release.yaml | 18 ++++++++++++++++-- Dockerfile | 5 +---- 3 files changed, 18 insertions(+), 7 deletions(-) diff --git a/.github/workflows/next.yaml b/.github/workflows/next.yaml index f85ad7a..895c34f 100644 --- a/.github/workflows/next.yaml +++ b/.github/workflows/next.yaml @@ -70,7 +70,7 @@ jobs: build-args: | IMAGE_TAG=ghcr.io/${{ github.repository }}:${{ matrix.tag }} secrets: | - MISE_GITHUB_TOKEN=${{ secrets.GITHUB_TOKEN }} + gh_token=${{ secrets.GITHUB_TOKEN }} - name: Attestation Provenance for ghcr.io/${{ github.repository }}:${{ matrix.tag }} uses: actions/attest-build-provenance@v2 diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index bb46be7..f4612c8 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -19,6 +19,14 @@ jobs: docker: name: Docker Release runs-on: ubuntu-latest + strategy: + matrix: + include: + - target: final + tag_suffix: '' + - target: debug-shell + tag_suffix: '-shell' + steps: - name: Check out the repo uses: actions/checkout@v4 @@ -30,6 +38,9 @@ jobs: images: ghcr.io/${{ github.repository }} tags: | type=semver,pattern={{version}} + flavor: | + latest=auto + suffix=${{ matrix.tag_suffix }},onlatest=true - name: Set up QEMU uses: docker/setup-qemu-action@v3 @@ -44,19 +55,22 @@ jobs: username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} - - name: Build and push Docker image + - name: Build and push ${{ fromJSON(steps.meta.outputs.json).tags[0] }} uses: docker/build-push-action@v6 id: push with: context: . file: ./Dockerfile + target: ${{ matrix.target }} push: true tags: ${{ steps.meta.outputs.tags }} labels: ${{ steps.meta.outputs.labels }} platforms: linux/amd64, linux/arm64 cache-from: type=gha cache-to: type=gha,mode=max - - name: Attestation Provenance + build-args: | + IMAGE_TAG=ghcr.io/${{ github.repository }}:${{ fromJSON(steps.meta.outputs.json).labels['org.opencontainers.image.version'] }} + - name: Attestation Provenance for ${{ fromJSON(steps.meta.outputs.json).tags[0] }} uses: actions/attest-build-provenance@v2 id: attest with: diff --git a/Dockerfile b/Dockerfile index 1c801ed..c0dea12 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,9 +1,6 @@ FROM --platform=$BUILDPLATFORM jdxcode/mise:latest AS mise-context COPY mise.toml .tool-versions ./ - -ARG MISE_GITHUB_TOKEN -ENV MISE_GITHUB_TOKEN=${MISE_GITHUB_TOKEN:-} -RUN mise install +RUN --mount=type=secret,id=gh_token,env=MISE_GITHUB_TOKEN mise install FROM --platform=$BUILDPLATFORM mise-context AS go-build WORKDIR /build/ From ce4617d0e12b90ca57aae4ea45a8026b1237bf3a Mon Sep 17 00:00:00 2001 From: Aarnav Tale Date: Tue, 24 Jun 2025 12:23:05 -0400 Subject: [PATCH 43/97] chore: walk back on nonroot, its too much of a breaking change --- CHANGELOG.md | 3 +-- Dockerfile | 26 +++++++++++++------------- 2 files changed, 14 insertions(+), 15 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 885e6a0..38045a5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,8 +5,7 @@ - Begin using a new SQLite database file in `/var/lib/headplane/hp_persist.db`. - The database is created automatically if it does not exist. - It currently stores SSH connection details and will migrate older data. -- The docker container now runs in a non-root, distroless image (closes [#255](https://github.com/tale/headplane/issues/255)). - - You may need to run `chown -R 65532:65532 ` on your data directory to ensure the container can write to it. +- The docker container now runs in a distroless image (closes [#255](https://github.com/tale/headplane/issues/255)). - A debug version of the container that runs as root and has a shell is available as `ghcr.io/tale/headplane:-shell`. - Removing a Split DNS record will no longer make the split domain unresolvable by clients (closes [#231](https://github.com/tale/headplane/issues/231)). - Reintroduce the toggle for overriding local DNS settings in the Headscale config (closes [#236](https://github.com/tale/headplane/issues/236)). diff --git a/Dockerfile b/Dockerfile index c0dea12..8bcfa66 100644 --- a/Dockerfile +++ b/Dockerfile @@ -36,16 +36,16 @@ ARG IMAGE_TAG RUN IMAGE_TAG=$IMAGE_TAG pnpm run build RUN mkdir -p /var/lib/headplane/agent -FROM gcr.io/distroless/nodejs22-debian12:nonroot AS final -COPY --from=js-build --chown=nonroot:nonroot /build/build/ /app/build/ -COPY --from=js-build --chown=nonroot:nonroot /build/drizzle /app/drizzle/ -COPY --from=js-build --chown=nonroot:nonroot /var/lib/headplane /var/lib/headplane -COPY --from=js-build --chown=nonroot:nonroot /build/node_modules/ /app/node_modules/ -COPY --from=go-build --chown=nonroot:nonroot /build/build/hp_agent /usr/libexec/headplane/agent +FROM gcr.io/distroless/nodejs22-debian12:latest AS final +COPY --from=js-build /build/build/ /app/build/ +COPY --from=js-build /build/drizzle /app/drizzle/ +COPY --from=js-build /var/lib/headplane /var/lib/headplane +COPY --from=js-build /build/node_modules/ /app/node_modules/ +COPY --from=go-build /build/build/hp_agent /usr/libexec/headplane/agent # Fake shell to inform the user that they should use the debug image -COPY --from=go-build --chown=nonroot:nonroot /build/build/sh /bin/sh -COPY --from=go-build --chown=nonroot:nonroot /build/build/sh /bin/bash +COPY --from=go-build /build/build/sh /bin/sh +COPY --from=go-build /build/build/sh /bin/bash WORKDIR /app CMD [ "/app/build/server/index.js" ] @@ -53,11 +53,11 @@ CMD [ "/app/build/server/index.js" ] FROM node:22-alpine AS debug-shell RUN apk add --no-cache bash curl git -COPY --from=js-build --chown=nonroot:nonroot /build/build/ /app/build/ -COPY --from=js-build --chown=nonroot:nonroot /build/drizzle /app/drizzle/ -COPY --from=js-build --chown=nonroot:nonroot /var/lib/headplane /var/lib/headplane -COPY --from=js-build --chown=nonroot:nonroot /build/node_modules/ /app/node_modules/ -COPY --from=go-build --chown=nonroot:nonroot /build/build/hp_agent /usr/libexec/headplane/agent +COPY --from=js-build /build/build/ /app/build/ +COPY --from=js-build /build/drizzle /app/drizzle/ +COPY --from=js-build /var/lib/headplane /var/lib/headplane +COPY --from=js-build /build/node_modules/ /app/node_modules/ +COPY --from=go-build /build/build/hp_agent /usr/libexec/headplane/agent WORKDIR /app CMD [ "node", "/app/build/server/index.js" ] From 7691f74d43d87d6d13b77ede4dea203386042c73 Mon Sep 17 00:00:00 2001 From: Aarnav Tale Date: Thu, 10 Jul 2025 11:30:23 -0400 Subject: [PATCH 44/97] fix: basename should always be prefix??? --- app/routes/ssh/console.tsx | 1 - vite.config.ts | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/app/routes/ssh/console.tsx b/app/routes/ssh/console.tsx index 7e47379..ff6ba74 100644 --- a/app/routes/ssh/console.tsx +++ b/app/routes/ssh/console.tsx @@ -139,7 +139,6 @@ export async function loader({ auth_key: preAuthKey.key, } satisfies EphemeralNodeInsert) .returning(); - console.log('Created ephemeral node:', ephemeralNode); return { ipnDetails: { diff --git a/vite.config.ts b/vite.config.ts index 48e4f03..aa21024 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -24,7 +24,7 @@ const config = await readFile('config.example.yaml', 'utf-8'); const { server } = parse(config); export default defineConfig(({ isSsrBuild }) => ({ - base: isSsrBuild ? `${prefix}/` : undefined, + base: `${prefix}/`, plugins: [ reactRouterHonoServer(), reactRouter(), From 5cfd9e411b8b0cff226bd33980c95dc98afcb3a9 Mon Sep 17 00:00:00 2001 From: Erik Parawell Date: Fri, 11 Jul 2025 18:49:43 -0700 Subject: [PATCH 45/97] Implement path loading Got the build working Maybe actually fix builds Copy drizzle as well --- .gitignore | 2 + app/server/config/loader.ts | 110 +++++++++-- app/server/config/schema.ts | 120 ++++++++++-- biome.json | 3 + flake.nix | 6 +- nix/package.nix | 25 ++- nix/wasm.nix | 35 ++++ package.json | 8 +- pnpm-lock.yaml | 318 ++++++++++++++++++++++++++++++-- tests/config.test.js | 353 ++++++++++++++++++++++++++++++++++++ tests/setupOverlayFs.js | 86 +++++++++ vitest.config.ts | 16 ++ 12 files changed, 1021 insertions(+), 61 deletions(-) create mode 100644 nix/wasm.nix create mode 100644 tests/config.test.js create mode 100644 tests/setupOverlayFs.js create mode 100644 vitest.config.ts diff --git a/.gitignore b/.gitignore index 0e2cf27..6368630 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,5 @@ node_modules .env app/hp_ssh.wasm app/wasm_exec.js + +/.direnv diff --git a/app/server/config/loader.ts b/app/server/config/loader.ts index 699479a..3dee4f3 100644 --- a/app/server/config/loader.ts +++ b/app/server/config/loader.ts @@ -1,5 +1,5 @@ import { constants, access, readFile } from 'node:fs/promises'; -import { env, exit } from 'node:process'; +import { env } from 'node:process'; import { type } from 'arktype'; import { configDotenv } from 'dotenv'; import { parseDocument } from 'yaml'; @@ -11,6 +11,28 @@ import { partialHeadplaneConfig, } from './schema'; +// Custom error for config issues +export class ConfigError extends Error { + constructor(message: string) { + super(message); + this.name = 'ConfigError'; + } +} + +/** + * Interpolate environment variables in a string + * Replaces ${VAR_NAME} patterns with the actual environment variable values + */ +function interpolateEnvVars(str: string): string { + return str.replace(/\$\{([^}]+)\}/g, (match, varName) => { + const value = env[varName]; + if (value === undefined) { + throw new ConfigError(`Environment variable "${varName}" not found`); + } + return value; + }); +} + // loadConfig is a has a lifetime of the entire application and is // used to load the configuration for Headplane. It is called once. // @@ -18,37 +40,83 @@ import { // But this may not be necessary as a use-case anyways export async function loadConfig({ loadEnv, path }: EnvOverrides) { log.debug('config', 'Loading configuration file: %s', path); - const valid = await validateConfigPath(path); - if (!valid) { - exit(1); - } + await validateConfigPath(path); const data = await loadConfigFile(path); if (!data) { - exit(1); + throw new ConfigError('Failed to load configuration file'); } let config = validateConfig({ ...data, debug: log.debugEnabled }); - if (!config) { - exit(1); - } if (!loadEnv) { log.debug('config', 'Environment variable overrides are disabled'); log.debug('config', 'This also disables the loading of a .env file'); + config = await loadSecretsFromFiles(config); + log.debug('config', 'Loaded file-based secrets'); return config; } log.info('config', 'Loading a .env file (if available)'); configDotenv({ override: true }); - config = coalesceEnv(config); - if (!config) { - exit(1); + const merged = coalesceEnv(config); + if (merged) config = merged; + if (config.headscale && typeof config.headscale.config_path === 'string') { + config.headscale.config_path = interpolateEnvVars( + config.headscale.config_path, + ); } + config = await loadSecretsFromFiles(config); + log.debug('config', 'Loaded file-based secrets'); + return config; } +/** + * Recursively walks the config object; for any key in the whitelist of secret path keys, + * reads that file and assigns its contents to the corresponding key + * without the suffix, then removes the "_path" property. + */ +const SECRET_PATH_KEYS = new Set([ + 'pre_authkey_path', + 'client_secret_path', + 'headscale_api_key_path', + 'cookie_secret_path', +]); +async function loadSecretsFromFiles(obj: T): Promise { + // Work with a Record so we can mutate/delete properties + const record = obj as Record; + + for (const key of Object.keys(record)) { + const val = record[key]; + + if (val && typeof val === 'object') { + // recurse into nested objects + record[key] = await loadSecretsFromFiles(val); + continue; + } + + if (SECRET_PATH_KEYS.has(key) && typeof val === 'string') { + try { + const path = interpolateEnvVars(val); + const content = await readFile(path, 'utf8'); + const secretKey = key.slice(0, -5); // drop '_path' + record[secretKey] = content.trim(); + delete record[key]; + log.debug('config', 'Loaded secret from %s → %s', val, secretKey); + } catch (err) { + if (err instanceof ConfigError) throw err; + log.error('config', 'Failed to read secret file %s: %s', val, err); + throw new ConfigError(`Failed to read secret file ${val}: ${err}`); + } + } + } + + // Cast back to the original T so callers keep their precise type + return record as T; +} + export async function hp_loadConfig() { // // OIDC Related Checks // if (config.oidc) { @@ -76,7 +144,9 @@ async function validateConfigPath(path: string) { } catch (error) { log.error('config', 'Unable to read a configuration file at %s', path); log.error('config', '%s', error); - return false; + throw new ConfigError( + `Unable to read configuration file at ${path}: ${error}`, + ); } } @@ -91,7 +161,7 @@ async function loadConfigFile(path: string): Promise { log.error('config', ` - ${error.toString()}`); } - return false; + throw new ConfigError(`Cannot parse configuration file at ${path}`); } if (configYaml.warnings.length > 0) { @@ -109,7 +179,7 @@ async function loadConfigFile(path: string): Promise { } catch (e) { log.error('config', 'Error reading configuration file at %s', path); log.error('config', '%s', e); - return false; + throw new ConfigError(`Error reading configuration file at ${path}: ${e}`); } } @@ -117,14 +187,14 @@ export function validateConfig(config: unknown) { log.debug('config', 'Validating Headplane configuration'); const result = headplaneConfig(config); if (result instanceof type.errors) { - log.error('config', 'Error validating Headplane configuration:'); + const errorMessages = []; for (const [number, error] of result.entries()) { - log.error('config', ` - (${number}): ${error.toString()}`); + const errorMsg = error.toString(); + log.error('config', ` - (${number}): ${errorMsg}`); + errorMessages.push(errorMsg); } - - return; + throw new ConfigError(errorMessages.join('\n')); } - return result; } diff --git a/app/server/config/schema.ts b/app/server/config/schema.ts index deb6b50..c1a9be6 100644 --- a/app/server/config/schema.ts +++ b/app/server/config/schema.ts @@ -20,8 +20,35 @@ const serverConfig = type({ host: 'string.ip', port: type('string | number.integer').pipe((v) => Number(v)), data_path: 'string = "/var/lib/headplane/"', - cookie_secret: '32 <= string <= 32', + cookie_secret: '(32 <= string <= 32)?', + cookie_secret_path: 'string?', cookie_secure: stringToBool, +}) + .narrow((obj: Record, ctx: any) => { + const hasVal = obj.cookie_secret != null && `${obj.cookie_secret}` !== ''; + const hasPath = + obj.cookie_secret_path != null && obj.cookie_secret_path !== ''; + if (hasVal && hasPath) + return ctx.reject( + `Only one of "cookie_secret" or "cookie_secret_path" may be set.`, + ); + if (!hasVal && !hasPath) + return ctx.reject( + `Either "cookie_secret" or "cookie_secret_path" must be provided for cookie_secret.`, + ); + return true; + }) + .onDeepUndeclaredKey('reject'); + +const partialServerConfig = type({ + host: 'string.ip?', + port: type('string | number.integer') + .pipe((v) => Number(v)) + .optional(), + data_path: 'string = "/var/lib/headplane/"', + cookie_secret: '32 <= string <= 32?', + cookie_secret_path: 'string?', + cookie_secure: stringToBool.optional(), }); const oidcConfig = type({ @@ -34,9 +61,41 @@ const oidcConfig = type({ redirect_uri: 'string.url?', user_storage_file: 'string = "/var/lib/headplane/users.json"', disable_api_key_login: stringToBool, - headscale_api_key: 'string', + headscale_api_key: 'string?', + headscale_api_key_path: 'string?', strict_validation: stringToBool.default(true), -}).onDeepUndeclaredKey('reject'); +}) + .narrow((obj: Record, ctx: any) => { + const hasVal = + obj.headscale_api_key != null && `${obj.headscale_api_key}` !== ''; + const hasPath = + obj.headscale_api_key_path != null && obj.headscale_api_key_path !== ''; + if (hasVal && hasPath) + return ctx.reject( + `Only one of "headscale_api_key" or "headscale_api_key_path" may be set.`, + ); + if (!hasVal && !hasPath) + return ctx.reject( + `Either "headscale_api_key" or "headscale_api_key_path" must be provided.`, + ); + return true; + }) + .onDeepUndeclaredKey('reject'); + +const partialOidcConfig = type({ + issuer: 'string.url?', + client_id: 'string?', + client_secret: 'string?', + client_secret_path: 'string?', + token_endpoint_auth_method: + '"client_secret_basic" | "client_secret_post" | "client_secret_jwt"?', + redirect_uri: 'string.url?', + user_storage_file: 'string = "/var/lib/headplane/users.json"', + disable_api_key_login: stringToBool.optional(), + headscale_api_key: 'string?', + headscale_api_key_path: 'string?', + strict_validation: stringToBool.default(true), +}); const headscaleConfig = type({ url: type('string.url').pipe((v) => (v.endsWith('/') ? v.slice(0, -1) : v)), @@ -47,26 +106,53 @@ const headscaleConfig = type({ dns_records_path: 'string?', }).onDeepUndeclaredKey('reject'); +const partialHeadscaleConfig = type({ + url: type('string.url') + .pipe((v) => (v.endsWith('/') ? v.slice(0, -1) : v)) + .optional(), + tls_cert_path: 'string?', + public_url: 'string.url?', + config_path: 'string?', + config_strict: stringToBool.optional(), + dns_records_path: 'string?', +}); + const agentConfig = type({ enabled: stringToBool.default(false), host_name: 'string = "headplane-agent"', - pre_authkey: 'string = ""', + pre_authkey: 'string?', + pre_authkey_path: 'string?', + cache_ttl: 'number.integer = 180000', + cache_path: 'string = "/var/lib/headplane/agent_cache.json"', + executable_path: 'string = "/usr/libexec/headplane/agent"', + work_dir: 'string = "/var/lib/headplane/agent"', +}) + .narrow((obj: Record, ctx: any) => { + const hasVal = obj.pre_authkey != null && `${obj.pre_authkey}` !== ''; + const hasPath = obj.pre_authkey_path != null && obj.pre_authkey_path !== ''; + if (hasVal && hasPath) + return ctx.reject( + `Only one of "pre_authkey" or "pre_authkey_path" may be set.`, + ); + if (!hasVal && !hasPath) + return ctx.reject( + `Either "pre_authkey" or "pre_authkey_path" must be provided.`, + ); + return true; + }) + .onDeepUndeclaredKey('reject'); + +const partialAgentConfig = type({ + enabled: stringToBool.default(false), + host_name: 'string = "headplane-agent"', + pre_authkey: 'string?', + pre_authkey_path: 'string?', cache_ttl: 'number.integer = 180000', cache_path: 'string = "/var/lib/headplane/agent_cache.json"', executable_path: 'string = "/usr/libexec/headplane/agent"', work_dir: 'string = "/var/lib/headplane/agent"', }); -const partialAgentConfig = type({ - enabled: stringToBool, - host_name: 'string | undefined', - pre_authkey: 'string | undefined', - cache_ttl: 'number.integer | undefined', - cache_path: 'string | undefined', - executable_path: 'string | undefined', - work_dir: 'string | undefined', -}).partial(); - const dockerConfig = type({ enabled: stringToBool, container_name: 'string = ""', @@ -115,10 +201,10 @@ export const headplaneConfig = type({ export const partialHeadplaneConfig = type({ debug: stringToBool, - server: serverConfig.partial(), - 'oidc?': oidcConfig.partial(), + server: partialServerConfig, + 'oidc?': partialOidcConfig, 'integration?': partialIntegrationConfig, - headscale: headscaleConfig.partial(), + headscale: partialHeadscaleConfig, }).partial(); export type HeadplaneConfig = typeof headplaneConfig.infer; diff --git a/biome.json b/biome.json index 302a432..dfae710 100644 --- a/biome.json +++ b/biome.json @@ -28,6 +28,9 @@ }, "correctness": { "useExhaustiveDependencies": "off" + }, + "suspicious": { + "noExplicitAny": "warn" } } }, diff --git a/flake.nix b/flake.nix index 9229532..afe0e4f 100644 --- a/flake.nix +++ b/flake.nix @@ -29,7 +29,8 @@ rec { in rec { formatter = pkgs.alejandra; packages = { - headplane = pkgs.callPackage ./nix/package.nix {}; + hp_ssh_wasm = pkgs.callPackage ./nix/wasm.nix {}; + headplane = pkgs.callPackage ./nix/package.nix { hp_ssh_wasm = packages.hp_ssh_wasm; }; headplane-agent = pkgs.callPackage ./nix/agent.nix {}; }; checks.default = pkgs.symlinkJoin { @@ -61,7 +62,8 @@ rec { }) // { overlays.default = final: prev: { - headplane = final.callPackage ./nix/package.nix {}; + hp_ssh_wasm = final.callPackage ./nix/wasm.nix {}; + headplane = final.callPackage ./nix/package.nix { hp_ssh_wasm = final.hp_ssh_wasm; }; headplane-agent = final.callPackage ./nix/agent.nix {}; }; nixosModules.headplane = import ./nix/module.nix; diff --git a/nix/package.nix b/nix/package.nix index b43a7cc..2208f51 100644 --- a/nix/package.nix +++ b/nix/package.nix @@ -5,12 +5,19 @@ nodejs_22, pnpm_10, stdenv, + hp_ssh_wasm, ... }: -stdenv.mkDerivation (finalAttrs: { - pname = "headplane"; - version = (builtins.fromJSON (builtins.readFile ../package.json)).version; +let + pkg = builtins.fromJSON (builtins.readFile ../package.json); + pname = pkg.name; + version = pkg.version; src = ../.; +in +stdenv.mkDerivation (finalAttrs: { + pname = pname; + version = version; + src = src; nativeBuildInputs = [ makeWrapper @@ -22,12 +29,14 @@ stdenv.mkDerivation (finalAttrs: { dontCheckForBrokenSymlinks = true; pnpmDeps = pnpm_10.fetchDeps { - inherit (finalAttrs) pname version src; - hash = "sha256-hu3028V/EWimYB1TGn7g06kJRIpZA6cuOIjPMEc8ddw="; + inherit pname version src; + hash = "sha256-GNSpFqPobX6MDPUXxz2XwdZ2Wt7boN8aok52pGgpGoM="; }; buildPhase = '' runHook preBuild + cp ${hp_ssh_wasm}/hp_ssh.wasm app/hp_ssh.wasm + cp ${hp_ssh_wasm}/wasm_exec.js app/wasm_exec.js pnpm build runHook postBuild ''; @@ -36,10 +45,12 @@ stdenv.mkDerivation (finalAttrs: { runHook preInstall mkdir -p $out/{bin,share/headplane} cp -r build $out/share/headplane/ + cp -r node_modules $out/share/headplane/ + cp -r drizzle $out/share/headplane/ sed -i "s;$PWD;../..;" $out/share/headplane/build/server/index.js makeWrapper ${lib.getExe nodejs_22} $out/bin/headplane \ - --chdir $out/share/headplane \ - --add-flags $out/share/headplane/build/server/index.js + --chdir $out/share/headplane \ + --add-flags $out/share/headplane/build/server/index.js runHook postInstall ''; }) diff --git a/nix/wasm.nix b/nix/wasm.nix new file mode 100644 index 0000000..e53b37e --- /dev/null +++ b/nix/wasm.nix @@ -0,0 +1,35 @@ +{ buildGoModule, go, lib, ... }: + +let + pkg = builtins.fromJSON (builtins.readFile ../package.json); + pname = "hp_ssh-wasm"; + version = pkg.version; + wasmExecJs = + if builtins.pathExists "${go}/share/go/lib/wasm/wasm_exec.js" then + "${go}/share/go/lib/wasm/wasm_exec.js" + else if builtins.pathExists "${go}/lib/wasm/wasm_exec.js" then + "${go}/lib/wasm/wasm_exec.js" + else + "${go}/share/go/misc/wasm/wasm_exec.js"; +in +buildGoModule rec { + inherit pname version; + src = ../.; + subPackages = [ "cmd/hp_ssh" ]; + vendorHash = "sha256-3hZzDORAH+D4FW6SkOv3Enddd+q36ZALryvCPD9E5Ac="; + env.CGO_ENABLED = 0; + + nativeBuildInputs = [ go ]; + + buildPhase = '' + export GOOS=js + export GOARCH=wasm + go build -o hp_ssh.wasm ./cmd/hp_ssh + ''; + + installPhase = '' + mkdir -p $out + cp hp_ssh.wasm $out/ + cp ${wasmExecJs} $out/wasm_exec.js + ''; +} diff --git a/package.json b/package.json index 235775a..ffc8546 100644 --- a/package.json +++ b/package.json @@ -9,7 +9,8 @@ "build": "react-router build", "dev": "HEADPLANE_LOAD_ENV_OVERRIDES=true HEADPLANE_CONFIG_PATH=./config.example.yaml react-router dev", "start": "node build/server/index.js", - "typecheck": "tsc" + "typecheck": "tsc", + "test": "vitest run" }, "dependencies": { "@dnd-kit/core": "^6.3.1", @@ -63,6 +64,7 @@ "@biomejs/biome": "^1.9.4", "@react-router/dev": "^7.6.1", "@tailwindcss/vite": "^4.1.8", + "@types/node": "^22.15.17", "@types/websocket": "^1.0.10", "drizzle-kit": "^0.31.1", "lefthook": "^1.11.13", @@ -74,7 +76,9 @@ "tailwindcss-react-aria-components": "^2.0.0", "typescript": "^5.8.3", "vite": "^6.3.5", - "vite-tsconfig-paths": "^5.1.4" + "vite-tsconfig-paths": "^5.1.4", + "js-yaml": "^4.1.0", + "vitest": "^3.1.3" }, "packageManager": "pnpm@10.4.0", "engines": { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 002e709..21dd17d 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -161,12 +161,18 @@ importers: '@tailwindcss/vite': specifier: ^4.1.8 version: 4.1.8(vite@6.3.5(@types/node@22.15.24)(jiti@2.4.2)(lightningcss@1.30.1)(terser@5.39.0)(tsx@4.19.4)(yaml@2.8.0)) + '@types/node': + specifier: ^22.15.17 + version: 22.15.24 '@types/websocket': specifier: ^1.0.10 version: 1.0.10 drizzle-kit: specifier: ^0.31.1 version: 0.31.1 + js-yaml: + specifier: ^4.1.0 + version: 4.1.0 lefthook: specifier: ^1.11.13 version: 1.11.13 @@ -197,6 +203,9 @@ importers: vite-tsconfig-paths: specifier: ^5.1.4 version: 5.1.4(typescript@5.8.3)(vite@6.3.5(@types/node@22.15.24)(jiti@2.4.2)(lightningcss@1.30.1)(terser@5.39.0)(tsx@4.19.4)(yaml@2.8.0)) + vitest: + specifier: ^3.1.3 + version: 3.2.4(@types/node@22.15.24)(jiti@2.4.2)(lightningcss@1.30.1)(terser@5.39.0)(tsx@4.19.4)(yaml@2.8.0) packages: @@ -1754,6 +1763,12 @@ packages: '@types/better-sqlite3@7.6.13': resolution: {integrity: sha512-NMv9ASNARoKksWtsq/SHakpYAYnhBrQgGD8zkLYk/jaK8jUGn08CfEdTRgYhMypUQAfzSP8W6gNLe0q19/t4VA==} + '@types/chai@5.2.2': + resolution: {integrity: sha512-8kB30R7Hwqf40JPiKhVzodJs2Qc1ZJ5zuT3uzw5Hq/dhNCl3G3l83jfpdI1e20BP348+fV7VIL/+FxaXkqBmWg==} + + '@types/deep-eql@4.0.2': + resolution: {integrity: sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==} + '@types/estree@1.0.6': resolution: {integrity: sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==} @@ -1769,9 +1784,6 @@ packages: '@types/node@20.17.52': resolution: {integrity: sha512-2aj++KfxubvW/Lc0YyXE3OEW7Es8TWn1MsRzYgcOGyTNQxi0L8rxQUCZ7ZbyOBWZQD5I63PV9egZWMsapVaklg==} - '@types/node@22.10.7': - resolution: {integrity: sha512-V09KvXxFiutGp6B7XkpaDXlNadZxrzajcY50EuoLIpQ6WWYCSvf19lVIazzfIzQvhUN2HjX12spLojTnhuKlGg==} - '@types/node@22.15.24': resolution: {integrity: sha512-w9CZGm9RDjzTh/D+hFwlBJ3ziUaVw7oufKA3vOFSOZlzmW9AkZnfjPb+DLnrV6qtgL/LNmP0/2zBNCFHL3F0ng==} @@ -1832,6 +1844,35 @@ packages: react: '>=16.8.0' react-dom: '>=16.8.0' + '@vitest/expect@3.2.4': + resolution: {integrity: sha512-Io0yyORnB6sikFlt8QW5K7slY4OjqNX9jmJQ02QDda8lyM6B5oNgVWoSoKPac8/kgnCUzuHQKrSLtu/uOqqrig==} + + '@vitest/mocker@3.2.4': + resolution: {integrity: sha512-46ryTE9RZO/rfDd7pEqFl7etuyzekzEhUbTW3BvmeO/BcCMEgq59BKhek3dXDWgAj4oMK6OZi+vRr1wPW6qjEQ==} + peerDependencies: + msw: ^2.4.9 + vite: ^5.0.0 || ^6.0.0 || ^7.0.0-0 + peerDependenciesMeta: + msw: + optional: true + vite: + optional: true + + '@vitest/pretty-format@3.2.4': + resolution: {integrity: sha512-IVNZik8IVRJRTr9fxlitMKeJeXFFFN0JaB9PHPGQ8NKQbGpfjlTx9zO4RefN8gp7eqjNy8nyK3NZmBzOPeIxtA==} + + '@vitest/runner@3.2.4': + resolution: {integrity: sha512-oukfKT9Mk41LreEW09vt45f8wx7DordoWUZMYdY/cyAk7w5TWkTRCNZYF7sX7n2wB7jyGAl74OxgwhPgKaqDMQ==} + + '@vitest/snapshot@3.2.4': + resolution: {integrity: sha512-dEYtS7qQP2CjU27QBC5oUOxLE/v5eLkGqPE0ZKEIDGMs4vKWe7IjgLOeauHsR0D5YuuycGRO5oSRXnwnmA78fQ==} + + '@vitest/spy@3.2.4': + resolution: {integrity: sha512-vAfasCOe6AIK70iP5UD11Ac4siNUNJ9i/9PZ3NKx07sG6sUxeag1LWdNrMWeKKYBLlzuK+Gn65Yd5nyL6ds+nw==} + + '@vitest/utils@3.2.4': + resolution: {integrity: sha512-fB2V0JFrQSMsCo9HiSq3Ezpdv4iYaXRG1Sx8edX3MwxfyNn83mKiGzOcH+Fkxt4MHxr3y42fQi1oeAInqgX2QA==} + '@xterm/addon-clipboard@0.1.0': resolution: {integrity: sha512-zdoM7p53T5sv/HbRTyp4hY0kKmEQ3MZvAvEtiXqNIHc/JdpqwByCtsTaQF5DX2n4hYdXRPO4P/eOS0QEhX1nPw==} peerDependencies: @@ -1894,6 +1935,10 @@ packages: arktype@2.1.20: resolution: {integrity: sha512-IZCEEXaJ8g+Ijd59WtSYwtjnqXiwM8sWQ5EjGamcto7+HVN9eK0C4p0zDlCuAwWhpqr6fIBkxPuYDl4/Mcj/+Q==} + assertion-error@2.0.1: + resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==} + engines: {node: '>=12'} + asynckit@0.4.0: resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} @@ -1982,6 +2027,14 @@ packages: caniuse-lite@1.0.30001718: resolution: {integrity: sha512-AflseV1ahcSunK53NfEs9gFWgOEmzr0f+kaMFA4xiLZlr9Hzt7HxcSpIFcnNCUkz6R6dWKa54rUz3HUmI3nVcw==} + chai@5.2.1: + resolution: {integrity: sha512-5nFxhUrX0PqtyogoYOA8IPswy5sZFTOsBFl/9bNsmDLgsxYTzSZQJDPppDnZPTQbzSEm0hqGjWPzRemQCYbD6A==} + engines: {node: '>=18'} + + check-error@2.1.1: + resolution: {integrity: sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw==} + engines: {node: '>= 16'} + chokidar@4.0.3: resolution: {integrity: sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==} engines: {node: '>= 14.16.0'} @@ -2068,6 +2121,10 @@ packages: babel-plugin-macros: optional: true + deep-eql@5.0.2: + resolution: {integrity: sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==} + engines: {node: '>=6'} + deep-extend@0.6.0: resolution: {integrity: sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==} engines: {node: '>=4.0.0'} @@ -2262,6 +2319,10 @@ packages: resolution: {integrity: sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==} engines: {node: '>=6'} + expect-type@1.2.2: + resolution: {integrity: sha512-JhFGDVJ7tmDJItKhYgJCGLOWjuK9vPxiXoUFLwLDc99NlmklilbiQJwoctZtt13+xMw91MCk/REan6MWHqDjyA==} + engines: {node: '>=12.0.0'} + fast-fifo@1.3.2: resolution: {integrity: sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==} @@ -2424,6 +2485,9 @@ packages: js-tokens@4.0.0: resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} + js-tokens@9.0.1: + resolution: {integrity: sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==} + js-yaml@4.1.0: resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==} hasBin: true @@ -2589,6 +2653,9 @@ packages: lodash@4.17.21: resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==} + loupe@3.1.4: + resolution: {integrity: sha512-wJzkKwJrheKtknCOKNEtDK4iqg/MxmZheEMtSTYvnzRdEYaZzmgH976nenp8WdJRdx5Vc1X/9MO0Oszl6ezeXg==} + lru-cache@10.2.2: resolution: {integrity: sha512-9hp3Vp2/hFQUiIwKo8XCeFVnrg8Pk3TYNPIR7tJADKi5YfcF7vEaK7avFHTlSy3kOKYaJQaalfEo6YuXdceBOQ==} engines: {node: 14 || >=16.14} @@ -2739,6 +2806,13 @@ packages: pathe@1.1.2: resolution: {integrity: sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==} + pathe@2.0.3: + resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==} + + pathval@2.0.1: + resolution: {integrity: sha512-//nshmD55c46FuFw26xV/xFAaB5HF9Xdap7HJBBnrKdAd6/GxDBaNA1870O79+9ueg61cZLSVc+OaFlfmObYVQ==} + engines: {node: '>= 14.16'} + picocolors@1.1.1: resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} @@ -2990,6 +3064,9 @@ packages: resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} engines: {node: '>=8'} + siginfo@2.0.0: + resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==} + signal-exit@4.1.0: resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} engines: {node: '>=14'} @@ -3041,6 +3118,12 @@ packages: sprintf-js@1.1.3: resolution: {integrity: sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==} + stackback@0.0.2: + resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==} + + std-env@3.9.0: + resolution: {integrity: sha512-UGvjygr6F6tpH7o2qyqR6QYpwraIjKSdtzyBdyytFOHmPZY917kwdwLG0RbOjWOnKmnm3PeHjaoLLMie7kPLQw==} + stream-buffers@3.0.3: resolution: {integrity: sha512-pqMqwQCso0PBJt2PQmDO0cFj0lyqmiwOMiMSkVtRokl7e+ZTRYgDHKnuZNbqjiJXgsg4nuqtD/zxuo9KqTp0Yw==} engines: {node: '>= 0.10.0'} @@ -3074,6 +3157,9 @@ packages: resolution: {integrity: sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==} engines: {node: '>=0.10.0'} + strip-literal@3.0.0: + resolution: {integrity: sha512-TcccoMhJOM3OebGhSBEmp3UZ2SfDMZUEBdRA/9ynfLi8yYajyWX3JiXArcJt4Umh4vISpspkQIY8ZZoCqjbviA==} + style-mod@4.1.2: resolution: {integrity: sha512-wnD1HyVqpJUI2+eKZ+eo1UwghftP6yuFheBqqe+bWCotBjC2K1YnteJILRMs3SM4V/0dLEW1SC27MWP5y+mwmw==} @@ -3122,10 +3208,28 @@ packages: text-decoder@1.2.3: resolution: {integrity: sha512-3/o9z3X0X0fTupwsYvR03pJ/DjWuqqrfwBgTQzdWDiQSm9KitAyz/9WqsT2JQW7KV2m+bC2ol/zqpW37NHxLaA==} + tinybench@2.9.0: + resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==} + + tinyexec@0.3.2: + resolution: {integrity: sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==} + tinyglobby@0.2.14: resolution: {integrity: sha512-tX5e7OM1HnYr2+a2C/4V0htOcSQcoSTH9KgJnVvNm5zm/cyEWKJ7j7YutsH9CxMdtOkkLFy2AHrMci9IM8IPZQ==} engines: {node: '>=12.0.0'} + tinypool@1.1.1: + resolution: {integrity: sha512-Zba82s87IFq9A9XmjiX5uZA/ARWDrB03OHlq+Vw1fSdt0I+4/Kutwy8BP4Y/y/aORMo61FQ0vIb5j44vSo5Pkg==} + engines: {node: ^18.0.0 || >=20.0.0} + + tinyrainbow@2.0.0: + resolution: {integrity: sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw==} + engines: {node: '>=14.0.0'} + + tinyspy@4.0.3: + resolution: {integrity: sha512-t2T/WLB2WRgZ9EpE4jgPJ9w+i66UZfDc8wHh0xrwiRNN+UwH98GIJkTeZqX9rg0i0ptwzqW+uYeIF0T4F8LR7A==} + engines: {node: '>=14.0.0'} + tr46@0.0.3: resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==} @@ -3165,9 +3269,6 @@ packages: undici-types@6.19.8: resolution: {integrity: sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==} - undici-types@6.20.0: - resolution: {integrity: sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==} - undici-types@6.21.0: resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==} @@ -3231,6 +3332,11 @@ packages: engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} hasBin: true + vite-node@3.2.4: + resolution: {integrity: sha512-EbKSKh+bh1E1IFxeO0pg1n4dvoOTt0UDiXMd/qn++r98+jPO1xtJilvXldeuQ8giIB5IkpjCgMleHMNEsGH6pg==} + engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} + hasBin: true + vite-tsconfig-paths@5.1.4: resolution: {integrity: sha512-cYj0LRuLV2c2sMqhqhGpaO3LretdtMn/BVX4cPLanIZuwwrkVl+lK84E/miEXkCHWXuq65rhNN4rXsBcOB3S4w==} peerDependencies: @@ -3279,6 +3385,34 @@ packages: yaml: optional: true + vitest@3.2.4: + resolution: {integrity: sha512-LUCP5ev3GURDysTWiP47wRRUpLKMOfPh+yKTx3kVIEiu5KOMeqzpnYNsKyOoVrULivR8tLcks4+lga33Whn90A==} + engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} + hasBin: true + peerDependencies: + '@edge-runtime/vm': '*' + '@types/debug': ^4.1.12 + '@types/node': ^18.0.0 || ^20.0.0 || >=22.0.0 + '@vitest/browser': 3.2.4 + '@vitest/ui': 3.2.4 + happy-dom: '*' + jsdom: '*' + peerDependenciesMeta: + '@edge-runtime/vm': + optional: true + '@types/debug': + optional: true + '@types/node': + optional: true + '@vitest/browser': + optional: true + '@vitest/ui': + optional: true + happy-dom: + optional: true + jsdom: + optional: true + w3c-keyname@2.2.8: resolution: {integrity: sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ==} @@ -3305,6 +3439,11 @@ packages: engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} hasBin: true + why-is-node-running@2.3.0: + resolution: {integrity: sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==} + engines: {node: '>=8'} + hasBin: true + wrap-ansi@7.0.0: resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} engines: {node: '>=10'} @@ -5287,6 +5426,12 @@ snapshots: '@types/node': 22.15.24 optional: true + '@types/chai@5.2.2': + dependencies: + '@types/deep-eql': 4.0.2 + + '@types/deep-eql@4.0.2': {} + '@types/estree@1.0.6': {} '@types/estree@1.0.7': {} @@ -5302,10 +5447,6 @@ snapshots: dependencies: undici-types: 6.19.8 - '@types/node@22.10.7': - dependencies: - undici-types: 6.20.0 - '@types/node@22.15.24': dependencies: undici-types: 6.21.0 @@ -5328,7 +5469,7 @@ snapshots: '@types/websocket@1.0.10': dependencies: - '@types/node': 22.10.7 + '@types/node': 22.15.24 '@types/ws@8.18.1': dependencies: @@ -5383,6 +5524,48 @@ snapshots: - '@codemirror/lint' - '@codemirror/search' + '@vitest/expect@3.2.4': + dependencies: + '@types/chai': 5.2.2 + '@vitest/spy': 3.2.4 + '@vitest/utils': 3.2.4 + chai: 5.2.1 + tinyrainbow: 2.0.0 + + '@vitest/mocker@3.2.4(vite@6.3.5(@types/node@22.15.24)(jiti@2.4.2)(lightningcss@1.30.1)(terser@5.39.0)(tsx@4.19.4)(yaml@2.8.0))': + dependencies: + '@vitest/spy': 3.2.4 + estree-walker: 3.0.3 + magic-string: 0.30.17 + optionalDependencies: + vite: 6.3.5(@types/node@22.15.24)(jiti@2.4.2)(lightningcss@1.30.1)(terser@5.39.0)(tsx@4.19.4)(yaml@2.8.0) + + '@vitest/pretty-format@3.2.4': + dependencies: + tinyrainbow: 2.0.0 + + '@vitest/runner@3.2.4': + dependencies: + '@vitest/utils': 3.2.4 + pathe: 2.0.3 + strip-literal: 3.0.0 + + '@vitest/snapshot@3.2.4': + dependencies: + '@vitest/pretty-format': 3.2.4 + magic-string: 0.30.17 + pathe: 2.0.3 + + '@vitest/spy@3.2.4': + dependencies: + tinyspy: 4.0.3 + + '@vitest/utils@3.2.4': + dependencies: + '@vitest/pretty-format': 3.2.4 + loupe: 3.1.4 + tinyrainbow: 2.0.0 + '@xterm/addon-clipboard@0.1.0(@xterm/xterm@5.5.0)': dependencies: '@xterm/xterm': 5.5.0 @@ -5430,6 +5613,8 @@ snapshots: '@ark/schema': 0.46.0 '@ark/util': 0.46.0 + assertion-error@2.0.1: {} + asynckit@0.4.0: {} b4a@1.6.7: {} @@ -5531,6 +5716,16 @@ snapshots: caniuse-lite@1.0.30001718: {} + chai@5.2.1: + dependencies: + assertion-error: 2.0.1 + check-error: 2.1.1 + deep-eql: 5.0.2 + loupe: 3.1.4 + pathval: 2.0.1 + + check-error@2.1.1: {} + chokidar@4.0.3: dependencies: readdirp: 4.1.2 @@ -5600,6 +5795,8 @@ snapshots: dedent@1.6.0: {} + deep-eql@5.0.2: {} + deep-extend@0.6.0: optional: true @@ -5741,6 +5938,8 @@ snapshots: expand-template@2.0.3: optional: true + expect-type@1.2.2: {} + fast-fifo@1.3.2: {} fdir@6.4.5(picomatch@4.0.2): @@ -5899,6 +6098,8 @@ snapshots: js-tokens@4.0.0: {} + js-tokens@9.0.1: {} + js-yaml@4.1.0: dependencies: argparse: 2.0.1 @@ -6034,6 +6235,8 @@ snapshots: lodash@4.17.21: {} + loupe@3.1.4: {} + lru-cache@10.2.2: {} lru-cache@5.1.1: @@ -6162,6 +6365,10 @@ snapshots: pathe@1.1.2: {} + pathe@2.0.3: {} + + pathval@2.0.1: {} + picocolors@1.1.1: {} picomatch@4.0.2: {} @@ -6464,6 +6671,8 @@ snapshots: shebang-regex@3.0.0: {} + siginfo@2.0.0: {} + signal-exit@4.1.0: {} simple-concat@1.0.1: @@ -6518,6 +6727,10 @@ snapshots: sprintf-js@1.1.3: {} + stackback@0.0.2: {} + + std-env@3.9.0: {} + stream-buffers@3.0.3: {} stream-slice@0.1.2: {} @@ -6557,6 +6770,10 @@ snapshots: strip-json-comments@2.0.1: optional: true + strip-literal@3.0.0: + dependencies: + js-tokens: 9.0.1 + style-mod@4.1.2: {} tailwind-merge@3.3.0: {} @@ -6627,11 +6844,21 @@ snapshots: dependencies: b4a: 1.6.7 + tinybench@2.9.0: {} + + tinyexec@0.3.2: {} + tinyglobby@0.2.14: dependencies: fdir: 6.4.5(picomatch@4.0.2) picomatch: 4.0.2 + tinypool@1.1.1: {} + + tinyrainbow@2.0.0: {} + + tinyspy@4.0.3: {} + tr46@0.0.3: {} tsconfck@3.1.4(typescript@5.8.3): @@ -6660,8 +6887,6 @@ snapshots: undici-types@6.19.8: {} - undici-types@6.20.0: {} - undici-types@6.21.0: {} undici@6.21.3: {} @@ -6731,6 +6956,27 @@ snapshots: - tsx - yaml + vite-node@3.2.4(@types/node@22.15.24)(jiti@2.4.2)(lightningcss@1.30.1)(terser@5.39.0)(tsx@4.19.4)(yaml@2.8.0): + dependencies: + cac: 6.7.14 + debug: 4.4.1 + es-module-lexer: 1.7.0 + pathe: 2.0.3 + vite: 6.3.5(@types/node@22.15.24)(jiti@2.4.2)(lightningcss@1.30.1)(terser@5.39.0)(tsx@4.19.4)(yaml@2.8.0) + transitivePeerDependencies: + - '@types/node' + - jiti + - less + - lightningcss + - sass + - sass-embedded + - stylus + - sugarss + - supports-color + - terser + - tsx + - yaml + vite-tsconfig-paths@5.1.4(typescript@5.8.3)(vite@6.3.5(@types/node@22.15.24)(jiti@2.4.2)(lightningcss@1.30.1)(terser@5.39.0)(tsx@4.19.4)(yaml@2.8.0)): dependencies: debug: 4.4.0 @@ -6759,6 +7005,47 @@ snapshots: tsx: 4.19.4 yaml: 2.8.0 + vitest@3.2.4(@types/node@22.15.24)(jiti@2.4.2)(lightningcss@1.30.1)(terser@5.39.0)(tsx@4.19.4)(yaml@2.8.0): + dependencies: + '@types/chai': 5.2.2 + '@vitest/expect': 3.2.4 + '@vitest/mocker': 3.2.4(vite@6.3.5(@types/node@22.15.24)(jiti@2.4.2)(lightningcss@1.30.1)(terser@5.39.0)(tsx@4.19.4)(yaml@2.8.0)) + '@vitest/pretty-format': 3.2.4 + '@vitest/runner': 3.2.4 + '@vitest/snapshot': 3.2.4 + '@vitest/spy': 3.2.4 + '@vitest/utils': 3.2.4 + chai: 5.2.1 + debug: 4.4.1 + expect-type: 1.2.2 + magic-string: 0.30.17 + pathe: 2.0.3 + picomatch: 4.0.2 + std-env: 3.9.0 + tinybench: 2.9.0 + tinyexec: 0.3.2 + tinyglobby: 0.2.14 + tinypool: 1.1.1 + tinyrainbow: 2.0.0 + vite: 6.3.5(@types/node@22.15.24)(jiti@2.4.2)(lightningcss@1.30.1)(terser@5.39.0)(tsx@4.19.4)(yaml@2.8.0) + vite-node: 3.2.4(@types/node@22.15.24)(jiti@2.4.2)(lightningcss@1.30.1)(terser@5.39.0)(tsx@4.19.4)(yaml@2.8.0) + why-is-node-running: 2.3.0 + optionalDependencies: + '@types/node': 22.15.24 + transitivePeerDependencies: + - jiti + - less + - lightningcss + - msw + - sass + - sass-embedded + - stylus + - sugarss + - supports-color + - terser + - tsx + - yaml + w3c-keyname@2.2.8: {} web-streams-polyfill@3.3.3: {} @@ -6781,6 +7068,11 @@ snapshots: dependencies: isexe: 2.0.0 + why-is-node-running@2.3.0: + dependencies: + siginfo: 2.0.0 + stackback: 0.0.2 + wrap-ansi@7.0.0: dependencies: ansi-styles: 4.3.0 diff --git a/tests/config.test.js b/tests/config.test.js new file mode 100644 index 0000000..5ee35a4 --- /dev/null +++ b/tests/config.test.js @@ -0,0 +1,353 @@ +import fs from 'node:fs'; +import os from 'node:os'; +import path from 'node:path'; +import { fileURLToPath } from 'node:url'; +import yaml from 'js-yaml'; +import { afterAll, beforeAll, beforeEach, describe, expect, it } from 'vitest'; +import { overlayFS } from './setupOverlayFs.js'; + +// Get the directory name in ESM +const __filename = fileURLToPath(import.meta.url); +const __dirname = path.dirname(__filename); + +// Helper to create a temp file with given content, return its path +function writeTempFile(baseName, content) { + const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'headplane-test-')); + const filePath = path.join(dir, baseName); + fs.writeFileSync(filePath, content); + return filePath; +} + +// Helper to create a temporary YAML config file with minimal required structure +function writeTempYamlConfig(customConfig = {}) { + const defaultConfig = { + debug: false, + server: { + host: '127.0.0.1', + port: 8080, + cookie_secret: '12345678901234567890123456789012', + cookie_secure: false, + agent: { + authkey: 'testagentauthkey', + ttl: 180000, + cache_path: '/tmp/agent_cache.json', + }, + }, + headscale: { url: 'http://localhost:8081', config_strict: false }, + }; + + function deepMerge(target, source) { + let output = { ...target }; + if (isObject(target) && isObject(source)) { + output = { ...target, ...source }; + for (const key of Object.keys(source)) { + if (isObject(source[key]) && key in target && isObject(target[key])) { + output[key] = deepMerge(target[key], source[key]); + } else { + output[key] = source[key]; + } + } + for (const sectionName of ['oidc', 'server', 'headscale']) { + if (output[sectionName] && source[sectionName]) { + const sectionSource = source[sectionName]; + const sectionDefault = target[sectionName] || {}; + const sectionOutput = output[sectionName]; + + for (const baseKey of ['cookie_secret', 'client_secret', 'api_key']) { + const valueKey = baseKey; + const pathKey = `${baseKey}_path`; + const sourceHasValue = Object.hasOwn(sectionSource, valueKey); + const sourceHasPath = Object.hasOwn(sectionSource, pathKey); + const defaultHasValue = Object.hasOwn(sectionDefault, valueKey); + const defaultHasPath = Object.hasOwn(sectionDefault, pathKey); + + if (sourceHasPath && !sourceHasValue && defaultHasValue) { + delete sectionOutput[valueKey]; + } else if (sourceHasValue && !sourceHasPath && defaultHasPath) { + delete sectionOutput[pathKey]; + } + } + } + } + } + return output; + } + + function isObject(item) { + return item && typeof item === 'object' && !Array.isArray(item); + } + + const merged = deepMerge(defaultConfig, customConfig); + const yamlContent = + typeof customConfig === 'string' ? customConfig : yaml.dump(merged); + return writeTempFile('config.yaml', yamlContent); +} + +// Store original process.env to restore after tests +const originalEnv = { ...process.env }; + +describe('Configuration Loading', () => { + let loadConfig; + let ConfigError; + + beforeAll(async () => { + const module = await import('../app/server/config/loader.js'); + loadConfig = module.loadConfig; + ConfigError = module.ConfigError; + }); + + beforeEach(() => { + // biome-ignore lint/performance/noDelete: needed for test cleanup + delete process.env.HEADPLANE_OIDC__CLIENT_SECRET_PATH; + // biome-ignore lint/performance/noDelete: needed for test cleanup + delete process.env.HEADPLANE_SERVER__COOKIE_SECRET_PATH; + // biome-ignore lint/performance/noDelete: needed for test cleanup + delete process.env.HEADPLANE_HEADSCALE__API_KEY_PATH; + // biome-ignore lint/performance/noDelete: needed for test cleanup + delete process.env.TEST_SECRET_DIR; + + // Clear overlayfs + overlayFS.clear(); + + // Create default files that the config expects + overlayFS.createFile( + '/var/lib/headplane/agent_cache.json', + JSON.stringify({}), + ); + overlayFS.createFile( + '/var/lib/headplane/users.json', + `[{"u":"acb3294f89a16b554e06b80d5266a3c8b09a883e1fa78ac459a550bf52a32564","c":65535}]`, + ); + overlayFS.createFile('/tmp/agent_cache.json', JSON.stringify({})); + + // Create test files that some tests expect to exist + overlayFS.createFile('irrelevant', 'irrelevant-content'); + overlayFS.createFile('placeholder', 'placeholder-content'); + }); + + afterAll(() => { + Object.assign(process.env, originalEnv); + overlayFS.clear(); + }); + + describe('OIDC Configuration', () => { + const minimalOidcFields = { + token_endpoint_auth_method: 'client_secret_basic', + disable_api_key_login: false, + headscale_api_key: 'dummyKey', + }; + + it('should load client_secret from file specified in client_secret_path', async () => { + const secretValue = 'yaml-file-oidc-secret'; + const secretPath = writeTempFile('oidc_secret.txt', secretValue); + const tempConfigPath = writeTempYamlConfig({ + oidc: { + issuer: 'https://example.com/oidc', + client_id: 'test', + client_secret_path: secretPath, + ...minimalOidcFields, + }, + }); + const config = await loadConfig({ loadEnv: false, path: tempConfigPath }); + expect(config.oidc.client_secret).toBe(secretValue); + }); + + it('should override YAML client_secret_path with environment variable', async () => { + const envValue = 'env-file-oidc-secret'; + const envPath = writeTempFile('env_oidc_secret.txt', envValue); + process.env.HEADPLANE_OIDC__CLIENT_SECRET_PATH = envPath; + // Instead of 'irrelevant', use a temp file that exists + const irrelevantPath = writeTempFile( + 'irrelevant.txt', + 'irrelevant-content', + ); + const tempConfigPath = writeTempYamlConfig({ + oidc: { + issuer: 'https://example.com/oidc', + client_id: 'test', + client_secret_path: irrelevantPath, + ...minimalOidcFields, + }, + }); + const config = await loadConfig({ loadEnv: true, path: tempConfigPath }); + expect(config.oidc.client_secret).toBe(envValue); + }); + + it('should handle environment variable interpolation in client_secret_path', async () => { + const value = 'interpolated-secret'; + const dir = fs.mkdtempSync( + path.join(os.tmpdir(), 'headplane-secret-dir-'), + ); + const file = path.join(dir, 'secret.txt'); + fs.writeFileSync(file, value); + process.env.TEST_SECRET_DIR = dir; + process.env.HEADPLANE_OIDC__CLIENT_SECRET_PATH = + '${TEST_SECRET_DIR}/secret.txt'; + // Instead of 'placeholder', use a temp file that exists + const placeholderPath = writeTempFile( + 'placeholder.txt', + 'placeholder-content', + ); + const tempConfigPath = writeTempYamlConfig({ + oidc: { + issuer: 'https://example.com/oidc', + client_id: 'test', + client_secret_path: placeholderPath, + ...minimalOidcFields, + }, + }); + const config = await loadConfig({ loadEnv: true, path: tempConfigPath }); + expect(config.oidc.client_secret).toBe(value); + }); + + it('should reject when client_secret_path points to non-existent file', async () => { + const tempConfigPath = writeTempYamlConfig({ + oidc: { + issuer: 'https://example.com/oidc', + client_id: 'test', + client_secret_path: '/no/such/file', + ...minimalOidcFields, + }, + }); + await expect( + loadConfig({ loadEnv: false, path: tempConfigPath }), + ).rejects.toThrow(ConfigError); + }); + + it('should reject when client_secret_path has unresolvable env var interpolation', async () => { + process.env.HEADPLANE_OIDC__CLIENT_SECRET_PATH = + '${MISSING_DIR}/secret.txt'; + const tempConfigPath = writeTempYamlConfig({ + oidc: { + issuer: 'https://example.com/oidc', + client_id: 'test', + client_secret_path: 'placeholder', + ...minimalOidcFields, + }, + }); + await expect( + loadConfig({ loadEnv: true, path: tempConfigPath }), + ).rejects.toThrow(/Environment variable "MISSING_DIR" not found/); + }); + }); + + describe('Server Configuration', () => { + it('should load cookie_secret directly from YAML', async () => { + const valid = 'abcdefghijklmnopqrstuvwxyz123456'; + const tempConfigPath = writeTempYamlConfig({ + server: { cookie_secret: valid }, + }); + const config = await loadConfig({ loadEnv: false, path: tempConfigPath }); + expect(config.server.cookie_secret).toBe(valid); + }); + + it('should load cookie_secret from file', async () => { + const secret = 'a'.repeat(32); + const secretPath = writeTempFile('cookie_secret.txt', secret); + const tempConfigPath = writeTempYamlConfig({ + server: { cookie_secret_path: secretPath }, + }); + const config = await loadConfig({ loadEnv: false, path: tempConfigPath }); + expect(config.server.cookie_secret).toBe(secret); + }); + + it('should reject when both cookie_secret and cookie_secret_path are in YAML', async () => { + const secretPath = writeTempFile('conflict.txt', 'x'.repeat(32)); + const tempConfigPath = writeTempYamlConfig({ + server: { + cookie_secret: '1'.repeat(32), + cookie_secret_path: secretPath, + }, + }); + await expect( + loadConfig({ loadEnv: false, path: tempConfigPath }), + ).rejects.toThrow( + /Only one of \"cookie_secret\" or \"cookie_secret_path\" may be set/, + ); + }); + + it('should reject when neither cookie_secret nor cookie_secret_path is provided', async () => { + const yaml = `debug: false +server: + host: \"127.0.0.1\" + port: 8080 + cookie_secure: false + agent: + authkey: \"key\" + ttl: 180000 + cache_path: \"/tmp/cache.json\" +headscale: + url: \"http://localhost\" + config_strict: false +`; + const tempConfigPath = writeTempYamlConfig(yaml); + await expect( + loadConfig({ loadEnv: false, path: tempConfigPath }), + ).rejects.toThrow( + /Either \"cookie_secret\" or \"cookie_secret_path\" must be provided for cookie_secret/, + ); + }); + }); + + describe('Headscale Configuration', () => { + it('should load headscale_api_key directly from YAML', async () => { + const tempConfigPath = writeTempYamlConfig({ + oidc: { + issuer: 'https://example.com/oidc', + client_id: 'test', + headscale_api_key: 'hs-yaml-key', + token_endpoint_auth_method: 'client_secret_basic', + disable_api_key_login: false, + }, + }); + const config = await loadConfig({ loadEnv: false, path: tempConfigPath }); + expect(config.oidc.headscale_api_key).toBe('hs-yaml-key'); + }); + + it('should load headscale_api_key from file', async () => { + const val = 'hs-file-key'; + const p = writeTempFile('hs_api_key.txt', val); + const tempConfigPath = writeTempYamlConfig({ + oidc: { + issuer: 'https://example.com/oidc', + client_id: 'test', + headscale_api_key_path: p, + token_endpoint_auth_method: 'client_secret_basic', + disable_api_key_login: false, + }, + }); + const config = await loadConfig({ loadEnv: false, path: tempConfigPath }); + expect(config.oidc.headscale_api_key).toBe(val); + }); + + it('should reject when both headscale_api_key and headscale_api_key_path are in YAML', async () => { + const p = writeTempFile('conflict.txt', 'irrelevant'); + const tempConfigPath = writeTempYamlConfig({ + oidc: { + issuer: 'https://example.com/oidc', + client_id: 'test', + headscale_api_key: 'key', + headscale_api_key_path: p, + token_endpoint_auth_method: 'client_secret_basic', + disable_api_key_login: false, + }, + }); + await expect( + loadConfig({ loadEnv: false, path: tempConfigPath }), + ).rejects.toThrow( + /Only one of \"headscale_api_key\" or \"headscale_api_key_path\" may be set/, + ); + }); + + it('should keep config_path string and interpolate env vars', async () => { + process.env.MY_HS_CONFIG_SUBDIR = 'hs-test'; + const cfgVal = '/etc/headscale-${MY_HS_CONFIG_SUBDIR}/config.yaml'; + const exp = '/etc/headscale-hs-test/config.yaml'; + const tempConfigPath = writeTempYamlConfig({ + headscale: { config_path: cfgVal }, + }); + const config = await loadConfig({ loadEnv: true, path: tempConfigPath }); + expect(config.headscale.config_path).toBe(exp); + }); + }); +}); diff --git a/tests/setupOverlayFs.js b/tests/setupOverlayFs.js new file mode 100644 index 0000000..439f02f --- /dev/null +++ b/tests/setupOverlayFs.js @@ -0,0 +1,86 @@ +import fs from 'node:fs'; +import fsPromises from 'node:fs/promises'; + +// Simple overlayfs implementation for tests +class OverlayFS { + constructor() { + this.overlays = new Map(); + } + + // Create a virtual file at the given path + createFile(filePath, content) { + this.overlays.set(filePath, content); + } + + // Check if a file exists in our overlay + exists(filePath) { + return this.overlays.has(filePath); + } + + // Read a file from our overlay + readFile(filePath) { + if (this.overlays.has(filePath)) { + return this.overlays.get(filePath); + } + throw new Error(`File not found: ${filePath}`); + } + + // Clean up all overlays + clear() { + this.overlays.clear(); + } +} + +// Global overlayfs instance +export const overlayFS = new OverlayFS(); + +// Monkey patch fs.readFileSync to use our overlay +const originalReadFileSync = fs.readFileSync; +fs.readFileSync = function (filePath, options) { + if (overlayFS.exists(filePath)) { + const content = overlayFS.readFile(filePath); + if (options?.encoding) { + return content; + } + return Buffer.from(content); + } + return originalReadFileSync.call(this, filePath, options); +}; + +// Monkey patch fs.promises.readFile (async) to use our overlay +const originalAsyncReadFile = fsPromises.readFile; +fsPromises.readFile = function (filePath, options) { + if (overlayFS.exists(filePath)) { + const content = overlayFS.readFile(filePath); + if (options?.encoding) { + return Promise.resolve(content); + } + return Promise.resolve(Buffer.from(content)); + } + return originalAsyncReadFile.call(this, filePath, options); +}; + +// Monkey patch fs.access to handle overlayfs directories +const originalAccess = fs.access; +fs.access = function (filePath, mode, callback) { + // Handle directories that should exist in our overlay + if (filePath === '/var/lib/headplane/' || filePath === '/var/lib/headplane') { + if (callback) { + callback(null); + } else { + return Promise.resolve(); + } + return; + } + return originalAccess.call(this, filePath, mode, callback); +}; + +// Monkey patch fs.promises.access to handle overlayfs directories +const originalAsyncAccess = fsPromises.access; +fsPromises.access = function (filePath, mode) { + // Handle directories that should exist in our overlay + if (filePath === '/var/lib/headplane/' || filePath === '/var/lib/headplane') { + return Promise.resolve(); + } + return originalAsyncAccess.call(this, filePath, mode); +}; diff --git a/vitest.config.ts b/vitest.config.ts new file mode 100644 index 0000000..a77f53c --- /dev/null +++ b/vitest.config.ts @@ -0,0 +1,16 @@ +import { resolve } from 'node:path'; +import { defineConfig } from 'vitest/config'; + +export default defineConfig({ + test: { + environment: 'node', + include: ['tests/**/*.test.js'], + exclude: ['node_modules/**', 'build/**'], + setupFiles: ['./tests/setupOverlayFs.js'], + }, + resolve: { + alias: { + '~': resolve(__dirname, './app'), + }, + }, +}); From 5ab9686460e6759c392fcb5a0df0ee9762a4c73e Mon Sep 17 00:00:00 2001 From: StealthBadger747 Date: Wed, 30 Jul 2025 18:28:48 +0500 Subject: [PATCH 46/97] Update the configuration README Revert flake.nix and package.nix to main; drop wasm.nix --- docs/Configuration.md | 47 +++++++++++++++++++++++++++++++++++++++++++ flake.nix | 6 ++---- nix/package.nix | 25 +++++++---------------- nix/wasm.nix | 35 -------------------------------- 4 files changed, 56 insertions(+), 57 deletions(-) delete mode 100644 nix/wasm.nix diff --git a/docs/Configuration.md b/docs/Configuration.md index 0c3770b..5c8bdaf 100644 --- a/docs/Configuration.md +++ b/docs/Configuration.md @@ -30,6 +30,53 @@ Setting this also tells Headplane to load the relative `.env` file into the envi > environment variables meaning you cannot specify variables such as > `HEADPLANE_DEBUG_LOG=true` or `HEADPLANE_CONFIG_PATH=/etc/headplane/config.yaml`. +## Sensitive Values +Headplane supports a dual-mode pattern for providing certain sensitive configuration values, such as secrets, keys, and private certificates. For each such field, you can either: + +1. Set the value directly in the configuration file (e.g., `cookie_secret: "your-32-character-long-secret"`) +2. Provide a path to a file containing the value using the `_path` suffixed key (e.g., `cookie_secret_path: "/path/to/your/cookie_secret_file"`) + +When using a `_path` option, the content of the specified file will be read and used as the value for the setting. These paths can include environment variable interpolation (e.g., `${CREDENTIALS_DIRECTORY}/my_secret_file`), which is useful for integration with tools like systemd's `LoadCredential`. + +**Important Rules for Dual-Mode Fields:** +- You **cannot** set both the direct value (e.g., `cookie_secret`) and its corresponding `_path` (e.g., `cookie_secret_path`) simultaneously. Doing so will result in a configuration error. +- If a `_path` is provided, the corresponding direct value field (if also present and not null) will usually be ignored or may cause validation errors depending on the specific field and loader logic. It's best to provide only one. +- The same dual-mode pattern works with environment variables. You can set `HEADPLANE_OIDC__CLIENT_SECRET` for a direct value or `HEADPLANE_OIDC__CLIENT_SECRET_PATH` to specify a path to a file containing the secret. + +**Note on Path Processing:** +Headplane uses a whitelist approach to determine which `_path` fields should have their content loaded as secrets. Only the explicitly whitelisted paths below will have their file content read and used as configuration values. All other paths with a `_path` suffix will have environment variables interpolated but will not have their content loaded. + +The following configuration options in Headplane are treated as secret paths: + +- **Server Settings (`server.*`):** + - `cookie_secret_path` (for web session encoding) + - *Note:* Either `cookie_secret` or `cookie_secret_path` must be provided for web session security. + +- **Headscale Connection Settings (`headscale.*`):** + - `tls_cert_path` (custom TLS certificate for connecting to Headscale) + - *Note:* This is treated as a regular path, not a secret path, so it will not have its content loaded. + +- **Integration Agent Settings (`integration.agent.*`):** + - `pre_authkey_path` (pre-auth key for the Headplane agent to connect to the Tailnet) + - *Note:* Either `pre_authkey` or `pre_authkey_path` must be provided when the agent is enabled. + +- **OIDC Settings (`oidc.*`):** + - `client_secret_path` (OIDC client secret) + - *Note:* Either `client_secret` or `client_secret_path` must be provided for OIDC authentication. + - `headscale_api_key_path` (Headscale API key used by Headplane during the OIDC authentication flow) + - *Note:* Either `headscale_api_key` or `headscale_api_key_path` must be provided for OIDC integration. + +**Distinction for Non-Secret Paths:** +Other configuration fields that end with `_path` are treated as regular paths and will not have their content loaded. For example: +- `server.agent.cache_path` (default: `/var/lib/headplane/agent_cache.json`): This is a direct file path where Headplane will *store* its agent cache data. +- `headscale.config_path`: This is an optional path to Headscale's `config.yaml` file, which Headplane might read or use for validation. + +All path fields (both secret and non-secret) support environment variable interpolation using the `${VAR_NAME}` syntax. + +The path-based secret loading mechanism also works with environment variables. For instance: +- `HEADPLANE_OIDC__CLIENT_SECRET_PATH=/path/to/secret/file` will load the OIDC client secret from the specified file +- `HEADPLANE_SERVER__COOKIE_SECRET_PATH=${CREDENTIALS_DIRECTORY}/cookie_secret` will use environment variable interpolation in the path + ## Debugging To enable debug logging, set the **`HEADPLANE_DEBUG_LOG=true`** environment variable. This will enable all debug logs for Headplane, which could fill up log space very quickly. diff --git a/flake.nix b/flake.nix index afe0e4f..9229532 100644 --- a/flake.nix +++ b/flake.nix @@ -29,8 +29,7 @@ rec { in rec { formatter = pkgs.alejandra; packages = { - hp_ssh_wasm = pkgs.callPackage ./nix/wasm.nix {}; - headplane = pkgs.callPackage ./nix/package.nix { hp_ssh_wasm = packages.hp_ssh_wasm; }; + headplane = pkgs.callPackage ./nix/package.nix {}; headplane-agent = pkgs.callPackage ./nix/agent.nix {}; }; checks.default = pkgs.symlinkJoin { @@ -62,8 +61,7 @@ rec { }) // { overlays.default = final: prev: { - hp_ssh_wasm = final.callPackage ./nix/wasm.nix {}; - headplane = final.callPackage ./nix/package.nix { hp_ssh_wasm = final.hp_ssh_wasm; }; + headplane = final.callPackage ./nix/package.nix {}; headplane-agent = final.callPackage ./nix/agent.nix {}; }; nixosModules.headplane = import ./nix/module.nix; diff --git a/nix/package.nix b/nix/package.nix index 2208f51..b43a7cc 100644 --- a/nix/package.nix +++ b/nix/package.nix @@ -5,19 +5,12 @@ nodejs_22, pnpm_10, stdenv, - hp_ssh_wasm, ... }: -let - pkg = builtins.fromJSON (builtins.readFile ../package.json); - pname = pkg.name; - version = pkg.version; - src = ../.; -in stdenv.mkDerivation (finalAttrs: { - pname = pname; - version = version; - src = src; + pname = "headplane"; + version = (builtins.fromJSON (builtins.readFile ../package.json)).version; + src = ../.; nativeBuildInputs = [ makeWrapper @@ -29,14 +22,12 @@ stdenv.mkDerivation (finalAttrs: { dontCheckForBrokenSymlinks = true; pnpmDeps = pnpm_10.fetchDeps { - inherit pname version src; - hash = "sha256-GNSpFqPobX6MDPUXxz2XwdZ2Wt7boN8aok52pGgpGoM="; + inherit (finalAttrs) pname version src; + hash = "sha256-hu3028V/EWimYB1TGn7g06kJRIpZA6cuOIjPMEc8ddw="; }; buildPhase = '' runHook preBuild - cp ${hp_ssh_wasm}/hp_ssh.wasm app/hp_ssh.wasm - cp ${hp_ssh_wasm}/wasm_exec.js app/wasm_exec.js pnpm build runHook postBuild ''; @@ -45,12 +36,10 @@ stdenv.mkDerivation (finalAttrs: { runHook preInstall mkdir -p $out/{bin,share/headplane} cp -r build $out/share/headplane/ - cp -r node_modules $out/share/headplane/ - cp -r drizzle $out/share/headplane/ sed -i "s;$PWD;../..;" $out/share/headplane/build/server/index.js makeWrapper ${lib.getExe nodejs_22} $out/bin/headplane \ - --chdir $out/share/headplane \ - --add-flags $out/share/headplane/build/server/index.js + --chdir $out/share/headplane \ + --add-flags $out/share/headplane/build/server/index.js runHook postInstall ''; }) diff --git a/nix/wasm.nix b/nix/wasm.nix deleted file mode 100644 index e53b37e..0000000 --- a/nix/wasm.nix +++ /dev/null @@ -1,35 +0,0 @@ -{ buildGoModule, go, lib, ... }: - -let - pkg = builtins.fromJSON (builtins.readFile ../package.json); - pname = "hp_ssh-wasm"; - version = pkg.version; - wasmExecJs = - if builtins.pathExists "${go}/share/go/lib/wasm/wasm_exec.js" then - "${go}/share/go/lib/wasm/wasm_exec.js" - else if builtins.pathExists "${go}/lib/wasm/wasm_exec.js" then - "${go}/lib/wasm/wasm_exec.js" - else - "${go}/share/go/misc/wasm/wasm_exec.js"; -in -buildGoModule rec { - inherit pname version; - src = ../.; - subPackages = [ "cmd/hp_ssh" ]; - vendorHash = "sha256-3hZzDORAH+D4FW6SkOv3Enddd+q36ZALryvCPD9E5Ac="; - env.CGO_ENABLED = 0; - - nativeBuildInputs = [ go ]; - - buildPhase = '' - export GOOS=js - export GOARCH=wasm - go build -o hp_ssh.wasm ./cmd/hp_ssh - ''; - - installPhase = '' - mkdir -p $out - cp hp_ssh.wasm $out/ - cp ${wasmExecJs} $out/wasm_exec.js - ''; -} From 51a85e1bfe32e676989e4d80aaa498e465fdf3f3 Mon Sep 17 00:00:00 2001 From: Igor Ramazanov Date: Sat, 2 Aug 2025 23:40:32 +0000 Subject: [PATCH 47/97] fix: nix update hash --- nix/package.nix | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nix/package.nix b/nix/package.nix index b43a7cc..9fe9d9a 100644 --- a/nix/package.nix +++ b/nix/package.nix @@ -23,7 +23,7 @@ stdenv.mkDerivation (finalAttrs: { pnpmDeps = pnpm_10.fetchDeps { inherit (finalAttrs) pname version src; - hash = "sha256-hu3028V/EWimYB1TGn7g06kJRIpZA6cuOIjPMEc8ddw="; + hash = "sha256-GNSpFqPobX6MDPUXxz2XwdZ2Wt7boN8aok52pGgpGoM="; }; buildPhase = '' From 7a34a01dd00bef10d47f0bd812b895b6f77242f8 Mon Sep 17 00:00:00 2001 From: StealthBadger747 Date: Tue, 15 Jul 2025 11:43:55 -0700 Subject: [PATCH 48/97] Got the build working --- nix/package.nix | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/nix/package.nix b/nix/package.nix index 9fe9d9a..3c5410f 100644 --- a/nix/package.nix +++ b/nix/package.nix @@ -5,8 +5,17 @@ nodejs_22, pnpm_10, stdenv, + go, ... }: +let + wasmExecJs = + if builtins.pathExists "${go}/share/go/lib/wasm/wasm_exec.js" + then "${go}/share/go/lib/wasm/wasm_exec.js" + else if builtins.pathExists "${go}/lib/wasm/wasm_exec.js" + then "${go}/lib/wasm/wasm_exec.js" + else "${go}/share/go/misc/wasm/wasm_exec.js"; +in stdenv.mkDerivation (finalAttrs: { pname = "headplane"; version = (builtins.fromJSON (builtins.readFile ../package.json)).version; @@ -17,6 +26,7 @@ stdenv.mkDerivation (finalAttrs: { nodejs_22 pnpm_10.configHook git + go ]; dontCheckForBrokenSymlinks = true; @@ -28,6 +38,15 @@ stdenv.mkDerivation (finalAttrs: { buildPhase = '' runHook preBuild + + # Build the Go WASM binary + export GOOS=js + export GOARCH=wasm + go build -o app/hp_ssh.wasm ./cmd/hp_ssh + + # Copy wasm_exec.js to the app directory (from Nix store) + cp ${wasmExecJs} app/wasm_exec.js + pnpm build runHook postBuild ''; From 8b3d0e9bb664f9554f5a604dfbaffc9e77bbab56 Mon Sep 17 00:00:00 2001 From: StealthBadger747 Date: Tue, 15 Jul 2025 12:32:54 -0700 Subject: [PATCH 49/97] Maybe actually fix builds --- flake.nix | 6 ++++-- nix/package.nix | 37 ++++++++++++++----------------------- nix/wasm.nix | 35 +++++++++++++++++++++++++++++++++++ 3 files changed, 53 insertions(+), 25 deletions(-) create mode 100644 nix/wasm.nix diff --git a/flake.nix b/flake.nix index 9229532..afe0e4f 100644 --- a/flake.nix +++ b/flake.nix @@ -29,7 +29,8 @@ rec { in rec { formatter = pkgs.alejandra; packages = { - headplane = pkgs.callPackage ./nix/package.nix {}; + hp_ssh_wasm = pkgs.callPackage ./nix/wasm.nix {}; + headplane = pkgs.callPackage ./nix/package.nix { hp_ssh_wasm = packages.hp_ssh_wasm; }; headplane-agent = pkgs.callPackage ./nix/agent.nix {}; }; checks.default = pkgs.symlinkJoin { @@ -61,7 +62,8 @@ rec { }) // { overlays.default = final: prev: { - headplane = final.callPackage ./nix/package.nix {}; + hp_ssh_wasm = final.callPackage ./nix/wasm.nix {}; + headplane = final.callPackage ./nix/package.nix { hp_ssh_wasm = final.hp_ssh_wasm; }; headplane-agent = final.callPackage ./nix/agent.nix {}; }; nixosModules.headplane = import ./nix/module.nix; diff --git a/nix/package.nix b/nix/package.nix index 3c5410f..0abb9d5 100644 --- a/nix/package.nix +++ b/nix/package.nix @@ -5,48 +5,38 @@ nodejs_22, pnpm_10, stdenv, - go, + hp_ssh_wasm, ... }: let - wasmExecJs = - if builtins.pathExists "${go}/share/go/lib/wasm/wasm_exec.js" - then "${go}/share/go/lib/wasm/wasm_exec.js" - else if builtins.pathExists "${go}/lib/wasm/wasm_exec.js" - then "${go}/lib/wasm/wasm_exec.js" - else "${go}/share/go/misc/wasm/wasm_exec.js"; + pkg = builtins.fromJSON (builtins.readFile ../package.json); + pname = pkg.name; + version = pkg.version; + src = ../.; in stdenv.mkDerivation (finalAttrs: { - pname = "headplane"; - version = (builtins.fromJSON (builtins.readFile ../package.json)).version; - src = ../.; + pname = pname; + version = version; + src = src; nativeBuildInputs = [ makeWrapper nodejs_22 pnpm_10.configHook git - go ]; dontCheckForBrokenSymlinks = true; pnpmDeps = pnpm_10.fetchDeps { - inherit (finalAttrs) pname version src; + inherit pname version src; hash = "sha256-GNSpFqPobX6MDPUXxz2XwdZ2Wt7boN8aok52pGgpGoM="; }; buildPhase = '' runHook preBuild - - # Build the Go WASM binary - export GOOS=js - export GOARCH=wasm - go build -o app/hp_ssh.wasm ./cmd/hp_ssh - - # Copy wasm_exec.js to the app directory (from Nix store) - cp ${wasmExecJs} app/wasm_exec.js - + cp ${hp_ssh_wasm}/hp_ssh.wasm app/hp_ssh.wasm + cp ${hp_ssh_wasm}/wasm_exec.js app/wasm_exec.js pnpm build runHook postBuild ''; @@ -55,10 +45,11 @@ stdenv.mkDerivation (finalAttrs: { runHook preInstall mkdir -p $out/{bin,share/headplane} cp -r build $out/share/headplane/ + cp -r node_modules $out/share/headplane/ sed -i "s;$PWD;../..;" $out/share/headplane/build/server/index.js makeWrapper ${lib.getExe nodejs_22} $out/bin/headplane \ - --chdir $out/share/headplane \ - --add-flags $out/share/headplane/build/server/index.js + --chdir $out/share/headplane \ + --add-flags $out/share/headplane/build/server/index.js runHook postInstall ''; }) diff --git a/nix/wasm.nix b/nix/wasm.nix new file mode 100644 index 0000000..e53b37e --- /dev/null +++ b/nix/wasm.nix @@ -0,0 +1,35 @@ +{ buildGoModule, go, lib, ... }: + +let + pkg = builtins.fromJSON (builtins.readFile ../package.json); + pname = "hp_ssh-wasm"; + version = pkg.version; + wasmExecJs = + if builtins.pathExists "${go}/share/go/lib/wasm/wasm_exec.js" then + "${go}/share/go/lib/wasm/wasm_exec.js" + else if builtins.pathExists "${go}/lib/wasm/wasm_exec.js" then + "${go}/lib/wasm/wasm_exec.js" + else + "${go}/share/go/misc/wasm/wasm_exec.js"; +in +buildGoModule rec { + inherit pname version; + src = ../.; + subPackages = [ "cmd/hp_ssh" ]; + vendorHash = "sha256-3hZzDORAH+D4FW6SkOv3Enddd+q36ZALryvCPD9E5Ac="; + env.CGO_ENABLED = 0; + + nativeBuildInputs = [ go ]; + + buildPhase = '' + export GOOS=js + export GOARCH=wasm + go build -o hp_ssh.wasm ./cmd/hp_ssh + ''; + + installPhase = '' + mkdir -p $out + cp hp_ssh.wasm $out/ + cp ${wasmExecJs} $out/wasm_exec.js + ''; +} From c3112507b53c73b2d03a200c4fbcb6e060a14c7d Mon Sep 17 00:00:00 2001 From: StealthBadger747 Date: Tue, 15 Jul 2025 14:46:25 -0700 Subject: [PATCH 50/97] Copy drizzle as well --- nix/package.nix | 1 + 1 file changed, 1 insertion(+) diff --git a/nix/package.nix b/nix/package.nix index 0abb9d5..2208f51 100644 --- a/nix/package.nix +++ b/nix/package.nix @@ -46,6 +46,7 @@ stdenv.mkDerivation (finalAttrs: { mkdir -p $out/{bin,share/headplane} cp -r build $out/share/headplane/ cp -r node_modules $out/share/headplane/ + cp -r drizzle $out/share/headplane/ sed -i "s;$PWD;../..;" $out/share/headplane/build/server/index.js makeWrapper ${lib.getExe nodejs_22} $out/bin/headplane \ --chdir $out/share/headplane \ From 7f952bcb3bd249dbdb05c98103e3120527224f0b Mon Sep 17 00:00:00 2001 From: Erik Parawell Date: Mon, 19 May 2025 11:22:41 -0700 Subject: [PATCH 51/97] Nix changes --- .github/CODEOWNERS | 3 + docs/Nix.md | 294 ++++++++++++++++++++++++++++---------------- nix/module.nix | 300 ++++++++++++++++++++++++++++++++++++++++++++- 3 files changed, 491 insertions(+), 106 deletions(-) create mode 100644 .github/CODEOWNERS diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 0000000..4d77bf7 --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1,3 @@ +* @tale + +/nix @tale @StealthBadger747 diff --git a/docs/Nix.md b/docs/Nix.md index 12f3c18..44bcc0e 100644 --- a/docs/Nix.md +++ b/docs/Nix.md @@ -3,125 +3,213 @@ [flake.nix](../flake.nix) provided: ``` $ nix flake show . --all-systems -git+file:///home/igor/personal/headplane?ref=refs/heads/nix&rev=2d78a95a0648a3778e114fb246ea436e96475d62 -├───devShell -│ ├───aarch64-darwin: development environment 'headplane' -│ ├───x86_64-darwin: development environment 'headplane' -│ └───x86_64-linux: development environment 'headplane' +warning: Git tree '/home/erikp/headplane' is dirty +git+file:///home/erikp/headplane +├───checks +│ ├───aarch64-darwin +│ │ └───default: derivation 'headplane-with-agent' +│ ├───x86_64-darwin +│ │ └───default: derivation 'headplane-with-agent' +│ └───x86_64-linux +│ └───default: derivation 'headplane-with-agent' +├───devShells +│ ├───aarch64-darwin +│ │ └───default: development environment 'headplane' +│ ├───x86_64-darwin +│ │ └───default: development environment 'headplane' +│ └───x86_64-linux +│ └───default: development environment 'headplane' ├───formatter -│ ├───aarch64-darwin: package 'alejandra-3.1.0' -│ ├───x86_64-darwin: package 'alejandra-3.1.0' -│ └───x86_64-linux: package 'alejandra-3.1.0' +│ ├───aarch64-darwin: package 'alejandra-4.0.0' +│ ├───x86_64-darwin: package 'alejandra-4.0.0' +│ └───x86_64-linux: package 'alejandra-4.0.0' ├───nixosModules │ └───headplane: NixOS module ├───overlays │ └───default: Nixpkgs overlay └───packages ├───aarch64-darwin - │ ├───headplane: package 'headplane-0.5.3-SNAPSHOT' - │ └───headplane-agent: package 'hp_agent-0.5.3-SNAPSHOT' + │ ├───headplane: package 'headplane-0.5.10' + │ └───headplane-agent: package 'hp_agent-0.5.10' ├───x86_64-darwin - │ ├───headplane: package 'headplane-0.5.3-SNAPSHOT' - │ └───headplane-agent: package 'hp_agent-0.5.3-SNAPSHOT' + │ ├───headplane: package 'headplane-0.5.10' + │ └───headplane-agent: package 'hp_agent-0.5.10' └───x86_64-linux - ├───headplane: package 'headplane-0.5.3-SNAPSHOT' - └───headplane-agent: package 'hp_agent-0.5.3-SNAPSHOT' + ├───headplane: package 'headplane-0.5.10' + └───headplane-agent: package 'hp_agent-0.5.10' ``` ## NixOS module options -Defined as `services.headplane.*`, check the `./nix/` directory for details. + +The Headplane NixOS module provides the following options under `services.headplane`: + +### Top-level options + +- **enable** (`bool`, default: `false`) + > Whether to enable the Headplane service. + +- **package** (`package`, default: `pkgs.headplane`) + > The Headplane package to use. + +- **settings** (submodule) + > Headplane configuration options. Generates a YAML config file. See: https://github.com/tale/headplane/blob/main/config.example.yaml + + #### settings.server + - **host** (`string`, default: `"0.0.0.0"`) + > The host address to bind to. Example: `"127.0.0.1"` + - **port** (`port`, default: `3000`) + > The port to listen on. + - **cookie_secret** (`null or string`, default: `null`) + > The secret used to encode and decode web sessions. Ensure that this is exactly 32 characters long. Can be either a direct string or a path to a file containing the secret. Example: `config.sops.secrets.headplane.path` + - **cookie_secret_path** (`null or path`, default: `null`) + > Path to a file containing the cookie secret. The secret must be exactly 32 characters long. Can be used instead of cookie_secret. Example: `config.sops.secrets.headplane_cookie.path` + - **cookie_secure** (`bool`, default: `true`) + > Should the cookies only work over HTTPS? Set to false if running via HTTP without a proxy. Recommended to be true in production. + - **agent** (submodule) + > Agent configuration for the Headplane agent. + - **authkey** (`null or string`, default: `null`) + > The auth key used to authenticate the agent with Headplane. Can be either a direct string or a path to a file containing the key. Example: `config.sops.secrets.agent_authkey.path` + - **authkey_path** (`null or path`, default: `null`) + > Path to a file containing the agent auth key. Can be used instead of authkey. Example: `config.sops.secrets.agent_authkey.path` + - **ttl** (`int`, default: `180000`) + > How long to cache agent information (in milliseconds). + - **cache_path** (`string`, default: `"/var/lib/headplane/agent_cache.json"`) + > Where to store the agent cache. + + #### settings.headscale + - **url** (`string`, default: `"http://127.0.0.1:8080"`) + > The URL to your Headscale instance. All API requests are routed through this URL. THIS IS NOT the gRPC endpoint, but the HTTP endpoint. IMPORTANT: If you are using TLS this MUST be set to `https://`. Example: `https://headscale.example.com` + - **tls_cert** (`null or string`, default: `null`) + > TLS certificate for HTTPS connections. Can be either a direct string or a path to a file containing the certificate. Example: `config.sops.secrets.tls_cert.path` + - **tls_cert_path** (`null or path`, default: `null`) + > Path to a file containing the TLS certificate. Can be used instead of tls_cert. Example: `config.sops.secrets.tls_cert.path` + - **tls_key** (`null or string`, default: `null`) + > TLS private key for HTTPS connections. Can be either a direct string or a path to a file containing the key. Example: `config.sops.secrets.tls_key.path` + - **tls_key_path** (`null or path`, default: `null`) + > Path to a file containing the TLS private key. Can be used instead of tls_key. Example: `config.sops.secrets.tls_key.path` + - **config_path** (`null or path`, default: `null`) + > Path to the Headscale configuration file. This is optional, but HIGHLY recommended for the best experience. If this is read only, Headplane will show your configuration settings in the Web UI, but they cannot be changed. Example: `/etc/headscale/config.yaml` + - **config_strict** (`bool`, default: `true`) + > Headplane internally validates the Headscale configuration to ensure that it changes the configuration in a safe way. If you want to disable this validation, set this to false. + + #### settings.integration + - **proc** (submodule) + > Native process integration settings. + - **enabled** (`bool`, default: `false`) + > Enable "Native" integration that works when Headscale and Headplane are running outside of a container. There is no additional configuration, but you need to ensure that the Headplane process can terminate the Headscale process. + + #### settings.oidc + - **issuer** (`string`, default: `""`) + > URL to OpenID issuer. Example: `https://authentik.parawell.cloud/application/o/test-headscale/` + - **client_id** (`string`, default: `""`) + > The client ID for the OIDC client. Example: `your-client-id` + - **client_secret** (`null or string`, default: `null`) + > The client secret for the OIDC client. Example: `config.sops.secrets.oidc_secret.path` + - **client_secret_path** (`null or path`, default: `null`) + > Path to a file containing the OIDC client secret. Can be used instead of client_secret. Example: `config.sops.secrets.oidc_client_secret.path` + - **disable_api_key_login** (`bool`, default: `false`) + > Whether to disable API key login. + - **token_endpoint_auth_method** (`enum ["client_secret_post", "client_secret_basic"]`, default: `"client_secret_post"`) + > The token endpoint authentication method. + - **headscale_api_key** (`null or string`, default: `null`) + > If you are using OIDC, you need to generate an API key that can be used to authenticate other sessions when signing in. This can be done with `headscale apikeys create --expiration 999d`. Example: `config.sops.secrets.headscale_api_key.path` + - **headscale_api_key_path** (`null or path`, default: `null`) + > Path to a file containing the Headscale API key. Can be used instead of headscale_api_key. Example: `config.sops.secrets.headscale_api_key.path` + - **redirect_uri** (`string`) + > This should point to your publicly accessible URL for your Headplane instance with /admin/oidc/callback. Example: `https://headscale.example.com/admin/oidc/callback` + +- **agent** (submodule) + > Standalone agent configuration. + - **enable** (`bool`, default: `false`) + > Whether to enable the Headplane agent. + - **package** (`package`, default: `pkgs.headplane-agent`) + > The Headplane agent package to use. + - **settings** (`attrsOf string or bool`, default: `{}`) + > Headplane agent env vars config. See: https://github.com/tale/headplane/blob/main/docs/Headplane-Agent.md ## Usage -1. Add the `github:tale/headplane` flake input. -2. Import a default overlay to add `pkgs.headplane` and `pkgs.headplane-agent`. -3. Import NixOS module for `services.headplane.*`. +1. Add the Headplane flake input to your `flake.nix`: + ```nix + { + inputs = { + nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; + headplane = { + url = "github:tale/headplane"; + inputs.nixpkgs.follows = "nixpkgs"; + }; + }; -```nix -# Your flake.nix -{ - inputs = { - nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; - headplane = { - url = "github:igor-ramazanov/headplane/nix"; - inputs.nixpkgs.follows = "nixpkgs"; - }; - }; + outputs = { nixpkgs, headplane, ... }: { + nixosConfigurations.your-hostname = nixpkgs.lib.nixosSystem { + system = "x86_64-linux"; # or your system architecture + modules = [ + # Import the Headplane NixOS module to get access to services.headplane.* options + headplane.nixosModules.headplane + + # Add the Headplane overlay to make pkgs.headplane and pkgs.headplane-agent available + { + nixpkgs.overlays = [ headplane.overlays.default ]; + } - outputs = { - nixpkgs, - headplane, - ... - }: { - nixosConfigurations.MY_MACHINE = nixpkgs.lib.nixosSystem { - system = "x86_64-linux"; - modules = [ - # provides `services.headplane.*` NixOS options. - headplane.nixosModules.headplane - { - # provides `pkgs.headplane` and `pkgs.headplane-agent`. - nixpkgs.overlays = [ headplane.overlays.default ]; - } - { - {config, pkgs, ...}: - let - format = pkgs.formats.yaml {}; + # Your Headplane configuration + ({ config, pkgs, lib, ... }: { + services.headplane = { + enable = true; - # A workaround generate a valid Headscale config accepted by Headplane when `config_strict == true`. - settings = lib.recursiveUpdate config.services.headscale.settings { - acme_email = "/dev/null"; - tls_cert_path = "/dev/null"; - tls_key_path = "/dev/null"; - policy.path = "/dev/null"; - oidc.client_secret_path = "/dev/null"; - }; + # Configure the standalone Headplane Agent + agent = { + enable = false; # Set to true if you want to run the agent (not ready yet as of writing this doc) + }; - headscaleConfig = format.generate "headscale.yml" settings; - in { - services.headplane = { - enable = true; - agent = { - # As an example only. - # Headplane Agent hasn't yet been ready at the moment of writing the doc. - enable = true; - settings = { - HEADPLANE_AGENT_DEBUG = true; - HEADPLANE_AGENT_HOSTNAME = "localhost"; - HEADPLANE_AGENT_TS_SERVER = "https://example.com"; - HEADPLANE_AGENT_TS_AUTHKEY = "xxxxxxxxxxxxxx"; - HEADPLANE_AGENT_HP_SERVER = "https://example.com/admin/dns"; - HEADPLANE_AGENT_HP_AUTHKEY = "xxxxxxxxxxxxxx"; - }; - }; - settings = { - server = { - host = "127.0.0.1"; - port = 3000; - cookie_secret = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"; - cookie_secure = true; - }; - headscale = { - url = "https://example.com"; - config_path = "${headscaleConfig}"; - config_strict = true; - }; - integration.proc.enabled = true; - oidc = { - issuer = "https://oidc.example.com"; - client_id = "headplane"; - client_secret = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"; - disable_api_key_login = true; - # Might needed when integrating with Authelia. - token_endpoint_auth_method = "client_secret_basic"; - headscale_api_key = "xxxxxxxxxx.xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"; - redirect_uri = "https://oidc.example.com/admin/oidc/callback"; - }; - }; - }; - } - } - ]; - }; - }; -} -``` + # Configure the Headplane server application + settings = { + server = { + # host = "0.0.0.0"; # Default: bind to all interfaces + # port = 3000; # Default port + cookie_secret_path = config.sops.secrets.headplane_cookie_secret.path; + # cookie_secret = "your-32-character-secret"; # Alternative to cookie_secret_path + cookie_secure = true; + + agent = { + enabled = false; + # authkey = "your-agent-authkey"; # Alternative to authkey_path + # authkey_path = config.sops.secrets.headplane_server_agent_authkey.path; + # ttl = 180000; # Default: 3 minutes in milliseconds + # cache_path = "/var/lib/headplane/agent_cache.json"; # Default cache location + }; + }; + + headscale = { + # url = "http://127.0.0.1:8080"; # Default Headscale URL + config_path = headscaleConfig; # Use the generated config file + # config_strict = true; # Default: strict config validation + # tls_cert = "your-tls-cert"; # Alternative to tls_cert_path + # tls_cert_path = config.sops.secrets.headplane_tls_cert.path; + # tls_key = "your-tls-key"; # Alternative to tls_key_path + # tls_key_path = config.sops.secrets.headplane_tls_key.path; + }; + + integration.proc.enabled = true; + + oidc = { + # issuer = ""; # Default: empty + # client_id = ""; # Default: empty + client_secret_path = config.sops.secrets.headplane_oidc_client_secret.path; + # client_secret = "your_oidc_client_secret"; # Alternative to client_secret_path + # disable_api_key_login = false; # Default: allow API key login + # token_endpoint_auth_method = "client_secret_post"; # Default auth method + headscale_api_key_path = config.sops.secrets.headplane_headscale_api_key.path; + # headscale_api_key = "your_headscale_api_key"; # Alternative to headscale_api_key_path + redirect_uri = "https://headscale.example.com/admin/oidc/callback"; + }; + }; + }; + }) + ]; + }; + }; + } + ``` + +For more details about each option, refer to the options documentation above. diff --git a/nix/module.nix b/nix/module.nix index 99476ab..b73f998 100644 --- a/nix/module.nix +++ b/nix/module.nix @@ -36,12 +36,305 @@ in { enable = mkEnableOption "headplane"; package = mkPackageOption pkgs "headplane" {}; - settings = mkOption { - type = types.submodule { + settings = lib.mkOption { + description = '' + Headplane configuration options. Generates a YAML config file. + See: https://github.com/tale/headplane/blob/main/config.example.yaml + ''; + type = lib.types.submodule { freeformType = settingsFormat.type; + + options = { + server = lib.mkOption { + type = lib.types.submodule { + options = { + host = lib.mkOption { + type = lib.types.str; + default = "0.0.0.0"; + description = "The host address to bind to."; + example = "127.0.0.1"; + }; + + port = lib.mkOption { + type = lib.types.port; + default = 3000; + description = "The port to listen on."; + }; + + cookie_secret = lib.mkOption { + type = lib.types.nullOr lib.types.str; + default = null; + description = '' + The secret used to encode and decode web sessions. + Ensure that this is exactly 32 characters long. + Can be either a direct string or a path to a file containing the secret. + ''; + example = "config.sops.secrets.headplane.path"; + }; + + cookie_secret_path = lib.mkOption { + type = lib.types.nullOr lib.types.path; + default = null; + description = '' + Path to a file containing the cookie secret. + The secret must be exactly 32 characters long. + Can be used instead of cookie_secret. + ''; + example = "config.sops.secrets.headplane_cookie.path"; + }; + + cookie_secure = lib.mkOption { + type = lib.types.bool; + default = true; + description = '' + Should the cookies only work over HTTPS? + Set to false if running via HTTP without a proxy. + Recommended to be true in production. + ''; + }; + + agent = lib.mkOption { + type = lib.types.submodule { + options = { + authkey = lib.mkOption { + type = lib.types.nullOr lib.types.str; + default = null; + description = '' + The auth key used to authenticate the agent with Headplane. + Can be either a direct string or a path to a file containing the key. + ''; + example = "config.sops.secrets.agent_authkey.path"; + }; + + authkey_path = lib.mkOption { + type = lib.types.nullOr lib.types.path; + default = null; + description = '' + Path to a file containing the agent auth key. + Can be used instead of authkey. + ''; + example = "config.sops.secrets.agent_authkey.path"; + }; + + ttl = lib.mkOption { + type = lib.types.int; + default = 180000; + description = "How long to cache agent information (in milliseconds)."; + }; + + cache_path = lib.mkOption { + type = lib.types.str; + default = "/var/lib/headplane/agent_cache.json"; + description = "Where to store the agent cache."; + }; + }; + }; + default = {}; + description = "Agent configuration for the Headplane agent."; + }; + }; + }; + default = {}; + description = "Server configuration for Headplane web application."; + }; + + headscale = lib.mkOption { + type = lib.types.submodule { + options = { + url = lib.mkOption { + type = lib.types.str; + default = "http://127.0.0.1:8080"; + description = '' + The URL to your Headscale instance. + All API requests are routed through this URL. + THIS IS NOT the gRPC endpoint, but the HTTP endpoint. + IMPORTANT: If you are using TLS this MUST be set to `https://`. + ''; + example = "https://headscale.example.com"; + }; + + tls_cert = lib.mkOption { + type = lib.types.nullOr lib.types.str; + default = null; + description = '' + TLS certificate for HTTPS connections. + Can be either a direct string or a path to a file containing the certificate. + ''; + example = "config.sops.secrets.tls_cert.path"; + }; + + tls_cert_path = lib.mkOption { + type = lib.types.nullOr lib.types.path; + default = null; + description = '' + Path to a file containing the TLS certificate. + Can be used instead of tls_cert. + ''; + example = "config.sops.secrets.tls_cert.path"; + }; + + tls_key = lib.mkOption { + type = lib.types.nullOr lib.types.str; + default = null; + description = '' + TLS private key for HTTPS connections. + Can be either a direct string or a path to a file containing the key. + ''; + example = "config.sops.secrets.tls_key.path"; + }; + + tls_key_path = lib.mkOption { + type = lib.types.nullOr lib.types.path; + default = null; + description = '' + Path to a file containing the TLS private key. + Can be used instead of tls_key. + ''; + example = "config.sops.secrets.tls_key.path"; + }; + + config_path = lib.mkOption { + type = lib.types.nullOr lib.types.path; + default = null; + description = '' + Path to the Headscale configuration file. + This is optional, but HIGHLY recommended for the best experience. + If this is read only, Headplane will show your configuration settings + in the Web UI, but they cannot be changed. + ''; + example = "/etc/headscale/config.yaml"; + }; + + config_strict = lib.mkOption { + type = lib.types.bool; + default = true; + description = '' + Headplane internally validates the Headscale configuration + to ensure that it changes the configuration in a safe way. + If you want to disable this validation, set this to false. + ''; + }; + }; + }; + default = {}; + description = "Headscale specific settings for Headplane integration."; + }; + + integration = lib.mkOption { + type = lib.types.submodule { + options = { + proc = lib.mkOption { + type = lib.types.submodule { + options = { + enabled = lib.mkOption { + type = lib.types.bool; + default = false; + description = '' + Enable "Native" integration that works when Headscale and + Headplane are running outside of a container. There is no additional + configuration, but you need to ensure that the Headplane process + can terminate the Headscale process. + ''; + }; + }; + }; + default = {}; + description = "Native process integration settings."; + }; + }; + }; + default = {}; + description = "Integration configurations for Headplane to interact with Headscale."; + }; + + oidc = lib.mkOption { + type = lib.types.submodule { + options = { + issuer = lib.mkOption { + type = lib.types.str; + default = ""; + description = "URL to OpenID issuer."; + example = "https://authentik.parawell.cloud/application/o/test-headscale/"; + }; + + client_id = lib.mkOption { + type = lib.types.str; + default = ""; + description = "The client ID for the OIDC client."; + example = "your-client-id"; + }; + + client_secret = lib.mkOption { + type = lib.types.nullOr lib.types.str; + default = null; + description = '' + The client secret for the OIDC client. + ''; + example = "config.sops.secrets.oidc_secret.path"; + }; + + client_secret_path = lib.mkOption { + type = lib.types.nullOr lib.types.path; + default = null; + description = '' + Path to a file containing the OIDC client secret. + Can be used instead of client_secret. + ''; + example = "config.sops.secrets.oidc_client_secret.path"; + }; + + disable_api_key_login = lib.mkOption { + type = lib.types.bool; + default = false; + description = "Whether to disable API key login."; + }; + + token_endpoint_auth_method = lib.mkOption { + type = lib.types.enum [ + "client_secret_post" + "client_secret_basic" + ]; + default = "client_secret_post"; + description = "The token endpoint authentication method."; + }; + + headscale_api_key = lib.mkOption { + type = lib.types.nullOr lib.types.str; + default = null; + description = '' + If you are using OIDC, you need to generate an API key + that can be used to authenticate other sessions when signing in. + This can be done with `headscale apikeys create --expiration 999d`. + ''; + example = "config.sops.secrets.headscale_api_key.path"; + }; + + headscale_api_key_path = lib.mkOption { + type = lib.types.nullOr lib.types.path; + default = null; + description = '' + Path to a file containing the Headscale API key. + Can be used instead of headscale_api_key. + ''; + example = "config.sops.secrets.headscale_api_key.path"; + }; + + redirect_uri = lib.mkOption { + type = lib.types.str; + description = '' + This should point to your publicly accessible URL + for your Headplane instance with /admin/oidc/callback. + ''; + example = "https://headscale.example.com/admin/oidc/callback"; + }; + }; + }; + default = {}; + description = "OIDC Configuration for authentication."; + }; + }; }; default = {}; - description = "Headplane config, generates a YAML config. See: https://github.com/tale/headplane/blob/main/config.example.yaml."; }; agent = mkOption { @@ -99,6 +392,7 @@ in { serviceConfig = { User = config.services.headscale.user; Group = config.services.headscale.group; + StateDirectory = "headplane"; ExecStart = "${pkgs.headplane}/bin/headplane"; Restart = "always"; From 943e5d2a15a801c46e1372949807d5e3611d7cd2 Mon Sep 17 00:00:00 2001 From: Igor Ramazanov Date: Tue, 20 May 2025 19:33:00 +0000 Subject: [PATCH 52/97] Remove inlined secrets options Leaving only `_path` variants. --- nix/module.nix | 61 -------------------------------------------------- 1 file changed, 61 deletions(-) diff --git a/nix/module.nix b/nix/module.nix index b73f998..bcea2f6 100644 --- a/nix/module.nix +++ b/nix/module.nix @@ -61,17 +61,6 @@ in { description = "The port to listen on."; }; - cookie_secret = lib.mkOption { - type = lib.types.nullOr lib.types.str; - default = null; - description = '' - The secret used to encode and decode web sessions. - Ensure that this is exactly 32 characters long. - Can be either a direct string or a path to a file containing the secret. - ''; - example = "config.sops.secrets.headplane.path"; - }; - cookie_secret_path = lib.mkOption { type = lib.types.nullOr lib.types.path; default = null; @@ -96,16 +85,6 @@ in { agent = lib.mkOption { type = lib.types.submodule { options = { - authkey = lib.mkOption { - type = lib.types.nullOr lib.types.str; - default = null; - description = '' - The auth key used to authenticate the agent with Headplane. - Can be either a direct string or a path to a file containing the key. - ''; - example = "config.sops.secrets.agent_authkey.path"; - }; - authkey_path = lib.mkOption { type = lib.types.nullOr lib.types.path; default = null; @@ -153,16 +132,6 @@ in { example = "https://headscale.example.com"; }; - tls_cert = lib.mkOption { - type = lib.types.nullOr lib.types.str; - default = null; - description = '' - TLS certificate for HTTPS connections. - Can be either a direct string or a path to a file containing the certificate. - ''; - example = "config.sops.secrets.tls_cert.path"; - }; - tls_cert_path = lib.mkOption { type = lib.types.nullOr lib.types.path; default = null; @@ -173,16 +142,6 @@ in { example = "config.sops.secrets.tls_cert.path"; }; - tls_key = lib.mkOption { - type = lib.types.nullOr lib.types.str; - default = null; - description = '' - TLS private key for HTTPS connections. - Can be either a direct string or a path to a file containing the key. - ''; - example = "config.sops.secrets.tls_key.path"; - }; - tls_key_path = lib.mkOption { type = lib.types.nullOr lib.types.path; default = null; @@ -264,15 +223,6 @@ in { example = "your-client-id"; }; - client_secret = lib.mkOption { - type = lib.types.nullOr lib.types.str; - default = null; - description = '' - The client secret for the OIDC client. - ''; - example = "config.sops.secrets.oidc_secret.path"; - }; - client_secret_path = lib.mkOption { type = lib.types.nullOr lib.types.path; default = null; @@ -298,17 +248,6 @@ in { description = "The token endpoint authentication method."; }; - headscale_api_key = lib.mkOption { - type = lib.types.nullOr lib.types.str; - default = null; - description = '' - If you are using OIDC, you need to generate an API key - that can be used to authenticate other sessions when signing in. - This can be done with `headscale apikeys create --expiration 999d`. - ''; - example = "config.sops.secrets.headscale_api_key.path"; - }; - headscale_api_key_path = lib.mkOption { type = lib.types.nullOr lib.types.path; default = null; From e0fde7d0f48e9c901bf8bc5200702ea8b932993e Mon Sep 17 00:00:00 2001 From: Igor Ramazanov Date: Tue, 20 May 2025 19:33:53 +0000 Subject: [PATCH 53/97] Turn `integration.proc.enabled` by default --- nix/module.nix | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nix/module.nix b/nix/module.nix index bcea2f6..d0b81d6 100644 --- a/nix/module.nix +++ b/nix/module.nix @@ -187,7 +187,7 @@ in { options = { enabled = lib.mkOption { type = lib.types.bool; - default = false; + default = true; description = '' Enable "Native" integration that works when Headscale and Headplane are running outside of a container. There is no additional From 441e31690be5b6429b6f061b27ff70dec0478627 Mon Sep 17 00:00:00 2001 From: StealthBadger747 Date: Tue, 20 May 2025 14:24:17 -0700 Subject: [PATCH 54/97] Cleanup doc and set sane defaults --- docs/Nix.md | 30 +++++++++++++----------------- nix/module.nix | 13 ++++--------- 2 files changed, 17 insertions(+), 26 deletions(-) diff --git a/docs/Nix.md b/docs/Nix.md index 44bcc0e..38334a0 100644 --- a/docs/Nix.md +++ b/docs/Nix.md @@ -165,43 +165,39 @@ The Headplane NixOS module provides the following options under `services.headpl # Configure the Headplane server application settings = { server = { - # host = "0.0.0.0"; # Default: bind to all interfaces - # port = 3000; # Default port + # host = "127.0.0.1"; + # port = 3000; cookie_secret_path = config.sops.secrets.headplane_cookie_secret.path; - # cookie_secret = "your-32-character-secret"; # Alternative to cookie_secret_path cookie_secure = true; agent = { enabled = false; - # authkey = "your-agent-authkey"; # Alternative to authkey_path # authkey_path = config.sops.secrets.headplane_server_agent_authkey.path; - # ttl = 180000; # Default: 3 minutes in milliseconds - # cache_path = "/var/lib/headplane/agent_cache.json"; # Default cache location + # ttl = 180000; # milliseconds + # cache_path = "/var/lib/headplane/agent_cache.json"; }; }; headscale = { - # url = "http://127.0.0.1:8080"; # Default Headscale URL + # url = "http://127.0.0.1:8080"; config_path = headscaleConfig; # Use the generated config file - # config_strict = true; # Default: strict config validation + # config_strict = true; # tls_cert = "your-tls-cert"; # Alternative to tls_cert_path # tls_cert_path = config.sops.secrets.headplane_tls_cert.path; # tls_key = "your-tls-key"; # Alternative to tls_key_path # tls_key_path = config.sops.secrets.headplane_tls_key.path; }; - integration.proc.enabled = true; - oidc = { - # issuer = ""; # Default: empty - # client_id = ""; # Default: empty - client_secret_path = config.sops.secrets.headplane_oidc_client_secret.path; + # issuer = ""; + # client_id = ""; + # client_secret_path = config.sops.secrets.headplane_oidc_client_secret.path; # client_secret = "your_oidc_client_secret"; # Alternative to client_secret_path - # disable_api_key_login = false; # Default: allow API key login - # token_endpoint_auth_method = "client_secret_post"; # Default auth method - headscale_api_key_path = config.sops.secrets.headplane_headscale_api_key.path; + # disable_api_key_login = false; + # token_endpoint_auth_method = "client_secret_post"; + # headscale_api_key_path = config.sops.secrets.headplane_headscale_api_key.path; # headscale_api_key = "your_headscale_api_key"; # Alternative to headscale_api_key_path - redirect_uri = "https://headscale.example.com/admin/oidc/callback"; + # redirect_uri = ""; }; }; }; diff --git a/nix/module.nix b/nix/module.nix index d0b81d6..8680677 100644 --- a/nix/module.nix +++ b/nix/module.nix @@ -50,9 +50,9 @@ in { options = { host = lib.mkOption { type = lib.types.str; - default = "0.0.0.0"; + default = "127.0.0.1"; description = "The host address to bind to."; - example = "127.0.0.1"; + example = "0.0.0.0"; }; port = lib.mkOption { @@ -67,7 +67,6 @@ in { description = '' Path to a file containing the cookie secret. The secret must be exactly 32 characters long. - Can be used instead of cookie_secret. ''; example = "config.sops.secrets.headplane_cookie.path"; }; @@ -90,7 +89,6 @@ in { default = null; description = '' Path to a file containing the agent auth key. - Can be used instead of authkey. ''; example = "config.sops.secrets.agent_authkey.path"; }; @@ -137,7 +135,6 @@ in { default = null; description = '' Path to a file containing the TLS certificate. - Can be used instead of tls_cert. ''; example = "config.sops.secrets.tls_cert.path"; }; @@ -147,7 +144,6 @@ in { default = null; description = '' Path to a file containing the TLS private key. - Can be used instead of tls_key. ''; example = "config.sops.secrets.tls_key.path"; }; @@ -213,7 +209,7 @@ in { type = lib.types.str; default = ""; description = "URL to OpenID issuer."; - example = "https://authentik.parawell.cloud/application/o/test-headscale/"; + example = "https://provider.example.com/issuer-url"; }; client_id = lib.mkOption { @@ -228,7 +224,6 @@ in { default = null; description = '' Path to a file containing the OIDC client secret. - Can be used instead of client_secret. ''; example = "config.sops.secrets.oidc_client_secret.path"; }; @@ -253,13 +248,13 @@ in { default = null; description = '' Path to a file containing the Headscale API key. - Can be used instead of headscale_api_key. ''; example = "config.sops.secrets.headscale_api_key.path"; }; redirect_uri = lib.mkOption { type = lib.types.str; + default = ""; description = '' This should point to your publicly accessible URL for your Headplane instance with /admin/oidc/callback. From d257b22f91b9e572ed39ccf23e342906b8495a52 Mon Sep 17 00:00:00 2001 From: Igor Ramazanov Date: Sun, 27 Jul 2025 02:18:50 +0000 Subject: [PATCH 55/97] chore: nix: `nix fmt` --- flake.nix | 4 +-- nix/package.nix | 73 ++++++++++++++++++++++++------------------------- nix/wasm.nix | 56 +++++++++++++++++++------------------ 3 files changed, 67 insertions(+), 66 deletions(-) diff --git a/flake.nix b/flake.nix index afe0e4f..c1c43e1 100644 --- a/flake.nix +++ b/flake.nix @@ -30,7 +30,7 @@ rec { formatter = pkgs.alejandra; packages = { hp_ssh_wasm = pkgs.callPackage ./nix/wasm.nix {}; - headplane = pkgs.callPackage ./nix/package.nix { hp_ssh_wasm = packages.hp_ssh_wasm; }; + headplane = pkgs.callPackage ./nix/package.nix {hp_ssh_wasm = packages.hp_ssh_wasm;}; headplane-agent = pkgs.callPackage ./nix/agent.nix {}; }; checks.default = pkgs.symlinkJoin { @@ -63,7 +63,7 @@ rec { // { overlays.default = final: prev: { hp_ssh_wasm = final.callPackage ./nix/wasm.nix {}; - headplane = final.callPackage ./nix/package.nix { hp_ssh_wasm = final.hp_ssh_wasm; }; + headplane = final.callPackage ./nix/package.nix {hp_ssh_wasm = final.hp_ssh_wasm;}; headplane-agent = final.callPackage ./nix/agent.nix {}; }; nixosModules.headplane = import ./nix/module.nix; diff --git a/nix/package.nix b/nix/package.nix index 2208f51..f3e2c9c 100644 --- a/nix/package.nix +++ b/nix/package.nix @@ -7,50 +7,49 @@ stdenv, hp_ssh_wasm, ... -}: -let +}: let pkg = builtins.fromJSON (builtins.readFile ../package.json); pname = pkg.name; version = pkg.version; src = ../.; in -stdenv.mkDerivation (finalAttrs: { - pname = pname; - version = version; - src = src; + stdenv.mkDerivation (finalAttrs: { + pname = pname; + version = version; + src = src; - nativeBuildInputs = [ - makeWrapper - nodejs_22 - pnpm_10.configHook - git - ]; + nativeBuildInputs = [ + makeWrapper + nodejs_22 + pnpm_10.configHook + git + ]; - dontCheckForBrokenSymlinks = true; + dontCheckForBrokenSymlinks = true; - pnpmDeps = pnpm_10.fetchDeps { - inherit pname version src; - hash = "sha256-GNSpFqPobX6MDPUXxz2XwdZ2Wt7boN8aok52pGgpGoM="; - }; + pnpmDeps = pnpm_10.fetchDeps { + inherit pname version src; + hash = "sha256-GNSpFqPobX6MDPUXxz2XwdZ2Wt7boN8aok52pGgpGoM="; + }; - buildPhase = '' - runHook preBuild - cp ${hp_ssh_wasm}/hp_ssh.wasm app/hp_ssh.wasm - cp ${hp_ssh_wasm}/wasm_exec.js app/wasm_exec.js - pnpm build - runHook postBuild - ''; + buildPhase = '' + runHook preBuild + cp ${hp_ssh_wasm}/hp_ssh.wasm app/hp_ssh.wasm + cp ${hp_ssh_wasm}/wasm_exec.js app/wasm_exec.js + pnpm build + runHook postBuild + ''; - installPhase = '' - runHook preInstall - mkdir -p $out/{bin,share/headplane} - cp -r build $out/share/headplane/ - cp -r node_modules $out/share/headplane/ - cp -r drizzle $out/share/headplane/ - sed -i "s;$PWD;../..;" $out/share/headplane/build/server/index.js - makeWrapper ${lib.getExe nodejs_22} $out/bin/headplane \ - --chdir $out/share/headplane \ - --add-flags $out/share/headplane/build/server/index.js - runHook postInstall - ''; -}) + installPhase = '' + runHook preInstall + mkdir -p $out/{bin,share/headplane} + cp -r build $out/share/headplane/ + cp -r node_modules $out/share/headplane/ + cp -r drizzle $out/share/headplane/ + sed -i "s;$PWD;../..;" $out/share/headplane/build/server/index.js + makeWrapper ${lib.getExe nodejs_22} $out/bin/headplane \ + --chdir $out/share/headplane \ + --add-flags $out/share/headplane/build/server/index.js + runHook postInstall + ''; + }) diff --git a/nix/wasm.nix b/nix/wasm.nix index e53b37e..d74c4c3 100644 --- a/nix/wasm.nix +++ b/nix/wasm.nix @@ -1,35 +1,37 @@ -{ buildGoModule, go, lib, ... }: - -let +{ + buildGoModule, + go, + lib, + ... +}: let pkg = builtins.fromJSON (builtins.readFile ../package.json); pname = "hp_ssh-wasm"; version = pkg.version; wasmExecJs = - if builtins.pathExists "${go}/share/go/lib/wasm/wasm_exec.js" then - "${go}/share/go/lib/wasm/wasm_exec.js" - else if builtins.pathExists "${go}/lib/wasm/wasm_exec.js" then - "${go}/lib/wasm/wasm_exec.js" - else - "${go}/share/go/misc/wasm/wasm_exec.js"; + if builtins.pathExists "${go}/share/go/lib/wasm/wasm_exec.js" + then "${go}/share/go/lib/wasm/wasm_exec.js" + else if builtins.pathExists "${go}/lib/wasm/wasm_exec.js" + then "${go}/lib/wasm/wasm_exec.js" + else "${go}/share/go/misc/wasm/wasm_exec.js"; in -buildGoModule rec { - inherit pname version; - src = ../.; - subPackages = [ "cmd/hp_ssh" ]; - vendorHash = "sha256-3hZzDORAH+D4FW6SkOv3Enddd+q36ZALryvCPD9E5Ac="; - env.CGO_ENABLED = 0; + buildGoModule rec { + inherit pname version; + src = ../.; + subPackages = ["cmd/hp_ssh"]; + vendorHash = "sha256-3hZzDORAH+D4FW6SkOv3Enddd+q36ZALryvCPD9E5Ac="; + env.CGO_ENABLED = 0; - nativeBuildInputs = [ go ]; + nativeBuildInputs = [go]; - buildPhase = '' - export GOOS=js - export GOARCH=wasm - go build -o hp_ssh.wasm ./cmd/hp_ssh - ''; + buildPhase = '' + export GOOS=js + export GOARCH=wasm + go build -o hp_ssh.wasm ./cmd/hp_ssh + ''; - installPhase = '' - mkdir -p $out - cp hp_ssh.wasm $out/ - cp ${wasmExecJs} $out/wasm_exec.js - ''; -} + installPhase = '' + mkdir -p $out + cp hp_ssh.wasm $out/ + cp ${wasmExecJs} $out/wasm_exec.js + ''; + } From 89e38e13569328040d7da2c4af4c14b674402926 Mon Sep 17 00:00:00 2001 From: Igor Ramazanov Date: Sun, 27 Jul 2025 02:19:45 +0000 Subject: [PATCH 56/97] feat: nix: rename `hp_ssh_wasm` to `headplane-ssh-wasm` for consistency --- flake.nix | 8 ++++---- nix/package.nix | 7 +++---- nix/{wasm.nix => ssh-wasm.nix} | 10 +++------- 3 files changed, 10 insertions(+), 15 deletions(-) rename nix/{wasm.nix => ssh-wasm.nix} (80%) diff --git a/flake.nix b/flake.nix index c1c43e1..4ed07bb 100644 --- a/flake.nix +++ b/flake.nix @@ -29,9 +29,9 @@ rec { in rec { formatter = pkgs.alejandra; packages = { - hp_ssh_wasm = pkgs.callPackage ./nix/wasm.nix {}; - headplane = pkgs.callPackage ./nix/package.nix {hp_ssh_wasm = packages.hp_ssh_wasm;}; + headplane = pkgs.callPackage ./nix/package.nix {headplane-ssh-wasm = packages.headplane-ssh-wasm;}; headplane-agent = pkgs.callPackage ./nix/agent.nix {}; + headplane-ssh-wasm = pkgs.callPackage ./nix/ssh-wasm.nix {}; }; checks.default = pkgs.symlinkJoin { name = "headplane-with-agent"; @@ -62,9 +62,9 @@ rec { }) // { overlays.default = final: prev: { - hp_ssh_wasm = final.callPackage ./nix/wasm.nix {}; - headplane = final.callPackage ./nix/package.nix {hp_ssh_wasm = final.hp_ssh_wasm;}; + headplane = final.callPackage ./nix/package.nix {headplane-ssh-wasm = final.headplane-ssh-wasm;}; headplane-agent = final.callPackage ./nix/agent.nix {}; + headplane-ssh-wasm = final.callPackage ./nix/ssh-wasm.nix {}; }; nixosModules.headplane = import ./nix/module.nix; }; diff --git a/nix/package.nix b/nix/package.nix index f3e2c9c..0997c04 100644 --- a/nix/package.nix +++ b/nix/package.nix @@ -1,12 +1,11 @@ { git, + headplane-ssh-wasm, lib, makeWrapper, nodejs_22, pnpm_10, stdenv, - hp_ssh_wasm, - ... }: let pkg = builtins.fromJSON (builtins.readFile ../package.json); pname = pkg.name; @@ -34,8 +33,8 @@ in buildPhase = '' runHook preBuild - cp ${hp_ssh_wasm}/hp_ssh.wasm app/hp_ssh.wasm - cp ${hp_ssh_wasm}/wasm_exec.js app/wasm_exec.js + cp ${headplane-ssh-wasm}/hp_ssh.wasm app/hp_ssh.wasm + cp ${headplane-ssh-wasm}/wasm_exec.js app/wasm_exec.js pnpm build runHook postBuild ''; diff --git a/nix/wasm.nix b/nix/ssh-wasm.nix similarity index 80% rename from nix/wasm.nix rename to nix/ssh-wasm.nix index d74c4c3..7f47495 100644 --- a/nix/wasm.nix +++ b/nix/ssh-wasm.nix @@ -1,12 +1,7 @@ { buildGoModule, go, - lib, - ... }: let - pkg = builtins.fromJSON (builtins.readFile ../package.json); - pname = "hp_ssh-wasm"; - version = pkg.version; wasmExecJs = if builtins.pathExists "${go}/share/go/lib/wasm/wasm_exec.js" then "${go}/share/go/lib/wasm/wasm_exec.js" @@ -14,8 +9,9 @@ then "${go}/lib/wasm/wasm_exec.js" else "${go}/share/go/misc/wasm/wasm_exec.js"; in - buildGoModule rec { - inherit pname version; + buildGoModule { + pname = "headplane-ssh-wasm"; + version = (builtins.fromJSON (builtins.readFile ../package.json)).version; src = ../.; subPackages = ["cmd/hp_ssh"]; vendorHash = "sha256-3hZzDORAH+D4FW6SkOv3Enddd+q36ZALryvCPD9E5Ac="; From c59632a0b7228e41aa7ca6193fbd7db5b66c0cac Mon Sep 17 00:00:00 2001 From: Igor Ramazanov Date: Sun, 27 Jul 2025 02:20:09 +0000 Subject: [PATCH 57/97] feat: nix: match NixOS module to the config schema --- nix/module.nix | 291 ++++++++++++++++++++++++++----------------------- 1 file changed, 157 insertions(+), 134 deletions(-) diff --git a/nix/module.nix b/nix/module.nix index 8680677..43af525 100644 --- a/nix/module.nix +++ b/nix/module.nix @@ -6,63 +6,61 @@ }: let inherit (lib) - attrsToList - listToAttrs - map + filterAttrs + filterAttrsRecursive mkEnableOption mkIf mkOption mkPackageOption - typeOf + recursiveUpdate types + updateManyAttrsByPath ; cfg = config.services.headplane; settingsFormat = pkgs.formats.yaml {}; - settingsFile = settingsFormat.generate "headplane-config.yaml" cfg.settings; - agentEnv = listToAttrs (map (n: { - name = n.name; - value = - if ((typeOf n.value) == "bool") - then - ( - if (n.value) - then "true" - else "false" - ) - else n.value; - }) (attrsToList cfg.agent.settings)); + settingsWithAgentExecutablePath = recursiveUpdate cfg.settings { + integration.agent.executable_path = "${cfg.settings.integration.agent.package}/bin/hp_agent"; + }; + settingsWithoutAgentPackage = + updateManyAttrsByPath [ + { + path = ["integration" "agent"]; + update = old: filterAttrs (key: value: key != "package") old; + } + ] + settingsWithAgentExecutablePath; + settingsWithoutNulls = filterAttrsRecursive (key: value: value != null) settingsWithoutAgentPackage; + settingsFile = settingsFormat.generate "headplane-config.yaml" settingsWithoutNulls; in { options.services.headplane = { enable = mkEnableOption "headplane"; package = mkPackageOption pkgs "headplane" {}; - settings = lib.mkOption { + settings = mkOption { description = '' Headplane configuration options. Generates a YAML config file. See: https://github.com/tale/headplane/blob/main/config.example.yaml ''; - type = lib.types.submodule { - freeformType = settingsFormat.type; - + type = types.submodule { options = { - server = lib.mkOption { - type = lib.types.submodule { + server = mkOption { + type = types.submodule { options = { - host = lib.mkOption { - type = lib.types.str; + host = mkOption { + type = types.str; default = "127.0.0.1"; description = "The host address to bind to."; example = "0.0.0.0"; }; - port = lib.mkOption { - type = lib.types.port; + port = mkOption { + type = types.port; default = 3000; description = "The port to listen on."; }; - cookie_secret_path = lib.mkOption { - type = lib.types.nullOr lib.types.path; + cookie_secret_path = mkOption { + type = types.nullOr types.path; default = null; description = '' Path to a file containing the cookie secret. @@ -71,8 +69,8 @@ in { example = "config.sops.secrets.headplane_cookie.path"; }; - cookie_secure = lib.mkOption { - type = lib.types.bool; + cookie_secure = mkOption { + type = types.bool; default = true; description = '' Should the cookies only work over HTTPS? @@ -81,33 +79,15 @@ in { ''; }; - agent = lib.mkOption { - type = lib.types.submodule { - options = { - authkey_path = lib.mkOption { - type = lib.types.nullOr lib.types.path; - default = null; - description = '' - Path to a file containing the agent auth key. - ''; - example = "config.sops.secrets.agent_authkey.path"; - }; - - ttl = lib.mkOption { - type = lib.types.int; - default = 180000; - description = "How long to cache agent information (in milliseconds)."; - }; - - cache_path = lib.mkOption { - type = lib.types.str; - default = "/var/lib/headplane/agent_cache.json"; - description = "Where to store the agent cache."; - }; - }; - }; - default = {}; - description = "Agent configuration for the Headplane agent."; + data_path = mkOption { + type = types.path; + default = "/var/lib/headplane"; + description = '' + The path to persist Headplane specific data. + All data going forward is stored in this directory, including the internal database and any cache related files. + Data formats prior to 0.6.1 will automatically be migrated. + ''; + example = "/var/lib/headplane"; }; }; }; @@ -115,11 +95,11 @@ in { description = "Server configuration for Headplane web application."; }; - headscale = lib.mkOption { - type = lib.types.submodule { + headscale = mkOption { + type = types.submodule { options = { - url = lib.mkOption { - type = lib.types.str; + url = mkOption { + type = types.str; default = "http://127.0.0.1:8080"; description = '' The URL to your Headscale instance. @@ -130,8 +110,8 @@ in { example = "https://headscale.example.com"; }; - tls_cert_path = lib.mkOption { - type = lib.types.nullOr lib.types.path; + tls_cert_path = mkOption { + type = types.nullOr types.path; default = null; description = '' Path to a file containing the TLS certificate. @@ -139,17 +119,15 @@ in { example = "config.sops.secrets.tls_cert.path"; }; - tls_key_path = lib.mkOption { - type = lib.types.nullOr lib.types.path; + public_url = mkOption { + type = types.nullOr types.str; default = null; - description = '' - Path to a file containing the TLS private key. - ''; - example = "config.sops.secrets.tls_key.path"; + description = "Public URL if differrent. This affects certain parts of the web UI."; + example = "https://headscale.example.com"; }; - config_path = lib.mkOption { - type = lib.types.nullOr lib.types.path; + config_path = mkOption { + type = types.nullOr types.path; default = null; description = '' Path to the Headscale configuration file. @@ -160,8 +138,8 @@ in { example = "/etc/headscale/config.yaml"; }; - config_strict = lib.mkOption { - type = lib.types.bool; + config_strict = mkOption { + type = types.bool; default = true; description = '' Headplane internally validates the Headscale configuration @@ -169,20 +147,93 @@ in { If you want to disable this validation, set this to false. ''; }; + + dns_records_path = mkOption { + type = types.nullOr types.path; + default = null; + description = '' + If you are using `dns.extra_records_path` in your Headscale configuration, you need to set this to the path for Headplane to be able to read the DNS records. + Ensure that the file is both readable and writable by the Headplane process. + When using this, Headplane will no longer need to automatically restart Headscale for DNS record changes. + ''; + example = "/var/lib/headplane/extra_records.json"; + }; }; }; default = {}; description = "Headscale specific settings for Headplane integration."; }; - integration = lib.mkOption { - type = lib.types.submodule { + integration = mkOption { + type = types.submodule { options = { - proc = lib.mkOption { - type = lib.types.submodule { + agent = mkOption { + type = types.submodule { options = { - enabled = lib.mkOption { - type = lib.types.bool; + enabled = mkOption { + type = types.bool; + default = false; + description = '' + The Headplane agent allows retrieving information about nodes. + This allows the UI to display version, OS, and connectivity data. + You will see the Headplane agent in your Tailnet as a node when it connects. + ''; + }; + + pre_authkey_path = mkOption { + type = types.nullOr types.path; + default = null; + description = '' + Path to a file containing the agent preauth key. + To connect to your Tailnet, you need to generate a pre-auth key. + This can be done via the web UI or through the `headscale` CLI. + ''; + example = "config.sops.secrets.agent_pre_authkey.path"; + }; + + host_name = mkOption { + type = types.str; + default = "headplane-agent"; + description = "Optionally change the name of the agent in the Tailnet"; + }; + + cache_ttl = mkOption { + type = types.int; + default = 180000; + description = '' + How long to cache agent information (in milliseconds). + If you want data to update faster, reduce the TTL, but this will increase the frequency of requests to Headscale. + ''; + }; + + cache_path = mkOption { + type = types.path; + default = "/var/lib/headplane/agent_cache.json"; + description = "Where to store the agent cache."; + }; + + work_dir = mkOption { + type = types.path; + default = "/var/lib/headplane/agent"; + description = '' + Do not change this unless you are running a custom deployment. + The work_dir represents where the agent will store its data to be able to automatically reauthenticate with your Tailnet. + It needs to be writable by the user running the Headplane process. + ''; + }; + + package = mkPackageOption pkgs "headplane-agent" {}; + }; + }; + default = {}; + description = "Agent configuration for the Headplane agent."; + }; + + proc = mkOption { + type = types.submodule { + options = { + enabled = mkOption { + type = types.bool; default = true; description = '' Enable "Native" integration that works when Headscale and @@ -202,25 +253,25 @@ in { description = "Integration configurations for Headplane to interact with Headscale."; }; - oidc = lib.mkOption { - type = lib.types.submodule { + oidc = mkOption { + type = types.submodule { options = { - issuer = lib.mkOption { - type = lib.types.str; + issuer = mkOption { + type = types.str; default = ""; description = "URL to OpenID issuer."; example = "https://provider.example.com/issuer-url"; }; - client_id = lib.mkOption { - type = lib.types.str; + client_id = mkOption { + type = types.str; default = ""; description = "The client ID for the OIDC client."; example = "your-client-id"; }; - client_secret_path = lib.mkOption { - type = lib.types.nullOr lib.types.path; + client_secret_path = mkOption { + type = types.nullOr types.path; default = null; description = '' Path to a file containing the OIDC client secret. @@ -228,23 +279,24 @@ in { example = "config.sops.secrets.oidc_client_secret.path"; }; - disable_api_key_login = lib.mkOption { - type = lib.types.bool; + disable_api_key_login = mkOption { + type = types.bool; default = false; description = "Whether to disable API key login."; }; - token_endpoint_auth_method = lib.mkOption { - type = lib.types.enum [ + token_endpoint_auth_method = mkOption { + type = types.enum [ "client_secret_post" "client_secret_basic" + "client_secret_jwt" ]; default = "client_secret_post"; description = "The token endpoint authentication method."; }; - headscale_api_key_path = lib.mkOption { - type = lib.types.nullOr lib.types.path; + headscale_api_key_path = mkOption { + type = types.nullOr types.path; default = null; description = '' Path to a file containing the Headscale API key. @@ -252,8 +304,8 @@ in { example = "config.sops.secrets.headscale_api_key.path"; }; - redirect_uri = lib.mkOption { - type = lib.types.str; + redirect_uri = mkOption { + type = types.str; default = ""; description = '' This should point to your publicly accessible URL @@ -261,6 +313,15 @@ in { ''; example = "https://headscale.example.com/admin/oidc/callback"; }; + + user_storage_file = mkOption { + type = types.path; + default = "/var/lib/headplane/users.json"; + description = '' + Path to a file containing the users and their permissions for Headplane. + ''; + example = "/var/lib/headplane/users.json"; + }; }; }; default = {}; @@ -270,20 +331,6 @@ in { }; default = {}; }; - - agent = mkOption { - type = types.submodule { - options = { - enable = mkEnableOption "headplane-agent"; - package = mkPackageOption pkgs "headplane-agent" {}; - settings = mkOption { - type = types.attrsOf [types.str types.bool]; - description = "Headplane agent env vars config. See: https://github.com/tale/headplane/blob/main/docs/Headplane-Agent.md"; - default = {}; - }; - }; - }; - }; }; config = mkIf cfg.enable { @@ -292,30 +339,6 @@ in { etc."headplane/config.yaml".source = "${settingsFile}"; }; - systemd.services.headplane-agent = - mkIf cfg.agent.enable - { - description = "Headplane side-running agent"; - - wantedBy = ["multi-user.target"]; - after = ["headplane.service"]; - requires = ["headplane.service"]; - - environment = agentEnv; - - serviceConfig = { - User = config.services.headscale.user; - Group = config.services.headscale.group; - - ExecStart = "${pkgs.headplane-agent}/bin/hp_agent"; - Restart = "always"; - RestartSec = 5; - - # TODO: Harden `systemd` security according to the "The Principle of Least Power". - # See: `$ systemd-analyze security headplane-agent`. - }; - }; - systemd.services.headplane = { description = "Headscale Web UI"; From ea004df2bb98b6172050c049bf2380faf632eef7 Mon Sep 17 00:00:00 2001 From: Igor Ramazanov Date: Sun, 27 Jul 2025 02:54:59 +0000 Subject: [PATCH 58/97] feat: nix: add `debug` logging flag --- nix/module.nix | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/nix/module.nix b/nix/module.nix index 43af525..9c163a1 100644 --- a/nix/module.nix +++ b/nix/module.nix @@ -36,6 +36,12 @@ in { enable = mkEnableOption "headplane"; package = mkPackageOption pkgs "headplane" {}; + debug = mkOption { + type = types.bool; + default = false; + description = "Enable debug logging"; + }; + settings = mkOption { description = '' Headplane configuration options. Generates a YAML config file. @@ -346,6 +352,8 @@ in { after = ["headscale.service"]; requires = ["headscale.service"]; + environment = {HEADPLANE_DEBUG_LOG = builtins.toString cfg.debug;}; + serviceConfig = { User = config.services.headscale.user; Group = config.services.headscale.group; From 344901d38c35871deb1ea1343112d5047777dc4c Mon Sep 17 00:00:00 2001 From: Igor Ramazanov Date: Sun, 27 Jul 2025 03:00:19 +0000 Subject: [PATCH 59/97] fix: `debug` log level when searching for a Headscale process --- app/server/config/integration/proc.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/server/config/integration/proc.ts b/app/server/config/integration/proc.ts index d6ca9b8..b677259 100644 --- a/app/server/config/integration/proc.ts +++ b/app/server/config/integration/proc.ts @@ -46,7 +46,7 @@ export default class ProcIntegration extends Integration { return pid; } catch (error) { - log.error('config', 'Failed to read %s: %s', path, error); + log.debug('config', 'Failed to read %s: %s', path, error); } }); From 8a9235836a25421e41a13cbdbfa6a992314008cb Mon Sep 17 00:00:00 2001 From: Igor Ramazanov Date: Sun, 27 Jul 2025 03:28:34 +0000 Subject: [PATCH 60/97] fix: agent inherits UID/GID from the parent process --- app/server/web/agent.ts | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/app/server/web/agent.ts b/app/server/web/agent.ts index 0e399e9..d090373 100644 --- a/app/server/web/agent.ts +++ b/app/server/web/agent.ts @@ -8,7 +8,7 @@ import { readFile, writeFile, } from 'node:fs/promises'; -import { exit } from 'node:process'; +import { exit, geteuid, getegid } from 'node:process'; import { createInterface } from 'node:readline'; import { setTimeout } from 'node:timers/promises'; import { type } from 'arktype'; @@ -114,6 +114,8 @@ class AgentManager { private spawnProcess: ChildProcess | null; private agentId: string | null; + private uid: number | null; + private gid: number | null; constructor( cache: TimedCache, @@ -126,6 +128,8 @@ class AgentManager { this.spawnProcess = null; this.agentId = null; this.startAgent(); + this.uid = geteuid ? geteuid() : null; + this.gid = getegid ? getegid() : null; process.on('SIGINT', () => { this.spawnProcess?.kill(); @@ -183,6 +187,8 @@ class AgentManager { this.restartCounter, ); this.spawnProcess = spawn(this.config.executable_path, [], { + ...(this.uid ? { uid: this.uid } : {}), + ...(this.gid ? { gid: this.gid } : {}), detached: false, stdio: ['pipe', 'pipe', 'pipe', 'pipe', 'pipe'], env: { From 1eaf3abe219da211350400667444ac6c91b92690 Mon Sep 17 00:00:00 2001 From: Igor Ramazanov Date: Mon, 28 Jul 2025 20:55:06 +0000 Subject: [PATCH 61/97] feat: nix: use updated `pnpm` fetcher version see: https://nixos.org/manual/nixpkgs/stable/#javascript-pnpm-fetcherVersion --- nix/package.nix | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/nix/package.nix b/nix/package.nix index 0997c04..3b2b201 100644 --- a/nix/package.nix +++ b/nix/package.nix @@ -28,7 +28,8 @@ in pnpmDeps = pnpm_10.fetchDeps { inherit pname version src; - hash = "sha256-GNSpFqPobX6MDPUXxz2XwdZ2Wt7boN8aok52pGgpGoM="; + fetcherVersion = 2; + hash = "sha256-zK9yxTHgtZ8ybVsjmY/ZoScOWu5kpvEL6pVdpQZOYA8="; }; buildPhase = '' From 1ce0dc375e88b371110756a913b62d1e9d9cf679 Mon Sep 17 00:00:00 2001 From: Igor Ramazanov Date: Tue, 29 Jul 2025 14:59:34 +0000 Subject: [PATCH 62/97] fix: remove `direnv` and `envrc` from the repo I've found it being clumsy and getting in the way. For example, in my `.envrc` I do `use flake . --override-input nixpkgs "$NIXPKGS"` to reuse an already existing instance of the `nixpkgs` on my machine. Feels like it should be up to a developer on how to use `nix develop`/`direnv` --- .envrc | 1 - 1 file changed, 1 deletion(-) delete mode 100644 .envrc diff --git a/.envrc b/.envrc deleted file mode 100644 index c4b17d7..0000000 --- a/.envrc +++ /dev/null @@ -1 +0,0 @@ -use_flake From ebd219b622e748df0c246b8ffdce93cb43aa95c2 Mon Sep 17 00:00:00 2001 From: Aarnav Tale Date: Tue, 5 Aug 2025 13:40:43 -0400 Subject: [PATCH 63/97] chore: update pnpm nix deps hash --- nix/package.nix | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nix/package.nix b/nix/package.nix index 3b2b201..ab8513d 100644 --- a/nix/package.nix +++ b/nix/package.nix @@ -29,7 +29,7 @@ in pnpmDeps = pnpm_10.fetchDeps { inherit pname version src; fetcherVersion = 2; - hash = "sha256-zK9yxTHgtZ8ybVsjmY/ZoScOWu5kpvEL6pVdpQZOYA8="; + hash = "sha256-GNSpFqPobX6MDPUXxz2XwdZ2Wt7boN8aok52pGgpGoM="; }; buildPhase = '' From 1355e1535de1e528b9adda29aa90b52aa55560ed Mon Sep 17 00:00:00 2001 From: Aarnav Tale Date: Tue, 5 Aug 2025 14:32:30 -0400 Subject: [PATCH 64/97] chore: update changelog --- CHANGELOG.md | 5 +++++ nix/package.nix | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 38045a5..c8f4e8b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,11 @@ - Add a build attestation to validate SLSA provenance for the Docker image. - Implement more accurate guessing on the PID with the `/proc` integration (via [#219](https://github.com/tale/headplane/pull/219)). - Usernames will now correctly fall back to emails if not provided (via [#257](https://github.com/tale/headplane/pull/257)). +- Configuration loading via paths is now supported for sensitive values (via [#283](https://github.com/tale/headplane/pulls/283)) + - Options like `server.cookie_secret_path` can override `server.cookie_secret` + - Environment variables are interpolatable into these paths + - See the full reference in the [docs](https://github.com/tale/headplane/blob/main/docs/Configuration.md#sensitive-values) +- The nix overlay build is fixed for the SSH module (via [#282](https://github.com/tale/headplane/pull/282)) ### 0.6.0 (May 25, 2025) - Headplane 0.6.0 now requires **Headscale 0.26.0** or newer. diff --git a/nix/package.nix b/nix/package.nix index ab8513d..b509dde 100644 --- a/nix/package.nix +++ b/nix/package.nix @@ -29,7 +29,7 @@ in pnpmDeps = pnpm_10.fetchDeps { inherit pname version src; fetcherVersion = 2; - hash = "sha256-GNSpFqPobX6MDPUXxz2XwdZ2Wt7boN8aok52pGgpGoM="; + hash = "sha256-CsrZjXl31sl/YRzpt/pyBtr4QKn1pLRHqu5hUcNVZbo="; }; buildPhase = '' From cdcb38f11e6900b9504c21ae787a4818fc0a85bb Mon Sep 17 00:00:00 2001 From: Igor Ramazanov Date: Sat, 9 Aug 2025 20:02:21 +0000 Subject: [PATCH 65/97] fix: nix: hash --- nix/package.nix | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nix/package.nix b/nix/package.nix index b509dde..bc6146b 100644 --- a/nix/package.nix +++ b/nix/package.nix @@ -29,7 +29,7 @@ in pnpmDeps = pnpm_10.fetchDeps { inherit pname version src; fetcherVersion = 2; - hash = "sha256-CsrZjXl31sl/YRzpt/pyBtr4QKn1pLRHqu5hUcNVZbo="; + hash = "sha256-GNSpFqPobX6MDPUXxz2XwdZ2Wt7boN8aok52pGgpGoM="; }; buildPhase = '' From fdbfe584c5a4ea88a675736d4fdc1e8fcb0526c5 Mon Sep 17 00:00:00 2001 From: Igor Ramazanov Date: Tue, 29 Jul 2025 15:22:28 +0000 Subject: [PATCH 66/97] feat: nix: add a new `mise` task and nix flake output to generate NixOS docs Adds a new Nix flake output `headplane-nixos-docs` which generates a Markdown page with all NixOS `services.headplane.settings.*` options. Replaces existing handwritten options documentation. --- docs/Nix.md | 265 +++++-------- docs/NixOS-options.md | 881 ++++++++++++++++++++++++++++++++++++++++++ flake.nix | 2 + mise.toml | 9 + nix/docs.nix | 24 ++ nix/module.nix | 312 +-------------- nix/options.nix | 320 +++++++++++++++ 7 files changed, 1331 insertions(+), 482 deletions(-) create mode 100644 docs/NixOS-options.md create mode 100644 nix/docs.nix create mode 100644 nix/options.nix diff --git a/docs/Nix.md b/docs/Nix.md index 38334a0..5782044 100644 --- a/docs/Nix.md +++ b/docs/Nix.md @@ -2,9 +2,8 @@ [flake.nix](../flake.nix) provided: ``` -$ nix flake show . --all-systems -warning: Git tree '/home/erikp/headplane' is dirty -git+file:///home/erikp/headplane +$ nix flake show github:tale/headplane --all-systems +github:tale/headplane/ec6d455461955242393b60d9ce60c5123fa9784f?narHash=sha256-CM/vXzUiOed7i1Pp15KyV4FuIvumRlXnpF33dSWZZH4%3D ├───checks │ ├───aarch64-darwin │ │ └───default: derivation 'headplane-with-agent' @@ -29,183 +28,107 @@ git+file:///home/erikp/headplane │ └───default: Nixpkgs overlay └───packages ├───aarch64-darwin - │ ├───headplane: package 'headplane-0.5.10' - │ └───headplane-agent: package 'hp_agent-0.5.10' + │ ├───headplane: package 'headplane-0.6.1' + │ ├───headplane-agent: package 'hp_agent-0.6.1' + │ ├───headplane-nixos-docs: package 'headplane-nixos-docs.md' + │ └───headplane-ssh-wasm: package 'headplane-ssh-wasm-0.6.1' ├───x86_64-darwin - │ ├───headplane: package 'headplane-0.5.10' - │ └───headplane-agent: package 'hp_agent-0.5.10' + │ ├───headplane: package 'headplane-0.6.1' + │ ├───headplane-agent: package 'hp_agent-0.6.1' + │ ├───headplane-nixos-docs: package 'headplane-nixos-docs.md' + │ └───headplane-ssh-wasm: package 'headplane-ssh-wasm-0.6.1' └───x86_64-linux - ├───headplane: package 'headplane-0.5.10' - └───headplane-agent: package 'hp_agent-0.5.10' + ├───headplane: package 'headplane-0.6.1' + ├───headplane-agent: package 'hp_agent-0.6.1' + ├───headplane-nixos-docs: package 'headplane-nixos-docs.md' + └───headplane-ssh-wasm: package 'headplane-ssh-wasm-0.6.1' ``` ## NixOS module options - -The Headplane NixOS module provides the following options under `services.headplane`: - -### Top-level options - -- **enable** (`bool`, default: `false`) - > Whether to enable the Headplane service. - -- **package** (`package`, default: `pkgs.headplane`) - > The Headplane package to use. - -- **settings** (submodule) - > Headplane configuration options. Generates a YAML config file. See: https://github.com/tale/headplane/blob/main/config.example.yaml - - #### settings.server - - **host** (`string`, default: `"0.0.0.0"`) - > The host address to bind to. Example: `"127.0.0.1"` - - **port** (`port`, default: `3000`) - > The port to listen on. - - **cookie_secret** (`null or string`, default: `null`) - > The secret used to encode and decode web sessions. Ensure that this is exactly 32 characters long. Can be either a direct string or a path to a file containing the secret. Example: `config.sops.secrets.headplane.path` - - **cookie_secret_path** (`null or path`, default: `null`) - > Path to a file containing the cookie secret. The secret must be exactly 32 characters long. Can be used instead of cookie_secret. Example: `config.sops.secrets.headplane_cookie.path` - - **cookie_secure** (`bool`, default: `true`) - > Should the cookies only work over HTTPS? Set to false if running via HTTP without a proxy. Recommended to be true in production. - - **agent** (submodule) - > Agent configuration for the Headplane agent. - - **authkey** (`null or string`, default: `null`) - > The auth key used to authenticate the agent with Headplane. Can be either a direct string or a path to a file containing the key. Example: `config.sops.secrets.agent_authkey.path` - - **authkey_path** (`null or path`, default: `null`) - > Path to a file containing the agent auth key. Can be used instead of authkey. Example: `config.sops.secrets.agent_authkey.path` - - **ttl** (`int`, default: `180000`) - > How long to cache agent information (in milliseconds). - - **cache_path** (`string`, default: `"/var/lib/headplane/agent_cache.json"`) - > Where to store the agent cache. - - #### settings.headscale - - **url** (`string`, default: `"http://127.0.0.1:8080"`) - > The URL to your Headscale instance. All API requests are routed through this URL. THIS IS NOT the gRPC endpoint, but the HTTP endpoint. IMPORTANT: If you are using TLS this MUST be set to `https://`. Example: `https://headscale.example.com` - - **tls_cert** (`null or string`, default: `null`) - > TLS certificate for HTTPS connections. Can be either a direct string or a path to a file containing the certificate. Example: `config.sops.secrets.tls_cert.path` - - **tls_cert_path** (`null or path`, default: `null`) - > Path to a file containing the TLS certificate. Can be used instead of tls_cert. Example: `config.sops.secrets.tls_cert.path` - - **tls_key** (`null or string`, default: `null`) - > TLS private key for HTTPS connections. Can be either a direct string or a path to a file containing the key. Example: `config.sops.secrets.tls_key.path` - - **tls_key_path** (`null or path`, default: `null`) - > Path to a file containing the TLS private key. Can be used instead of tls_key. Example: `config.sops.secrets.tls_key.path` - - **config_path** (`null or path`, default: `null`) - > Path to the Headscale configuration file. This is optional, but HIGHLY recommended for the best experience. If this is read only, Headplane will show your configuration settings in the Web UI, but they cannot be changed. Example: `/etc/headscale/config.yaml` - - **config_strict** (`bool`, default: `true`) - > Headplane internally validates the Headscale configuration to ensure that it changes the configuration in a safe way. If you want to disable this validation, set this to false. - - #### settings.integration - - **proc** (submodule) - > Native process integration settings. - - **enabled** (`bool`, default: `false`) - > Enable "Native" integration that works when Headscale and Headplane are running outside of a container. There is no additional configuration, but you need to ensure that the Headplane process can terminate the Headscale process. - - #### settings.oidc - - **issuer** (`string`, default: `""`) - > URL to OpenID issuer. Example: `https://authentik.parawell.cloud/application/o/test-headscale/` - - **client_id** (`string`, default: `""`) - > The client ID for the OIDC client. Example: `your-client-id` - - **client_secret** (`null or string`, default: `null`) - > The client secret for the OIDC client. Example: `config.sops.secrets.oidc_secret.path` - - **client_secret_path** (`null or path`, default: `null`) - > Path to a file containing the OIDC client secret. Can be used instead of client_secret. Example: `config.sops.secrets.oidc_client_secret.path` - - **disable_api_key_login** (`bool`, default: `false`) - > Whether to disable API key login. - - **token_endpoint_auth_method** (`enum ["client_secret_post", "client_secret_basic"]`, default: `"client_secret_post"`) - > The token endpoint authentication method. - - **headscale_api_key** (`null or string`, default: `null`) - > If you are using OIDC, you need to generate an API key that can be used to authenticate other sessions when signing in. This can be done with `headscale apikeys create --expiration 999d`. Example: `config.sops.secrets.headscale_api_key.path` - - **headscale_api_key_path** (`null or path`, default: `null`) - > Path to a file containing the Headscale API key. Can be used instead of headscale_api_key. Example: `config.sops.secrets.headscale_api_key.path` - - **redirect_uri** (`string`) - > This should point to your publicly accessible URL for your Headplane instance with /admin/oidc/callback. Example: `https://headscale.example.com/admin/oidc/callback` - -- **agent** (submodule) - > Standalone agent configuration. - - **enable** (`bool`, default: `false`) - > Whether to enable the Headplane agent. - - **package** (`package`, default: `pkgs.headplane-agent`) - > The Headplane agent package to use. - - **settings** (`attrsOf string or bool`, default: `{}`) - > Headplane agent env vars config. See: https://github.com/tale/headplane/blob/main/docs/Headplane-Agent.md +Defined as `services.headplane.*`, check the `./nix/` directory for details.\ +The full (generated by `mise run nixos-docs`) list of `services.headplane.settings.*` options: [./NixOS-options.md](./NixOS-options.md) ## Usage +1. Add the `github:tale/headplane` flake input. +2. Import a default overlay to add `pkgs.headplane`. +3. Import NixOS module for `services.headplane.*`. -1. Add the Headplane flake input to your `flake.nix`: - ```nix - { - inputs = { - nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; - headplane = { - url = "github:tale/headplane"; - inputs.nixpkgs.follows = "nixpkgs"; - }; - }; +```nix +# Your flake.nix +{ + inputs = { + nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; + headplane = { + url = "github:tale/headplane"; + inputs.nixpkgs.follows = "nixpkgs"; + }; + }; - outputs = { nixpkgs, headplane, ... }: { - nixosConfigurations.your-hostname = nixpkgs.lib.nixosSystem { - system = "x86_64-linux"; # or your system architecture - modules = [ - # Import the Headplane NixOS module to get access to services.headplane.* options - headplane.nixosModules.headplane - - # Add the Headplane overlay to make pkgs.headplane and pkgs.headplane-agent available - { - nixpkgs.overlays = [ headplane.overlays.default ]; - } + outputs = { + nixpkgs, + headplane, + ... + }: { + nixosConfigurations.MY_MACHINE = nixpkgs.lib.nixosSystem { + system = "x86_64-linux"; + modules = [ + # provides `services.headplane.*` NixOS options. + headplane.nixosModules.headplane + { + # provides `pkgs.headplane` + nixpkgs.overlays = [ headplane.overlays.default ]; + } + { + {config, pkgs, ...}: + let + format = pkgs.formats.yaml {}; - # Your Headplane configuration - ({ config, pkgs, lib, ... }: { - services.headplane = { - enable = true; + # A workaround generate a valid Headscale config accepted by Headplane when `config_strict == true`. + settings = lib.recursiveUpdate config.services.headscale.settings { + tls_cert_path = "/dev/null"; + tls_key_path = "/dev/null"; + policy.path = "/dev/null"; + }; - # Configure the standalone Headplane Agent - agent = { - enable = false; # Set to true if you want to run the agent (not ready yet as of writing this doc) - }; + headscaleConfig = format.generate "headscale.yml" settings; + in { + services.headplane = { + enable = true; + settings = { + server = { + host = "127.0.0.1"; + port = 3000; + # Using `sops-nix` as an example, can be a path to any file with a secret. + cookie_secret_path = config.sops.secrets."headplane/serverCookieSecret".path; + }; + headscale = { + url = "https://example.com"; + config_path = "${headscaleConfig}"; + }; + integration.agent = { + enabled = true; + # Using `sops-nix` as an example, can be a path to any file with a secret. + pre_authkey_path = config.sops.secrets."headplane/integrationAgentPreAuthkeyPath".path; + }; + oidc = { + issuer = "https://oidc.example.com"; + client_id = "headplane"; + # Using `sops-nix` as an example, can be a path to any file with a secret. + client_secret_path = config.sops.secrets."headplane/oidcClientSecret".path; + disable_api_key_login = true; + # Might needed when integrating with Authelia. + token_endpoint_auth_method = "client_secret_basic"; + # Using `sops-nix` as an example, can be a path to any file with a secret. + headscale_api_key_path = config.sops.secrets."headplane/oidcHeadscaleApiKey".path; + redirect_uri = "https://oidc.example.com/admin/oidc/callback"; + }; + }; + }; + } + } + ]; + }; + }; +} +``` - # Configure the Headplane server application - settings = { - server = { - # host = "127.0.0.1"; - # port = 3000; - cookie_secret_path = config.sops.secrets.headplane_cookie_secret.path; - cookie_secure = true; - - agent = { - enabled = false; - # authkey_path = config.sops.secrets.headplane_server_agent_authkey.path; - # ttl = 180000; # milliseconds - # cache_path = "/var/lib/headplane/agent_cache.json"; - }; - }; - - headscale = { - # url = "http://127.0.0.1:8080"; - config_path = headscaleConfig; # Use the generated config file - # config_strict = true; - # tls_cert = "your-tls-cert"; # Alternative to tls_cert_path - # tls_cert_path = config.sops.secrets.headplane_tls_cert.path; - # tls_key = "your-tls-key"; # Alternative to tls_key_path - # tls_key_path = config.sops.secrets.headplane_tls_key.path; - }; - - oidc = { - # issuer = ""; - # client_id = ""; - # client_secret_path = config.sops.secrets.headplane_oidc_client_secret.path; - # client_secret = "your_oidc_client_secret"; # Alternative to client_secret_path - # disable_api_key_login = false; - # token_endpoint_auth_method = "client_secret_post"; - # headscale_api_key_path = config.sops.secrets.headplane_headscale_api_key.path; - # headscale_api_key = "your_headscale_api_key"; # Alternative to headscale_api_key_path - # redirect_uri = ""; - }; - }; - }; - }) - ]; - }; - }; - } - ``` - -For more details about each option, refer to the options documentation above. diff --git a/docs/NixOS-options.md b/docs/NixOS-options.md new file mode 100644 index 0000000..4d6800a --- /dev/null +++ b/docs/NixOS-options.md @@ -0,0 +1,881 @@ +## services\.headplane\.enable + + + +Whether to enable headplane\. + + + +*Type:* +boolean + + + +*Default:* +` false ` + + + +*Example:* +` true ` + +*Declared by:* + - [/nix/store/k8cbmm2xy0s8gk2kggksxy6zffzry4jg-source/nix/options\.nix](file:///nix/store/k8cbmm2xy0s8gk2kggksxy6zffzry4jg-source/nix/options.nix) + + + +## services\.headplane\.package + + + +The headplane package to use\. + + + +*Type:* +package + + + +*Default:* +` pkgs.headplane ` + +*Declared by:* + - [/nix/store/k8cbmm2xy0s8gk2kggksxy6zffzry4jg-source/nix/options\.nix](file:///nix/store/k8cbmm2xy0s8gk2kggksxy6zffzry4jg-source/nix/options.nix) + + + +## services\.headplane\.debug + +Enable debug logging + + + +*Type:* +boolean + + + +*Default:* +` false ` + +*Declared by:* + - [/nix/store/k8cbmm2xy0s8gk2kggksxy6zffzry4jg-source/nix/options\.nix](file:///nix/store/k8cbmm2xy0s8gk2kggksxy6zffzry4jg-source/nix/options.nix) + + + +## services\.headplane\.settings + + + +Headplane configuration options\. Generates a YAML config file\. +See: https://github\.com/tale/headplane/blob/main/config\.example\.yaml + + + +*Type:* +submodule + + + +*Default:* +` { } ` + +*Declared by:* + - [/nix/store/k8cbmm2xy0s8gk2kggksxy6zffzry4jg-source/nix/options\.nix](file:///nix/store/k8cbmm2xy0s8gk2kggksxy6zffzry4jg-source/nix/options.nix) + + + +## services\.headplane\.settings\.headscale + + + +Headscale specific settings for Headplane integration\. + + + +*Type:* +submodule + + + +*Default:* +` { } ` + +*Declared by:* + - [/nix/store/k8cbmm2xy0s8gk2kggksxy6zffzry4jg-source/nix/options\.nix](file:///nix/store/k8cbmm2xy0s8gk2kggksxy6zffzry4jg-source/nix/options.nix) + + + +## services\.headplane\.settings\.headscale\.config_path + + + +Path to the Headscale configuration file\. +This is optional, but HIGHLY recommended for the best experience\. +If this is read only, Headplane will show your configuration settings +in the Web UI, but they cannot be changed\. + + + +*Type:* +null or absolute path + + + +*Default:* +` null ` + + + +*Example:* +` "/etc/headscale/config.yaml" ` + +*Declared by:* + - [/nix/store/k8cbmm2xy0s8gk2kggksxy6zffzry4jg-source/nix/options\.nix](file:///nix/store/k8cbmm2xy0s8gk2kggksxy6zffzry4jg-source/nix/options.nix) + + + +## services\.headplane\.settings\.headscale\.config_strict + + + +Headplane internally validates the Headscale configuration +to ensure that it changes the configuration in a safe way\. +If you want to disable this validation, set this to false\. + + + +*Type:* +boolean + + + +*Default:* +` true ` + +*Declared by:* + - [/nix/store/k8cbmm2xy0s8gk2kggksxy6zffzry4jg-source/nix/options\.nix](file:///nix/store/k8cbmm2xy0s8gk2kggksxy6zffzry4jg-source/nix/options.nix) + + + +## services\.headplane\.settings\.headscale\.dns_records_path + + + +If you are using ` dns.extra_records_path ` in your Headscale configuration, you need to set this to the path for Headplane to be able to read the DNS records\. +Ensure that the file is both readable and writable by the Headplane process\. +When using this, Headplane will no longer need to automatically restart Headscale for DNS record changes\. + + + +*Type:* +null or absolute path + + + +*Default:* +` null ` + + + +*Example:* +` "/var/lib/headplane/extra_records.json" ` + +*Declared by:* + - [/nix/store/k8cbmm2xy0s8gk2kggksxy6zffzry4jg-source/nix/options\.nix](file:///nix/store/k8cbmm2xy0s8gk2kggksxy6zffzry4jg-source/nix/options.nix) + + + +## services\.headplane\.settings\.headscale\.public_url + + + +Public URL if differrent\. This affects certain parts of the web UI\. + + + +*Type:* +null or string + + + +*Default:* +` null ` + + + +*Example:* +` "https://headscale.example.com" ` + +*Declared by:* + - [/nix/store/k8cbmm2xy0s8gk2kggksxy6zffzry4jg-source/nix/options\.nix](file:///nix/store/k8cbmm2xy0s8gk2kggksxy6zffzry4jg-source/nix/options.nix) + + + +## services\.headplane\.settings\.headscale\.tls_cert_path + + + +Path to a file containing the TLS certificate\. + + + +*Type:* +null or absolute path + + + +*Default:* +` null ` + + + +*Example:* +` "config.sops.secrets.tls_cert.path" ` + +*Declared by:* + - [/nix/store/k8cbmm2xy0s8gk2kggksxy6zffzry4jg-source/nix/options\.nix](file:///nix/store/k8cbmm2xy0s8gk2kggksxy6zffzry4jg-source/nix/options.nix) + + + +## services\.headplane\.settings\.headscale\.url + + + +The URL to your Headscale instance\. +All API requests are routed through this URL\. +THIS IS NOT the gRPC endpoint, but the HTTP endpoint\. +IMPORTANT: If you are using TLS this MUST be set to ` https:// `\. + + + +*Type:* +string + + + +*Default:* +` "http://127.0.0.1:8080" ` + + + +*Example:* +` "https://headscale.example.com" ` + +*Declared by:* + - [/nix/store/k8cbmm2xy0s8gk2kggksxy6zffzry4jg-source/nix/options\.nix](file:///nix/store/k8cbmm2xy0s8gk2kggksxy6zffzry4jg-source/nix/options.nix) + + + +## services\.headplane\.settings\.integration + + + +Integration configurations for Headplane to interact with Headscale\. + + + +*Type:* +submodule + + + +*Default:* +` { } ` + +*Declared by:* + - [/nix/store/k8cbmm2xy0s8gk2kggksxy6zffzry4jg-source/nix/options\.nix](file:///nix/store/k8cbmm2xy0s8gk2kggksxy6zffzry4jg-source/nix/options.nix) + + + +## services\.headplane\.settings\.integration\.agent + + + +Agent configuration for the Headplane agent\. + + + +*Type:* +submodule + + + +*Default:* +` { } ` + +*Declared by:* + - [/nix/store/k8cbmm2xy0s8gk2kggksxy6zffzry4jg-source/nix/options\.nix](file:///nix/store/k8cbmm2xy0s8gk2kggksxy6zffzry4jg-source/nix/options.nix) + + + +## services\.headplane\.settings\.integration\.agent\.enabled + + + +The Headplane agent allows retrieving information about nodes\. +This allows the UI to display version, OS, and connectivity data\. +You will see the Headplane agent in your Tailnet as a node when it connects\. + + + +*Type:* +boolean + + + +*Default:* +` false ` + +*Declared by:* + - [/nix/store/k8cbmm2xy0s8gk2kggksxy6zffzry4jg-source/nix/options\.nix](file:///nix/store/k8cbmm2xy0s8gk2kggksxy6zffzry4jg-source/nix/options.nix) + + + +## services\.headplane\.settings\.integration\.agent\.package + + + +The headplane-agent package to use\. + + + +*Type:* +package + + + +*Default:* +` pkgs.headplane-agent ` + +*Declared by:* + - [/nix/store/k8cbmm2xy0s8gk2kggksxy6zffzry4jg-source/nix/options\.nix](file:///nix/store/k8cbmm2xy0s8gk2kggksxy6zffzry4jg-source/nix/options.nix) + + + +## services\.headplane\.settings\.integration\.agent\.cache_path + + + +Where to store the agent cache\. + + + +*Type:* +absolute path + + + +*Default:* +` "/var/lib/headplane/agent_cache.json" ` + +*Declared by:* + - [/nix/store/k8cbmm2xy0s8gk2kggksxy6zffzry4jg-source/nix/options\.nix](file:///nix/store/k8cbmm2xy0s8gk2kggksxy6zffzry4jg-source/nix/options.nix) + + + +## services\.headplane\.settings\.integration\.agent\.cache_ttl + + + +How long to cache agent information (in milliseconds)\. +If you want data to update faster, reduce the TTL, but this will increase the frequency of requests to Headscale\. + + + +*Type:* +signed integer + + + +*Default:* +` 180000 ` + +*Declared by:* + - [/nix/store/k8cbmm2xy0s8gk2kggksxy6zffzry4jg-source/nix/options\.nix](file:///nix/store/k8cbmm2xy0s8gk2kggksxy6zffzry4jg-source/nix/options.nix) + + + +## services\.headplane\.settings\.integration\.agent\.host_name + + + +Optionally change the name of the agent in the Tailnet + + + +*Type:* +string + + + +*Default:* +` "headplane-agent" ` + +*Declared by:* + - [/nix/store/k8cbmm2xy0s8gk2kggksxy6zffzry4jg-source/nix/options\.nix](file:///nix/store/k8cbmm2xy0s8gk2kggksxy6zffzry4jg-source/nix/options.nix) + + + +## services\.headplane\.settings\.integration\.agent\.pre_authkey_path + + + +Path to a file containing the agent preauth key\. +To connect to your Tailnet, you need to generate a pre-auth key\. +This can be done via the web UI or through the ` headscale ` CLI\. + + + +*Type:* +null or absolute path + + + +*Default:* +` null ` + + + +*Example:* +` "config.sops.secrets.agent_pre_authkey.path" ` + +*Declared by:* + - [/nix/store/k8cbmm2xy0s8gk2kggksxy6zffzry4jg-source/nix/options\.nix](file:///nix/store/k8cbmm2xy0s8gk2kggksxy6zffzry4jg-source/nix/options.nix) + + + +## services\.headplane\.settings\.integration\.agent\.work_dir + + + +Do not change this unless you are running a custom deployment\. +The work_dir represents where the agent will store its data to be able to automatically reauthenticate with your Tailnet\. +It needs to be writable by the user running the Headplane process\. + + + +*Type:* +absolute path + + + +*Default:* +` "/var/lib/headplane/agent" ` + +*Declared by:* + - [/nix/store/k8cbmm2xy0s8gk2kggksxy6zffzry4jg-source/nix/options\.nix](file:///nix/store/k8cbmm2xy0s8gk2kggksxy6zffzry4jg-source/nix/options.nix) + + + +## services\.headplane\.settings\.integration\.proc + + + +Native process integration settings\. + + + +*Type:* +submodule + + + +*Default:* +` { } ` + +*Declared by:* + - [/nix/store/k8cbmm2xy0s8gk2kggksxy6zffzry4jg-source/nix/options\.nix](file:///nix/store/k8cbmm2xy0s8gk2kggksxy6zffzry4jg-source/nix/options.nix) + + + +## services\.headplane\.settings\.integration\.proc\.enabled + + + +Enable “Native” integration that works when Headscale and +Headplane are running outside of a container\. There is no additional +configuration, but you need to ensure that the Headplane process +can terminate the Headscale process\. + + + +*Type:* +boolean + + + +*Default:* +` true ` + +*Declared by:* + - [/nix/store/k8cbmm2xy0s8gk2kggksxy6zffzry4jg-source/nix/options\.nix](file:///nix/store/k8cbmm2xy0s8gk2kggksxy6zffzry4jg-source/nix/options.nix) + + + +## services\.headplane\.settings\.oidc + + + +OIDC Configuration for authentication\. + + + +*Type:* +submodule + + + +*Default:* +` { } ` + +*Declared by:* + - [/nix/store/k8cbmm2xy0s8gk2kggksxy6zffzry4jg-source/nix/options\.nix](file:///nix/store/k8cbmm2xy0s8gk2kggksxy6zffzry4jg-source/nix/options.nix) + + + +## services\.headplane\.settings\.oidc\.client_id + + + +The client ID for the OIDC client\. + + + +*Type:* +string + + + +*Default:* +` "" ` + + + +*Example:* +` "your-client-id" ` + +*Declared by:* + - [/nix/store/k8cbmm2xy0s8gk2kggksxy6zffzry4jg-source/nix/options\.nix](file:///nix/store/k8cbmm2xy0s8gk2kggksxy6zffzry4jg-source/nix/options.nix) + + + +## services\.headplane\.settings\.oidc\.client_secret_path + + + +Path to a file containing the OIDC client secret\. + + + +*Type:* +null or absolute path + + + +*Default:* +` null ` + + + +*Example:* +` "config.sops.secrets.oidc_client_secret.path" ` + +*Declared by:* + - [/nix/store/k8cbmm2xy0s8gk2kggksxy6zffzry4jg-source/nix/options\.nix](file:///nix/store/k8cbmm2xy0s8gk2kggksxy6zffzry4jg-source/nix/options.nix) + + + +## services\.headplane\.settings\.oidc\.disable_api_key_login + + + +Whether to disable API key login\. + + + +*Type:* +boolean + + + +*Default:* +` false ` + +*Declared by:* + - [/nix/store/k8cbmm2xy0s8gk2kggksxy6zffzry4jg-source/nix/options\.nix](file:///nix/store/k8cbmm2xy0s8gk2kggksxy6zffzry4jg-source/nix/options.nix) + + + +## services\.headplane\.settings\.oidc\.headscale_api_key_path + + + +Path to a file containing the Headscale API key\. + + + +*Type:* +null or absolute path + + + +*Default:* +` null ` + + + +*Example:* +` "config.sops.secrets.headscale_api_key.path" ` + +*Declared by:* + - [/nix/store/k8cbmm2xy0s8gk2kggksxy6zffzry4jg-source/nix/options\.nix](file:///nix/store/k8cbmm2xy0s8gk2kggksxy6zffzry4jg-source/nix/options.nix) + + + +## services\.headplane\.settings\.oidc\.issuer + + + +URL to OpenID issuer\. + + + +*Type:* +string + + + +*Default:* +` "" ` + + + +*Example:* +` "https://provider.example.com/issuer-url" ` + +*Declared by:* + - [/nix/store/k8cbmm2xy0s8gk2kggksxy6zffzry4jg-source/nix/options\.nix](file:///nix/store/k8cbmm2xy0s8gk2kggksxy6zffzry4jg-source/nix/options.nix) + + + +## services\.headplane\.settings\.oidc\.redirect_uri + + + +This should point to your publicly accessible URL +for your Headplane instance with /admin/oidc/callback\. + + + +*Type:* +string + + + +*Default:* +` "" ` + + + +*Example:* +` "https://headscale.example.com/admin/oidc/callback" ` + +*Declared by:* + - [/nix/store/k8cbmm2xy0s8gk2kggksxy6zffzry4jg-source/nix/options\.nix](file:///nix/store/k8cbmm2xy0s8gk2kggksxy6zffzry4jg-source/nix/options.nix) + + + +## services\.headplane\.settings\.oidc\.token_endpoint_auth_method + + + +The token endpoint authentication method\. + + + +*Type:* +one of “client_secret_post”, “client_secret_basic”, “client_secret_jwt” + + + +*Default:* +` "client_secret_post" ` + +*Declared by:* + - [/nix/store/k8cbmm2xy0s8gk2kggksxy6zffzry4jg-source/nix/options\.nix](file:///nix/store/k8cbmm2xy0s8gk2kggksxy6zffzry4jg-source/nix/options.nix) + + + +## services\.headplane\.settings\.oidc\.user_storage_file + + + +Path to a file containing the users and their permissions for Headplane\. + + + +*Type:* +absolute path + + + +*Default:* +` "/var/lib/headplane/users.json" ` + + + +*Example:* +` "/var/lib/headplane/users.json" ` + +*Declared by:* + - [/nix/store/k8cbmm2xy0s8gk2kggksxy6zffzry4jg-source/nix/options\.nix](file:///nix/store/k8cbmm2xy0s8gk2kggksxy6zffzry4jg-source/nix/options.nix) + + + +## services\.headplane\.settings\.server + + + +Server configuration for Headplane web application\. + + + +*Type:* +submodule + + + +*Default:* +` { } ` + +*Declared by:* + - [/nix/store/k8cbmm2xy0s8gk2kggksxy6zffzry4jg-source/nix/options\.nix](file:///nix/store/k8cbmm2xy0s8gk2kggksxy6zffzry4jg-source/nix/options.nix) + + + +## services\.headplane\.settings\.server\.cookie_secret_path + + + +Path to a file containing the cookie secret\. +The secret must be exactly 32 characters long\. + + + +*Type:* +null or absolute path + + + +*Default:* +` null ` + + + +*Example:* +` "config.sops.secrets.headplane_cookie.path" ` + +*Declared by:* + - [/nix/store/k8cbmm2xy0s8gk2kggksxy6zffzry4jg-source/nix/options\.nix](file:///nix/store/k8cbmm2xy0s8gk2kggksxy6zffzry4jg-source/nix/options.nix) + + + +## services\.headplane\.settings\.server\.cookie_secure + + + +Should the cookies only work over HTTPS? +Set to false if running via HTTP without a proxy\. +Recommended to be true in production\. + + + +*Type:* +boolean + + + +*Default:* +` true ` + +*Declared by:* + - [/nix/store/k8cbmm2xy0s8gk2kggksxy6zffzry4jg-source/nix/options\.nix](file:///nix/store/k8cbmm2xy0s8gk2kggksxy6zffzry4jg-source/nix/options.nix) + + + +## services\.headplane\.settings\.server\.data_path + + + +The path to persist Headplane specific data\. +All data going forward is stored in this directory, including the internal database and any cache related files\. +Data formats prior to 0\.6\.1 will automatically be migrated\. + + + +*Type:* +absolute path + + + +*Default:* +` "/var/lib/headplane" ` + + + +*Example:* +` "/var/lib/headplane" ` + +*Declared by:* + - [/nix/store/k8cbmm2xy0s8gk2kggksxy6zffzry4jg-source/nix/options\.nix](file:///nix/store/k8cbmm2xy0s8gk2kggksxy6zffzry4jg-source/nix/options.nix) + + + +## services\.headplane\.settings\.server\.host + + + +The host address to bind to\. + + + +*Type:* +string + + + +*Default:* +` "127.0.0.1" ` + + + +*Example:* +` "0.0.0.0" ` + +*Declared by:* + - [/nix/store/k8cbmm2xy0s8gk2kggksxy6zffzry4jg-source/nix/options\.nix](file:///nix/store/k8cbmm2xy0s8gk2kggksxy6zffzry4jg-source/nix/options.nix) + + + +## services\.headplane\.settings\.server\.port + + + +The port to listen on\. + + + +*Type:* +16 bit unsigned integer; between 0 and 65535 (both inclusive) + + + +*Default:* +` 3000 ` + +*Declared by:* + - [/nix/store/k8cbmm2xy0s8gk2kggksxy6zffzry4jg-source/nix/options\.nix](file:///nix/store/k8cbmm2xy0s8gk2kggksxy6zffzry4jg-source/nix/options.nix) + + diff --git a/flake.nix b/flake.nix index 4ed07bb..d544dd0 100644 --- a/flake.nix +++ b/flake.nix @@ -31,6 +31,7 @@ rec { packages = { headplane = pkgs.callPackage ./nix/package.nix {headplane-ssh-wasm = packages.headplane-ssh-wasm;}; headplane-agent = pkgs.callPackage ./nix/agent.nix {}; + headplane-nixos-docs = pkgs.callPackage ./nix/docs.nix {}; headplane-ssh-wasm = pkgs.callPackage ./nix/ssh-wasm.nix {}; }; checks.default = pkgs.symlinkJoin { @@ -64,6 +65,7 @@ rec { overlays.default = final: prev: { headplane = final.callPackage ./nix/package.nix {headplane-ssh-wasm = final.headplane-ssh-wasm;}; headplane-agent = final.callPackage ./nix/agent.nix {}; + headplane-nixos-docs = final.callPackage ./nix/docs.nix {}; headplane-ssh-wasm = final.callPackage ./nix/ssh-wasm.nix {}; }; nixosModules.headplane = import ./nix/module.nix; diff --git a/mise.toml b/mise.toml index 559c243..a158439 100644 --- a/mise.toml +++ b/mise.toml @@ -32,6 +32,15 @@ run = [ 'go build -ldflags="-s -w -X main.imageTag=$IMAGE_TAG" -o build/sh ./cmd/fake_sh' ] +[tasks.build-nixos-docs] +alias = ["nixos-docs"] +description = "Builds NixOS module documentation" +run = [ + 'nix build .#headplane-nixos-docs', + 'cp --dereference --force ./result ./docs/NixOS-options.md', + 'sed -i "s;\[/nix.*;\[nix/options.nix\](https://github.com/tale/headplane/blob/main/nix/options.nix);" ./docs/NixOS-options.md' +] + [tasks.generate-caddy-certs] alias = ["mkcert"] dir = "{{cwd}}/test/caddy/certs" diff --git a/nix/docs.nix b/nix/docs.nix new file mode 100644 index 0000000..84ea453 --- /dev/null +++ b/nix/docs.nix @@ -0,0 +1,24 @@ +{ + lib, + nixosOptionsDoc, + runCommand, + ... +}: let + eval = lib.evalModules { + modules = [./options.nix]; + }; + transformOptions = opt: + if (lib.hasPrefix "_" opt.name) + then + opt + // { + internal = true; + visible = false; + } + else opt; + optionsDoc = nixosOptionsDoc { + inherit (eval) options; + inherit transformOptions; + }; +in + runCommand "headplane-nixos-docs.md" {} "cat ${optionsDoc.optionsCommonMark} > $out" diff --git a/nix/module.nix b/nix/module.nix index 9c163a1..0c30179 100644 --- a/nix/module.nix +++ b/nix/module.nix @@ -8,12 +8,8 @@ (lib) filterAttrs filterAttrsRecursive - mkEnableOption mkIf - mkOption - mkPackageOption recursiveUpdate - types updateManyAttrsByPath ; cfg = config.services.headplane; @@ -32,313 +28,7 @@ settingsWithoutNulls = filterAttrsRecursive (key: value: value != null) settingsWithoutAgentPackage; settingsFile = settingsFormat.generate "headplane-config.yaml" settingsWithoutNulls; in { - options.services.headplane = { - enable = mkEnableOption "headplane"; - package = mkPackageOption pkgs "headplane" {}; - - debug = mkOption { - type = types.bool; - default = false; - description = "Enable debug logging"; - }; - - settings = mkOption { - description = '' - Headplane configuration options. Generates a YAML config file. - See: https://github.com/tale/headplane/blob/main/config.example.yaml - ''; - type = types.submodule { - options = { - server = mkOption { - type = types.submodule { - options = { - host = mkOption { - type = types.str; - default = "127.0.0.1"; - description = "The host address to bind to."; - example = "0.0.0.0"; - }; - - port = mkOption { - type = types.port; - default = 3000; - description = "The port to listen on."; - }; - - cookie_secret_path = mkOption { - type = types.nullOr types.path; - default = null; - description = '' - Path to a file containing the cookie secret. - The secret must be exactly 32 characters long. - ''; - example = "config.sops.secrets.headplane_cookie.path"; - }; - - cookie_secure = mkOption { - type = types.bool; - default = true; - description = '' - Should the cookies only work over HTTPS? - Set to false if running via HTTP without a proxy. - Recommended to be true in production. - ''; - }; - - data_path = mkOption { - type = types.path; - default = "/var/lib/headplane"; - description = '' - The path to persist Headplane specific data. - All data going forward is stored in this directory, including the internal database and any cache related files. - Data formats prior to 0.6.1 will automatically be migrated. - ''; - example = "/var/lib/headplane"; - }; - }; - }; - default = {}; - description = "Server configuration for Headplane web application."; - }; - - headscale = mkOption { - type = types.submodule { - options = { - url = mkOption { - type = types.str; - default = "http://127.0.0.1:8080"; - description = '' - The URL to your Headscale instance. - All API requests are routed through this URL. - THIS IS NOT the gRPC endpoint, but the HTTP endpoint. - IMPORTANT: If you are using TLS this MUST be set to `https://`. - ''; - example = "https://headscale.example.com"; - }; - - tls_cert_path = mkOption { - type = types.nullOr types.path; - default = null; - description = '' - Path to a file containing the TLS certificate. - ''; - example = "config.sops.secrets.tls_cert.path"; - }; - - public_url = mkOption { - type = types.nullOr types.str; - default = null; - description = "Public URL if differrent. This affects certain parts of the web UI."; - example = "https://headscale.example.com"; - }; - - config_path = mkOption { - type = types.nullOr types.path; - default = null; - description = '' - Path to the Headscale configuration file. - This is optional, but HIGHLY recommended for the best experience. - If this is read only, Headplane will show your configuration settings - in the Web UI, but they cannot be changed. - ''; - example = "/etc/headscale/config.yaml"; - }; - - config_strict = mkOption { - type = types.bool; - default = true; - description = '' - Headplane internally validates the Headscale configuration - to ensure that it changes the configuration in a safe way. - If you want to disable this validation, set this to false. - ''; - }; - - dns_records_path = mkOption { - type = types.nullOr types.path; - default = null; - description = '' - If you are using `dns.extra_records_path` in your Headscale configuration, you need to set this to the path for Headplane to be able to read the DNS records. - Ensure that the file is both readable and writable by the Headplane process. - When using this, Headplane will no longer need to automatically restart Headscale for DNS record changes. - ''; - example = "/var/lib/headplane/extra_records.json"; - }; - }; - }; - default = {}; - description = "Headscale specific settings for Headplane integration."; - }; - - integration = mkOption { - type = types.submodule { - options = { - agent = mkOption { - type = types.submodule { - options = { - enabled = mkOption { - type = types.bool; - default = false; - description = '' - The Headplane agent allows retrieving information about nodes. - This allows the UI to display version, OS, and connectivity data. - You will see the Headplane agent in your Tailnet as a node when it connects. - ''; - }; - - pre_authkey_path = mkOption { - type = types.nullOr types.path; - default = null; - description = '' - Path to a file containing the agent preauth key. - To connect to your Tailnet, you need to generate a pre-auth key. - This can be done via the web UI or through the `headscale` CLI. - ''; - example = "config.sops.secrets.agent_pre_authkey.path"; - }; - - host_name = mkOption { - type = types.str; - default = "headplane-agent"; - description = "Optionally change the name of the agent in the Tailnet"; - }; - - cache_ttl = mkOption { - type = types.int; - default = 180000; - description = '' - How long to cache agent information (in milliseconds). - If you want data to update faster, reduce the TTL, but this will increase the frequency of requests to Headscale. - ''; - }; - - cache_path = mkOption { - type = types.path; - default = "/var/lib/headplane/agent_cache.json"; - description = "Where to store the agent cache."; - }; - - work_dir = mkOption { - type = types.path; - default = "/var/lib/headplane/agent"; - description = '' - Do not change this unless you are running a custom deployment. - The work_dir represents where the agent will store its data to be able to automatically reauthenticate with your Tailnet. - It needs to be writable by the user running the Headplane process. - ''; - }; - - package = mkPackageOption pkgs "headplane-agent" {}; - }; - }; - default = {}; - description = "Agent configuration for the Headplane agent."; - }; - - proc = mkOption { - type = types.submodule { - options = { - enabled = mkOption { - type = types.bool; - default = true; - description = '' - Enable "Native" integration that works when Headscale and - Headplane are running outside of a container. There is no additional - configuration, but you need to ensure that the Headplane process - can terminate the Headscale process. - ''; - }; - }; - }; - default = {}; - description = "Native process integration settings."; - }; - }; - }; - default = {}; - description = "Integration configurations for Headplane to interact with Headscale."; - }; - - oidc = mkOption { - type = types.submodule { - options = { - issuer = mkOption { - type = types.str; - default = ""; - description = "URL to OpenID issuer."; - example = "https://provider.example.com/issuer-url"; - }; - - client_id = mkOption { - type = types.str; - default = ""; - description = "The client ID for the OIDC client."; - example = "your-client-id"; - }; - - client_secret_path = mkOption { - type = types.nullOr types.path; - default = null; - description = '' - Path to a file containing the OIDC client secret. - ''; - example = "config.sops.secrets.oidc_client_secret.path"; - }; - - disable_api_key_login = mkOption { - type = types.bool; - default = false; - description = "Whether to disable API key login."; - }; - - token_endpoint_auth_method = mkOption { - type = types.enum [ - "client_secret_post" - "client_secret_basic" - "client_secret_jwt" - ]; - default = "client_secret_post"; - description = "The token endpoint authentication method."; - }; - - headscale_api_key_path = mkOption { - type = types.nullOr types.path; - default = null; - description = '' - Path to a file containing the Headscale API key. - ''; - example = "config.sops.secrets.headscale_api_key.path"; - }; - - redirect_uri = mkOption { - type = types.str; - default = ""; - description = '' - This should point to your publicly accessible URL - for your Headplane instance with /admin/oidc/callback. - ''; - example = "https://headscale.example.com/admin/oidc/callback"; - }; - - user_storage_file = mkOption { - type = types.path; - default = "/var/lib/headplane/users.json"; - description = '' - Path to a file containing the users and their permissions for Headplane. - ''; - example = "/var/lib/headplane/users.json"; - }; - }; - }; - default = {}; - description = "OIDC Configuration for authentication."; - }; - }; - }; - default = {}; - }; - }; - + imports = [./options.nix]; config = mkIf cfg.enable { environment = { systemPackages = [cfg.package]; diff --git a/nix/options.nix b/nix/options.nix new file mode 100644 index 0000000..d175597 --- /dev/null +++ b/nix/options.nix @@ -0,0 +1,320 @@ +{ + lib, + pkgs, + ... +}: let + inherit + (lib) + mkEnableOption + mkOption + mkPackageOption + types + ; +in { + options.services.headplane = { + enable = mkEnableOption "headplane"; + package = mkPackageOption pkgs "headplane" {}; + + debug = mkOption { + type = types.bool; + default = false; + description = "Enable debug logging"; + }; + + settings = mkOption { + description = '' + Headplane configuration options. Generates a YAML config file. + See: https://github.com/tale/headplane/blob/main/config.example.yaml + ''; + type = types.submodule { + options = { + server = mkOption { + type = types.submodule { + options = { + host = mkOption { + type = types.str; + default = "127.0.0.1"; + description = "The host address to bind to."; + example = "0.0.0.0"; + }; + + port = mkOption { + type = types.port; + default = 3000; + description = "The port to listen on."; + }; + + cookie_secret_path = mkOption { + type = types.nullOr types.path; + default = null; + description = '' + Path to a file containing the cookie secret. + The secret must be exactly 32 characters long. + ''; + example = "config.sops.secrets.headplane_cookie.path"; + }; + + cookie_secure = mkOption { + type = types.bool; + default = true; + description = '' + Should the cookies only work over HTTPS? + Set to false if running via HTTP without a proxy. + Recommended to be true in production. + ''; + }; + + data_path = mkOption { + type = types.path; + default = "/var/lib/headplane"; + description = '' + The path to persist Headplane specific data. + All data going forward is stored in this directory, including the internal database and any cache related files. + Data formats prior to 0.6.1 will automatically be migrated. + ''; + example = "/var/lib/headplane"; + }; + }; + }; + default = {}; + description = "Server configuration for Headplane web application."; + }; + + headscale = mkOption { + type = types.submodule { + options = { + url = mkOption { + type = types.str; + default = "http://127.0.0.1:8080"; + description = '' + The URL to your Headscale instance. + All API requests are routed through this URL. + THIS IS NOT the gRPC endpoint, but the HTTP endpoint. + IMPORTANT: If you are using TLS this MUST be set to `https://`. + ''; + example = "https://headscale.example.com"; + }; + + tls_cert_path = mkOption { + type = types.nullOr types.path; + default = null; + description = '' + Path to a file containing the TLS certificate. + ''; + example = "config.sops.secrets.tls_cert.path"; + }; + + public_url = mkOption { + type = types.nullOr types.str; + default = null; + description = "Public URL if differrent. This affects certain parts of the web UI."; + example = "https://headscale.example.com"; + }; + + config_path = mkOption { + type = types.nullOr types.path; + default = null; + description = '' + Path to the Headscale configuration file. + This is optional, but HIGHLY recommended for the best experience. + If this is read only, Headplane will show your configuration settings + in the Web UI, but they cannot be changed. + ''; + example = "/etc/headscale/config.yaml"; + }; + + config_strict = mkOption { + type = types.bool; + default = true; + description = '' + Headplane internally validates the Headscale configuration + to ensure that it changes the configuration in a safe way. + If you want to disable this validation, set this to false. + ''; + }; + + dns_records_path = mkOption { + type = types.nullOr types.path; + default = null; + description = '' + If you are using `dns.extra_records_path` in your Headscale configuration, you need to set this to the path for Headplane to be able to read the DNS records. + Ensure that the file is both readable and writable by the Headplane process. + When using this, Headplane will no longer need to automatically restart Headscale for DNS record changes. + ''; + example = "/var/lib/headplane/extra_records.json"; + }; + }; + }; + default = {}; + description = "Headscale specific settings for Headplane integration."; + }; + + integration = mkOption { + type = types.submodule { + options = { + agent = mkOption { + type = types.submodule { + options = { + enabled = mkOption { + type = types.bool; + default = false; + description = '' + The Headplane agent allows retrieving information about nodes. + This allows the UI to display version, OS, and connectivity data. + You will see the Headplane agent in your Tailnet as a node when it connects. + ''; + }; + + pre_authkey_path = mkOption { + type = types.nullOr types.path; + default = null; + description = '' + Path to a file containing the agent preauth key. + To connect to your Tailnet, you need to generate a pre-auth key. + This can be done via the web UI or through the `headscale` CLI. + ''; + example = "config.sops.secrets.agent_pre_authkey.path"; + }; + + host_name = mkOption { + type = types.str; + default = "headplane-agent"; + description = "Optionally change the name of the agent in the Tailnet"; + }; + + cache_ttl = mkOption { + type = types.int; + default = 180000; + description = '' + How long to cache agent information (in milliseconds). + If you want data to update faster, reduce the TTL, but this will increase the frequency of requests to Headscale. + ''; + }; + + cache_path = mkOption { + type = types.path; + default = "/var/lib/headplane/agent_cache.json"; + description = "Where to store the agent cache."; + }; + + work_dir = mkOption { + type = types.path; + default = "/var/lib/headplane/agent"; + description = '' + Do not change this unless you are running a custom deployment. + The work_dir represents where the agent will store its data to be able to automatically reauthenticate with your Tailnet. + It needs to be writable by the user running the Headplane process. + ''; + }; + + package = mkPackageOption pkgs "headplane-agent" {}; + }; + }; + default = {}; + description = "Agent configuration for the Headplane agent."; + }; + + proc = mkOption { + type = types.submodule { + options = { + enabled = mkOption { + type = types.bool; + default = true; + description = '' + Enable "Native" integration that works when Headscale and + Headplane are running outside of a container. There is no additional + configuration, but you need to ensure that the Headplane process + can terminate the Headscale process. + ''; + }; + }; + }; + default = {}; + description = "Native process integration settings."; + }; + }; + }; + default = {}; + description = "Integration configurations for Headplane to interact with Headscale."; + }; + + oidc = mkOption { + type = types.submodule { + options = { + issuer = mkOption { + type = types.str; + default = ""; + description = "URL to OpenID issuer."; + example = "https://provider.example.com/issuer-url"; + }; + + client_id = mkOption { + type = types.str; + default = ""; + description = "The client ID for the OIDC client."; + example = "your-client-id"; + }; + + client_secret_path = mkOption { + type = types.nullOr types.path; + default = null; + description = '' + Path to a file containing the OIDC client secret. + ''; + example = "config.sops.secrets.oidc_client_secret.path"; + }; + + disable_api_key_login = mkOption { + type = types.bool; + default = false; + description = "Whether to disable API key login."; + }; + + token_endpoint_auth_method = mkOption { + type = types.enum [ + "client_secret_post" + "client_secret_basic" + "client_secret_jwt" + ]; + default = "client_secret_post"; + description = "The token endpoint authentication method."; + }; + + headscale_api_key_path = mkOption { + type = types.nullOr types.path; + default = null; + description = '' + Path to a file containing the Headscale API key. + ''; + example = "config.sops.secrets.headscale_api_key.path"; + }; + + redirect_uri = mkOption { + type = types.str; + default = ""; + description = '' + This should point to your publicly accessible URL + for your Headplane instance with /admin/oidc/callback. + ''; + example = "https://headscale.example.com/admin/oidc/callback"; + }; + + user_storage_file = mkOption { + type = types.path; + default = "/var/lib/headplane/users.json"; + description = '' + Path to a file containing the users and their permissions for Headplane. + ''; + example = "/var/lib/headplane/users.json"; + }; + }; + }; + default = {}; + description = "OIDC Configuration for authentication."; + }; + }; + }; + default = {}; + }; + }; +} From 9192013bfeca51f6bb32aa67a26e35fffe2f876d Mon Sep 17 00:00:00 2001 From: Igor Ramazanov Date: Sat, 9 Aug 2025 21:35:27 +0000 Subject: [PATCH 67/97] feat: documentation website using Vitepress Adds documentation website using https://vitepress.dev. Some decisions are questionable, for example creation of a symbolic link for the README in the `docs/`. I wanted to preserve the ability to read docs using Github. Maybe not needed. Another issue, is the page outline for the `NixOS` chapter is too narrow. But should serve as a scaffold/basis for doing it properly. --- .github/workflows/docs.yml | 47 + .gitignore | 2 + README.md | 22 +- docs/.vitepress/config.ts | 43 + docs/Bare-Metal.md | 4 +- docs/CHANGELOG.md | 1 + docs/Configuration.md | 2 +- docs/Integrated-Mode.md | 10 +- docs/Nix.md | 4 +- docs/NixOS-options.md | 74 +- docs/README.md | 1 + docs/Simple-Mode.md | 6 +- {assets => docs/assets}/acls-dark.png | Bin {assets => docs/assets}/acls-light.png | Bin {assets => docs/assets}/dns-dark.png | Bin {assets => docs/assets}/dns-light.png | Bin {assets => docs/assets}/machine-dark.png | Bin {assets => docs/assets}/machine-light.png | Bin {assets => docs/assets}/preview-dark.png | Bin {assets => docs/assets}/preview-light.png | Bin docs/index.md | 22 + nix/package.nix | 2 +- package.json | 18 +- pnpm-lock.yaml | 1295 +++++++++++++++++++++ 24 files changed, 1487 insertions(+), 66 deletions(-) create mode 100644 .github/workflows/docs.yml create mode 100644 docs/.vitepress/config.ts create mode 120000 docs/CHANGELOG.md create mode 120000 docs/README.md rename {assets => docs/assets}/acls-dark.png (100%) rename {assets => docs/assets}/acls-light.png (100%) rename {assets => docs/assets}/dns-dark.png (100%) rename {assets => docs/assets}/dns-light.png (100%) rename {assets => docs/assets}/machine-dark.png (100%) rename {assets => docs/assets}/machine-light.png (100%) rename {assets => docs/assets}/preview-dark.png (100%) rename {assets => docs/assets}/preview-light.png (100%) create mode 100644 docs/index.md diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml new file mode 100644 index 0000000..299ac8c --- /dev/null +++ b/.github/workflows/docs.yml @@ -0,0 +1,47 @@ +name: Deploy VitePress site to Pages +on: + push: + branches: ["main", "next", "next-nix-generate-docs"] + 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 + with: + fetch-depth: 1 + - uses: pnpm/action-setup@v3 + - name: Setup Node + uses: actions/setup-node@v4 + with: + node-version: 22 + cache: pnpm + - name: Setup Pages + uses: actions/configure-pages@v4 + - name: Install dependencies + run: pnpm install + - name: Build with VitePress + run: pnpm run docs:build + - name: Upload artifact + uses: actions/upload-pages-artifact@v3 + with: + path: docs/.vitepress/dist + deploy: + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + needs: build + runs-on: ubuntu-latest + name: Deploy + steps: + - name: Deploy to GitHub Pages + id: deployment + uses: actions/deploy-pages@v4 diff --git a/.gitignore b/.gitignore index 6368630..f32916b 100644 --- a/.gitignore +++ b/.gitignore @@ -6,5 +6,7 @@ node_modules .env app/hp_ssh.wasm app/wasm_exec.js +/docs/.vitepress/dist/ +/docs/.vitepress/cache/ /.direnv diff --git a/README.md b/README.md index 28086f3..01e16c2 100644 --- a/README.md +++ b/README.md @@ -4,15 +4,15 @@ Preview @@ -44,12 +44,12 @@ There are 2 ways to deploy Headplane: Simple mode does not include the automatic management of DNS and Headplane settings, requiring manual editing and reloading when making changes. -### Versioning +## Versioning Headplane uses [semantic versioning](https://semver.org/) for its releases (since v0.6.0). Pre-release builds are available under the `next` tag and get updated when a new release PR is opened and actively in testing. -### Contributing +## Contributing Headplane is an open-source project and contributions are welcome! If you have any suggestions, bug reports, or feature requests, please open an issue. Also refer to the [contributor guidelines](./docs/CONTRIBUTING.md) for more info. @@ -59,30 +59,30 @@ refer to the [contributor guidelines](./docs/CONTRIBUTING.md) for more info. ACLs Machine Management diff --git a/docs/.vitepress/config.ts b/docs/.vitepress/config.ts new file mode 100644 index 0000000..18950f6 --- /dev/null +++ b/docs/.vitepress/config.ts @@ -0,0 +1,43 @@ +import { defineConfig } from "vitepress"; + +// https://vitepress.dev/reference/site-config +export default defineConfig({ + title: "Headplane", + description: "Documentation", + // base: "https://headplane.net", + ignoreDeadLinks: ["/docs/Integrated-Mode", "/docs/Simple-Mode"], + themeConfig: { + // https://vitepress.dev/reference/default-theme-config + nav: [{ text: "Home", link: "/" }], + + sidebar: [ + { + text: "Chapters", + items: [ + { text: "Getting Started", link: "/README" }, + { text: "Configuration", link: "/Configuration" }, + { text: "Bare-Metal Mode", link: "/Bare-Metal" }, + { text: "Integrated Mode", link: "/Integrated-Mode" }, + { text: "Simple Mode", link: "/Simple-Mode" }, + { text: "Nix", link: "/Nix" }, + { text: "NixOS", link: "/NixOS-options" }, + { text: "Security", link: "/SECURITY" }, + { text: "Contributing", link: "/CONTRIBUTING" }, + { text: "Changelog", link: "/CHANGELOG" }, + ], + }, + ], + + socialLinks: [ + { icon: "github", link: "https://github.com/tale/headplane" }, + ], + + lastUpdated: { + text: "Updated at", + formatOptions: { + dateStyle: "full", + timeStyle: "medium", + }, + }, + }, +}); diff --git a/docs/Bare-Metal.md b/docs/Bare-Metal.md index 1283ab3..e8a1725 100644 --- a/docs/Bare-Metal.md +++ b/docs/Bare-Metal.md @@ -42,7 +42,7 @@ The structure of this folder is very important and should not be tampered with. ### Integrated Mode Since you are running Headplane in Bare-Metal, you most likely also are running -Headscale in Bare-Metal. Refer to the [**Integrated Mode**](/docs/Integrated-Mode.md) +Headscale in Bare-Metal. Refer to the [**Integrated Mode**](./Integrated-Mode.md) guide for instructions on setting up the integrated mode in Native Linux (/proc). ### Changing the Admin Path @@ -58,7 +58,7 @@ Just keep in mind that the admin path is not configurable at runtime, so you will need to rebuild the project if you want to change it. Also, anything aside from `/admin` is not officially supported and could break in future versions. -> Refer to the [**Configuration**](/docs/Configuration.md) guide for help with +> Refer to the [**Configuration**](./Configuration.md) guide for help with > setting up your `config.yaml` file to the appropriate values. ### Systemd Unit diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md new file mode 120000 index 0000000..81dc0a0 --- /dev/null +++ b/docs/CHANGELOG.md @@ -0,0 +1 @@ +./../CHANGELOG.md \ No newline at end of file diff --git a/docs/Configuration.md b/docs/Configuration.md index 5c8bdaf..7d07ac0 100644 --- a/docs/Configuration.md +++ b/docs/Configuration.md @@ -3,7 +3,7 @@ > Since 0.5, you will need to manually migrate your configuration to the new format. Headplane uses a configuration file to manage its settings -([**config.example.yaml**](../config.example.yaml)). By default, Headplane looks +([**config.example.yaml**](https://github.com/tale/headplane/blob/main/config.example.yaml)). By default, Headplane looks for a the file at `/etc/headplane/config.yaml`. This can be changed using the **`HEADPLANE_CONFIG_PATH`** environment variable to point to a different location. diff --git a/docs/Integrated-Mode.md b/docs/Integrated-Mode.md index ad7e1ff..85e5709 100644 --- a/docs/Integrated-Mode.md +++ b/docs/Integrated-Mode.md @@ -3,15 +3,15 @@ Integration Preview @@ -21,7 +21,7 @@ deployment method for most users, as it provides a more feature-complete experience. ## Deployment -> If you are not looking to deploy with Docker, follow the [**Bare-Metal**](/docs/Bare-Metal.md) deployment guide. +> If you are not looking to deploy with Docker, follow the [**Bare-Metal**](./Bare-Metal.md) deployment guide. > Refer to the `Integrated Mode` section at the bottom for caveats. Requirements: @@ -73,7 +73,7 @@ This will result in the Headplane UI being available at the `/admin` path of the server you deployed it on. The `/admin` path is currently not configurable unless you build the container yourself or run Headplane in Bare-Metal mode. -> Refer to the [**Configuration**](/docs/Configuration.md) guide for help with +> Refer to the [**Configuration**](./Configuration.md) guide for help with > setting up your `config.yaml` file to the appropriate values. ## Docker Integration diff --git a/docs/Nix.md b/docs/Nix.md index 5782044..12678ad 100644 --- a/docs/Nix.md +++ b/docs/Nix.md @@ -1,7 +1,7 @@ # Nix -[flake.nix](../flake.nix) provided: -``` +[flake.nix](https://github.com/tale/headplane/blob/main/flake.nix) provided: +```sh $ nix flake show github:tale/headplane --all-systems github:tale/headplane/ec6d455461955242393b60d9ce60c5123fa9784f?narHash=sha256-CM/vXzUiOed7i1Pp15KyV4FuIvumRlXnpF33dSWZZH4%3D ├───checks diff --git a/docs/NixOS-options.md b/docs/NixOS-options.md index 4d6800a..165afa6 100644 --- a/docs/NixOS-options.md +++ b/docs/NixOS-options.md @@ -20,7 +20,7 @@ boolean ` true ` *Declared by:* - - [/nix/store/k8cbmm2xy0s8gk2kggksxy6zffzry4jg-source/nix/options\.nix](file:///nix/store/k8cbmm2xy0s8gk2kggksxy6zffzry4jg-source/nix/options.nix) + - [nix/options.nix](https://github.com/tale/headplane/blob/main/nix/options.nix) @@ -41,7 +41,7 @@ package ` pkgs.headplane ` *Declared by:* - - [/nix/store/k8cbmm2xy0s8gk2kggksxy6zffzry4jg-source/nix/options\.nix](file:///nix/store/k8cbmm2xy0s8gk2kggksxy6zffzry4jg-source/nix/options.nix) + - [nix/options.nix](https://github.com/tale/headplane/blob/main/nix/options.nix) @@ -60,7 +60,7 @@ boolean ` false ` *Declared by:* - - [/nix/store/k8cbmm2xy0s8gk2kggksxy6zffzry4jg-source/nix/options\.nix](file:///nix/store/k8cbmm2xy0s8gk2kggksxy6zffzry4jg-source/nix/options.nix) + - [nix/options.nix](https://github.com/tale/headplane/blob/main/nix/options.nix) @@ -82,7 +82,7 @@ submodule ` { } ` *Declared by:* - - [/nix/store/k8cbmm2xy0s8gk2kggksxy6zffzry4jg-source/nix/options\.nix](file:///nix/store/k8cbmm2xy0s8gk2kggksxy6zffzry4jg-source/nix/options.nix) + - [nix/options.nix](https://github.com/tale/headplane/blob/main/nix/options.nix) @@ -103,7 +103,7 @@ submodule ` { } ` *Declared by:* - - [/nix/store/k8cbmm2xy0s8gk2kggksxy6zffzry4jg-source/nix/options\.nix](file:///nix/store/k8cbmm2xy0s8gk2kggksxy6zffzry4jg-source/nix/options.nix) + - [nix/options.nix](https://github.com/tale/headplane/blob/main/nix/options.nix) @@ -132,7 +132,7 @@ null or absolute path ` "/etc/headscale/config.yaml" ` *Declared by:* - - [/nix/store/k8cbmm2xy0s8gk2kggksxy6zffzry4jg-source/nix/options\.nix](file:///nix/store/k8cbmm2xy0s8gk2kggksxy6zffzry4jg-source/nix/options.nix) + - [nix/options.nix](https://github.com/tale/headplane/blob/main/nix/options.nix) @@ -155,7 +155,7 @@ boolean ` true ` *Declared by:* - - [/nix/store/k8cbmm2xy0s8gk2kggksxy6zffzry4jg-source/nix/options\.nix](file:///nix/store/k8cbmm2xy0s8gk2kggksxy6zffzry4jg-source/nix/options.nix) + - [nix/options.nix](https://github.com/tale/headplane/blob/main/nix/options.nix) @@ -183,7 +183,7 @@ null or absolute path ` "/var/lib/headplane/extra_records.json" ` *Declared by:* - - [/nix/store/k8cbmm2xy0s8gk2kggksxy6zffzry4jg-source/nix/options\.nix](file:///nix/store/k8cbmm2xy0s8gk2kggksxy6zffzry4jg-source/nix/options.nix) + - [nix/options.nix](https://github.com/tale/headplane/blob/main/nix/options.nix) @@ -209,7 +209,7 @@ null or string ` "https://headscale.example.com" ` *Declared by:* - - [/nix/store/k8cbmm2xy0s8gk2kggksxy6zffzry4jg-source/nix/options\.nix](file:///nix/store/k8cbmm2xy0s8gk2kggksxy6zffzry4jg-source/nix/options.nix) + - [nix/options.nix](https://github.com/tale/headplane/blob/main/nix/options.nix) @@ -235,7 +235,7 @@ null or absolute path ` "config.sops.secrets.tls_cert.path" ` *Declared by:* - - [/nix/store/k8cbmm2xy0s8gk2kggksxy6zffzry4jg-source/nix/options\.nix](file:///nix/store/k8cbmm2xy0s8gk2kggksxy6zffzry4jg-source/nix/options.nix) + - [nix/options.nix](https://github.com/tale/headplane/blob/main/nix/options.nix) @@ -264,7 +264,7 @@ string ` "https://headscale.example.com" ` *Declared by:* - - [/nix/store/k8cbmm2xy0s8gk2kggksxy6zffzry4jg-source/nix/options\.nix](file:///nix/store/k8cbmm2xy0s8gk2kggksxy6zffzry4jg-source/nix/options.nix) + - [nix/options.nix](https://github.com/tale/headplane/blob/main/nix/options.nix) @@ -285,7 +285,7 @@ submodule ` { } ` *Declared by:* - - [/nix/store/k8cbmm2xy0s8gk2kggksxy6zffzry4jg-source/nix/options\.nix](file:///nix/store/k8cbmm2xy0s8gk2kggksxy6zffzry4jg-source/nix/options.nix) + - [nix/options.nix](https://github.com/tale/headplane/blob/main/nix/options.nix) @@ -306,7 +306,7 @@ submodule ` { } ` *Declared by:* - - [/nix/store/k8cbmm2xy0s8gk2kggksxy6zffzry4jg-source/nix/options\.nix](file:///nix/store/k8cbmm2xy0s8gk2kggksxy6zffzry4jg-source/nix/options.nix) + - [nix/options.nix](https://github.com/tale/headplane/blob/main/nix/options.nix) @@ -329,7 +329,7 @@ boolean ` false ` *Declared by:* - - [/nix/store/k8cbmm2xy0s8gk2kggksxy6zffzry4jg-source/nix/options\.nix](file:///nix/store/k8cbmm2xy0s8gk2kggksxy6zffzry4jg-source/nix/options.nix) + - [nix/options.nix](https://github.com/tale/headplane/blob/main/nix/options.nix) @@ -350,7 +350,7 @@ package ` pkgs.headplane-agent ` *Declared by:* - - [/nix/store/k8cbmm2xy0s8gk2kggksxy6zffzry4jg-source/nix/options\.nix](file:///nix/store/k8cbmm2xy0s8gk2kggksxy6zffzry4jg-source/nix/options.nix) + - [nix/options.nix](https://github.com/tale/headplane/blob/main/nix/options.nix) @@ -371,7 +371,7 @@ absolute path ` "/var/lib/headplane/agent_cache.json" ` *Declared by:* - - [/nix/store/k8cbmm2xy0s8gk2kggksxy6zffzry4jg-source/nix/options\.nix](file:///nix/store/k8cbmm2xy0s8gk2kggksxy6zffzry4jg-source/nix/options.nix) + - [nix/options.nix](https://github.com/tale/headplane/blob/main/nix/options.nix) @@ -393,7 +393,7 @@ signed integer ` 180000 ` *Declared by:* - - [/nix/store/k8cbmm2xy0s8gk2kggksxy6zffzry4jg-source/nix/options\.nix](file:///nix/store/k8cbmm2xy0s8gk2kggksxy6zffzry4jg-source/nix/options.nix) + - [nix/options.nix](https://github.com/tale/headplane/blob/main/nix/options.nix) @@ -414,7 +414,7 @@ string ` "headplane-agent" ` *Declared by:* - - [/nix/store/k8cbmm2xy0s8gk2kggksxy6zffzry4jg-source/nix/options\.nix](file:///nix/store/k8cbmm2xy0s8gk2kggksxy6zffzry4jg-source/nix/options.nix) + - [nix/options.nix](https://github.com/tale/headplane/blob/main/nix/options.nix) @@ -442,7 +442,7 @@ null or absolute path ` "config.sops.secrets.agent_pre_authkey.path" ` *Declared by:* - - [/nix/store/k8cbmm2xy0s8gk2kggksxy6zffzry4jg-source/nix/options\.nix](file:///nix/store/k8cbmm2xy0s8gk2kggksxy6zffzry4jg-source/nix/options.nix) + - [nix/options.nix](https://github.com/tale/headplane/blob/main/nix/options.nix) @@ -465,7 +465,7 @@ absolute path ` "/var/lib/headplane/agent" ` *Declared by:* - - [/nix/store/k8cbmm2xy0s8gk2kggksxy6zffzry4jg-source/nix/options\.nix](file:///nix/store/k8cbmm2xy0s8gk2kggksxy6zffzry4jg-source/nix/options.nix) + - [nix/options.nix](https://github.com/tale/headplane/blob/main/nix/options.nix) @@ -486,7 +486,7 @@ submodule ` { } ` *Declared by:* - - [/nix/store/k8cbmm2xy0s8gk2kggksxy6zffzry4jg-source/nix/options\.nix](file:///nix/store/k8cbmm2xy0s8gk2kggksxy6zffzry4jg-source/nix/options.nix) + - [nix/options.nix](https://github.com/tale/headplane/blob/main/nix/options.nix) @@ -510,7 +510,7 @@ boolean ` true ` *Declared by:* - - [/nix/store/k8cbmm2xy0s8gk2kggksxy6zffzry4jg-source/nix/options\.nix](file:///nix/store/k8cbmm2xy0s8gk2kggksxy6zffzry4jg-source/nix/options.nix) + - [nix/options.nix](https://github.com/tale/headplane/blob/main/nix/options.nix) @@ -531,7 +531,7 @@ submodule ` { } ` *Declared by:* - - [/nix/store/k8cbmm2xy0s8gk2kggksxy6zffzry4jg-source/nix/options\.nix](file:///nix/store/k8cbmm2xy0s8gk2kggksxy6zffzry4jg-source/nix/options.nix) + - [nix/options.nix](https://github.com/tale/headplane/blob/main/nix/options.nix) @@ -557,7 +557,7 @@ string ` "your-client-id" ` *Declared by:* - - [/nix/store/k8cbmm2xy0s8gk2kggksxy6zffzry4jg-source/nix/options\.nix](file:///nix/store/k8cbmm2xy0s8gk2kggksxy6zffzry4jg-source/nix/options.nix) + - [nix/options.nix](https://github.com/tale/headplane/blob/main/nix/options.nix) @@ -583,7 +583,7 @@ null or absolute path ` "config.sops.secrets.oidc_client_secret.path" ` *Declared by:* - - [/nix/store/k8cbmm2xy0s8gk2kggksxy6zffzry4jg-source/nix/options\.nix](file:///nix/store/k8cbmm2xy0s8gk2kggksxy6zffzry4jg-source/nix/options.nix) + - [nix/options.nix](https://github.com/tale/headplane/blob/main/nix/options.nix) @@ -604,7 +604,7 @@ boolean ` false ` *Declared by:* - - [/nix/store/k8cbmm2xy0s8gk2kggksxy6zffzry4jg-source/nix/options\.nix](file:///nix/store/k8cbmm2xy0s8gk2kggksxy6zffzry4jg-source/nix/options.nix) + - [nix/options.nix](https://github.com/tale/headplane/blob/main/nix/options.nix) @@ -630,7 +630,7 @@ null or absolute path ` "config.sops.secrets.headscale_api_key.path" ` *Declared by:* - - [/nix/store/k8cbmm2xy0s8gk2kggksxy6zffzry4jg-source/nix/options\.nix](file:///nix/store/k8cbmm2xy0s8gk2kggksxy6zffzry4jg-source/nix/options.nix) + - [nix/options.nix](https://github.com/tale/headplane/blob/main/nix/options.nix) @@ -656,7 +656,7 @@ string ` "https://provider.example.com/issuer-url" ` *Declared by:* - - [/nix/store/k8cbmm2xy0s8gk2kggksxy6zffzry4jg-source/nix/options\.nix](file:///nix/store/k8cbmm2xy0s8gk2kggksxy6zffzry4jg-source/nix/options.nix) + - [nix/options.nix](https://github.com/tale/headplane/blob/main/nix/options.nix) @@ -683,7 +683,7 @@ string ` "https://headscale.example.com/admin/oidc/callback" ` *Declared by:* - - [/nix/store/k8cbmm2xy0s8gk2kggksxy6zffzry4jg-source/nix/options\.nix](file:///nix/store/k8cbmm2xy0s8gk2kggksxy6zffzry4jg-source/nix/options.nix) + - [nix/options.nix](https://github.com/tale/headplane/blob/main/nix/options.nix) @@ -704,7 +704,7 @@ one of “client_secret_post”, “client_secret_basic”, “client_secret_jwt ` "client_secret_post" ` *Declared by:* - - [/nix/store/k8cbmm2xy0s8gk2kggksxy6zffzry4jg-source/nix/options\.nix](file:///nix/store/k8cbmm2xy0s8gk2kggksxy6zffzry4jg-source/nix/options.nix) + - [nix/options.nix](https://github.com/tale/headplane/blob/main/nix/options.nix) @@ -730,7 +730,7 @@ absolute path ` "/var/lib/headplane/users.json" ` *Declared by:* - - [/nix/store/k8cbmm2xy0s8gk2kggksxy6zffzry4jg-source/nix/options\.nix](file:///nix/store/k8cbmm2xy0s8gk2kggksxy6zffzry4jg-source/nix/options.nix) + - [nix/options.nix](https://github.com/tale/headplane/blob/main/nix/options.nix) @@ -751,7 +751,7 @@ submodule ` { } ` *Declared by:* - - [/nix/store/k8cbmm2xy0s8gk2kggksxy6zffzry4jg-source/nix/options\.nix](file:///nix/store/k8cbmm2xy0s8gk2kggksxy6zffzry4jg-source/nix/options.nix) + - [nix/options.nix](https://github.com/tale/headplane/blob/main/nix/options.nix) @@ -778,7 +778,7 @@ null or absolute path ` "config.sops.secrets.headplane_cookie.path" ` *Declared by:* - - [/nix/store/k8cbmm2xy0s8gk2kggksxy6zffzry4jg-source/nix/options\.nix](file:///nix/store/k8cbmm2xy0s8gk2kggksxy6zffzry4jg-source/nix/options.nix) + - [nix/options.nix](https://github.com/tale/headplane/blob/main/nix/options.nix) @@ -801,7 +801,7 @@ boolean ` true ` *Declared by:* - - [/nix/store/k8cbmm2xy0s8gk2kggksxy6zffzry4jg-source/nix/options\.nix](file:///nix/store/k8cbmm2xy0s8gk2kggksxy6zffzry4jg-source/nix/options.nix) + - [nix/options.nix](https://github.com/tale/headplane/blob/main/nix/options.nix) @@ -829,7 +829,7 @@ absolute path ` "/var/lib/headplane" ` *Declared by:* - - [/nix/store/k8cbmm2xy0s8gk2kggksxy6zffzry4jg-source/nix/options\.nix](file:///nix/store/k8cbmm2xy0s8gk2kggksxy6zffzry4jg-source/nix/options.nix) + - [nix/options.nix](https://github.com/tale/headplane/blob/main/nix/options.nix) @@ -855,7 +855,7 @@ string ` "0.0.0.0" ` *Declared by:* - - [/nix/store/k8cbmm2xy0s8gk2kggksxy6zffzry4jg-source/nix/options\.nix](file:///nix/store/k8cbmm2xy0s8gk2kggksxy6zffzry4jg-source/nix/options.nix) + - [nix/options.nix](https://github.com/tale/headplane/blob/main/nix/options.nix) @@ -876,6 +876,6 @@ The port to listen on\. ` 3000 ` *Declared by:* - - [/nix/store/k8cbmm2xy0s8gk2kggksxy6zffzry4jg-source/nix/options\.nix](file:///nix/store/k8cbmm2xy0s8gk2kggksxy6zffzry4jg-source/nix/options.nix) + - [nix/options.nix](https://github.com/tale/headplane/blob/main/nix/options.nix) diff --git a/docs/README.md b/docs/README.md new file mode 120000 index 0000000..3dfb7d7 --- /dev/null +++ b/docs/README.md @@ -0,0 +1 @@ +./../README.md \ No newline at end of file diff --git a/docs/Simple-Mode.md b/docs/Simple-Mode.md index b6ba6d0..2660a44 100644 --- a/docs/Simple-Mode.md +++ b/docs/Simple-Mode.md @@ -4,10 +4,10 @@ Simple mode enables you to quickly deploy Headplane and is recommended for any testing or simple environments. It does not include the automatic management of DNS and Headplane settings, requiring manual editing and reloading when making changes. If you're looking for a more feature-complete deployment method, check -out [**Integrated Mode**](/docs/Integrated-Mode.md). +out [**Integrated Mode**](./Integrated-Mode.md). ## Deployment -> If you are not looking to deploy with Docker, follow the [**Bare-Metal**](/docs/Bare-Metal.md) deployment guide. +> If you are not looking to deploy with Docker, follow the [**Bare-Metal**](./Bare-Metal.md) deployment guide. Requirements: - Docker and Docker Compose @@ -33,5 +33,5 @@ This will result in the Headplane UI being available at the `/admin` path of the server you deployed it on. The `/admin` path is currently not configurable unless you build the container yourself or run Headplane in Bare-Metal mode. -> Refer to the [**Configuration**](/docs/Configuration.md) guide for help with +> Refer to the [**Configuration**](./Configuration.md) guide for help with > setting up your `config.yaml` file to the appropriate values. diff --git a/assets/acls-dark.png b/docs/assets/acls-dark.png similarity index 100% rename from assets/acls-dark.png rename to docs/assets/acls-dark.png diff --git a/assets/acls-light.png b/docs/assets/acls-light.png similarity index 100% rename from assets/acls-light.png rename to docs/assets/acls-light.png diff --git a/assets/dns-dark.png b/docs/assets/dns-dark.png similarity index 100% rename from assets/dns-dark.png rename to docs/assets/dns-dark.png diff --git a/assets/dns-light.png b/docs/assets/dns-light.png similarity index 100% rename from assets/dns-light.png rename to docs/assets/dns-light.png diff --git a/assets/machine-dark.png b/docs/assets/machine-dark.png similarity index 100% rename from assets/machine-dark.png rename to docs/assets/machine-dark.png diff --git a/assets/machine-light.png b/docs/assets/machine-light.png similarity index 100% rename from assets/machine-light.png rename to docs/assets/machine-light.png diff --git a/assets/preview-dark.png b/docs/assets/preview-dark.png similarity index 100% rename from assets/preview-dark.png rename to docs/assets/preview-dark.png diff --git a/assets/preview-light.png b/docs/assets/preview-light.png similarity index 100% rename from assets/preview-light.png rename to docs/assets/preview-light.png diff --git a/docs/index.md b/docs/index.md new file mode 100644 index 0000000..98e8701 --- /dev/null +++ b/docs/index.md @@ -0,0 +1,22 @@ +--- +# https://vitepress.dev/reference/default-theme-home-page +layout: home + +hero: + name: "A feature-complete Web UI for Headscale" + text: "Documentation" + tagline: "A feature-complete Web UI for Headscale" + actions: + - theme: brand + text: Getting Started + link: /README + +features: + - title: Web SSH + details: Connect to machines directly from your browser + - title: OIDC + details: Login through your favourite SSO provider + - title: And many more + details: Setup ACLs, DNS, pre-auth keys, etc. +--- + diff --git a/nix/package.nix b/nix/package.nix index bc6146b..6e4df32 100644 --- a/nix/package.nix +++ b/nix/package.nix @@ -29,7 +29,7 @@ in pnpmDeps = pnpm_10.fetchDeps { inherit pname version src; fetcherVersion = 2; - hash = "sha256-GNSpFqPobX6MDPUXxz2XwdZ2Wt7boN8aok52pGgpGoM="; + hash = "sha256-0r228fDAMcEmVDE4jrMBRzZyQPp3aE4SyxGp7Oap9dI="; }; buildPhase = '' diff --git a/package.json b/package.json index ffc8546..192c0b2 100644 --- a/package.json +++ b/package.json @@ -10,7 +10,10 @@ "dev": "HEADPLANE_LOAD_ENV_OVERRIDES=true HEADPLANE_CONFIG_PATH=./config.example.yaml react-router dev", "start": "node build/server/index.js", "typecheck": "tsc", - "test": "vitest run" + "test": "vitest run", + "docs:dev": "vitepress dev docs", + "docs:build": "vitepress build docs", + "docs:preview": "vitepress preview docs" }, "dependencies": { "@dnd-kit/core": "^6.3.1", @@ -67,6 +70,7 @@ "@types/node": "^22.15.17", "@types/websocket": "^1.0.10", "drizzle-kit": "^0.31.1", + "js-yaml": "^4.1.0", "lefthook": "^1.11.13", "postcss": "^8.5.4", "react-router-dom": "^7.6.1", @@ -77,7 +81,7 @@ "typescript": "^5.8.3", "vite": "^6.3.5", "vite-tsconfig-paths": "^5.1.4", - "js-yaml": "^4.1.0", + "vitepress": "^1.6.4", "vitest": "^3.1.3" }, "packageManager": "pnpm@10.4.0", @@ -90,8 +94,14 @@ "react-router-hono-server": "patches/react-router-hono-server.patch" }, "supportedArchitectures": { - "os": ["current", "linux"], - "cpu": ["x64", "arm64"] + "os": [ + "current", + "linux" + ], + "cpu": [ + "x64", + "arm64" + ] }, "onlyBuiltDependencies": [ "@biomejs/biome", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 21dd17d..e53843f 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -203,12 +203,91 @@ importers: vite-tsconfig-paths: specifier: ^5.1.4 version: 5.1.4(typescript@5.8.3)(vite@6.3.5(@types/node@22.15.24)(jiti@2.4.2)(lightningcss@1.30.1)(terser@5.39.0)(tsx@4.19.4)(yaml@2.8.0)) + vitepress: + specifier: ^1.6.4 + version: 1.6.4(@algolia/client-search@5.35.0)(@types/node@22.15.24)(@types/react@19.1.6)(lightningcss@1.30.1)(postcss@8.5.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(search-insights@2.17.3)(terser@5.39.0)(typescript@5.8.3) vitest: specifier: ^3.1.3 version: 3.2.4(@types/node@22.15.24)(jiti@2.4.2)(lightningcss@1.30.1)(terser@5.39.0)(tsx@4.19.4)(yaml@2.8.0) packages: + '@algolia/abtesting@1.1.0': + resolution: {integrity: sha512-sEyWjw28a/9iluA37KLGu8vjxEIlb60uxznfTUmXImy7H5NvbpSO6yYgmgH5KiD7j+zTUUihiST0jEP12IoXow==} + engines: {node: '>= 14.0.0'} + + '@algolia/autocomplete-core@1.17.7': + resolution: {integrity: sha512-BjiPOW6ks90UKl7TwMv7oNQMnzU+t/wk9mgIDi6b1tXpUek7MW0lbNOUHpvam9pe3lVCf4xPFT+lK7s+e+fs7Q==} + + '@algolia/autocomplete-plugin-algolia-insights@1.17.7': + resolution: {integrity: sha512-Jca5Ude6yUOuyzjnz57og7Et3aXjbwCSDf/8onLHSQgw1qW3ALl9mrMWaXb5FmPVkV3EtkD2F/+NkT6VHyPu9A==} + peerDependencies: + search-insights: '>= 1 < 3' + + '@algolia/autocomplete-preset-algolia@1.17.7': + resolution: {integrity: sha512-ggOQ950+nwbWROq2MOCIL71RE0DdQZsceqrg32UqnhDz8FlO9rL8ONHNsI2R1MH0tkgVIDKI/D0sMiUchsFdWA==} + peerDependencies: + '@algolia/client-search': '>= 4.9.1 < 6' + algoliasearch: '>= 4.9.1 < 6' + + '@algolia/autocomplete-shared@1.17.7': + resolution: {integrity: sha512-o/1Vurr42U/qskRSuhBH+VKxMvkkUVTLU6WZQr+L5lGZZLYWyhdzWjW0iGXY7EkwRTjBqvN2EsR81yCTGV/kmg==} + peerDependencies: + '@algolia/client-search': '>= 4.9.1 < 6' + algoliasearch: '>= 4.9.1 < 6' + + '@algolia/client-abtesting@5.35.0': + resolution: {integrity: sha512-uUdHxbfHdoppDVflCHMxRlj49/IllPwwQ2cQ8DLC4LXr3kY96AHBpW0dMyi6ygkn2MtFCc6BxXCzr668ZRhLBQ==} + engines: {node: '>= 14.0.0'} + + '@algolia/client-analytics@5.35.0': + resolution: {integrity: sha512-SunAgwa9CamLcRCPnPHx1V2uxdQwJGqb1crYrRWktWUdld0+B2KyakNEeVn5lln4VyeNtW17Ia7V7qBWyM/Skw==} + engines: {node: '>= 14.0.0'} + + '@algolia/client-common@5.35.0': + resolution: {integrity: sha512-ipE0IuvHu/bg7TjT2s+187kz/E3h5ssfTtjpg1LbWMgxlgiaZIgTTbyynM7NfpSJSKsgQvCQxWjGUO51WSCu7w==} + engines: {node: '>= 14.0.0'} + + '@algolia/client-insights@5.35.0': + resolution: {integrity: sha512-UNbCXcBpqtzUucxExwTSfAe8gknAJ485NfPN6o1ziHm6nnxx97piIbcBQ3edw823Tej2Wxu1C0xBY06KgeZ7gA==} + engines: {node: '>= 14.0.0'} + + '@algolia/client-personalization@5.35.0': + resolution: {integrity: sha512-/KWjttZ6UCStt4QnWoDAJ12cKlQ+fkpMtyPmBgSS2WThJQdSV/4UWcqCUqGH7YLbwlj3JjNirCu3Y7uRTClxvA==} + engines: {node: '>= 14.0.0'} + + '@algolia/client-query-suggestions@5.35.0': + resolution: {integrity: sha512-8oCuJCFf/71IYyvQQC+iu4kgViTODbXDk3m7yMctEncRSRV+u2RtDVlpGGfPlJQOrAY7OONwJlSHkmbbm2Kp/w==} + engines: {node: '>= 14.0.0'} + + '@algolia/client-search@5.35.0': + resolution: {integrity: sha512-FfmdHTrXhIduWyyuko1YTcGLuicVbhUyRjO3HbXE4aP655yKZgdTIfMhZ/V5VY9bHuxv/fGEh3Od1Lvv2ODNTg==} + engines: {node: '>= 14.0.0'} + + '@algolia/ingestion@1.35.0': + resolution: {integrity: sha512-gPzACem9IL1Co8mM1LKMhzn1aSJmp+Vp434An4C0OBY4uEJRcqsLN3uLBlY+bYvFg8C8ImwM9YRiKczJXRk0XA==} + engines: {node: '>= 14.0.0'} + + '@algolia/monitoring@1.35.0': + resolution: {integrity: sha512-w9MGFLB6ashI8BGcQoVt7iLgDIJNCn4OIu0Q0giE3M2ItNrssvb8C0xuwJQyTy1OFZnemG0EB1OvXhIHOvQwWw==} + engines: {node: '>= 14.0.0'} + + '@algolia/recommend@5.35.0': + resolution: {integrity: sha512-AhrVgaaXAb8Ue0u2nuRWwugt0dL5UmRgS9LXe0Hhz493a8KFeZVUE56RGIV3hAa6tHzmAV7eIoqcWTQvxzlJeQ==} + engines: {node: '>= 14.0.0'} + + '@algolia/requester-browser-xhr@5.35.0': + resolution: {integrity: sha512-diY415KLJZ6x1Kbwl9u96Jsz0OstE3asjXtJ9pmk1d+5gPuQ5jQyEsgC+WmEXzlec3iuVszm8AzNYYaqw6B+Zw==} + engines: {node: '>= 14.0.0'} + + '@algolia/requester-fetch@5.35.0': + resolution: {integrity: sha512-uydqnSmpAjrgo8bqhE9N1wgcB98psTRRQXcjc4izwMB7yRl9C8uuAQ/5YqRj04U0mMQ+fdu2fcNF6m9+Z1BzDQ==} + engines: {node: '>= 14.0.0'} + + '@algolia/requester-node-http@5.35.0': + resolution: {integrity: sha512-RgLX78ojYOrThJHrIiPzT4HW3yfQa0D7K+MQ81rhxqaNyNBu4F1r+72LNHYH/Z+y9I1Mrjrd/c/Ue5zfDgAEjQ==} + engines: {node: '>= 14.0.0'} + '@ampproject/remapping@2.3.0': resolution: {integrity: sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==} engines: {node: '>=6.0.0'} @@ -302,6 +381,11 @@ packages: engines: {node: '>=6.0.0'} hasBin: true + '@babel/parser@7.28.0': + resolution: {integrity: sha512-jVZGvOxOuNSsuQuLRTh13nU0AogFlw32w/MT+LV6D3sP5WdbW61E77RnkbaO2dUvmPAYrBDJXGn5gGS6tH4j8g==} + engines: {node: '>=6.0.0'} + hasBin: true + '@babel/plugin-syntax-decorators@7.27.1': resolution: {integrity: sha512-YMq8Z87Lhl8EGkmb0MwYkt36QnxC+fzCgrl66ereamPlYToRpIk5nUjKUY3QKLWq8mwUB1BgbeXcTJhZOCDg5A==} engines: {node: '>=6.9.0'} @@ -354,6 +438,10 @@ packages: resolution: {integrity: sha512-Y1GkI4ktrtvmawoSq+4FCVHNryea6uR+qUQy0AGxLSsjCX0nVmkYQMBLHDkXZuo5hGx7eYdnIaslsdBFm7zbUw==} engines: {node: '>=6.9.0'} + '@babel/types@7.28.2': + resolution: {integrity: sha512-ruv7Ae4J5dUYULmeXw1gmb7rYRz57OWCPM57pHojnLq/3Z1CK2lNSLTCVjxVk1F/TZHwOZZrOWi0ur95BbLxNQ==} + engines: {node: '>=6.9.0'} + '@biomejs/biome@1.9.4': resolution: {integrity: sha512-1rkd7G70+o9KkTn5KLmDYXihGoTaIGO9PIIN2ZB7UJxFrWw04CZHPYiMRjYsaDvVV7hP1dYNRLxSANLaBFGpog==} engines: {node: '>=14.21.3'} @@ -473,6 +561,29 @@ packages: peerDependencies: react: '>=16.8.0' + '@docsearch/css@3.8.2': + resolution: {integrity: sha512-y05ayQFyUmCXze79+56v/4HpycYF3uFqB78pLPrSV5ZKAlDuIAAJNhaRi8tTdRNXh05yxX/TyNnzD6LwSM89vQ==} + + '@docsearch/js@3.8.2': + resolution: {integrity: sha512-Q5wY66qHn0SwA7Taa0aDbHiJvaFJLOJyHmooQ7y8hlwwQLQ/5WwCcoX0g7ii04Qi2DJlHsd0XXzJ8Ypw9+9YmQ==} + + '@docsearch/react@3.8.2': + resolution: {integrity: sha512-xCRrJQlTt8N9GU0DG4ptwHRkfnSnD/YpdeaXe02iKfqs97TkZJv60yE+1eq/tjPcVnTW8dP5qLP7itifFVV5eg==} + peerDependencies: + '@types/react': '>= 16.8.0 < 19.0.0' + react: '>= 16.8.0 < 19.0.0' + react-dom: '>= 16.8.0 < 19.0.0' + search-insights: '>= 1 < 3' + peerDependenciesMeta: + '@types/react': + optional: true + react: + optional: true + react-dom: + optional: true + search-insights: + optional: true + '@drizzle-team/brocli@0.10.2': resolution: {integrity: sha512-z33Il7l5dKjUgGULTqBsQBQwckHh5AbIuxhdsIxDDiZAzBOrZO6q9ogcWC65kU382AfynTfgNumVcNIjuIua6w==} @@ -487,6 +598,12 @@ packages: resolution: {integrity: sha512-FxEMIkJKnodyA1OaCUoEvbYRkoZlLZ4d/eXFu9Fh8CbBBgP5EmZxrfTRyN0qpXZ4vOvqnE5YdRdcrmUUXuU+dA==} deprecated: 'Merged into tsx: https://tsx.is' + '@esbuild/aix-ppc64@0.21.5': + resolution: {integrity: sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==} + engines: {node: '>=12'} + cpu: [ppc64] + os: [aix] + '@esbuild/aix-ppc64@0.25.5': resolution: {integrity: sha512-9o3TMmpmftaCMepOdA5k/yDw8SfInyzWWTjYTFCX3kPSDJMROQTb8jg+h9Cnwnmm1vOzvxN7gIfB5V2ewpjtGA==} engines: {node: '>=18'} @@ -499,6 +616,12 @@ packages: cpu: [arm64] os: [android] + '@esbuild/android-arm64@0.21.5': + resolution: {integrity: sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==} + engines: {node: '>=12'} + cpu: [arm64] + os: [android] + '@esbuild/android-arm64@0.25.5': resolution: {integrity: sha512-VGzGhj4lJO+TVGV1v8ntCZWJktV7SGCs3Pn1GRWI1SBFtRALoomm8k5E9Pmwg3HOAal2VDc2F9+PM/rEY6oIDg==} engines: {node: '>=18'} @@ -511,6 +634,12 @@ packages: cpu: [arm] os: [android] + '@esbuild/android-arm@0.21.5': + resolution: {integrity: sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==} + engines: {node: '>=12'} + cpu: [arm] + os: [android] + '@esbuild/android-arm@0.25.5': resolution: {integrity: sha512-AdJKSPeEHgi7/ZhuIPtcQKr5RQdo6OO2IL87JkianiMYMPbCtot9fxPbrMiBADOWWm3T2si9stAiVsGbTQFkbA==} engines: {node: '>=18'} @@ -523,6 +652,12 @@ packages: cpu: [x64] os: [android] + '@esbuild/android-x64@0.21.5': + resolution: {integrity: sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==} + engines: {node: '>=12'} + cpu: [x64] + os: [android] + '@esbuild/android-x64@0.25.5': resolution: {integrity: sha512-D2GyJT1kjvO//drbRT3Hib9XPwQeWd9vZoBJn+bu/lVsOZ13cqNdDeqIF/xQ5/VmWvMduP6AmXvylO/PIc2isw==} engines: {node: '>=18'} @@ -535,6 +670,12 @@ packages: cpu: [arm64] os: [darwin] + '@esbuild/darwin-arm64@0.21.5': + resolution: {integrity: sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==} + engines: {node: '>=12'} + cpu: [arm64] + os: [darwin] + '@esbuild/darwin-arm64@0.25.5': resolution: {integrity: sha512-GtaBgammVvdF7aPIgH2jxMDdivezgFu6iKpmT+48+F8Hhg5J/sfnDieg0aeG/jfSvkYQU2/pceFPDKlqZzwnfQ==} engines: {node: '>=18'} @@ -547,6 +688,12 @@ packages: cpu: [x64] os: [darwin] + '@esbuild/darwin-x64@0.21.5': + resolution: {integrity: sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==} + engines: {node: '>=12'} + cpu: [x64] + os: [darwin] + '@esbuild/darwin-x64@0.25.5': resolution: {integrity: sha512-1iT4FVL0dJ76/q1wd7XDsXrSW+oLoquptvh4CLR4kITDtqi2e/xwXwdCVH8hVHU43wgJdsq7Gxuzcs6Iq/7bxQ==} engines: {node: '>=18'} @@ -559,6 +706,12 @@ packages: cpu: [arm64] os: [freebsd] + '@esbuild/freebsd-arm64@0.21.5': + resolution: {integrity: sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==} + engines: {node: '>=12'} + cpu: [arm64] + os: [freebsd] + '@esbuild/freebsd-arm64@0.25.5': resolution: {integrity: sha512-nk4tGP3JThz4La38Uy/gzyXtpkPW8zSAmoUhK9xKKXdBCzKODMc2adkB2+8om9BDYugz+uGV7sLmpTYzvmz6Sw==} engines: {node: '>=18'} @@ -571,6 +724,12 @@ packages: cpu: [x64] os: [freebsd] + '@esbuild/freebsd-x64@0.21.5': + resolution: {integrity: sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==} + engines: {node: '>=12'} + cpu: [x64] + os: [freebsd] + '@esbuild/freebsd-x64@0.25.5': resolution: {integrity: sha512-PrikaNjiXdR2laW6OIjlbeuCPrPaAl0IwPIaRv+SMV8CiM8i2LqVUHFC1+8eORgWyY7yhQY+2U2fA55mBzReaw==} engines: {node: '>=18'} @@ -583,6 +742,12 @@ packages: cpu: [arm64] os: [linux] + '@esbuild/linux-arm64@0.21.5': + resolution: {integrity: sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==} + engines: {node: '>=12'} + cpu: [arm64] + os: [linux] + '@esbuild/linux-arm64@0.25.5': resolution: {integrity: sha512-Z9kfb1v6ZlGbWj8EJk9T6czVEjjq2ntSYLY2cw6pAZl4oKtfgQuS4HOq41M/BcoLPzrUbNd+R4BXFyH//nHxVg==} engines: {node: '>=18'} @@ -595,6 +760,12 @@ packages: cpu: [arm] os: [linux] + '@esbuild/linux-arm@0.21.5': + resolution: {integrity: sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==} + engines: {node: '>=12'} + cpu: [arm] + os: [linux] + '@esbuild/linux-arm@0.25.5': resolution: {integrity: sha512-cPzojwW2okgh7ZlRpcBEtsX7WBuqbLrNXqLU89GxWbNt6uIg78ET82qifUy3W6OVww6ZWobWub5oqZOVtwolfw==} engines: {node: '>=18'} @@ -607,6 +778,12 @@ packages: cpu: [ia32] os: [linux] + '@esbuild/linux-ia32@0.21.5': + resolution: {integrity: sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==} + engines: {node: '>=12'} + cpu: [ia32] + os: [linux] + '@esbuild/linux-ia32@0.25.5': resolution: {integrity: sha512-sQ7l00M8bSv36GLV95BVAdhJ2QsIbCuCjh/uYrWiMQSUuV+LpXwIqhgJDcvMTj+VsQmqAHL2yYaasENvJ7CDKA==} engines: {node: '>=18'} @@ -619,6 +796,12 @@ packages: cpu: [loong64] os: [linux] + '@esbuild/linux-loong64@0.21.5': + resolution: {integrity: sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==} + engines: {node: '>=12'} + cpu: [loong64] + os: [linux] + '@esbuild/linux-loong64@0.25.5': resolution: {integrity: sha512-0ur7ae16hDUC4OL5iEnDb0tZHDxYmuQyhKhsPBV8f99f6Z9KQM02g33f93rNH5A30agMS46u2HP6qTdEt6Q1kg==} engines: {node: '>=18'} @@ -631,6 +814,12 @@ packages: cpu: [mips64el] os: [linux] + '@esbuild/linux-mips64el@0.21.5': + resolution: {integrity: sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==} + engines: {node: '>=12'} + cpu: [mips64el] + os: [linux] + '@esbuild/linux-mips64el@0.25.5': resolution: {integrity: sha512-kB/66P1OsHO5zLz0i6X0RxlQ+3cu0mkxS3TKFvkb5lin6uwZ/ttOkP3Z8lfR9mJOBk14ZwZ9182SIIWFGNmqmg==} engines: {node: '>=18'} @@ -643,6 +832,12 @@ packages: cpu: [ppc64] os: [linux] + '@esbuild/linux-ppc64@0.21.5': + resolution: {integrity: sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==} + engines: {node: '>=12'} + cpu: [ppc64] + os: [linux] + '@esbuild/linux-ppc64@0.25.5': resolution: {integrity: sha512-UZCmJ7r9X2fe2D6jBmkLBMQetXPXIsZjQJCjgwpVDz+YMcS6oFR27alkgGv3Oqkv07bxdvw7fyB71/olceJhkQ==} engines: {node: '>=18'} @@ -655,6 +850,12 @@ packages: cpu: [riscv64] os: [linux] + '@esbuild/linux-riscv64@0.21.5': + resolution: {integrity: sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==} + engines: {node: '>=12'} + cpu: [riscv64] + os: [linux] + '@esbuild/linux-riscv64@0.25.5': resolution: {integrity: sha512-kTxwu4mLyeOlsVIFPfQo+fQJAV9mh24xL+y+Bm6ej067sYANjyEw1dNHmvoqxJUCMnkBdKpvOn0Ahql6+4VyeA==} engines: {node: '>=18'} @@ -667,6 +868,12 @@ packages: cpu: [s390x] os: [linux] + '@esbuild/linux-s390x@0.21.5': + resolution: {integrity: sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==} + engines: {node: '>=12'} + cpu: [s390x] + os: [linux] + '@esbuild/linux-s390x@0.25.5': resolution: {integrity: sha512-K2dSKTKfmdh78uJ3NcWFiqyRrimfdinS5ErLSn3vluHNeHVnBAFWC8a4X5N+7FgVE1EjXS1QDZbpqZBjfrqMTQ==} engines: {node: '>=18'} @@ -679,6 +886,12 @@ packages: cpu: [x64] os: [linux] + '@esbuild/linux-x64@0.21.5': + resolution: {integrity: sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==} + engines: {node: '>=12'} + cpu: [x64] + os: [linux] + '@esbuild/linux-x64@0.25.5': resolution: {integrity: sha512-uhj8N2obKTE6pSZ+aMUbqq+1nXxNjZIIjCjGLfsWvVpy7gKCOL6rsY1MhRh9zLtUtAI7vpgLMK6DxjO8Qm9lJw==} engines: {node: '>=18'} @@ -697,6 +910,12 @@ packages: cpu: [x64] os: [netbsd] + '@esbuild/netbsd-x64@0.21.5': + resolution: {integrity: sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==} + engines: {node: '>=12'} + cpu: [x64] + os: [netbsd] + '@esbuild/netbsd-x64@0.25.5': resolution: {integrity: sha512-WOb5fKrvVTRMfWFNCroYWWklbnXH0Q5rZppjq0vQIdlsQKuw6mdSihwSo4RV/YdQ5UCKKvBy7/0ZZYLBZKIbwQ==} engines: {node: '>=18'} @@ -715,6 +934,12 @@ packages: cpu: [x64] os: [openbsd] + '@esbuild/openbsd-x64@0.21.5': + resolution: {integrity: sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==} + engines: {node: '>=12'} + cpu: [x64] + os: [openbsd] + '@esbuild/openbsd-x64@0.25.5': resolution: {integrity: sha512-G4hE405ErTWraiZ8UiSoesH8DaCsMm0Cay4fsFWOOUcz8b8rC6uCvnagr+gnioEjWn0wC+o1/TAHt+It+MpIMg==} engines: {node: '>=18'} @@ -727,6 +952,12 @@ packages: cpu: [x64] os: [sunos] + '@esbuild/sunos-x64@0.21.5': + resolution: {integrity: sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==} + engines: {node: '>=12'} + cpu: [x64] + os: [sunos] + '@esbuild/sunos-x64@0.25.5': resolution: {integrity: sha512-l+azKShMy7FxzY0Rj4RCt5VD/q8mG/e+mDivgspo+yL8zW7qEwctQ6YqKX34DTEleFAvCIUviCFX1SDZRSyMQA==} engines: {node: '>=18'} @@ -739,6 +970,12 @@ packages: cpu: [arm64] os: [win32] + '@esbuild/win32-arm64@0.21.5': + resolution: {integrity: sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==} + engines: {node: '>=12'} + cpu: [arm64] + os: [win32] + '@esbuild/win32-arm64@0.25.5': resolution: {integrity: sha512-O2S7SNZzdcFG7eFKgvwUEZ2VG9D/sn/eIiz8XRZ1Q/DO5a3s76Xv0mdBzVM5j5R639lXQmPmSo0iRpHqUUrsxw==} engines: {node: '>=18'} @@ -751,6 +988,12 @@ packages: cpu: [ia32] os: [win32] + '@esbuild/win32-ia32@0.21.5': + resolution: {integrity: sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==} + engines: {node: '>=12'} + cpu: [ia32] + os: [win32] + '@esbuild/win32-ia32@0.25.5': resolution: {integrity: sha512-onOJ02pqs9h1iMJ1PQphR+VZv8qBMQ77Klcsqv9CNW2w6yLqoURLcgERAIurY6QE63bbLuqgP9ATqajFLK5AMQ==} engines: {node: '>=18'} @@ -763,6 +1006,12 @@ packages: cpu: [x64] os: [win32] + '@esbuild/win32-x64@0.21.5': + resolution: {integrity: sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==} + engines: {node: '>=12'} + cpu: [x64] + os: [win32] + '@esbuild/win32-x64@0.25.5': resolution: {integrity: sha512-TXv6YnJ8ZMVdX+SXWVBo/0p8LTcrUYngpWjvm91TMjjBQii7Oz11Lw5lbDV5Y0TzuhSJHwiH4hEtC1I42mMS0g==} engines: {node: '>=18'} @@ -817,6 +1066,12 @@ packages: wrangler: optional: true + '@iconify-json/simple-icons@1.2.46': + resolution: {integrity: sha512-MJfKQDhOMQD5Fc8PcTtCdFX0oBf/nKVfp69ScdEKIXW0JXELX5V2Ld45EsjShi8aJ6DNhdDtSDZvKuDnkDiKnw==} + + '@iconify/types@2.0.0': + resolution: {integrity: sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg==} + '@internationalized/date@3.8.1': resolution: {integrity: sha512-PgVE6B6eIZtzf9Gu5HvJxRK3ufUFz9DhspELuhW/N0GuMGMTLvPQNRkHP2hTuP9lblOk+f+1xi96sPiPXANXAA==} @@ -1664,6 +1919,30 @@ packages: cpu: [x64] os: [win32] + '@shikijs/core@2.5.0': + resolution: {integrity: sha512-uu/8RExTKtavlpH7XqnVYBrfBkUc20ngXiX9NSrBhOVZYv/7XQRKUyhtkeflY5QsxC0GbJThCerruZfsUaSldg==} + + '@shikijs/engine-javascript@2.5.0': + resolution: {integrity: sha512-VjnOpnQf8WuCEZtNUdjjwGUbtAVKuZkVQ/5cHy/tojVVRIRtlWMYVjyWhxOmIq05AlSOv72z7hRNRGVBgQOl0w==} + + '@shikijs/engine-oniguruma@2.5.0': + resolution: {integrity: sha512-pGd1wRATzbo/uatrCIILlAdFVKdxImWJGQ5rFiB5VZi2ve5xj3Ax9jny8QvkaV93btQEwR/rSz5ERFpC5mKNIw==} + + '@shikijs/langs@2.5.0': + resolution: {integrity: sha512-Qfrrt5OsNH5R+5tJ/3uYBBZv3SuGmnRPejV9IlIbFH3HTGLDlkqgHymAlzklVmKBjAaVmkPkyikAV/sQ1wSL+w==} + + '@shikijs/themes@2.5.0': + resolution: {integrity: sha512-wGrk+R8tJnO0VMzmUExHR+QdSaPUl/NKs+a4cQQRWyoc3YFbUzuLEi/KWK1hj+8BfHRKm2jNhhJck1dfstJpiw==} + + '@shikijs/transformers@2.5.0': + resolution: {integrity: sha512-SI494W5X60CaUwgi8u4q4m4s3YAFSxln3tzNjOSYqq54wlVgz0/NbbXEb3mdLbqMBztcmS7bVTaEd2w0qMmfeg==} + + '@shikijs/types@2.5.0': + resolution: {integrity: sha512-ygl5yhxki9ZLNuNpPitBWvcy9fsSKKaRuO4BAlMyagszQidxcpLAr0qiW/q43DtSIDxO6hEbtYLiFZNXO/hdGw==} + + '@shikijs/vscode-textmate@10.0.2': + resolution: {integrity: sha512-83yeghZ2xxin3Nj8z1NMd/NCuca+gsYXswywDy5bHvwlWL8tpTQmzGeUuHd9FC3E/SBEMvzJRwWEOz5gGes9Qg==} + '@shopify/lang-jsonc@1.0.1': resolution: {integrity: sha512-KrBrRFhvr1qJiZBODAtqbL1u1e67UR3plBN79Z8nd5TQAAzpx66jS4zs7Ss9M22ygGrpWFhyhSoNVlp5VCYktQ==} @@ -1775,9 +2054,24 @@ packages: '@types/estree@1.0.7': resolution: {integrity: sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ==} + '@types/hast@3.0.4': + resolution: {integrity: sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==} + '@types/js-yaml@4.0.9': resolution: {integrity: sha512-k4MGaQl5TGo/iipqb2UDG2UwjXziSWkh0uysQelTlJpX1qGlpUZYm8PnO4DxG1qBomtJUdYJ6qR6xdIah10JLg==} + '@types/linkify-it@5.0.0': + resolution: {integrity: sha512-sVDA58zAw4eWAffKOaQH5/5j3XeayukzDk+ewSsnv3p4yJEZHCCzMDiZM8e0OUrRvmpGZ85jf4yDHkHsgBNr9Q==} + + '@types/markdown-it@14.1.2': + resolution: {integrity: sha512-promo4eFwuiW+TfGxhi+0x3czqTYJkG8qB17ZUJiVF10Xm7NLVRSLUsfRTU/6h1e24VvRnXCx+hG7li58lkzog==} + + '@types/mdast@4.0.4': + resolution: {integrity: sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==} + + '@types/mdurl@2.0.0': + resolution: {integrity: sha512-RGdgjQUZba5p6QEFAVx2OGb8rQDL/cPRG7GiedRzMcJ1tYnUANBncjbSB1NRGwbvjcPeikRABz2nshyPk1bhWg==} + '@types/node-fetch@2.6.12': resolution: {integrity: sha512-8nneRWKCg3rMtF69nLQJnOYUcbafYeFSjqkw3jCRLsqkWFlHaoQrr5mXmofFGOx3DKn7UfmBMyov8ySvLRVldA==} @@ -1803,6 +2097,12 @@ packages: '@types/stream-buffers@3.0.7': resolution: {integrity: sha512-azOCy05sXVXrO+qklf0c/B07H/oHaIuDDAiHPVwlk3A9Ek+ksHyTeMajLZl3r76FxpPpxem//4Te61G1iW3Giw==} + '@types/unist@3.0.3': + resolution: {integrity: sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==} + + '@types/web-bluetooth@0.0.21': + resolution: {integrity: sha512-oIQLCGWtcFZy2JW77j9k8nHzAOpqMHLQejDA48XXMWH6tjCQHz5RCFz1bzsmROyL6PUm+LLnUiI4BCn221inxA==} + '@types/websocket@1.0.10': resolution: {integrity: sha512-svjGZvPB7EzuYS94cI7a+qhwgGU1y89wUgjT6E2wVUfmAGIvRfT7obBvRtnhXCSsoMdlG4gBFGE7MfkIXZLoww==} @@ -1844,6 +2144,16 @@ packages: react: '>=16.8.0' react-dom: '>=16.8.0' + '@ungap/structured-clone@1.3.0': + resolution: {integrity: sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==} + + '@vitejs/plugin-vue@5.2.4': + resolution: {integrity: sha512-7Yx/SXSOcQq5HiiV3orevHUFn+pmMB4cgbEkDYgnkUWb0WfeQ/wa2yFv6D5ICiCQOVpjA7vYDXrC7AGO8yjDHA==} + engines: {node: ^18.0.0 || >=20.0.0} + peerDependencies: + vite: ^5.0.0 || ^6.0.0 + vue: ^3.2.25 + '@vitest/expect@3.2.4': resolution: {integrity: sha512-Io0yyORnB6sikFlt8QW5K7slY4OjqNX9jmJQ02QDda8lyM6B5oNgVWoSoKPac8/kgnCUzuHQKrSLtu/uOqqrig==} @@ -1873,6 +2183,94 @@ packages: '@vitest/utils@3.2.4': resolution: {integrity: sha512-fB2V0JFrQSMsCo9HiSq3Ezpdv4iYaXRG1Sx8edX3MwxfyNn83mKiGzOcH+Fkxt4MHxr3y42fQi1oeAInqgX2QA==} + '@vue/compiler-core@3.5.18': + resolution: {integrity: sha512-3slwjQrrV1TO8MoXgy3aynDQ7lslj5UqDxuHnrzHtpON5CBinhWjJETciPngpin/T3OuW3tXUf86tEurusnztw==} + + '@vue/compiler-dom@3.5.18': + resolution: {integrity: sha512-RMbU6NTU70++B1JyVJbNbeFkK+A+Q7y9XKE2EM4NLGm2WFR8x9MbAtWxPPLdm0wUkuZv9trpwfSlL6tjdIa1+A==} + + '@vue/compiler-sfc@3.5.18': + resolution: {integrity: sha512-5aBjvGqsWs+MoxswZPoTB9nSDb3dhd1x30xrrltKujlCxo48j8HGDNj3QPhF4VIS0VQDUrA1xUfp2hEa+FNyXA==} + + '@vue/compiler-ssr@3.5.18': + resolution: {integrity: sha512-xM16Ak7rSWHkM3m22NlmcdIM+K4BMyFARAfV9hYFl+SFuRzrZ3uGMNW05kA5pmeMa0X9X963Kgou7ufdbpOP9g==} + + '@vue/devtools-api@7.7.7': + resolution: {integrity: sha512-lwOnNBH2e7x1fIIbVT7yF5D+YWhqELm55/4ZKf45R9T8r9dE2AIOy8HKjfqzGsoTHFbWbr337O4E0A0QADnjBg==} + + '@vue/devtools-kit@7.7.7': + resolution: {integrity: sha512-wgoZtxcTta65cnZ1Q6MbAfePVFxfM+gq0saaeytoph7nEa7yMXoi6sCPy4ufO111B9msnw0VOWjPEFCXuAKRHA==} + + '@vue/devtools-shared@7.7.7': + resolution: {integrity: sha512-+udSj47aRl5aKb0memBvcUG9koarqnxNM5yjuREvqwK6T3ap4mn3Zqqc17QrBFTqSMjr3HK1cvStEZpMDpfdyw==} + + '@vue/reactivity@3.5.18': + resolution: {integrity: sha512-x0vPO5Imw+3sChLM5Y+B6G1zPjwdOri9e8V21NnTnlEvkxatHEH5B5KEAJcjuzQ7BsjGrKtfzuQ5eQwXh8HXBg==} + + '@vue/runtime-core@3.5.18': + resolution: {integrity: sha512-DUpHa1HpeOQEt6+3nheUfqVXRog2kivkXHUhoqJiKR33SO4x+a5uNOMkV487WPerQkL0vUuRvq/7JhRgLW3S+w==} + + '@vue/runtime-dom@3.5.18': + resolution: {integrity: sha512-YwDj71iV05j4RnzZnZtGaXwPoUWeRsqinblgVJwR8XTXYZ9D5PbahHQgsbmzUvCWNF6x7siQ89HgnX5eWkr3mw==} + + '@vue/server-renderer@3.5.18': + resolution: {integrity: sha512-PvIHLUoWgSbDG7zLHqSqaCoZvHi6NNmfVFOqO+OnwvqMz/tqQr3FuGWS8ufluNddk7ZLBJYMrjcw1c6XzR12mA==} + peerDependencies: + vue: 3.5.18 + + '@vue/shared@3.5.18': + resolution: {integrity: sha512-cZy8Dq+uuIXbxCZpuLd2GJdeSO/lIzIspC2WtkqIpje5QyFbvLaI5wZtdUjLHjGZrlVX6GilejatWwVYYRc8tA==} + + '@vueuse/core@12.8.2': + resolution: {integrity: sha512-HbvCmZdzAu3VGi/pWYm5Ut+Kd9mn1ZHnn4L5G8kOQTPs/IwIAmJoBrmYk2ckLArgMXZj0AW3n5CAejLUO+PhdQ==} + + '@vueuse/integrations@12.8.2': + resolution: {integrity: sha512-fbGYivgK5uBTRt7p5F3zy6VrETlV9RtZjBqd1/HxGdjdckBgBM4ugP8LHpjolqTj14TXTxSK1ZfgPbHYyGuH7g==} + peerDependencies: + async-validator: ^4 + axios: ^1 + change-case: ^5 + drauu: ^0.4 + focus-trap: ^7 + fuse.js: ^7 + idb-keyval: ^6 + jwt-decode: ^4 + nprogress: ^0.2 + qrcode: ^1.5 + sortablejs: ^1 + universal-cookie: ^7 + peerDependenciesMeta: + async-validator: + optional: true + axios: + optional: true + change-case: + optional: true + drauu: + optional: true + focus-trap: + optional: true + fuse.js: + optional: true + idb-keyval: + optional: true + jwt-decode: + optional: true + nprogress: + optional: true + qrcode: + optional: true + sortablejs: + optional: true + universal-cookie: + optional: true + + '@vueuse/metadata@12.8.2': + resolution: {integrity: sha512-rAyLGEuoBJ/Il5AmFHiziCPdQzRt88VxR+Y/A/QhJ1EWtWqPBBAxTAFaSkviwEuOEZNtW8pvkPgoCZQ+HxqW1A==} + + '@vueuse/shared@12.8.2': + resolution: {integrity: sha512-dznP38YzxZoNloI0qpEfpkms8knDtaoQ6Y/sfS0L7Yki4zh40LFHEhur0odJC6xTHG5dxWVPiUWBXn+wCG2s5w==} + '@xterm/addon-clipboard@0.1.0': resolution: {integrity: sha512-zdoM7p53T5sv/HbRTyp4hY0kKmEQ3MZvAvEtiXqNIHc/JdpqwByCtsTaQF5DX2n4hYdXRPO4P/eOS0QEhX1nPw==} peerDependencies: @@ -1910,6 +2308,10 @@ packages: resolution: {integrity: sha512-jRR5wdylq8CkOe6hei19GGZnxM6rBGwFl3Bg0YItGDimvjGtAvdZk4Pu6Cl4u4Igsws4a1fd1Vq3ezrhn4KmFw==} engines: {node: '>= 14'} + algoliasearch@5.35.0: + resolution: {integrity: sha512-Y+moNhsqgLmvJdgTsO4GZNgsaDWv8AOGAaPeIeHKlDn/XunoAqYbA+XNpBd1dW8GOXAUDyxC9Rxc7AV4kpFcIg==} + engines: {node: '>= 14.0.0'} + ansi-regex@5.0.1: resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} engines: {node: '>=8'} @@ -1995,6 +2397,9 @@ packages: peerDependencies: react: '>=17.0.1' + birpc@2.5.0: + resolution: {integrity: sha512-VSWO/W6nNQdyP520F1mhf+Lc2f8pjGQOtoHHm7Ze8Go1kX7akpVIrtTa0fn+HB0QJEDVacl6aO08YE0PgXfdnQ==} + bl@4.1.0: resolution: {integrity: sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==} @@ -2027,10 +2432,19 @@ packages: caniuse-lite@1.0.30001718: resolution: {integrity: sha512-AflseV1ahcSunK53NfEs9gFWgOEmzr0f+kaMFA4xiLZlr9Hzt7HxcSpIFcnNCUkz6R6dWKa54rUz3HUmI3nVcw==} + ccount@2.0.1: + resolution: {integrity: sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==} + chai@5.2.1: resolution: {integrity: sha512-5nFxhUrX0PqtyogoYOA8IPswy5sZFTOsBFl/9bNsmDLgsxYTzSZQJDPppDnZPTQbzSEm0hqGjWPzRemQCYbD6A==} engines: {node: '>=18'} + character-entities-html4@2.1.0: + resolution: {integrity: sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==} + + character-entities-legacy@3.0.0: + resolution: {integrity: sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==} + check-error@2.1.1: resolution: {integrity: sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw==} engines: {node: '>= 16'} @@ -2064,6 +2478,9 @@ packages: resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==} engines: {node: '>= 0.8'} + comma-separated-tokens@2.0.3: + resolution: {integrity: sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==} + commander@2.20.3: resolution: {integrity: sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==} @@ -2074,6 +2491,10 @@ packages: resolution: {integrity: sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA==} engines: {node: '>=18'} + copy-anything@3.0.5: + resolution: {integrity: sha512-yCEafptTtb4bk7GLEQoM8KVJpxAfdBJYaXyzQEgQQQgYrZiDp8SJmGKlYza6CYjEDNstAdNdKA3UuoULlEbS6w==} + engines: {node: '>=12.13'} + crelt@1.0.6: resolution: {integrity: sha512-VQ2MBenTq1fWZUH9DJNGti7kKv6EeAuYr3cLwxUWhIu1baTaXh4Ib5W2CqHVqib4/MqbYGJqiL3Zb8GJZr3l4g==} @@ -2133,6 +2554,10 @@ packages: resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} engines: {node: '>=0.4.0'} + dequal@2.0.3: + resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==} + engines: {node: '>=6'} + detect-libc@2.0.2: resolution: {integrity: sha512-UX6sGumvvqSaXgdKGUsgZWqcUyIXZ/vZTrlRT/iobiKhGL0zL4d3osHj3uqllWJK+i+sixDS/3COVEOFbupFyw==} engines: {node: '>=8'} @@ -2141,6 +2566,9 @@ packages: resolution: {integrity: sha512-3UDv+G9CsCKO1WKMGw9fwq/SWJYbI0c5Y7LU1AXYoDdbhE2AHQ6N6Nb34sG8Fj7T5APy8qXDCKuuIHd1BR0tVA==} engines: {node: '>=8'} + devlop@1.1.0: + resolution: {integrity: sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==} + dotenv@16.5.0: resolution: {integrity: sha512-m/C+AwOAr9/W1UOIZUo232ejMNnJAJtYQjUbHoNTBNTJSvqzzDh7vnrei3o3r3m9blf6ZoDkvcw0VmozNRFJxg==} engines: {node: '>=12'} @@ -2251,6 +2679,9 @@ packages: electron-to-chromium@1.5.160: resolution: {integrity: sha512-8yQk54/CoCQT8GX3zuxqPBwMAQuIr6dWI/qO8Aah/JAZwB5XmCbEElsqb1n4pzc2vpkTdfc/kbyNPJOjswfbgg==} + emoji-regex-xs@1.0.0: + resolution: {integrity: sha512-LRlerrMYoIDrT6jgpeZ2YYl/L8EulRTt5hQcYjy5AInh7HWXKimpqx68aknBFpGL2+/IcogTcaydJEgaTmOpDg==} + emoji-regex@8.0.0: resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} @@ -2264,6 +2695,10 @@ packages: resolution: {integrity: sha512-ZSW3ma5GkcQBIpwZTSRAI8N71Uuwgs93IezB7mf7R60tC8ZbJideoDNKjHn2O9KIlx6rkGTTEk1xUCK2E1Y2Yg==} engines: {node: '>=10.13.0'} + entities@4.5.0: + resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==} + engines: {node: '>=0.12'} + err-code@2.0.3: resolution: {integrity: sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA==} @@ -2296,6 +2731,11 @@ packages: engines: {node: '>=12'} hasBin: true + esbuild@0.21.5: + resolution: {integrity: sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==} + engines: {node: '>=12'} + hasBin: true + esbuild@0.25.5: resolution: {integrity: sha512-P8OtKZRv/5J5hhz0cUAdu/cLuPIKXpQl1R9pZtvmHWQvrAUVd0UNIPT4IB4W3rNOqVO0rlqHmCIbSwxh/c9yUQ==} engines: {node: '>=18'} @@ -2341,6 +2781,9 @@ packages: file-uri-to-path@1.0.0: resolution: {integrity: sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==} + focus-trap@7.6.5: + resolution: {integrity: sha512-7Ke1jyybbbPZyZXFxEftUtxFGLMpE2n6A+z//m4CRDlj0hW+o3iYSmh8nFlYMurOiJVDmJRilUQtJr08KfIxlg==} + foreground-child@3.3.0: resolution: {integrity: sha512-Ld2g8rrAyMYFXBhEqMz8ZAHBi4J4uS1i/CxGMDnjyFWddMXLVcDp051DZfu+t7+ab7Wv6SMqpWmyFIj5UbfFvg==} engines: {node: '>=14'} @@ -2421,10 +2864,19 @@ packages: resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} engines: {node: '>= 0.4'} + hast-util-to-html@9.0.5: + resolution: {integrity: sha512-OguPdidb+fbHQSU4Q4ZiLKnzWo8Wwsf5bZfbvu7//a9oTYoqD/fWpe96NuHkoS9h0ccGOTe0C4NGXdtS0iObOw==} + + hast-util-whitespace@3.0.0: + resolution: {integrity: sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==} + hono@4.7.10: resolution: {integrity: sha512-QkACju9MiN59CKSY5JsGZCYmPZkA6sIW6OFCUp7qDjZu6S6KHtJHhAc9Uy9mV9F8PJ1/HQ3ybZF2yjCa/73fvQ==} engines: {node: '>=16.9.0'} + hookable@5.5.3: + resolution: {integrity: sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ==} + hosted-git-info@6.1.3: resolution: {integrity: sha512-HVJyzUrLIL1c0QmviVh5E8VGyUS7xCFPS6yydaVd1UegW+ibV/CohqTH9MkOLDp5o+rb82DMo77PTuc9F/8GKw==} engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} @@ -2433,6 +2885,9 @@ packages: resolution: {integrity: sha512-A91dYTeIB6NoXG+PxTQpCCDDnfHsW9kc06Lvpu1TEe9gnd6ZFeiBoRO9JvzEv6xK7EX97/dUE8g/vBMTqTS3CA==} engines: {node: '>=14'} + html-void-elements@3.0.0: + resolution: {integrity: sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg==} + ieee754@1.2.1: resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==} @@ -2457,6 +2912,10 @@ packages: resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} engines: {node: '>=8'} + is-what@4.1.16: + resolution: {integrity: sha512-ZhMwEosbFJkA0YhFnNDgTM4ZxDRsS6HqTo7qsZM08fehyRYIYa0yHu5R6mgo1n/8MgaPBXiPimPD77baVFYg+A==} + engines: {node: '>=12.13'} + isbot@5.1.28: resolution: {integrity: sha512-qrOp4g3xj8YNse4biorv6O5ZShwsJM0trsoda4y7j/Su7ZtTTfVXFzbKkpgcSoDrHS8FcTuUwcU04YimZlZOxw==} engines: {node: '>=18'} @@ -2675,10 +3134,31 @@ packages: magic-string@0.30.17: resolution: {integrity: sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==} + mark.js@8.11.1: + resolution: {integrity: sha512-1I+1qpDt4idfgLQG+BNWmrqku+7/2bi5nLf4YwF8y8zXvmfiTBY3PV3ZibfrjBueCByROpuBjLLFCajqkgYoLQ==} + math-intrinsics@1.1.0: resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} engines: {node: '>= 0.4'} + mdast-util-to-hast@13.2.0: + resolution: {integrity: sha512-QGYKEuUsYT9ykKBCMOEDLsU5JRObWQusAolFMeko/tYPufNkRffBAQjIE+99jbA87xv6FgmjLtwjh9wBWajwAA==} + + micromark-util-character@2.1.1: + resolution: {integrity: sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==} + + micromark-util-encode@2.0.1: + resolution: {integrity: sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw==} + + micromark-util-sanitize-uri@2.0.1: + resolution: {integrity: sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ==} + + micromark-util-symbol@2.0.1: + resolution: {integrity: sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==} + + micromark-util-types@2.0.2: + resolution: {integrity: sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA==} + mime-db@1.52.0: resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==} engines: {node: '>= 0.6'} @@ -2707,10 +3187,16 @@ packages: resolution: {integrity: sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==} engines: {node: '>=16 || 14 >=14.17'} + minisearch@7.1.2: + resolution: {integrity: sha512-R1Pd9eF+MD5JYDDSPAp/q1ougKglm14uEkPMvQ/05RGmx6G9wvmLTrTI/Q5iPNJLYqNdsDQ7qTGIcNWR+FrHmA==} + minizlib@3.0.1: resolution: {integrity: sha512-umcy022ILvb5/3Djuu8LWeqUa8D68JaBzlttKeMWen48SjabqS3iY5w/vzeMzMUNhLDifyhbOwKDSznB1vvrwg==} engines: {node: '>= 18'} + mitt@3.0.1: + resolution: {integrity: sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==} + mkdirp-classic@0.5.3: resolution: {integrity: sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==} @@ -2789,6 +3275,9 @@ packages: once@1.4.0: resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} + oniguruma-to-es@3.1.1: + resolution: {integrity: sha512-bUH8SDvPkH3ho3dvwJwfonjlQ4R80vjyvrU8YpxuROddv55vAEJrTuCuCVUhhsHbtlD9tGGbaNApGQckXhS8iQ==} + openid-client@6.5.0: resolution: {integrity: sha512-fAfYaTnOYE2kQCqEJGX9KDObW2aw7IQy4jWpU/+3D3WoCFLbix5Hg6qIPQ6Js9r7f8jDUmsnnguRNCSw4wU/IQ==} @@ -2813,6 +3302,9 @@ packages: resolution: {integrity: sha512-//nshmD55c46FuFw26xV/xFAaB5HF9Xdap7HJBBnrKdAd6/GxDBaNA1870O79+9ueg61cZLSVc+OaFlfmObYVQ==} engines: {node: '>= 14.16'} + perfect-debounce@1.0.0: + resolution: {integrity: sha512-xCy9V055GLEqoFaHoC1SoLIaLmWctgCUaBaWxDZ7/Zx4CTyX7cJQLJOok/orfjZAh9kEYpjJa4d0KcJmCbctZA==} + picocolors@1.1.1: resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} @@ -2834,6 +3326,10 @@ packages: resolution: {integrity: sha512-QSa9EBe+uwlGTFmHsPKokv3B/oEMQZxfqW0QqNCyhpa6mB1afzulwn8hihglqAb2pOw+BJgNlmXQ8la2VeHB7w==} engines: {node: ^10 || ^12 || >=14} + postcss@8.5.6: + resolution: {integrity: sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==} + engines: {node: ^10 || ^12 || >=14} + preact@10.26.7: resolution: {integrity: sha512-43xS+QYc1X1IPbw03faSgY6I6OYWcLrJRv3hU0+qMOfh/XCHcP0MX2CVjNARYR2cC/guu975sta4OcjlczxD7g==} @@ -2866,6 +3362,9 @@ packages: resolution: {integrity: sha512-y+WKFlBR8BGXnsNlIHFGPZmyDf3DFMoLhaflAnyZgV6rG6xu+JwesTo2Q9R6XwYmtmwAFCkAk3e35jEdoeh/3g==} engines: {node: '>=10'} + property-information@7.1.0: + resolution: {integrity: sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ==} + pump@3.0.2: resolution: {integrity: sha512-tUPXtzlGM8FE3P0ZL6DVs/3P58k9nk8/jZeQCurTJylQA8qFYzHFfhBJkuqyE0FifOsQ0uKWekiZ5g8wtr28cw==} @@ -2983,6 +3482,15 @@ packages: resolution: {integrity: sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==} engines: {node: '>= 14.18.0'} + regex-recursion@6.0.2: + resolution: {integrity: sha512-0YCaSCq2VRIebiaUviZNs0cBz1kg5kVS2UKUfNIx8YVs1cN3AV7NTctO5FOKBA+UT2BPJIWZauYHPqJODG50cg==} + + regex-utilities@2.3.0: + resolution: {integrity: sha512-8VhliFJAWRaUiVvREIiW2NXXTmHs4vMNnSzuJVhscgmGav3g9VDxLrQndI3dZZVVdp0ZO/5v0xmX516/7M9cng==} + + regex@6.0.1: + resolution: {integrity: sha512-uorlqlzAKjKQZ5P+kTJr3eeJGSVroLKoHmquUj4zHWuR+hEyNqlXsSKlYYF5F4NI6nl7tWCs0apKJ0lmfsXAPA==} + remix-utils@8.7.0: resolution: {integrity: sha512-oRumofsYFaxHyPtqLuYe3g2nQi4SMYjCoebaeed0gYHIOKBiPPYdNP6cgmQbFjQQ5pwXV+uQiKLqO6pM9ep3VA==} engines: {node: '>=20.0.0'} @@ -3029,6 +3537,9 @@ packages: rfc4648@1.5.4: resolution: {integrity: sha512-rRg/6Lb+IGfJqO05HZkN50UtY7K/JhxJag1kP23+zyMfrvoB0B7RWv06MbOzoc79RgCdNTiUaNsTT1AJZ7Z+cg==} + rfdc@1.4.1: + resolution: {integrity: sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==} + rimraf@5.0.10: resolution: {integrity: sha512-l0OE8wL34P4nJH/H2ffoaniAokM2qSmrtXHmlpvYr5AVVX8msAyW0l8NVJFDxlSK4u3Uh/f41cQheDVdnYijwQ==} hasBin: true @@ -3044,6 +3555,9 @@ packages: scheduler@0.26.0: resolution: {integrity: sha512-NlHwttCI/l5gCPR3D1nNXtWABUmBwvZpEQiD4IXSbIDq8BzLIK/7Ir5gTFSGZDUu37K5cMNp0hFtzO38sC7gWA==} + search-insights@2.17.3: + resolution: {integrity: sha512-RQPdCYTa8A68uM2jwxoY842xDhvx3E5LFL1LxvxCNMev4o5mLuokczhzjAgGwUZBAmOKZknArSxLKmXtIi2AxQ==} + semver@6.3.1: resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} hasBin: true @@ -3064,6 +3578,9 @@ packages: resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} engines: {node: '>=8'} + shiki@2.5.0: + resolution: {integrity: sha512-mI//trrsaiCIPsja5CNfsyNOqgAZUb6VpJA+340toL42UpzQlXpwRV9nch69X6gaUxrr9kaOOa6e3y3uAkGFxQ==} + siginfo@2.0.0: resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==} @@ -3103,6 +3620,9 @@ packages: resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==} engines: {node: '>=0.10.0'} + space-separated-tokens@2.0.2: + resolution: {integrity: sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==} + spdx-correct@3.2.0: resolution: {integrity: sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA==} @@ -3115,6 +3635,10 @@ packages: spdx-license-ids@3.0.21: resolution: {integrity: sha512-Bvg/8F5XephndSK3JffaRqdT+gyhfqIPwDHpX80tJrF8QQRYMo8sNMeaZ2Dp5+jhwKnUmIOyFFQfHRkjJm5nXg==} + speakingurl@14.0.1: + resolution: {integrity: sha512-1POYv7uv2gXoyGFpBCmpDVSNV74IfsWlDW216UPjbWufNf+bSU6GdbDsxdcxtfwb4xlI3yxzOTKClUosxARYrQ==} + engines: {node: '>=0.10.0'} + sprintf-js@1.1.3: resolution: {integrity: sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==} @@ -3145,6 +3669,9 @@ packages: string_decoder@1.3.0: resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==} + stringify-entities@4.0.4: + resolution: {integrity: sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==} + strip-ansi@6.0.1: resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} engines: {node: '>=8'} @@ -3163,6 +3690,13 @@ packages: style-mod@4.1.2: resolution: {integrity: sha512-wnD1HyVqpJUI2+eKZ+eo1UwghftP6yuFheBqqe+bWCotBjC2K1YnteJILRMs3SM4V/0dLEW1SC27MWP5y+mwmw==} + superjson@2.2.2: + resolution: {integrity: sha512-5JRxVqC8I8NuOUjzBbvVJAKNM8qoVuH0O77h4WInc/qC2q5IreqKxYwgkga3PfA22OayK2ikceb/B26dztPl+Q==} + engines: {node: '>=16'} + + tabbable@6.2.0: + resolution: {integrity: sha512-Cat63mxsVJlzYvN51JmVXIgNoUokrIaT2zLclCXjRd8boZ0004U4KCs/sToJ75C6sdlByWxpYnb5Boif1VSFew==} + tailwind-merge@3.3.0: resolution: {integrity: sha512-fyW/pEfcQSiigd5SNn0nApUOxx0zB/dm6UDU/rEwc2c3sX2smWUNbapHv+QRqLGVp9GWX3THIa7MUGPo+YkDzQ==} @@ -3233,6 +3767,9 @@ packages: tr46@0.0.3: resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==} + trim-lines@3.0.1: + resolution: {integrity: sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==} + tsconfck@3.1.4: resolution: {integrity: sha512-kdqWFGVJqe+KGYvlSO9NIaWn9jT1Ny4oKVzAJsKii5eoE9snzTJzL4+MMVOMn+fikWGFmKEylcXL710V/kIPJQ==} engines: {node: ^18 || >=20} @@ -3280,6 +3817,21 @@ packages: resolution: {integrity: sha512-u5otvFBOBZvmdjWLVW+5DAc9Nkq8f24g0O9oY7qw2JVIF1VocIFoyz9JFkuVOS2j41AufeO0xnlweJ2RLT8nGw==} engines: {node: '>=20.18.1'} + unist-util-is@6.0.0: + resolution: {integrity: sha512-2qCTHimwdxLfz+YzdGfkqNlH0tLi9xjTnHddPmJwtIG9MGsdbutfTc4P+haPD7l7Cjxf/WZj+we5qfVPvvxfYw==} + + unist-util-position@5.0.0: + resolution: {integrity: sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA==} + + unist-util-stringify-position@4.0.0: + resolution: {integrity: sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==} + + unist-util-visit-parents@6.0.1: + resolution: {integrity: sha512-L/PqWzfTP9lzzEa6CKs0k2nARxTdZduw3zyh8d2NVBnsyvHjSX4TWse388YrrQKbvI8w20fGjGlhgT96WwKykw==} + + unist-util-visit@5.0.0: + resolution: {integrity: sha512-MR04uvD+07cwl/yhVuVWAtw+3GOR/knlL55Nd/wAdblk27GCVt3lqpTivy/tkJcZoNPzTwS1Y+KMojlLDhoTzg==} + universalify@2.0.1: resolution: {integrity: sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==} engines: {node: '>= 10.0.0'} @@ -3327,6 +3879,12 @@ packages: resolution: {integrity: sha512-OljLrQ9SQdOUqTaQxqL5dEfZWrXExyyWsozYlAWFawPVNuD83igl7uJD2RTkNMbniIYgt8l81eCJGIdQF7avLQ==} engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} + vfile-message@4.0.3: + resolution: {integrity: sha512-QTHzsGd1EhbZs4AsQ20JX1rC3cOlt/IWJruk893DfLRr57lcnOeMaWG4K0JrRta4mIJZKth2Au3mM3u03/JWKw==} + + vfile@6.0.3: + resolution: {integrity: sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==} + vite-node@3.0.0-beta.2: resolution: {integrity: sha512-ofTf6cfRdL30Wbl9n/BX81EyIR5s4PReLmSurrxQ+koLaWUNOEo8E0lCM53OJkb8vpa2URM2nSrxZsIFyvY1rg==} engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} @@ -3345,6 +3903,37 @@ packages: vite: optional: true + vite@5.4.19: + resolution: {integrity: sha512-qO3aKv3HoQC8QKiNSTuUM1l9o/XX3+c+VTgLHbJWHZGeTPVAg2XwazI9UWzoxjIJCGCV2zU60uqMzjeLZuULqA==} + engines: {node: ^18.0.0 || >=20.0.0} + hasBin: true + peerDependencies: + '@types/node': ^18.0.0 || >=20.0.0 + less: '*' + lightningcss: ^1.21.0 + sass: '*' + sass-embedded: '*' + stylus: '*' + sugarss: '*' + terser: ^5.4.0 + peerDependenciesMeta: + '@types/node': + optional: true + less: + optional: true + lightningcss: + optional: true + sass: + optional: true + sass-embedded: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + vite@6.3.5: resolution: {integrity: sha512-cZn6NDFE7wdTpINgs++ZJ4N49W2vRp8LCKrn3Ob1kYNtOo21vfDoaV5GzBfLU4MovSAB8uNRm4jgzVQZ+mBzPQ==} engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} @@ -3385,6 +3974,18 @@ packages: yaml: optional: true + vitepress@1.6.4: + resolution: {integrity: sha512-+2ym1/+0VVrbhNyRoFFesVvBvHAVMZMK0rw60E3X/5349M1GuVdKeazuksqopEdvkKwKGs21Q729jX81/bkBJg==} + hasBin: true + peerDependencies: + markdown-it-mathjax3: ^4 + postcss: ^8 + peerDependenciesMeta: + markdown-it-mathjax3: + optional: true + postcss: + optional: true + vitest@3.2.4: resolution: {integrity: sha512-LUCP5ev3GURDysTWiP47wRRUpLKMOfPh+yKTx3kVIEiu5KOMeqzpnYNsKyOoVrULivR8tLcks4+lga33Whn90A==} engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} @@ -3413,6 +4014,14 @@ packages: jsdom: optional: true + vue@3.5.18: + resolution: {integrity: sha512-7W4Y4ZbMiQ3SEo+m9lnoNpV9xG7QVMLa+/0RFwwiAVkeYoyGXqWE85jabU4pllJNUzqfLShJ5YLptewhCWUgNA==} + peerDependencies: + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + w3c-keyname@2.2.8: resolution: {integrity: sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ==} @@ -3494,8 +4103,123 @@ packages: zod@3.25.33: resolution: {integrity: sha512-RnBGYCwJFrLi/hUmeqbYjSIrS/strWjN5PHWgUfyVq96nKycSa4gp4+p6hQGwvcabZs0DWRGzyLhEeQWl+5NhA==} + zwitch@2.0.4: + resolution: {integrity: sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==} + snapshots: + '@algolia/abtesting@1.1.0': + dependencies: + '@algolia/client-common': 5.35.0 + '@algolia/requester-browser-xhr': 5.35.0 + '@algolia/requester-fetch': 5.35.0 + '@algolia/requester-node-http': 5.35.0 + + '@algolia/autocomplete-core@1.17.7(@algolia/client-search@5.35.0)(algoliasearch@5.35.0)(search-insights@2.17.3)': + dependencies: + '@algolia/autocomplete-plugin-algolia-insights': 1.17.7(@algolia/client-search@5.35.0)(algoliasearch@5.35.0)(search-insights@2.17.3) + '@algolia/autocomplete-shared': 1.17.7(@algolia/client-search@5.35.0)(algoliasearch@5.35.0) + transitivePeerDependencies: + - '@algolia/client-search' + - algoliasearch + - search-insights + + '@algolia/autocomplete-plugin-algolia-insights@1.17.7(@algolia/client-search@5.35.0)(algoliasearch@5.35.0)(search-insights@2.17.3)': + dependencies: + '@algolia/autocomplete-shared': 1.17.7(@algolia/client-search@5.35.0)(algoliasearch@5.35.0) + search-insights: 2.17.3 + transitivePeerDependencies: + - '@algolia/client-search' + - algoliasearch + + '@algolia/autocomplete-preset-algolia@1.17.7(@algolia/client-search@5.35.0)(algoliasearch@5.35.0)': + dependencies: + '@algolia/autocomplete-shared': 1.17.7(@algolia/client-search@5.35.0)(algoliasearch@5.35.0) + '@algolia/client-search': 5.35.0 + algoliasearch: 5.35.0 + + '@algolia/autocomplete-shared@1.17.7(@algolia/client-search@5.35.0)(algoliasearch@5.35.0)': + dependencies: + '@algolia/client-search': 5.35.0 + algoliasearch: 5.35.0 + + '@algolia/client-abtesting@5.35.0': + dependencies: + '@algolia/client-common': 5.35.0 + '@algolia/requester-browser-xhr': 5.35.0 + '@algolia/requester-fetch': 5.35.0 + '@algolia/requester-node-http': 5.35.0 + + '@algolia/client-analytics@5.35.0': + dependencies: + '@algolia/client-common': 5.35.0 + '@algolia/requester-browser-xhr': 5.35.0 + '@algolia/requester-fetch': 5.35.0 + '@algolia/requester-node-http': 5.35.0 + + '@algolia/client-common@5.35.0': {} + + '@algolia/client-insights@5.35.0': + dependencies: + '@algolia/client-common': 5.35.0 + '@algolia/requester-browser-xhr': 5.35.0 + '@algolia/requester-fetch': 5.35.0 + '@algolia/requester-node-http': 5.35.0 + + '@algolia/client-personalization@5.35.0': + dependencies: + '@algolia/client-common': 5.35.0 + '@algolia/requester-browser-xhr': 5.35.0 + '@algolia/requester-fetch': 5.35.0 + '@algolia/requester-node-http': 5.35.0 + + '@algolia/client-query-suggestions@5.35.0': + dependencies: + '@algolia/client-common': 5.35.0 + '@algolia/requester-browser-xhr': 5.35.0 + '@algolia/requester-fetch': 5.35.0 + '@algolia/requester-node-http': 5.35.0 + + '@algolia/client-search@5.35.0': + dependencies: + '@algolia/client-common': 5.35.0 + '@algolia/requester-browser-xhr': 5.35.0 + '@algolia/requester-fetch': 5.35.0 + '@algolia/requester-node-http': 5.35.0 + + '@algolia/ingestion@1.35.0': + dependencies: + '@algolia/client-common': 5.35.0 + '@algolia/requester-browser-xhr': 5.35.0 + '@algolia/requester-fetch': 5.35.0 + '@algolia/requester-node-http': 5.35.0 + + '@algolia/monitoring@1.35.0': + dependencies: + '@algolia/client-common': 5.35.0 + '@algolia/requester-browser-xhr': 5.35.0 + '@algolia/requester-fetch': 5.35.0 + '@algolia/requester-node-http': 5.35.0 + + '@algolia/recommend@5.35.0': + dependencies: + '@algolia/client-common': 5.35.0 + '@algolia/requester-browser-xhr': 5.35.0 + '@algolia/requester-fetch': 5.35.0 + '@algolia/requester-node-http': 5.35.0 + + '@algolia/requester-browser-xhr@5.35.0': + dependencies: + '@algolia/client-common': 5.35.0 + + '@algolia/requester-fetch@5.35.0': + dependencies: + '@algolia/client-common': 5.35.0 + + '@algolia/requester-node-http@5.35.0': + dependencies: + '@algolia/client-common': 5.35.0 + '@ampproject/remapping@2.3.0': dependencies: '@jridgewell/gen-mapping': 0.3.8 @@ -3628,6 +4352,10 @@ snapshots: dependencies: '@babel/types': 7.27.3 + '@babel/parser@7.28.0': + dependencies: + '@babel/types': 7.28.2 + '@babel/plugin-syntax-decorators@7.27.1(@babel/core@7.27.3)': dependencies: '@babel/core': 7.27.3 @@ -3698,6 +4426,11 @@ snapshots: '@babel/helper-string-parser': 7.27.1 '@babel/helper-validator-identifier': 7.27.1 + '@babel/types@7.28.2': + dependencies: + '@babel/helper-string-parser': 7.27.1 + '@babel/helper-validator-identifier': 7.27.1 + '@biomejs/biome@1.9.4': optionalDependencies: '@biomejs/cli-darwin-arm64': 1.9.4 @@ -3836,6 +4569,33 @@ snapshots: react: 19.1.0 tslib: 2.6.2 + '@docsearch/css@3.8.2': {} + + '@docsearch/js@3.8.2(@algolia/client-search@5.35.0)(@types/react@19.1.6)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(search-insights@2.17.3)': + dependencies: + '@docsearch/react': 3.8.2(@algolia/client-search@5.35.0)(@types/react@19.1.6)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(search-insights@2.17.3) + preact: 10.26.7 + transitivePeerDependencies: + - '@algolia/client-search' + - '@types/react' + - react + - react-dom + - search-insights + + '@docsearch/react@3.8.2(@algolia/client-search@5.35.0)(@types/react@19.1.6)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(search-insights@2.17.3)': + dependencies: + '@algolia/autocomplete-core': 1.17.7(@algolia/client-search@5.35.0)(algoliasearch@5.35.0)(search-insights@2.17.3) + '@algolia/autocomplete-preset-algolia': 1.17.7(@algolia/client-search@5.35.0)(algoliasearch@5.35.0) + '@docsearch/css': 3.8.2 + algoliasearch: 5.35.0 + optionalDependencies: + '@types/react': 19.1.6 + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) + search-insights: 2.17.3 + transitivePeerDependencies: + - '@algolia/client-search' + '@drizzle-team/brocli@0.10.2': {} '@drizzle-team/brocli@0.11.0': {} @@ -3850,102 +4610,153 @@ snapshots: '@esbuild-kit/core-utils': 3.3.2 get-tsconfig: 4.10.1 + '@esbuild/aix-ppc64@0.21.5': + optional: true + '@esbuild/aix-ppc64@0.25.5': optional: true '@esbuild/android-arm64@0.18.20': optional: true + '@esbuild/android-arm64@0.21.5': + optional: true + '@esbuild/android-arm64@0.25.5': optional: true '@esbuild/android-arm@0.18.20': optional: true + '@esbuild/android-arm@0.21.5': + optional: true + '@esbuild/android-arm@0.25.5': optional: true '@esbuild/android-x64@0.18.20': optional: true + '@esbuild/android-x64@0.21.5': + optional: true + '@esbuild/android-x64@0.25.5': optional: true '@esbuild/darwin-arm64@0.18.20': optional: true + '@esbuild/darwin-arm64@0.21.5': + optional: true + '@esbuild/darwin-arm64@0.25.5': optional: true '@esbuild/darwin-x64@0.18.20': optional: true + '@esbuild/darwin-x64@0.21.5': + optional: true + '@esbuild/darwin-x64@0.25.5': optional: true '@esbuild/freebsd-arm64@0.18.20': optional: true + '@esbuild/freebsd-arm64@0.21.5': + optional: true + '@esbuild/freebsd-arm64@0.25.5': optional: true '@esbuild/freebsd-x64@0.18.20': optional: true + '@esbuild/freebsd-x64@0.21.5': + optional: true + '@esbuild/freebsd-x64@0.25.5': optional: true '@esbuild/linux-arm64@0.18.20': optional: true + '@esbuild/linux-arm64@0.21.5': + optional: true + '@esbuild/linux-arm64@0.25.5': optional: true '@esbuild/linux-arm@0.18.20': optional: true + '@esbuild/linux-arm@0.21.5': + optional: true + '@esbuild/linux-arm@0.25.5': optional: true '@esbuild/linux-ia32@0.18.20': optional: true + '@esbuild/linux-ia32@0.21.5': + optional: true + '@esbuild/linux-ia32@0.25.5': optional: true '@esbuild/linux-loong64@0.18.20': optional: true + '@esbuild/linux-loong64@0.21.5': + optional: true + '@esbuild/linux-loong64@0.25.5': optional: true '@esbuild/linux-mips64el@0.18.20': optional: true + '@esbuild/linux-mips64el@0.21.5': + optional: true + '@esbuild/linux-mips64el@0.25.5': optional: true '@esbuild/linux-ppc64@0.18.20': optional: true + '@esbuild/linux-ppc64@0.21.5': + optional: true + '@esbuild/linux-ppc64@0.25.5': optional: true '@esbuild/linux-riscv64@0.18.20': optional: true + '@esbuild/linux-riscv64@0.21.5': + optional: true + '@esbuild/linux-riscv64@0.25.5': optional: true '@esbuild/linux-s390x@0.18.20': optional: true + '@esbuild/linux-s390x@0.21.5': + optional: true + '@esbuild/linux-s390x@0.25.5': optional: true '@esbuild/linux-x64@0.18.20': optional: true + '@esbuild/linux-x64@0.21.5': + optional: true + '@esbuild/linux-x64@0.25.5': optional: true @@ -3955,6 +4766,9 @@ snapshots: '@esbuild/netbsd-x64@0.18.20': optional: true + '@esbuild/netbsd-x64@0.21.5': + optional: true + '@esbuild/netbsd-x64@0.25.5': optional: true @@ -3964,30 +4778,45 @@ snapshots: '@esbuild/openbsd-x64@0.18.20': optional: true + '@esbuild/openbsd-x64@0.21.5': + optional: true + '@esbuild/openbsd-x64@0.25.5': optional: true '@esbuild/sunos-x64@0.18.20': optional: true + '@esbuild/sunos-x64@0.21.5': + optional: true + '@esbuild/sunos-x64@0.25.5': optional: true '@esbuild/win32-arm64@0.18.20': optional: true + '@esbuild/win32-arm64@0.21.5': + optional: true + '@esbuild/win32-arm64@0.25.5': optional: true '@esbuild/win32-ia32@0.18.20': optional: true + '@esbuild/win32-ia32@0.21.5': + optional: true + '@esbuild/win32-ia32@0.25.5': optional: true '@esbuild/win32-x64@0.18.20': optional: true + '@esbuild/win32-x64@0.21.5': + optional: true + '@esbuild/win32-x64@0.25.5': optional: true @@ -4040,6 +4869,12 @@ snapshots: hono: 4.7.10 minimatch: 9.0.5 + '@iconify-json/simple-icons@1.2.46': + dependencies: + '@iconify/types': 2.0.0 + + '@iconify/types@2.0.0': {} + '@internationalized/date@3.8.1': dependencies: '@swc/helpers': 0.5.17 @@ -5341,6 +6176,46 @@ snapshots: '@rollup/rollup-win32-x64-msvc@4.36.0': optional: true + '@shikijs/core@2.5.0': + dependencies: + '@shikijs/engine-javascript': 2.5.0 + '@shikijs/engine-oniguruma': 2.5.0 + '@shikijs/types': 2.5.0 + '@shikijs/vscode-textmate': 10.0.2 + '@types/hast': 3.0.4 + hast-util-to-html: 9.0.5 + + '@shikijs/engine-javascript@2.5.0': + dependencies: + '@shikijs/types': 2.5.0 + '@shikijs/vscode-textmate': 10.0.2 + oniguruma-to-es: 3.1.1 + + '@shikijs/engine-oniguruma@2.5.0': + dependencies: + '@shikijs/types': 2.5.0 + '@shikijs/vscode-textmate': 10.0.2 + + '@shikijs/langs@2.5.0': + dependencies: + '@shikijs/types': 2.5.0 + + '@shikijs/themes@2.5.0': + dependencies: + '@shikijs/types': 2.5.0 + + '@shikijs/transformers@2.5.0': + dependencies: + '@shikijs/core': 2.5.0 + '@shikijs/types': 2.5.0 + + '@shikijs/types@2.5.0': + dependencies: + '@shikijs/vscode-textmate': 10.0.2 + '@types/hast': 3.0.4 + + '@shikijs/vscode-textmate@10.0.2': {} + '@shopify/lang-jsonc@1.0.1': dependencies: '@lezer/highlight': 1.2.1 @@ -5436,8 +6311,25 @@ snapshots: '@types/estree@1.0.7': {} + '@types/hast@3.0.4': + dependencies: + '@types/unist': 3.0.3 + '@types/js-yaml@4.0.9': {} + '@types/linkify-it@5.0.0': {} + + '@types/markdown-it@14.1.2': + dependencies: + '@types/linkify-it': 5.0.0 + '@types/mdurl': 2.0.0 + + '@types/mdast@4.0.4': + dependencies: + '@types/unist': 3.0.3 + + '@types/mdurl@2.0.0': {} + '@types/node-fetch@2.6.12': dependencies: '@types/node': 22.15.24 @@ -5467,6 +6359,10 @@ snapshots: dependencies: '@types/node': 22.15.24 + '@types/unist@3.0.3': {} + + '@types/web-bluetooth@0.0.21': {} + '@types/websocket@1.0.10': dependencies: '@types/node': 22.15.24 @@ -5524,6 +6420,13 @@ snapshots: - '@codemirror/lint' - '@codemirror/search' + '@ungap/structured-clone@1.3.0': {} + + '@vitejs/plugin-vue@5.2.4(vite@5.4.19(@types/node@22.15.24)(lightningcss@1.30.1)(terser@5.39.0))(vue@3.5.18(typescript@5.8.3))': + dependencies: + vite: 5.4.19(@types/node@22.15.24)(lightningcss@1.30.1)(terser@5.39.0) + vue: 3.5.18(typescript@5.8.3) + '@vitest/expect@3.2.4': dependencies: '@types/chai': 5.2.2 @@ -5566,6 +6469,105 @@ snapshots: loupe: 3.1.4 tinyrainbow: 2.0.0 + '@vue/compiler-core@3.5.18': + dependencies: + '@babel/parser': 7.28.0 + '@vue/shared': 3.5.18 + entities: 4.5.0 + estree-walker: 2.0.2 + source-map-js: 1.2.1 + + '@vue/compiler-dom@3.5.18': + dependencies: + '@vue/compiler-core': 3.5.18 + '@vue/shared': 3.5.18 + + '@vue/compiler-sfc@3.5.18': + dependencies: + '@babel/parser': 7.28.0 + '@vue/compiler-core': 3.5.18 + '@vue/compiler-dom': 3.5.18 + '@vue/compiler-ssr': 3.5.18 + '@vue/shared': 3.5.18 + estree-walker: 2.0.2 + magic-string: 0.30.17 + postcss: 8.5.6 + source-map-js: 1.2.1 + + '@vue/compiler-ssr@3.5.18': + dependencies: + '@vue/compiler-dom': 3.5.18 + '@vue/shared': 3.5.18 + + '@vue/devtools-api@7.7.7': + dependencies: + '@vue/devtools-kit': 7.7.7 + + '@vue/devtools-kit@7.7.7': + dependencies: + '@vue/devtools-shared': 7.7.7 + birpc: 2.5.0 + hookable: 5.5.3 + mitt: 3.0.1 + perfect-debounce: 1.0.0 + speakingurl: 14.0.1 + superjson: 2.2.2 + + '@vue/devtools-shared@7.7.7': + dependencies: + rfdc: 1.4.1 + + '@vue/reactivity@3.5.18': + dependencies: + '@vue/shared': 3.5.18 + + '@vue/runtime-core@3.5.18': + dependencies: + '@vue/reactivity': 3.5.18 + '@vue/shared': 3.5.18 + + '@vue/runtime-dom@3.5.18': + dependencies: + '@vue/reactivity': 3.5.18 + '@vue/runtime-core': 3.5.18 + '@vue/shared': 3.5.18 + csstype: 3.1.3 + + '@vue/server-renderer@3.5.18(vue@3.5.18(typescript@5.8.3))': + dependencies: + '@vue/compiler-ssr': 3.5.18 + '@vue/shared': 3.5.18 + vue: 3.5.18(typescript@5.8.3) + + '@vue/shared@3.5.18': {} + + '@vueuse/core@12.8.2(typescript@5.8.3)': + dependencies: + '@types/web-bluetooth': 0.0.21 + '@vueuse/metadata': 12.8.2 + '@vueuse/shared': 12.8.2(typescript@5.8.3) + vue: 3.5.18(typescript@5.8.3) + transitivePeerDependencies: + - typescript + + '@vueuse/integrations@12.8.2(focus-trap@7.6.5)(typescript@5.8.3)': + dependencies: + '@vueuse/core': 12.8.2(typescript@5.8.3) + '@vueuse/shared': 12.8.2(typescript@5.8.3) + vue: 3.5.18(typescript@5.8.3) + optionalDependencies: + focus-trap: 7.6.5 + transitivePeerDependencies: + - typescript + + '@vueuse/metadata@12.8.2': {} + + '@vueuse/shared@12.8.2(typescript@5.8.3)': + dependencies: + vue: 3.5.18(typescript@5.8.3) + transitivePeerDependencies: + - typescript + '@xterm/addon-clipboard@0.1.0(@xterm/xterm@5.5.0)': dependencies: '@xterm/xterm': 5.5.0 @@ -5594,6 +6596,23 @@ snapshots: agent-base@7.1.3: {} + algoliasearch@5.35.0: + dependencies: + '@algolia/abtesting': 1.1.0 + '@algolia/client-abtesting': 5.35.0 + '@algolia/client-analytics': 5.35.0 + '@algolia/client-common': 5.35.0 + '@algolia/client-insights': 5.35.0 + '@algolia/client-personalization': 5.35.0 + '@algolia/client-query-suggestions': 5.35.0 + '@algolia/client-search': 5.35.0 + '@algolia/ingestion': 1.35.0 + '@algolia/monitoring': 1.35.0 + '@algolia/recommend': 5.35.0 + '@algolia/requester-browser-xhr': 5.35.0 + '@algolia/requester-fetch': 5.35.0 + '@algolia/requester-node-http': 5.35.0 + ansi-regex@5.0.1: {} ansi-regex@6.0.1: {} @@ -5676,6 +6695,8 @@ snapshots: transitivePeerDependencies: - '@types/react' + birpc@2.5.0: {} + bl@4.1.0: dependencies: buffer: 5.7.1 @@ -5716,6 +6737,8 @@ snapshots: caniuse-lite@1.0.30001718: {} + ccount@2.0.1: {} + chai@5.2.1: dependencies: assertion-error: 2.0.1 @@ -5724,6 +6747,10 @@ snapshots: loupe: 3.1.4 pathval: 2.0.1 + character-entities-html4@2.1.0: {} + + character-entities-legacy@3.0.0: {} + check-error@2.1.1: {} chokidar@4.0.3: @@ -5759,6 +6786,8 @@ snapshots: dependencies: delayed-stream: 1.0.0 + comma-separated-tokens@2.0.3: {} + commander@2.20.3: optional: true @@ -5766,6 +6795,10 @@ snapshots: cookie@1.0.2: {} + copy-anything@3.0.5: + dependencies: + is-what: 4.1.16 + crelt@1.0.6: {} cross-spawn@7.0.3: @@ -5802,10 +6835,16 @@ snapshots: delayed-stream@1.0.0: {} + dequal@2.0.3: {} + detect-libc@2.0.2: {} detect-libc@2.0.4: {} + devlop@1.1.0: + dependencies: + dequal: 2.0.3 + dotenv@16.5.0: {} drizzle-kit@0.31.1: @@ -5833,6 +6872,8 @@ snapshots: electron-to-chromium@1.5.160: {} + emoji-regex-xs@1.0.0: {} + emoji-regex@8.0.0: {} emoji-regex@9.2.2: {} @@ -5846,6 +6887,8 @@ snapshots: graceful-fs: 4.2.11 tapable: 2.2.2 + entities@4.5.0: {} + err-code@2.0.3: {} es-define-property@1.0.1: {} @@ -5897,6 +6940,32 @@ snapshots: '@esbuild/win32-ia32': 0.18.20 '@esbuild/win32-x64': 0.18.20 + esbuild@0.21.5: + optionalDependencies: + '@esbuild/aix-ppc64': 0.21.5 + '@esbuild/android-arm': 0.21.5 + '@esbuild/android-arm64': 0.21.5 + '@esbuild/android-x64': 0.21.5 + '@esbuild/darwin-arm64': 0.21.5 + '@esbuild/darwin-x64': 0.21.5 + '@esbuild/freebsd-arm64': 0.21.5 + '@esbuild/freebsd-x64': 0.21.5 + '@esbuild/linux-arm': 0.21.5 + '@esbuild/linux-arm64': 0.21.5 + '@esbuild/linux-ia32': 0.21.5 + '@esbuild/linux-loong64': 0.21.5 + '@esbuild/linux-mips64el': 0.21.5 + '@esbuild/linux-ppc64': 0.21.5 + '@esbuild/linux-riscv64': 0.21.5 + '@esbuild/linux-s390x': 0.21.5 + '@esbuild/linux-x64': 0.21.5 + '@esbuild/netbsd-x64': 0.21.5 + '@esbuild/openbsd-x64': 0.21.5 + '@esbuild/sunos-x64': 0.21.5 + '@esbuild/win32-arm64': 0.21.5 + '@esbuild/win32-ia32': 0.21.5 + '@esbuild/win32-x64': 0.21.5 + esbuild@0.25.5: optionalDependencies: '@esbuild/aix-ppc64': 0.25.5 @@ -5954,6 +7023,10 @@ snapshots: file-uri-to-path@1.0.0: optional: true + focus-trap@7.6.5: + dependencies: + tabbable: 6.2.0 + foreground-child@3.3.0: dependencies: cross-spawn: 7.0.3 @@ -6041,14 +7114,36 @@ snapshots: dependencies: function-bind: 1.1.2 + hast-util-to-html@9.0.5: + dependencies: + '@types/hast': 3.0.4 + '@types/unist': 3.0.3 + ccount: 2.0.1 + comma-separated-tokens: 2.0.3 + hast-util-whitespace: 3.0.0 + html-void-elements: 3.0.0 + mdast-util-to-hast: 13.2.0 + property-information: 7.1.0 + space-separated-tokens: 2.0.2 + stringify-entities: 4.0.4 + zwitch: 2.0.4 + + hast-util-whitespace@3.0.0: + dependencies: + '@types/hast': 3.0.4 + hono@4.7.10: {} + hookable@5.5.3: {} + hosted-git-info@6.1.3: dependencies: lru-cache: 7.18.3 hpagent@1.2.0: {} + html-void-elements@3.0.0: {} + ieee754@1.2.1: optional: true @@ -6076,6 +7171,8 @@ snapshots: is-fullwidth-code-point@3.0.0: {} + is-what@4.1.16: {} + isbot@5.1.28: {} isexe@2.0.0: {} @@ -6253,8 +7350,39 @@ snapshots: dependencies: '@jridgewell/sourcemap-codec': 1.5.0 + mark.js@8.11.1: {} + math-intrinsics@1.1.0: {} + mdast-util-to-hast@13.2.0: + dependencies: + '@types/hast': 3.0.4 + '@types/mdast': 4.0.4 + '@ungap/structured-clone': 1.3.0 + devlop: 1.1.0 + micromark-util-sanitize-uri: 2.0.1 + trim-lines: 3.0.1 + unist-util-position: 5.0.0 + unist-util-visit: 5.0.0 + vfile: 6.0.3 + + micromark-util-character@2.1.1: + dependencies: + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-util-encode@2.0.1: {} + + micromark-util-sanitize-uri@2.0.1: + dependencies: + micromark-util-character: 2.1.1 + micromark-util-encode: 2.0.1 + micromark-util-symbol: 2.0.1 + + micromark-util-symbol@2.0.1: {} + + micromark-util-types@2.0.2: {} + mime-db@1.52.0: {} mime-types@2.1.35: @@ -6275,11 +7403,15 @@ snapshots: minipass@7.1.2: {} + minisearch@7.1.2: {} + minizlib@3.0.1: dependencies: minipass: 7.1.2 rimraf: 5.0.10 + mitt@3.0.1: {} + mkdirp-classic@0.5.3: optional: true @@ -6349,6 +7481,12 @@ snapshots: dependencies: wrappy: 1.0.2 + oniguruma-to-es@3.1.1: + dependencies: + emoji-regex-xs: 1.0.0 + regex: 6.0.1 + regex-recursion: 6.0.2 + openid-client@6.5.0: dependencies: jose: 6.0.11 @@ -6369,6 +7507,8 @@ snapshots: pathval@2.0.1: {} + perfect-debounce@1.0.0: {} + picocolors@1.1.1: {} picomatch@4.0.2: {} @@ -6387,6 +7527,12 @@ snapshots: picocolors: 1.1.1 source-map-js: 1.2.1 + postcss@8.5.6: + dependencies: + nanoid: 3.3.11 + picocolors: 1.1.1 + source-map-js: 1.2.1 + preact@10.26.7: {} prebuild-install@7.1.3: @@ -6418,6 +7564,8 @@ snapshots: err-code: 2.0.3 retry: 0.12.0 + property-information@7.1.0: {} + pump@3.0.2: dependencies: end-of-stream: 1.4.4 @@ -6611,6 +7759,16 @@ snapshots: readdirp@4.1.2: {} + regex-recursion@6.0.2: + dependencies: + regex-utilities: 2.3.0 + + regex-utilities@2.3.0: {} + + regex@6.0.1: + dependencies: + regex-utilities: 2.3.0 + remix-utils@8.7.0(react-router@7.6.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react@19.1.0)(zod@3.25.33): dependencies: type-fest: 4.41.0 @@ -6625,6 +7783,8 @@ snapshots: rfc4648@1.5.4: {} + rfdc@1.4.1: {} + rimraf@5.0.10: dependencies: glob: 10.4.5 @@ -6659,6 +7819,8 @@ snapshots: scheduler@0.26.0: {} + search-insights@2.17.3: {} + semver@6.3.1: {} semver@7.7.2: {} @@ -6671,6 +7833,17 @@ snapshots: shebang-regex@3.0.0: {} + shiki@2.5.0: + dependencies: + '@shikijs/core': 2.5.0 + '@shikijs/engine-javascript': 2.5.0 + '@shikijs/engine-oniguruma': 2.5.0 + '@shikijs/langs': 2.5.0 + '@shikijs/themes': 2.5.0 + '@shikijs/types': 2.5.0 + '@shikijs/vscode-textmate': 10.0.2 + '@types/hast': 3.0.4 + siginfo@2.0.0: {} signal-exit@4.1.0: {} @@ -6711,6 +7884,8 @@ snapshots: source-map@0.6.1: {} + space-separated-tokens@2.0.2: {} + spdx-correct@3.2.0: dependencies: spdx-expression-parse: 3.0.1 @@ -6725,6 +7900,8 @@ snapshots: spdx-license-ids@3.0.21: {} + speakingurl@14.0.1: {} + sprintf-js@1.1.3: {} stackback@0.0.2: {} @@ -6759,6 +7936,11 @@ snapshots: safe-buffer: 5.2.1 optional: true + stringify-entities@4.0.4: + dependencies: + character-entities-html4: 2.1.0 + character-entities-legacy: 3.0.0 + strip-ansi@6.0.1: dependencies: ansi-regex: 5.0.1 @@ -6776,6 +7958,12 @@ snapshots: style-mod@4.1.2: {} + superjson@2.2.2: + dependencies: + copy-anything: 3.0.5 + + tabbable@6.2.0: {} + tailwind-merge@3.3.0: {} tailwindcss-animate@1.0.7(tailwindcss@4.1.8): @@ -6861,6 +8049,8 @@ snapshots: tr46@0.0.3: {} + trim-lines@3.0.1: {} + tsconfck@3.1.4(typescript@5.8.3): optionalDependencies: typescript: 5.8.3 @@ -6893,6 +8083,29 @@ snapshots: undici@7.10.0: {} + unist-util-is@6.0.0: + dependencies: + '@types/unist': 3.0.3 + + unist-util-position@5.0.0: + dependencies: + '@types/unist': 3.0.3 + + unist-util-stringify-position@4.0.0: + dependencies: + '@types/unist': 3.0.3 + + unist-util-visit-parents@6.0.1: + dependencies: + '@types/unist': 3.0.3 + unist-util-is: 6.0.0 + + unist-util-visit@5.0.0: + dependencies: + '@types/unist': 3.0.3 + unist-util-is: 6.0.0 + unist-util-visit-parents: 6.0.1 + universalify@2.0.1: {} unplugin@2.1.0: @@ -6935,6 +8148,16 @@ snapshots: validate-npm-package-name@5.0.1: {} + vfile-message@4.0.3: + dependencies: + '@types/unist': 3.0.3 + unist-util-stringify-position: 4.0.0 + + vfile@6.0.3: + dependencies: + '@types/unist': 3.0.3 + vfile-message: 4.0.3 + vite-node@3.0.0-beta.2(@types/node@22.15.24)(jiti@2.4.2)(lightningcss@1.30.1)(terser@5.39.0)(tsx@4.19.4)(yaml@2.8.0): dependencies: cac: 6.7.14 @@ -6988,6 +8211,17 @@ snapshots: - supports-color - typescript + vite@5.4.19(@types/node@22.15.24)(lightningcss@1.30.1)(terser@5.39.0): + dependencies: + esbuild: 0.21.5 + postcss: 8.5.4 + rollup: 4.36.0 + optionalDependencies: + '@types/node': 22.15.24 + fsevents: 2.3.3 + lightningcss: 1.30.1 + terser: 5.39.0 + vite@6.3.5(@types/node@22.15.24)(jiti@2.4.2)(lightningcss@1.30.1)(terser@5.39.0)(tsx@4.19.4)(yaml@2.8.0): dependencies: esbuild: 0.25.5 @@ -7005,6 +8239,55 @@ snapshots: tsx: 4.19.4 yaml: 2.8.0 + vitepress@1.6.4(@algolia/client-search@5.35.0)(@types/node@22.15.24)(@types/react@19.1.6)(lightningcss@1.30.1)(postcss@8.5.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(search-insights@2.17.3)(terser@5.39.0)(typescript@5.8.3): + dependencies: + '@docsearch/css': 3.8.2 + '@docsearch/js': 3.8.2(@algolia/client-search@5.35.0)(@types/react@19.1.6)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(search-insights@2.17.3) + '@iconify-json/simple-icons': 1.2.46 + '@shikijs/core': 2.5.0 + '@shikijs/transformers': 2.5.0 + '@shikijs/types': 2.5.0 + '@types/markdown-it': 14.1.2 + '@vitejs/plugin-vue': 5.2.4(vite@5.4.19(@types/node@22.15.24)(lightningcss@1.30.1)(terser@5.39.0))(vue@3.5.18(typescript@5.8.3)) + '@vue/devtools-api': 7.7.7 + '@vue/shared': 3.5.18 + '@vueuse/core': 12.8.2(typescript@5.8.3) + '@vueuse/integrations': 12.8.2(focus-trap@7.6.5)(typescript@5.8.3) + focus-trap: 7.6.5 + mark.js: 8.11.1 + minisearch: 7.1.2 + shiki: 2.5.0 + vite: 5.4.19(@types/node@22.15.24)(lightningcss@1.30.1)(terser@5.39.0) + vue: 3.5.18(typescript@5.8.3) + optionalDependencies: + postcss: 8.5.4 + transitivePeerDependencies: + - '@algolia/client-search' + - '@types/node' + - '@types/react' + - async-validator + - axios + - change-case + - drauu + - fuse.js + - idb-keyval + - jwt-decode + - less + - lightningcss + - nprogress + - qrcode + - react + - react-dom + - sass + - sass-embedded + - search-insights + - sortablejs + - stylus + - sugarss + - terser + - typescript + - universal-cookie + vitest@3.2.4(@types/node@22.15.24)(jiti@2.4.2)(lightningcss@1.30.1)(terser@5.39.0)(tsx@4.19.4)(yaml@2.8.0): dependencies: '@types/chai': 5.2.2 @@ -7046,6 +8329,16 @@ snapshots: - tsx - yaml + vue@3.5.18(typescript@5.8.3): + dependencies: + '@vue/compiler-dom': 3.5.18 + '@vue/compiler-sfc': 3.5.18 + '@vue/runtime-dom': 3.5.18 + '@vue/server-renderer': 3.5.18(vue@3.5.18(typescript@5.8.3)) + '@vue/shared': 3.5.18 + optionalDependencies: + typescript: 5.8.3 + w3c-keyname@2.2.8: {} web-streams-polyfill@3.3.3: {} @@ -7105,3 +8398,5 @@ snapshots: zod@3.25.33: optional: true + + zwitch@2.0.4: {} From c98dc5ed1d1c7b2d166c893a3ee896203c849965 Mon Sep 17 00:00:00 2001 From: Igor Ramazanov Date: Sat, 9 Aug 2025 22:56:18 +0000 Subject: [PATCH 68/97] feat: nix: docs: improve NixOS docs generations --- docs/NixOS-options.md | 887 +++++++++--------------------------------- nix/docs.js | 37 ++ nix/docs.nix | 5 +- 3 files changed, 233 insertions(+), 696 deletions(-) create mode 100644 nix/docs.js diff --git a/docs/NixOS-options.md b/docs/NixOS-options.md index 165afa6..275b8ba 100644 --- a/docs/NixOS-options.md +++ b/docs/NixOS-options.md @@ -1,881 +1,378 @@ -## services\.headplane\.enable +# NixOS module options +All options must be under `services.headplane`. +For example: `settings.headscale.config_path` becomes `services.headplane.settings.headscale.config_path`. -Whether to enable headplane\. +## debug +*Description:* Enable debug logging +*Type:* boolean +*Default:* `false` -*Type:* -boolean +## enable +*Description:* Whether to enable headplane. +*Type:* boolean -*Default:* -` false ` +*Default:* `false` +*Example:* `true` -*Example:* -` true ` +## package +*Description:* The headplane package to use. -*Declared by:* - - [nix/options.nix](https://github.com/tale/headplane/blob/main/nix/options.nix) +*Type:* package +*Default:* `pkgs.headplane` -## services\.headplane\.package +## settings +*Description:* Headplane configuration options. Generates a YAML config file. +See: https://github.com/tale/headplane/blob/main/config.example.yaml +*Type:* submodule -The headplane package to use\. +*Default:* `{ }` +## settings.headscale +*Description:* Headscale specific settings for Headplane integration. -*Type:* -package +*Type:* submodule +*Default:* `{ }` -*Default:* -` pkgs.headplane ` - -*Declared by:* - - [nix/options.nix](https://github.com/tale/headplane/blob/main/nix/options.nix) - - - -## services\.headplane\.debug - -Enable debug logging - - - -*Type:* -boolean - - - -*Default:* -` false ` - -*Declared by:* - - [nix/options.nix](https://github.com/tale/headplane/blob/main/nix/options.nix) - - - -## services\.headplane\.settings - - - -Headplane configuration options\. Generates a YAML config file\. -See: https://github\.com/tale/headplane/blob/main/config\.example\.yaml - - - -*Type:* -submodule - - - -*Default:* -` { } ` - -*Declared by:* - - [nix/options.nix](https://github.com/tale/headplane/blob/main/nix/options.nix) - - - -## services\.headplane\.settings\.headscale - - - -Headscale specific settings for Headplane integration\. - - - -*Type:* -submodule - - - -*Default:* -` { } ` - -*Declared by:* - - [nix/options.nix](https://github.com/tale/headplane/blob/main/nix/options.nix) - - - -## services\.headplane\.settings\.headscale\.config_path - - - -Path to the Headscale configuration file\. -This is optional, but HIGHLY recommended for the best experience\. +## settings.headscale.config_path +*Description:* Path to the Headscale configuration file. +This is optional, but HIGHLY recommended for the best experience. If this is read only, Headplane will show your configuration settings -in the Web UI, but they cannot be changed\. +in the Web UI, but they cannot be changed. +*Type:* null or absolute path -*Type:* -null or absolute path +*Default:* `null` +*Example:* `"/etc/headscale/config.yaml"` -*Default:* -` null ` +## settings.headscale.config_strict +*Description:* Headplane internally validates the Headscale configuration +to ensure that it changes the configuration in a safe way. +If you want to disable this validation, set this to false. +*Type:* boolean -*Example:* -` "/etc/headscale/config.yaml" ` +*Default:* `true` -*Declared by:* - - [nix/options.nix](https://github.com/tale/headplane/blob/main/nix/options.nix) +## settings.headscale.dns_records_path +*Description:* If you are using `dns.extra_records_path` in your Headscale configuration, you need to set this to the path for Headplane to be able to read the DNS records. +Ensure that the file is both readable and writable by the Headplane process. +When using this, Headplane will no longer need to automatically restart Headscale for DNS record changes. -## services\.headplane\.settings\.headscale\.config_strict +*Type:* null or absolute path +*Default:* `null` +*Example:* `"/var/lib/headplane/extra_records.json"` -Headplane internally validates the Headscale configuration -to ensure that it changes the configuration in a safe way\. -If you want to disable this validation, set this to false\. +## settings.headscale.public_url +*Description:* Public URL if differrent. This affects certain parts of the web UI. +*Type:* null or string -*Type:* -boolean +*Default:* `null` +*Example:* `"https://headscale.example.com"` -*Default:* -` true ` +## settings.headscale.tls_cert_path +*Description:* Path to a file containing the TLS certificate. -*Declared by:* - - [nix/options.nix](https://github.com/tale/headplane/blob/main/nix/options.nix) +*Type:* null or absolute path +*Default:* `null` -## services\.headplane\.settings\.headscale\.dns_records_path +*Example:* `"config.sops.secrets.tls_cert.path"` +## settings.headscale.url +*Description:* The URL to your Headscale instance. +All API requests are routed through this URL. +THIS IS NOT the gRPC endpoint, but the HTTP endpoint. +IMPORTANT: If you are using TLS this MUST be set to `https://`. -If you are using ` dns.extra_records_path ` in your Headscale configuration, you need to set this to the path for Headplane to be able to read the DNS records\. -Ensure that the file is both readable and writable by the Headplane process\. -When using this, Headplane will no longer need to automatically restart Headscale for DNS record changes\. +*Type:* string +*Default:* `"http://127.0.0.1:8080"` -*Type:* -null or absolute path +*Example:* `"https://headscale.example.com"` +## settings.integration +*Description:* Integration configurations for Headplane to interact with Headscale. -*Default:* -` null ` +*Type:* submodule +*Default:* `{ }` -*Example:* -` "/var/lib/headplane/extra_records.json" ` +## settings.integration.agent +*Description:* Agent configuration for the Headplane agent. -*Declared by:* - - [nix/options.nix](https://github.com/tale/headplane/blob/main/nix/options.nix) +*Type:* submodule +*Default:* `{ }` -## services\.headplane\.settings\.headscale\.public_url +## settings.integration.agent.cache_path +*Description:* Where to store the agent cache. +*Type:* absolute path +*Default:* `"/var/lib/headplane/agent_cache.json"` -Public URL if differrent\. This affects certain parts of the web UI\. +## settings.integration.agent.cache_ttl +*Description:* How long to cache agent information (in milliseconds). +If you want data to update faster, reduce the TTL, but this will increase the frequency of requests to Headscale. -*Type:* -null or string +*Type:* signed integer +*Default:* `180000` -*Default:* -` null ` +## settings.integration.agent.enabled +*Description:* The Headplane agent allows retrieving information about nodes. +This allows the UI to display version, OS, and connectivity data. +You will see the Headplane agent in your Tailnet as a node when it connects. +*Type:* boolean -*Example:* -` "https://headscale.example.com" ` +*Default:* `false` -*Declared by:* - - [nix/options.nix](https://github.com/tale/headplane/blob/main/nix/options.nix) +## settings.integration.agent.host_name +*Description:* Optionally change the name of the agent in the Tailnet +*Type:* string -## services\.headplane\.settings\.headscale\.tls_cert_path +*Default:* `"headplane-agent"` +## settings.integration.agent.package +*Description:* The headplane-agent package to use. -Path to a file containing the TLS certificate\. +*Type:* package +*Default:* `pkgs.headplane-agent` -*Type:* -null or absolute path +## settings.integration.agent.pre_authkey_path +*Description:* Path to a file containing the agent preauth key. +To connect to your Tailnet, you need to generate a pre-auth key. +This can be done via the web UI or through the `headscale` CLI. +*Type:* null or absolute path -*Default:* -` null ` +*Default:* `null` +*Example:* `"config.sops.secrets.agent_pre_authkey.path"` -*Example:* -` "config.sops.secrets.tls_cert.path" ` +## settings.integration.agent.work_dir +*Description:* Do not change this unless you are running a custom deployment. +The work_dir represents where the agent will store its data to be able to automatically reauthenticate with your Tailnet. +It needs to be writable by the user running the Headplane process. -*Declared by:* - - [nix/options.nix](https://github.com/tale/headplane/blob/main/nix/options.nix) +*Type:* absolute path +*Default:* `"/var/lib/headplane/agent"` -## services\.headplane\.settings\.headscale\.url +## settings.integration.proc +*Description:* Native process integration settings. +*Type:* submodule -The URL to your Headscale instance\. -All API requests are routed through this URL\. -THIS IS NOT the gRPC endpoint, but the HTTP endpoint\. -IMPORTANT: If you are using TLS this MUST be set to ` https:// `\. +*Default:* `{ }` - -*Type:* -string - - - -*Default:* -` "http://127.0.0.1:8080" ` - - - -*Example:* -` "https://headscale.example.com" ` - -*Declared by:* - - [nix/options.nix](https://github.com/tale/headplane/blob/main/nix/options.nix) - - - -## services\.headplane\.settings\.integration - - - -Integration configurations for Headplane to interact with Headscale\. - - - -*Type:* -submodule - - - -*Default:* -` { } ` - -*Declared by:* - - [nix/options.nix](https://github.com/tale/headplane/blob/main/nix/options.nix) - - - -## services\.headplane\.settings\.integration\.agent - - - -Agent configuration for the Headplane agent\. - - - -*Type:* -submodule - - - -*Default:* -` { } ` - -*Declared by:* - - [nix/options.nix](https://github.com/tale/headplane/blob/main/nix/options.nix) - - - -## services\.headplane\.settings\.integration\.agent\.enabled - - - -The Headplane agent allows retrieving information about nodes\. -This allows the UI to display version, OS, and connectivity data\. -You will see the Headplane agent in your Tailnet as a node when it connects\. - - - -*Type:* -boolean - - - -*Default:* -` false ` - -*Declared by:* - - [nix/options.nix](https://github.com/tale/headplane/blob/main/nix/options.nix) - - - -## services\.headplane\.settings\.integration\.agent\.package - - - -The headplane-agent package to use\. - - - -*Type:* -package - - - -*Default:* -` pkgs.headplane-agent ` - -*Declared by:* - - [nix/options.nix](https://github.com/tale/headplane/blob/main/nix/options.nix) - - - -## services\.headplane\.settings\.integration\.agent\.cache_path - - - -Where to store the agent cache\. - - - -*Type:* -absolute path - - - -*Default:* -` "/var/lib/headplane/agent_cache.json" ` - -*Declared by:* - - [nix/options.nix](https://github.com/tale/headplane/blob/main/nix/options.nix) - - - -## services\.headplane\.settings\.integration\.agent\.cache_ttl - - - -How long to cache agent information (in milliseconds)\. -If you want data to update faster, reduce the TTL, but this will increase the frequency of requests to Headscale\. - - - -*Type:* -signed integer - - - -*Default:* -` 180000 ` - -*Declared by:* - - [nix/options.nix](https://github.com/tale/headplane/blob/main/nix/options.nix) - - - -## services\.headplane\.settings\.integration\.agent\.host_name - - - -Optionally change the name of the agent in the Tailnet - - - -*Type:* -string - - - -*Default:* -` "headplane-agent" ` - -*Declared by:* - - [nix/options.nix](https://github.com/tale/headplane/blob/main/nix/options.nix) - - - -## services\.headplane\.settings\.integration\.agent\.pre_authkey_path - - - -Path to a file containing the agent preauth key\. -To connect to your Tailnet, you need to generate a pre-auth key\. -This can be done via the web UI or through the ` headscale ` CLI\. - - - -*Type:* -null or absolute path - - - -*Default:* -` null ` - - - -*Example:* -` "config.sops.secrets.agent_pre_authkey.path" ` - -*Declared by:* - - [nix/options.nix](https://github.com/tale/headplane/blob/main/nix/options.nix) - - - -## services\.headplane\.settings\.integration\.agent\.work_dir - - - -Do not change this unless you are running a custom deployment\. -The work_dir represents where the agent will store its data to be able to automatically reauthenticate with your Tailnet\. -It needs to be writable by the user running the Headplane process\. - - - -*Type:* -absolute path - - - -*Default:* -` "/var/lib/headplane/agent" ` - -*Declared by:* - - [nix/options.nix](https://github.com/tale/headplane/blob/main/nix/options.nix) - - - -## services\.headplane\.settings\.integration\.proc - - - -Native process integration settings\. - - - -*Type:* -submodule - - - -*Default:* -` { } ` - -*Declared by:* - - [nix/options.nix](https://github.com/tale/headplane/blob/main/nix/options.nix) - - - -## services\.headplane\.settings\.integration\.proc\.enabled - - - -Enable “Native” integration that works when Headscale and -Headplane are running outside of a container\. There is no additional +## settings.integration.proc.enabled +*Description:* Enable "Native" integration that works when Headscale and +Headplane are running outside of a container. There is no additional configuration, but you need to ensure that the Headplane process -can terminate the Headscale process\. +can terminate the Headscale process. +*Type:* boolean -*Type:* -boolean +*Default:* `true` +## settings.oidc +*Description:* OIDC Configuration for authentication. -*Default:* -` true ` +*Type:* submodule -*Declared by:* - - [nix/options.nix](https://github.com/tale/headplane/blob/main/nix/options.nix) +*Default:* `{ }` +## settings.oidc.client_id +*Description:* The client ID for the OIDC client. -## services\.headplane\.settings\.oidc +*Type:* string +*Default:* `""` +*Example:* `"your-client-id"` -OIDC Configuration for authentication\. +## settings.oidc.client_secret_path +*Description:* Path to a file containing the OIDC client secret. -*Type:* -submodule +*Type:* null or absolute path +*Default:* `null` +*Example:* `"config.sops.secrets.oidc_client_secret.path"` -*Default:* -` { } ` -*Declared by:* - - [nix/options.nix](https://github.com/tale/headplane/blob/main/nix/options.nix) +## settings.oidc.disable_api_key_login +*Description:* Whether to disable API key login. +*Type:* boolean +*Default:* `false` -## services\.headplane\.settings\.oidc\.client_id +## settings.oidc.headscale_api_key_path +*Description:* Path to a file containing the Headscale API key. -The client ID for the OIDC client\. +*Type:* null or absolute path +*Default:* `null` +*Example:* `"config.sops.secrets.headscale_api_key.path"` -*Type:* -string +## settings.oidc.issuer +*Description:* URL to OpenID issuer. +*Type:* string -*Default:* -` "" ` +*Default:* `""` +*Example:* `"https://provider.example.com/issuer-url"` -*Example:* -` "your-client-id" ` +## settings.oidc.redirect_uri +*Description:* This should point to your publicly accessible URL +for your Headplane instance with /admin/oidc/callback. -*Declared by:* - - [nix/options.nix](https://github.com/tale/headplane/blob/main/nix/options.nix) +*Type:* string +*Default:* `""` -## services\.headplane\.settings\.oidc\.client_secret_path +*Example:* `"https://headscale.example.com/admin/oidc/callback"` +## settings.oidc.token_endpoint_auth_method +*Description:* The token endpoint authentication method. -Path to a file containing the OIDC client secret\. +*Type:* one of "client_secret_post", "client_secret_basic", "client_secret_jwt" +*Default:* `"client_secret_post"` -*Type:* -null or absolute path +## settings.oidc.user_storage_file +*Description:* Path to a file containing the users and their permissions for Headplane. +*Type:* absolute path -*Default:* -` null ` +*Default:* `"/var/lib/headplane/users.json"` +*Example:* `"/var/lib/headplane/users.json"` -*Example:* -` "config.sops.secrets.oidc_client_secret.path" ` +## settings.server +*Description:* Server configuration for Headplane web application. -*Declared by:* - - [nix/options.nix](https://github.com/tale/headplane/blob/main/nix/options.nix) +*Type:* submodule +*Default:* `{ }` -## services\.headplane\.settings\.oidc\.disable_api_key_login +## settings.server.cookie_secret_path +*Description:* Path to a file containing the cookie secret. +The secret must be exactly 32 characters long. +*Type:* null or absolute path -Whether to disable API key login\. +*Default:* `null` +*Example:* `"config.sops.secrets.headplane_cookie.path"` -*Type:* -boolean +## settings.server.cookie_secure +*Description:* Should the cookies only work over HTTPS? +Set to false if running via HTTP without a proxy. +Recommended to be true in production. +*Type:* boolean -*Default:* -` false ` +*Default:* `true` -*Declared by:* - - [nix/options.nix](https://github.com/tale/headplane/blob/main/nix/options.nix) +## settings.server.data_path +*Description:* The path to persist Headplane specific data. +All data going forward is stored in this directory, including the internal database and any cache related files. +Data formats prior to 0.6.1 will automatically be migrated. -## services\.headplane\.settings\.oidc\.headscale_api_key_path +*Type:* absolute path +*Default:* `"/var/lib/headplane"` +*Example:* `"/var/lib/headplane"` -Path to a file containing the Headscale API key\. +## settings.server.host +*Description:* The host address to bind to. +*Type:* string -*Type:* -null or absolute path +*Default:* `"127.0.0.1"` +*Example:* `"0.0.0.0"` -*Default:* -` null ` +## settings.server.port +*Description:* The port to listen on. +*Type:* 16 bit unsigned integer; between 0 and 65535 (both inclusive) - -*Example:* -` "config.sops.secrets.headscale_api_key.path" ` - -*Declared by:* - - [nix/options.nix](https://github.com/tale/headplane/blob/main/nix/options.nix) - - - -## services\.headplane\.settings\.oidc\.issuer - - - -URL to OpenID issuer\. - - - -*Type:* -string - - - -*Default:* -` "" ` - - - -*Example:* -` "https://provider.example.com/issuer-url" ` - -*Declared by:* - - [nix/options.nix](https://github.com/tale/headplane/blob/main/nix/options.nix) - - - -## services\.headplane\.settings\.oidc\.redirect_uri - - - -This should point to your publicly accessible URL -for your Headplane instance with /admin/oidc/callback\. - - - -*Type:* -string - - - -*Default:* -` "" ` - - - -*Example:* -` "https://headscale.example.com/admin/oidc/callback" ` - -*Declared by:* - - [nix/options.nix](https://github.com/tale/headplane/blob/main/nix/options.nix) - - - -## services\.headplane\.settings\.oidc\.token_endpoint_auth_method - - - -The token endpoint authentication method\. - - - -*Type:* -one of “client_secret_post”, “client_secret_basic”, “client_secret_jwt” - - - -*Default:* -` "client_secret_post" ` - -*Declared by:* - - [nix/options.nix](https://github.com/tale/headplane/blob/main/nix/options.nix) - - - -## services\.headplane\.settings\.oidc\.user_storage_file - - - -Path to a file containing the users and their permissions for Headplane\. - - - -*Type:* -absolute path - - - -*Default:* -` "/var/lib/headplane/users.json" ` - - - -*Example:* -` "/var/lib/headplane/users.json" ` - -*Declared by:* - - [nix/options.nix](https://github.com/tale/headplane/blob/main/nix/options.nix) - - - -## services\.headplane\.settings\.server - - - -Server configuration for Headplane web application\. - - - -*Type:* -submodule - - - -*Default:* -` { } ` - -*Declared by:* - - [nix/options.nix](https://github.com/tale/headplane/blob/main/nix/options.nix) - - - -## services\.headplane\.settings\.server\.cookie_secret_path - - - -Path to a file containing the cookie secret\. -The secret must be exactly 32 characters long\. - - - -*Type:* -null or absolute path - - - -*Default:* -` null ` - - - -*Example:* -` "config.sops.secrets.headplane_cookie.path" ` - -*Declared by:* - - [nix/options.nix](https://github.com/tale/headplane/blob/main/nix/options.nix) - - - -## services\.headplane\.settings\.server\.cookie_secure - - - -Should the cookies only work over HTTPS? -Set to false if running via HTTP without a proxy\. -Recommended to be true in production\. - - - -*Type:* -boolean - - - -*Default:* -` true ` - -*Declared by:* - - [nix/options.nix](https://github.com/tale/headplane/blob/main/nix/options.nix) - - - -## services\.headplane\.settings\.server\.data_path - - - -The path to persist Headplane specific data\. -All data going forward is stored in this directory, including the internal database and any cache related files\. -Data formats prior to 0\.6\.1 will automatically be migrated\. - - - -*Type:* -absolute path - - - -*Default:* -` "/var/lib/headplane" ` - - - -*Example:* -` "/var/lib/headplane" ` - -*Declared by:* - - [nix/options.nix](https://github.com/tale/headplane/blob/main/nix/options.nix) - - - -## services\.headplane\.settings\.server\.host - - - -The host address to bind to\. - - - -*Type:* -string - - - -*Default:* -` "127.0.0.1" ` - - - -*Example:* -` "0.0.0.0" ` - -*Declared by:* - - [nix/options.nix](https://github.com/tale/headplane/blob/main/nix/options.nix) - - - -## services\.headplane\.settings\.server\.port - - - -The port to listen on\. - - - -*Type:* -16 bit unsigned integer; between 0 and 65535 (both inclusive) - - - -*Default:* -` 3000 ` - -*Declared by:* - - [nix/options.nix](https://github.com/tale/headplane/blob/main/nix/options.nix) - +*Default:* `3000` diff --git a/nix/docs.js b/nix/docs.js new file mode 100644 index 0000000..426d60a --- /dev/null +++ b/nix/docs.js @@ -0,0 +1,37 @@ +import fs from "node:fs"; + +function renderOptions(options) { + const blocks = Object.keys(options).map((key) => { + const opt = options[key]; + const name = key.split(".").slice(2).join("."); + const lines = []; + lines.push(`## ${name}`); + lines.push(`*Description:* ${opt.description}\n`); + lines.push(`*Type:* ${opt.type}\n`); + if (opt.default) { + lines.push(`*Default:* \`${opt.default.text}\`\n`); + } + if (opt.example) { + lines.push(`*Example:* \`${opt.example.text}\`\n`); + } + return lines.join("\n"); + }); + + return [ + `# NixOS module options + | + |All options must be under \`services.headplane\`. + | + |For example: \`settings.headscale.config_path\` becomes \`services.headplane.settings.headscale.config_path\`.` + .split("|") + .map((s) => s.replace(/\n\s+/g, "")) + .join("\n"), + ] + .concat(blocks) + .join("\n\n"); +} + +const filename = process.argv[2]; +const file = fs.readFileSync(filename); +const json = JSON.parse(file); +console.log(renderOptions(json)); diff --git a/nix/docs.nix b/nix/docs.nix index 84ea453..a33b04f 100644 --- a/nix/docs.nix +++ b/nix/docs.nix @@ -2,6 +2,7 @@ lib, nixosOptionsDoc, runCommand, + nodejs, ... }: let eval = lib.evalModules { @@ -21,4 +22,6 @@ inherit transformOptions; }; in - runCommand "headplane-nixos-docs.md" {} "cat ${optionsDoc.optionsCommonMark} > $out" + runCommand "headplane-nixos-docs.json" {} '' + ${nodejs}/bin/node ${./docs.js} ${optionsDoc.optionsJSON}/share/doc/nixos/options.json > $out + '' From bc3fc5ec5de8d63cd41b438e0727f1846faf8069 Mon Sep 17 00:00:00 2001 From: Aarnav Tale Date: Mon, 18 Aug 2025 13:10:53 -0400 Subject: [PATCH 69/97] fix: turns out we need isSsrBuild in vite.config.ts still --- vite.config.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vite.config.ts b/vite.config.ts index aa21024..48e4f03 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -24,7 +24,7 @@ const config = await readFile('config.example.yaml', 'utf-8'); const { server } = parse(config); export default defineConfig(({ isSsrBuild }) => ({ - base: `${prefix}/`, + base: isSsrBuild ? `${prefix}/` : undefined, plugins: [ reactRouterHonoServer(), reactRouter(), From 361bc493c05c23acb2c763dc29fea2a2dbe311b4 Mon Sep 17 00:00:00 2001 From: Aarnav Tale Date: Mon, 18 Aug 2025 13:28:16 -0400 Subject: [PATCH 70/97] chore: pin vitepress to 2.0.0 and add sponsor link --- docs/.vitepress/config.ts | 48 +- package.json | 12 +- pnpm-lock.yaml | 1060 ++++++++++++++----------------------- 3 files changed, 428 insertions(+), 692 deletions(-) diff --git a/docs/.vitepress/config.ts b/docs/.vitepress/config.ts index 18950f6..15c7bbe 100644 --- a/docs/.vitepress/config.ts +++ b/docs/.vitepress/config.ts @@ -1,42 +1,42 @@ -import { defineConfig } from "vitepress"; +import { defineConfig } from 'vitepress'; -// https://vitepress.dev/reference/site-config export default defineConfig({ - title: "Headplane", - description: "Documentation", - // base: "https://headplane.net", - ignoreDeadLinks: ["/docs/Integrated-Mode", "/docs/Simple-Mode"], + title: 'Headplane', + description: 'The missing dashboard for Headscale', + ignoreDeadLinks: ['/docs/Integrated-Mode', '/docs/Simple-Mode'], + cleanUrls: true, themeConfig: { - // https://vitepress.dev/reference/default-theme-config - nav: [{ text: "Home", link: "/" }], - + nav: [ + { text: 'Home', link: '/' }, + { text: 'Sponsor Headplane', link: 'https://github.com/sponsors/tale' }, + ], sidebar: [ { - text: "Chapters", + text: 'Chapters', items: [ - { text: "Getting Started", link: "/README" }, - { text: "Configuration", link: "/Configuration" }, - { text: "Bare-Metal Mode", link: "/Bare-Metal" }, - { text: "Integrated Mode", link: "/Integrated-Mode" }, - { text: "Simple Mode", link: "/Simple-Mode" }, - { text: "Nix", link: "/Nix" }, - { text: "NixOS", link: "/NixOS-options" }, - { text: "Security", link: "/SECURITY" }, - { text: "Contributing", link: "/CONTRIBUTING" }, - { text: "Changelog", link: "/CHANGELOG" }, + { text: 'Getting Started', link: '/README' }, + { text: 'Configuration', link: '/Configuration' }, + { text: 'Bare-Metal Mode', link: '/Bare-Metal' }, + { text: 'Integrated Mode', link: '/Integrated-Mode' }, + { text: 'Simple Mode', link: '/Simple-Mode' }, + { text: 'Nix', link: '/Nix' }, + { text: 'NixOS', link: '/NixOS-options' }, + { text: 'Security', link: '/SECURITY' }, + { text: 'Contributing', link: '/CONTRIBUTING' }, + { text: 'Changelog', link: '/CHANGELOG' }, ], }, ], socialLinks: [ - { icon: "github", link: "https://github.com/tale/headplane" }, + { icon: 'github', link: 'https://github.com/tale/headplane' }, ], lastUpdated: { - text: "Updated at", + text: 'Updated at', formatOptions: { - dateStyle: "full", - timeStyle: "medium", + dateStyle: 'full', + timeStyle: 'medium', }, }, }, diff --git a/package.json b/package.json index 192c0b2..d2daa95 100644 --- a/package.json +++ b/package.json @@ -81,7 +81,7 @@ "typescript": "^5.8.3", "vite": "^6.3.5", "vite-tsconfig-paths": "^5.1.4", - "vitepress": "^1.6.4", + "vitepress": "next", "vitest": "^3.1.3" }, "packageManager": "pnpm@10.4.0", @@ -94,14 +94,8 @@ "react-router-hono-server": "patches/react-router-hono-server.patch" }, "supportedArchitectures": { - "os": [ - "current", - "linux" - ], - "cpu": [ - "x64", - "arm64" - ] + "os": ["current", "linux"], + "cpu": ["x64", "arm64"] }, "onlyBuiltDependencies": [ "@biomejs/biome", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index e53843f..26f179a 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -184,7 +184,7 @@ importers: version: 7.6.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0) react-scan: specifier: ^0.3.4 - version: 0.3.4(@types/react@19.1.6)(react-dom@19.1.0(react@19.1.0))(react-router-dom@7.6.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react-router@7.6.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react@19.1.0)(rollup@4.36.0) + version: 0.3.4(@types/react@19.1.6)(react-dom@19.1.0(react@19.1.0))(react-router-dom@7.6.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react-router@7.6.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react@19.1.0)(rollup@4.46.3) tailwindcss: specifier: ^4.1.8 version: 4.1.8 @@ -204,90 +204,14 @@ importers: specifier: ^5.1.4 version: 5.1.4(typescript@5.8.3)(vite@6.3.5(@types/node@22.15.24)(jiti@2.4.2)(lightningcss@1.30.1)(terser@5.39.0)(tsx@4.19.4)(yaml@2.8.0)) vitepress: - specifier: ^1.6.4 - version: 1.6.4(@algolia/client-search@5.35.0)(@types/node@22.15.24)(@types/react@19.1.6)(lightningcss@1.30.1)(postcss@8.5.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(search-insights@2.17.3)(terser@5.39.0)(typescript@5.8.3) + specifier: next + version: 2.0.0-alpha.11(@types/node@22.15.24)(jiti@2.4.2)(lightningcss@1.30.1)(postcss@8.5.4)(terser@5.39.0)(tsx@4.19.4)(typescript@5.8.3)(yaml@2.8.0) vitest: specifier: ^3.1.3 version: 3.2.4(@types/node@22.15.24)(jiti@2.4.2)(lightningcss@1.30.1)(terser@5.39.0)(tsx@4.19.4)(yaml@2.8.0) packages: - '@algolia/abtesting@1.1.0': - resolution: {integrity: sha512-sEyWjw28a/9iluA37KLGu8vjxEIlb60uxznfTUmXImy7H5NvbpSO6yYgmgH5KiD7j+zTUUihiST0jEP12IoXow==} - engines: {node: '>= 14.0.0'} - - '@algolia/autocomplete-core@1.17.7': - resolution: {integrity: sha512-BjiPOW6ks90UKl7TwMv7oNQMnzU+t/wk9mgIDi6b1tXpUek7MW0lbNOUHpvam9pe3lVCf4xPFT+lK7s+e+fs7Q==} - - '@algolia/autocomplete-plugin-algolia-insights@1.17.7': - resolution: {integrity: sha512-Jca5Ude6yUOuyzjnz57og7Et3aXjbwCSDf/8onLHSQgw1qW3ALl9mrMWaXb5FmPVkV3EtkD2F/+NkT6VHyPu9A==} - peerDependencies: - search-insights: '>= 1 < 3' - - '@algolia/autocomplete-preset-algolia@1.17.7': - resolution: {integrity: sha512-ggOQ950+nwbWROq2MOCIL71RE0DdQZsceqrg32UqnhDz8FlO9rL8ONHNsI2R1MH0tkgVIDKI/D0sMiUchsFdWA==} - peerDependencies: - '@algolia/client-search': '>= 4.9.1 < 6' - algoliasearch: '>= 4.9.1 < 6' - - '@algolia/autocomplete-shared@1.17.7': - resolution: {integrity: sha512-o/1Vurr42U/qskRSuhBH+VKxMvkkUVTLU6WZQr+L5lGZZLYWyhdzWjW0iGXY7EkwRTjBqvN2EsR81yCTGV/kmg==} - peerDependencies: - '@algolia/client-search': '>= 4.9.1 < 6' - algoliasearch: '>= 4.9.1 < 6' - - '@algolia/client-abtesting@5.35.0': - resolution: {integrity: sha512-uUdHxbfHdoppDVflCHMxRlj49/IllPwwQ2cQ8DLC4LXr3kY96AHBpW0dMyi6ygkn2MtFCc6BxXCzr668ZRhLBQ==} - engines: {node: '>= 14.0.0'} - - '@algolia/client-analytics@5.35.0': - resolution: {integrity: sha512-SunAgwa9CamLcRCPnPHx1V2uxdQwJGqb1crYrRWktWUdld0+B2KyakNEeVn5lln4VyeNtW17Ia7V7qBWyM/Skw==} - engines: {node: '>= 14.0.0'} - - '@algolia/client-common@5.35.0': - resolution: {integrity: sha512-ipE0IuvHu/bg7TjT2s+187kz/E3h5ssfTtjpg1LbWMgxlgiaZIgTTbyynM7NfpSJSKsgQvCQxWjGUO51WSCu7w==} - engines: {node: '>= 14.0.0'} - - '@algolia/client-insights@5.35.0': - resolution: {integrity: sha512-UNbCXcBpqtzUucxExwTSfAe8gknAJ485NfPN6o1ziHm6nnxx97piIbcBQ3edw823Tej2Wxu1C0xBY06KgeZ7gA==} - engines: {node: '>= 14.0.0'} - - '@algolia/client-personalization@5.35.0': - resolution: {integrity: sha512-/KWjttZ6UCStt4QnWoDAJ12cKlQ+fkpMtyPmBgSS2WThJQdSV/4UWcqCUqGH7YLbwlj3JjNirCu3Y7uRTClxvA==} - engines: {node: '>= 14.0.0'} - - '@algolia/client-query-suggestions@5.35.0': - resolution: {integrity: sha512-8oCuJCFf/71IYyvQQC+iu4kgViTODbXDk3m7yMctEncRSRV+u2RtDVlpGGfPlJQOrAY7OONwJlSHkmbbm2Kp/w==} - engines: {node: '>= 14.0.0'} - - '@algolia/client-search@5.35.0': - resolution: {integrity: sha512-FfmdHTrXhIduWyyuko1YTcGLuicVbhUyRjO3HbXE4aP655yKZgdTIfMhZ/V5VY9bHuxv/fGEh3Od1Lvv2ODNTg==} - engines: {node: '>= 14.0.0'} - - '@algolia/ingestion@1.35.0': - resolution: {integrity: sha512-gPzACem9IL1Co8mM1LKMhzn1aSJmp+Vp434An4C0OBY4uEJRcqsLN3uLBlY+bYvFg8C8ImwM9YRiKczJXRk0XA==} - engines: {node: '>= 14.0.0'} - - '@algolia/monitoring@1.35.0': - resolution: {integrity: sha512-w9MGFLB6ashI8BGcQoVt7iLgDIJNCn4OIu0Q0giE3M2ItNrssvb8C0xuwJQyTy1OFZnemG0EB1OvXhIHOvQwWw==} - engines: {node: '>= 14.0.0'} - - '@algolia/recommend@5.35.0': - resolution: {integrity: sha512-AhrVgaaXAb8Ue0u2nuRWwugt0dL5UmRgS9LXe0Hhz493a8KFeZVUE56RGIV3hAa6tHzmAV7eIoqcWTQvxzlJeQ==} - engines: {node: '>= 14.0.0'} - - '@algolia/requester-browser-xhr@5.35.0': - resolution: {integrity: sha512-diY415KLJZ6x1Kbwl9u96Jsz0OstE3asjXtJ9pmk1d+5gPuQ5jQyEsgC+WmEXzlec3iuVszm8AzNYYaqw6B+Zw==} - engines: {node: '>= 14.0.0'} - - '@algolia/requester-fetch@5.35.0': - resolution: {integrity: sha512-uydqnSmpAjrgo8bqhE9N1wgcB98psTRRQXcjc4izwMB7yRl9C8uuAQ/5YqRj04U0mMQ+fdu2fcNF6m9+Z1BzDQ==} - engines: {node: '>= 14.0.0'} - - '@algolia/requester-node-http@5.35.0': - resolution: {integrity: sha512-RgLX78ojYOrThJHrIiPzT4HW3yfQa0D7K+MQ81rhxqaNyNBu4F1r+72LNHYH/Z+y9I1Mrjrd/c/Ue5zfDgAEjQ==} - engines: {node: '>= 14.0.0'} - '@ampproject/remapping@2.3.0': resolution: {integrity: sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==} engines: {node: '>=6.0.0'} @@ -561,28 +485,11 @@ packages: peerDependencies: react: '>=16.8.0' - '@docsearch/css@3.8.2': - resolution: {integrity: sha512-y05ayQFyUmCXze79+56v/4HpycYF3uFqB78pLPrSV5ZKAlDuIAAJNhaRi8tTdRNXh05yxX/TyNnzD6LwSM89vQ==} + '@docsearch/css@4.0.0-beta.7': + resolution: {integrity: sha512-hBIwf14yLasrUcDNS7jrneM1ibFD/JFJVDjdxd1h/LUHx7eyLrS726pKHVr3cTdToNXP/7jrTbnC1MAuDHPoow==} - '@docsearch/js@3.8.2': - resolution: {integrity: sha512-Q5wY66qHn0SwA7Taa0aDbHiJvaFJLOJyHmooQ7y8hlwwQLQ/5WwCcoX0g7ii04Qi2DJlHsd0XXzJ8Ypw9+9YmQ==} - - '@docsearch/react@3.8.2': - resolution: {integrity: sha512-xCRrJQlTt8N9GU0DG4ptwHRkfnSnD/YpdeaXe02iKfqs97TkZJv60yE+1eq/tjPcVnTW8dP5qLP7itifFVV5eg==} - peerDependencies: - '@types/react': '>= 16.8.0 < 19.0.0' - react: '>= 16.8.0 < 19.0.0' - react-dom: '>= 16.8.0 < 19.0.0' - search-insights: '>= 1 < 3' - peerDependenciesMeta: - '@types/react': - optional: true - react: - optional: true - react-dom: - optional: true - search-insights: - optional: true + '@docsearch/js@4.0.0-beta.7': + resolution: {integrity: sha512-0RJALbDpLMuFy3H/26rjms/qwi5KjsGMN8Lu4k/bs6kBfOWHUN6Dzg/ybj8qB2OLdT2UegsavRIDZKW3QrzQ4Q==} '@drizzle-team/brocli@0.10.2': resolution: {integrity: sha512-z33Il7l5dKjUgGULTqBsQBQwckHh5AbIuxhdsIxDDiZAzBOrZO6q9ogcWC65kU382AfynTfgNumVcNIjuIua6w==} @@ -598,12 +505,6 @@ packages: resolution: {integrity: sha512-FxEMIkJKnodyA1OaCUoEvbYRkoZlLZ4d/eXFu9Fh8CbBBgP5EmZxrfTRyN0qpXZ4vOvqnE5YdRdcrmUUXuU+dA==} deprecated: 'Merged into tsx: https://tsx.is' - '@esbuild/aix-ppc64@0.21.5': - resolution: {integrity: sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==} - engines: {node: '>=12'} - cpu: [ppc64] - os: [aix] - '@esbuild/aix-ppc64@0.25.5': resolution: {integrity: sha512-9o3TMmpmftaCMepOdA5k/yDw8SfInyzWWTjYTFCX3kPSDJMROQTb8jg+h9Cnwnmm1vOzvxN7gIfB5V2ewpjtGA==} engines: {node: '>=18'} @@ -616,12 +517,6 @@ packages: cpu: [arm64] os: [android] - '@esbuild/android-arm64@0.21.5': - resolution: {integrity: sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==} - engines: {node: '>=12'} - cpu: [arm64] - os: [android] - '@esbuild/android-arm64@0.25.5': resolution: {integrity: sha512-VGzGhj4lJO+TVGV1v8ntCZWJktV7SGCs3Pn1GRWI1SBFtRALoomm8k5E9Pmwg3HOAal2VDc2F9+PM/rEY6oIDg==} engines: {node: '>=18'} @@ -634,12 +529,6 @@ packages: cpu: [arm] os: [android] - '@esbuild/android-arm@0.21.5': - resolution: {integrity: sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==} - engines: {node: '>=12'} - cpu: [arm] - os: [android] - '@esbuild/android-arm@0.25.5': resolution: {integrity: sha512-AdJKSPeEHgi7/ZhuIPtcQKr5RQdo6OO2IL87JkianiMYMPbCtot9fxPbrMiBADOWWm3T2si9stAiVsGbTQFkbA==} engines: {node: '>=18'} @@ -652,12 +541,6 @@ packages: cpu: [x64] os: [android] - '@esbuild/android-x64@0.21.5': - resolution: {integrity: sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==} - engines: {node: '>=12'} - cpu: [x64] - os: [android] - '@esbuild/android-x64@0.25.5': resolution: {integrity: sha512-D2GyJT1kjvO//drbRT3Hib9XPwQeWd9vZoBJn+bu/lVsOZ13cqNdDeqIF/xQ5/VmWvMduP6AmXvylO/PIc2isw==} engines: {node: '>=18'} @@ -670,12 +553,6 @@ packages: cpu: [arm64] os: [darwin] - '@esbuild/darwin-arm64@0.21.5': - resolution: {integrity: sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==} - engines: {node: '>=12'} - cpu: [arm64] - os: [darwin] - '@esbuild/darwin-arm64@0.25.5': resolution: {integrity: sha512-GtaBgammVvdF7aPIgH2jxMDdivezgFu6iKpmT+48+F8Hhg5J/sfnDieg0aeG/jfSvkYQU2/pceFPDKlqZzwnfQ==} engines: {node: '>=18'} @@ -688,12 +565,6 @@ packages: cpu: [x64] os: [darwin] - '@esbuild/darwin-x64@0.21.5': - resolution: {integrity: sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==} - engines: {node: '>=12'} - cpu: [x64] - os: [darwin] - '@esbuild/darwin-x64@0.25.5': resolution: {integrity: sha512-1iT4FVL0dJ76/q1wd7XDsXrSW+oLoquptvh4CLR4kITDtqi2e/xwXwdCVH8hVHU43wgJdsq7Gxuzcs6Iq/7bxQ==} engines: {node: '>=18'} @@ -706,12 +577,6 @@ packages: cpu: [arm64] os: [freebsd] - '@esbuild/freebsd-arm64@0.21.5': - resolution: {integrity: sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==} - engines: {node: '>=12'} - cpu: [arm64] - os: [freebsd] - '@esbuild/freebsd-arm64@0.25.5': resolution: {integrity: sha512-nk4tGP3JThz4La38Uy/gzyXtpkPW8zSAmoUhK9xKKXdBCzKODMc2adkB2+8om9BDYugz+uGV7sLmpTYzvmz6Sw==} engines: {node: '>=18'} @@ -724,12 +589,6 @@ packages: cpu: [x64] os: [freebsd] - '@esbuild/freebsd-x64@0.21.5': - resolution: {integrity: sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==} - engines: {node: '>=12'} - cpu: [x64] - os: [freebsd] - '@esbuild/freebsd-x64@0.25.5': resolution: {integrity: sha512-PrikaNjiXdR2laW6OIjlbeuCPrPaAl0IwPIaRv+SMV8CiM8i2LqVUHFC1+8eORgWyY7yhQY+2U2fA55mBzReaw==} engines: {node: '>=18'} @@ -742,12 +601,6 @@ packages: cpu: [arm64] os: [linux] - '@esbuild/linux-arm64@0.21.5': - resolution: {integrity: sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==} - engines: {node: '>=12'} - cpu: [arm64] - os: [linux] - '@esbuild/linux-arm64@0.25.5': resolution: {integrity: sha512-Z9kfb1v6ZlGbWj8EJk9T6czVEjjq2ntSYLY2cw6pAZl4oKtfgQuS4HOq41M/BcoLPzrUbNd+R4BXFyH//nHxVg==} engines: {node: '>=18'} @@ -760,12 +613,6 @@ packages: cpu: [arm] os: [linux] - '@esbuild/linux-arm@0.21.5': - resolution: {integrity: sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==} - engines: {node: '>=12'} - cpu: [arm] - os: [linux] - '@esbuild/linux-arm@0.25.5': resolution: {integrity: sha512-cPzojwW2okgh7ZlRpcBEtsX7WBuqbLrNXqLU89GxWbNt6uIg78ET82qifUy3W6OVww6ZWobWub5oqZOVtwolfw==} engines: {node: '>=18'} @@ -778,12 +625,6 @@ packages: cpu: [ia32] os: [linux] - '@esbuild/linux-ia32@0.21.5': - resolution: {integrity: sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==} - engines: {node: '>=12'} - cpu: [ia32] - os: [linux] - '@esbuild/linux-ia32@0.25.5': resolution: {integrity: sha512-sQ7l00M8bSv36GLV95BVAdhJ2QsIbCuCjh/uYrWiMQSUuV+LpXwIqhgJDcvMTj+VsQmqAHL2yYaasENvJ7CDKA==} engines: {node: '>=18'} @@ -796,12 +637,6 @@ packages: cpu: [loong64] os: [linux] - '@esbuild/linux-loong64@0.21.5': - resolution: {integrity: sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==} - engines: {node: '>=12'} - cpu: [loong64] - os: [linux] - '@esbuild/linux-loong64@0.25.5': resolution: {integrity: sha512-0ur7ae16hDUC4OL5iEnDb0tZHDxYmuQyhKhsPBV8f99f6Z9KQM02g33f93rNH5A30agMS46u2HP6qTdEt6Q1kg==} engines: {node: '>=18'} @@ -814,12 +649,6 @@ packages: cpu: [mips64el] os: [linux] - '@esbuild/linux-mips64el@0.21.5': - resolution: {integrity: sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==} - engines: {node: '>=12'} - cpu: [mips64el] - os: [linux] - '@esbuild/linux-mips64el@0.25.5': resolution: {integrity: sha512-kB/66P1OsHO5zLz0i6X0RxlQ+3cu0mkxS3TKFvkb5lin6uwZ/ttOkP3Z8lfR9mJOBk14ZwZ9182SIIWFGNmqmg==} engines: {node: '>=18'} @@ -832,12 +661,6 @@ packages: cpu: [ppc64] os: [linux] - '@esbuild/linux-ppc64@0.21.5': - resolution: {integrity: sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==} - engines: {node: '>=12'} - cpu: [ppc64] - os: [linux] - '@esbuild/linux-ppc64@0.25.5': resolution: {integrity: sha512-UZCmJ7r9X2fe2D6jBmkLBMQetXPXIsZjQJCjgwpVDz+YMcS6oFR27alkgGv3Oqkv07bxdvw7fyB71/olceJhkQ==} engines: {node: '>=18'} @@ -850,12 +673,6 @@ packages: cpu: [riscv64] os: [linux] - '@esbuild/linux-riscv64@0.21.5': - resolution: {integrity: sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==} - engines: {node: '>=12'} - cpu: [riscv64] - os: [linux] - '@esbuild/linux-riscv64@0.25.5': resolution: {integrity: sha512-kTxwu4mLyeOlsVIFPfQo+fQJAV9mh24xL+y+Bm6ej067sYANjyEw1dNHmvoqxJUCMnkBdKpvOn0Ahql6+4VyeA==} engines: {node: '>=18'} @@ -868,12 +685,6 @@ packages: cpu: [s390x] os: [linux] - '@esbuild/linux-s390x@0.21.5': - resolution: {integrity: sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==} - engines: {node: '>=12'} - cpu: [s390x] - os: [linux] - '@esbuild/linux-s390x@0.25.5': resolution: {integrity: sha512-K2dSKTKfmdh78uJ3NcWFiqyRrimfdinS5ErLSn3vluHNeHVnBAFWC8a4X5N+7FgVE1EjXS1QDZbpqZBjfrqMTQ==} engines: {node: '>=18'} @@ -886,12 +697,6 @@ packages: cpu: [x64] os: [linux] - '@esbuild/linux-x64@0.21.5': - resolution: {integrity: sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==} - engines: {node: '>=12'} - cpu: [x64] - os: [linux] - '@esbuild/linux-x64@0.25.5': resolution: {integrity: sha512-uhj8N2obKTE6pSZ+aMUbqq+1nXxNjZIIjCjGLfsWvVpy7gKCOL6rsY1MhRh9zLtUtAI7vpgLMK6DxjO8Qm9lJw==} engines: {node: '>=18'} @@ -910,12 +715,6 @@ packages: cpu: [x64] os: [netbsd] - '@esbuild/netbsd-x64@0.21.5': - resolution: {integrity: sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==} - engines: {node: '>=12'} - cpu: [x64] - os: [netbsd] - '@esbuild/netbsd-x64@0.25.5': resolution: {integrity: sha512-WOb5fKrvVTRMfWFNCroYWWklbnXH0Q5rZppjq0vQIdlsQKuw6mdSihwSo4RV/YdQ5UCKKvBy7/0ZZYLBZKIbwQ==} engines: {node: '>=18'} @@ -934,12 +733,6 @@ packages: cpu: [x64] os: [openbsd] - '@esbuild/openbsd-x64@0.21.5': - resolution: {integrity: sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==} - engines: {node: '>=12'} - cpu: [x64] - os: [openbsd] - '@esbuild/openbsd-x64@0.25.5': resolution: {integrity: sha512-G4hE405ErTWraiZ8UiSoesH8DaCsMm0Cay4fsFWOOUcz8b8rC6uCvnagr+gnioEjWn0wC+o1/TAHt+It+MpIMg==} engines: {node: '>=18'} @@ -952,12 +745,6 @@ packages: cpu: [x64] os: [sunos] - '@esbuild/sunos-x64@0.21.5': - resolution: {integrity: sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==} - engines: {node: '>=12'} - cpu: [x64] - os: [sunos] - '@esbuild/sunos-x64@0.25.5': resolution: {integrity: sha512-l+azKShMy7FxzY0Rj4RCt5VD/q8mG/e+mDivgspo+yL8zW7qEwctQ6YqKX34DTEleFAvCIUviCFX1SDZRSyMQA==} engines: {node: '>=18'} @@ -970,12 +757,6 @@ packages: cpu: [arm64] os: [win32] - '@esbuild/win32-arm64@0.21.5': - resolution: {integrity: sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==} - engines: {node: '>=12'} - cpu: [arm64] - os: [win32] - '@esbuild/win32-arm64@0.25.5': resolution: {integrity: sha512-O2S7SNZzdcFG7eFKgvwUEZ2VG9D/sn/eIiz8XRZ1Q/DO5a3s76Xv0mdBzVM5j5R639lXQmPmSo0iRpHqUUrsxw==} engines: {node: '>=18'} @@ -988,12 +769,6 @@ packages: cpu: [ia32] os: [win32] - '@esbuild/win32-ia32@0.21.5': - resolution: {integrity: sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==} - engines: {node: '>=12'} - cpu: [ia32] - os: [win32] - '@esbuild/win32-ia32@0.25.5': resolution: {integrity: sha512-onOJ02pqs9h1iMJ1PQphR+VZv8qBMQ77Klcsqv9CNW2w6yLqoURLcgERAIurY6QE63bbLuqgP9ATqajFLK5AMQ==} engines: {node: '>=18'} @@ -1006,12 +781,6 @@ packages: cpu: [x64] os: [win32] - '@esbuild/win32-x64@0.21.5': - resolution: {integrity: sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==} - engines: {node: '>=12'} - cpu: [x64] - os: [win32] - '@esbuild/win32-x64@0.25.5': resolution: {integrity: sha512-TXv6YnJ8ZMVdX+SXWVBo/0p8LTcrUYngpWjvm91TMjjBQii7Oz11Lw5lbDV5Y0TzuhSJHwiH4hEtC1I42mMS0g==} engines: {node: '>=18'} @@ -1066,8 +835,8 @@ packages: wrangler: optional: true - '@iconify-json/simple-icons@1.2.46': - resolution: {integrity: sha512-MJfKQDhOMQD5Fc8PcTtCdFX0oBf/nKVfp69ScdEKIXW0JXELX5V2Ld45EsjShi8aJ6DNhdDtSDZvKuDnkDiKnw==} + '@iconify-json/simple-icons@1.2.48': + resolution: {integrity: sha512-EACOtZMoPJtERiAbX1De0asrrCtlwI27+03c9OJlYWsly9w1O5vcD8rTzh+kDPjo+K8FOVnq2Qy+h/CzljSKDA==} '@iconify/types@2.0.0': resolution: {integrity: sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg==} @@ -1815,6 +1584,9 @@ packages: peerDependencies: react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + '@rolldown/pluginutils@1.0.0-beta.29': + resolution: {integrity: sha512-NIJgOsMjbxAXvoGq/X0gD7VPMQ8j9g0BiDaNjVNVjvl+iKXxL3Jre0v31RmBYeLEmkbj2s02v8vFTbUXi5XS2Q==} + '@rollup/pluginutils@5.1.4': resolution: {integrity: sha512-USm05zrsFxYLPdWWq+K3STlWiT/3ELn3RcV5hJMghpeAIhxfsUIg6mt12CBJBInWMV4VneoV7SfGv8xIwo2qNQ==} engines: {node: '>=14.0.0'} @@ -1829,116 +1601,216 @@ packages: cpu: [arm] os: [android] + '@rollup/rollup-android-arm-eabi@4.46.3': + resolution: {integrity: sha512-UmTdvXnLlqQNOCJnyksjPs1G4GqXNGW1LrzCe8+8QoaLhhDeTXYBgJ3k6x61WIhlHX2U+VzEJ55TtIjR/HTySA==} + cpu: [arm] + os: [android] + '@rollup/rollup-android-arm64@4.36.0': resolution: {integrity: sha512-NyfuLvdPdNUfUNeYKUwPwKsE5SXa2J6bCt2LdB/N+AxShnkpiczi3tcLJrm5mA+eqpy0HmaIY9F6XCa32N5yzg==} cpu: [arm64] os: [android] + '@rollup/rollup-android-arm64@4.46.3': + resolution: {integrity: sha512-8NoxqLpXm7VyeI0ocidh335D6OKT0UJ6fHdnIxf3+6oOerZZc+O7r+UhvROji6OspyPm+rrIdb1gTXtVIqn+Sg==} + cpu: [arm64] + os: [android] + '@rollup/rollup-darwin-arm64@4.36.0': resolution: {integrity: sha512-JQ1Jk5G4bGrD4pWJQzWsD8I1n1mgPXq33+/vP4sk8j/z/C2siRuxZtaUA7yMTf71TCZTZl/4e1bfzwUmFb3+rw==} cpu: [arm64] os: [darwin] + '@rollup/rollup-darwin-arm64@4.46.3': + resolution: {integrity: sha512-csnNavqZVs1+7/hUKtgjMECsNG2cdB8F7XBHP6FfQjqhjF8rzMzb3SLyy/1BG7YSfQ+bG75Ph7DyedbUqwq1rA==} + cpu: [arm64] + os: [darwin] + '@rollup/rollup-darwin-x64@4.36.0': resolution: {integrity: sha512-6c6wMZa1lrtiRsbDziCmjE53YbTkxMYhhnWnSW8R/yqsM7a6mSJ3uAVT0t8Y/DGt7gxUWYuFM4bwWk9XCJrFKA==} cpu: [x64] os: [darwin] + '@rollup/rollup-darwin-x64@4.46.3': + resolution: {integrity: sha512-r2MXNjbuYabSIX5yQqnT8SGSQ26XQc8fmp6UhlYJd95PZJkQD1u82fWP7HqvGUf33IsOC6qsiV+vcuD4SDP6iw==} + cpu: [x64] + os: [darwin] + '@rollup/rollup-freebsd-arm64@4.36.0': resolution: {integrity: sha512-KXVsijKeJXOl8QzXTsA+sHVDsFOmMCdBRgFmBb+mfEb/7geR7+C8ypAml4fquUt14ZyVXaw2o1FWhqAfOvA4sg==} cpu: [arm64] os: [freebsd] + '@rollup/rollup-freebsd-arm64@4.46.3': + resolution: {integrity: sha512-uluObTmgPJDuJh9xqxyr7MV61Imq+0IvVsAlWyvxAaBSNzCcmZlhfYcRhCdMaCsy46ccZa7vtDDripgs9Jkqsw==} + cpu: [arm64] + os: [freebsd] + '@rollup/rollup-freebsd-x64@4.36.0': resolution: {integrity: sha512-dVeWq1ebbvByI+ndz4IJcD4a09RJgRYmLccwlQ8bPd4olz3Y213uf1iwvc7ZaxNn2ab7bjc08PrtBgMu6nb4pQ==} cpu: [x64] os: [freebsd] + '@rollup/rollup-freebsd-x64@4.46.3': + resolution: {integrity: sha512-AVJXEq9RVHQnejdbFvh1eWEoobohUYN3nqJIPI4mNTMpsyYN01VvcAClxflyk2HIxvLpRcRggpX1m9hkXkpC/A==} + cpu: [x64] + os: [freebsd] + '@rollup/rollup-linux-arm-gnueabihf@4.36.0': resolution: {integrity: sha512-bvXVU42mOVcF4le6XSjscdXjqx8okv4n5vmwgzcmtvFdifQ5U4dXFYaCB87namDRKlUL9ybVtLQ9ztnawaSzvg==} cpu: [arm] os: [linux] + '@rollup/rollup-linux-arm-gnueabihf@4.46.3': + resolution: {integrity: sha512-byyflM+huiwHlKi7VHLAYTKr67X199+V+mt1iRgJenAI594vcmGGddWlu6eHujmcdl6TqSNnvqaXJqZdnEWRGA==} + cpu: [arm] + os: [linux] + '@rollup/rollup-linux-arm-musleabihf@4.36.0': resolution: {integrity: sha512-JFIQrDJYrxOnyDQGYkqnNBtjDwTgbasdbUiQvcU8JmGDfValfH1lNpng+4FWlhaVIR4KPkeddYjsVVbmJYvDcg==} cpu: [arm] os: [linux] + '@rollup/rollup-linux-arm-musleabihf@4.46.3': + resolution: {integrity: sha512-aLm3NMIjr4Y9LklrH5cu7yybBqoVCdr4Nvnm8WB7PKCn34fMCGypVNpGK0JQWdPAzR/FnoEoFtlRqZbBBLhVoQ==} + cpu: [arm] + os: [linux] + '@rollup/rollup-linux-arm64-gnu@4.36.0': resolution: {integrity: sha512-KqjYVh3oM1bj//5X7k79PSCZ6CvaVzb7Qs7VMWS+SlWB5M8p3FqufLP9VNp4CazJ0CsPDLwVD9r3vX7Ci4J56A==} cpu: [arm64] os: [linux] + '@rollup/rollup-linux-arm64-gnu@4.46.3': + resolution: {integrity: sha512-VtilE6eznJRDIoFOzaagQodUksTEfLIsvXymS+UdJiSXrPW7Ai+WG4uapAc3F7Hgs791TwdGh4xyOzbuzIZrnw==} + cpu: [arm64] + os: [linux] + '@rollup/rollup-linux-arm64-musl@4.36.0': resolution: {integrity: sha512-QiGnhScND+mAAtfHqeT+cB1S9yFnNQ/EwCg5yE3MzoaZZnIV0RV9O5alJAoJKX/sBONVKeZdMfO8QSaWEygMhw==} cpu: [arm64] os: [linux] + '@rollup/rollup-linux-arm64-musl@4.46.3': + resolution: {integrity: sha512-dG3JuS6+cRAL0GQ925Vppafi0qwZnkHdPeuZIxIPXqkCLP02l7ka+OCyBoDEv8S+nKHxfjvjW4OZ7hTdHkx8/w==} + cpu: [arm64] + os: [linux] + '@rollup/rollup-linux-loongarch64-gnu@4.36.0': resolution: {integrity: sha512-1ZPyEDWF8phd4FQtTzMh8FQwqzvIjLsl6/84gzUxnMNFBtExBtpL51H67mV9xipuxl1AEAerRBgBwFNpkw8+Lg==} cpu: [loong64] os: [linux] + '@rollup/rollup-linux-loongarch64-gnu@4.46.3': + resolution: {integrity: sha512-iU8DxnxEKJptf8Vcx4XvAUdpkZfaz0KWfRrnIRrOndL0SvzEte+MTM7nDH4A2Now4FvTZ01yFAgj6TX/mZl8hQ==} + cpu: [loong64] + os: [linux] + '@rollup/rollup-linux-powerpc64le-gnu@4.36.0': resolution: {integrity: sha512-VMPMEIUpPFKpPI9GZMhJrtu8rxnp6mJR3ZzQPykq4xc2GmdHj3Q4cA+7avMyegXy4n1v+Qynr9fR88BmyO74tg==} cpu: [ppc64] os: [linux] + '@rollup/rollup-linux-ppc64-gnu@4.46.3': + resolution: {integrity: sha512-VrQZp9tkk0yozJoQvQcqlWiqaPnLM6uY1qPYXvukKePb0fqaiQtOdMJSxNFUZFsGw5oA5vvVokjHrx8a9Qsz2A==} + cpu: [ppc64] + os: [linux] + '@rollup/rollup-linux-riscv64-gnu@4.36.0': resolution: {integrity: sha512-ttE6ayb/kHwNRJGYLpuAvB7SMtOeQnVXEIpMtAvx3kepFQeowVED0n1K9nAdraHUPJ5hydEMxBpIR7o4nrm8uA==} cpu: [riscv64] os: [linux] + '@rollup/rollup-linux-riscv64-gnu@4.46.3': + resolution: {integrity: sha512-uf2eucWSUb+M7b0poZ/08LsbcRgaDYL8NCGjUeFMwCWFwOuFcZ8D9ayPl25P3pl+D2FH45EbHdfyUesQ2Lt9wA==} + cpu: [riscv64] + os: [linux] + + '@rollup/rollup-linux-riscv64-musl@4.46.3': + resolution: {integrity: sha512-7tnUcDvN8DHm/9ra+/nF7lLzYHDeODKKKrh6JmZejbh1FnCNZS8zMkZY5J4sEipy2OW1d1Ncc4gNHUd0DLqkSg==} + cpu: [riscv64] + os: [linux] + '@rollup/rollup-linux-s390x-gnu@4.36.0': resolution: {integrity: sha512-4a5gf2jpS0AIe7uBjxDeUMNcFmaRTbNv7NxI5xOCs4lhzsVyGR/0qBXduPnoWf6dGC365saTiwag8hP1imTgag==} cpu: [s390x] os: [linux] + '@rollup/rollup-linux-s390x-gnu@4.46.3': + resolution: {integrity: sha512-MUpAOallJim8CsJK+4Lc9tQzlfPbHxWDrGXZm2z6biaadNpvh3a5ewcdat478W+tXDoUiHwErX/dOql7ETcLqg==} + cpu: [s390x] + os: [linux] + '@rollup/rollup-linux-x64-gnu@4.36.0': resolution: {integrity: sha512-5KtoW8UWmwFKQ96aQL3LlRXX16IMwyzMq/jSSVIIyAANiE1doaQsx/KRyhAvpHlPjPiSU/AYX/8m+lQ9VToxFQ==} cpu: [x64] os: [linux] + '@rollup/rollup-linux-x64-gnu@4.46.3': + resolution: {integrity: sha512-F42IgZI4JicE2vM2PWCe0N5mR5vR0gIdORPqhGQ32/u1S1v3kLtbZ0C/mi9FFk7C5T0PgdeyWEPajPjaUpyoKg==} + cpu: [x64] + os: [linux] + '@rollup/rollup-linux-x64-musl@4.36.0': resolution: {integrity: sha512-sycrYZPrv2ag4OCvaN5js+f01eoZ2U+RmT5as8vhxiFz+kxwlHrsxOwKPSA8WyS+Wc6Epid9QeI/IkQ9NkgYyQ==} cpu: [x64] os: [linux] + '@rollup/rollup-linux-x64-musl@4.46.3': + resolution: {integrity: sha512-oLc+JrwwvbimJUInzx56Q3ujL3Kkhxehg7O1gWAYzm8hImCd5ld1F2Gry5YDjR21MNb5WCKhC9hXgU7rRlyegQ==} + cpu: [x64] + os: [linux] + '@rollup/rollup-win32-arm64-msvc@4.36.0': resolution: {integrity: sha512-qbqt4N7tokFwwSVlWDsjfoHgviS3n/vZ8LK0h1uLG9TYIRuUTJC88E1xb3LM2iqZ/WTqNQjYrtmtGmrmmawB6A==} cpu: [arm64] os: [win32] + '@rollup/rollup-win32-arm64-msvc@4.46.3': + resolution: {integrity: sha512-lOrQ+BVRstruD1fkWg9yjmumhowR0oLAAzavB7yFSaGltY8klttmZtCLvOXCmGE9mLIn8IBV/IFrQOWz5xbFPg==} + cpu: [arm64] + os: [win32] + '@rollup/rollup-win32-ia32-msvc@4.36.0': resolution: {integrity: sha512-t+RY0JuRamIocMuQcfwYSOkmdX9dtkr1PbhKW42AMvaDQa+jOdpUYysroTF/nuPpAaQMWp7ye+ndlmmthieJrQ==} cpu: [ia32] os: [win32] + '@rollup/rollup-win32-ia32-msvc@4.46.3': + resolution: {integrity: sha512-vvrVKPRS4GduGR7VMH8EylCBqsDcw6U+/0nPDuIjXQRbHJc6xOBj+frx8ksfZAh6+Fptw5wHrN7etlMmQnPQVg==} + cpu: [ia32] + os: [win32] + '@rollup/rollup-win32-x64-msvc@4.36.0': resolution: {integrity: sha512-aRXd7tRZkWLqGbChgcMMDEHjOKudo1kChb1Jt1IfR8cY/KIpgNviLeJy5FUb9IpSuQj8dU2fAYNMPW/hLKOSTw==} cpu: [x64] os: [win32] - '@shikijs/core@2.5.0': - resolution: {integrity: sha512-uu/8RExTKtavlpH7XqnVYBrfBkUc20ngXiX9NSrBhOVZYv/7XQRKUyhtkeflY5QsxC0GbJThCerruZfsUaSldg==} + '@rollup/rollup-win32-x64-msvc@4.46.3': + resolution: {integrity: sha512-fi3cPxCnu3ZeM3EwKZPgXbWoGzm2XHgB/WShKI81uj8wG0+laobmqy5wbgEwzstlbLu4MyO8C19FyhhWseYKNQ==} + cpu: [x64] + os: [win32] - '@shikijs/engine-javascript@2.5.0': - resolution: {integrity: sha512-VjnOpnQf8WuCEZtNUdjjwGUbtAVKuZkVQ/5cHy/tojVVRIRtlWMYVjyWhxOmIq05AlSOv72z7hRNRGVBgQOl0w==} + '@shikijs/core@3.9.2': + resolution: {integrity: sha512-3q/mzmw09B2B6PgFNeiaN8pkNOixWS726IHmJEpjDAcneDPMQmUg2cweT9cWXY4XcyQS3i6mOOUgQz9RRUP6HA==} - '@shikijs/engine-oniguruma@2.5.0': - resolution: {integrity: sha512-pGd1wRATzbo/uatrCIILlAdFVKdxImWJGQ5rFiB5VZi2ve5xj3Ax9jny8QvkaV93btQEwR/rSz5ERFpC5mKNIw==} + '@shikijs/engine-javascript@3.9.2': + resolution: {integrity: sha512-kUTRVKPsB/28H5Ko6qEsyudBiWEDLst+Sfi+hwr59E0GLHV0h8RfgbQU7fdN5Lt9A8R1ulRiZyTvAizkROjwDA==} - '@shikijs/langs@2.5.0': - resolution: {integrity: sha512-Qfrrt5OsNH5R+5tJ/3uYBBZv3SuGmnRPejV9IlIbFH3HTGLDlkqgHymAlzklVmKBjAaVmkPkyikAV/sQ1wSL+w==} + '@shikijs/engine-oniguruma@3.9.2': + resolution: {integrity: sha512-Vn/w5oyQ6TUgTVDIC/BrpXwIlfK6V6kGWDVVz2eRkF2v13YoENUvaNwxMsQU/t6oCuZKzqp9vqtEtEzKl9VegA==} - '@shikijs/themes@2.5.0': - resolution: {integrity: sha512-wGrk+R8tJnO0VMzmUExHR+QdSaPUl/NKs+a4cQQRWyoc3YFbUzuLEi/KWK1hj+8BfHRKm2jNhhJck1dfstJpiw==} + '@shikijs/langs@3.9.2': + resolution: {integrity: sha512-X1Q6wRRQXY7HqAuX3I8WjMscjeGjqXCg/Sve7J2GWFORXkSrXud23UECqTBIdCSNKJioFtmUGJQNKtlMMZMn0w==} - '@shikijs/transformers@2.5.0': - resolution: {integrity: sha512-SI494W5X60CaUwgi8u4q4m4s3YAFSxln3tzNjOSYqq54wlVgz0/NbbXEb3mdLbqMBztcmS7bVTaEd2w0qMmfeg==} + '@shikijs/themes@3.9.2': + resolution: {integrity: sha512-6z5lBPBMRfLyyEsgf6uJDHPa6NAGVzFJqH4EAZ+03+7sedYir2yJBRu2uPZOKmj43GyhVHWHvyduLDAwJQfDjA==} - '@shikijs/types@2.5.0': - resolution: {integrity: sha512-ygl5yhxki9ZLNuNpPitBWvcy9fsSKKaRuO4BAlMyagszQidxcpLAr0qiW/q43DtSIDxO6hEbtYLiFZNXO/hdGw==} + '@shikijs/transformers@3.9.2': + resolution: {integrity: sha512-MW5hT4TyUp6bNAgTExRYLk1NNasVQMTCw1kgbxHcEC0O5cbepPWaB+1k+JzW9r3SP2/R8kiens8/3E6hGKfgsA==} + + '@shikijs/types@3.9.2': + resolution: {integrity: sha512-/M5L0Uc2ljyn2jKvj4Yiah7ow/W+DJSglVafvWAJ/b8AZDeeRAdMu3c2riDzB7N42VD+jSnWxeP9AKtd4TfYVw==} '@shikijs/vscode-textmate@10.0.2': resolution: {integrity: sha512-83yeghZ2xxin3Nj8z1NMd/NCuca+gsYXswywDy5bHvwlWL8tpTQmzGeUuHd9FC3E/SBEMvzJRwWEOz5gGes9Qg==} @@ -2054,6 +1926,9 @@ packages: '@types/estree@1.0.7': resolution: {integrity: sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ==} + '@types/estree@1.0.8': + resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} + '@types/hast@3.0.4': resolution: {integrity: sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==} @@ -2147,11 +2022,11 @@ packages: '@ungap/structured-clone@1.3.0': resolution: {integrity: sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==} - '@vitejs/plugin-vue@5.2.4': - resolution: {integrity: sha512-7Yx/SXSOcQq5HiiV3orevHUFn+pmMB4cgbEkDYgnkUWb0WfeQ/wa2yFv6D5ICiCQOVpjA7vYDXrC7AGO8yjDHA==} - engines: {node: ^18.0.0 || >=20.0.0} + '@vitejs/plugin-vue@6.0.1': + resolution: {integrity: sha512-+MaE752hU0wfPFJEUAIxqw18+20euHHdxVtMvbFcOEpjEyfqXH/5DCoTHiVJ0J29EhTJdoTkjEv5YBKU9dnoTw==} + engines: {node: ^20.19.0 || >=22.12.0} peerDependencies: - vite: ^5.0.0 || ^6.0.0 + vite: ^5.0.0 || ^6.0.0 || ^7.0.0 vue: ^3.2.25 '@vitest/expect@3.2.4': @@ -2195,14 +2070,14 @@ packages: '@vue/compiler-ssr@3.5.18': resolution: {integrity: sha512-xM16Ak7rSWHkM3m22NlmcdIM+K4BMyFARAfV9hYFl+SFuRzrZ3uGMNW05kA5pmeMa0X9X963Kgou7ufdbpOP9g==} - '@vue/devtools-api@7.7.7': - resolution: {integrity: sha512-lwOnNBH2e7x1fIIbVT7yF5D+YWhqELm55/4ZKf45R9T8r9dE2AIOy8HKjfqzGsoTHFbWbr337O4E0A0QADnjBg==} + '@vue/devtools-api@8.0.0': + resolution: {integrity: sha512-I2jF/knesMU36zTw1hnExjoixDZvDoantiWKVrHpLd2J160zqqe8vp3vrGfjWdfuHmPJwSXe/YNG3rYOYiwy1Q==} - '@vue/devtools-kit@7.7.7': - resolution: {integrity: sha512-wgoZtxcTta65cnZ1Q6MbAfePVFxfM+gq0saaeytoph7nEa7yMXoi6sCPy4ufO111B9msnw0VOWjPEFCXuAKRHA==} + '@vue/devtools-kit@8.0.0': + resolution: {integrity: sha512-b11OeQODkE0bctdT0RhL684pEV2DPXJ80bjpywVCbFn1PxuL3bmMPDoJKjbMnnoWbrnUYXYzFfmMWBZAMhORkQ==} - '@vue/devtools-shared@7.7.7': - resolution: {integrity: sha512-+udSj47aRl5aKb0memBvcUG9koarqnxNM5yjuREvqwK6T3ap4mn3Zqqc17QrBFTqSMjr3HK1cvStEZpMDpfdyw==} + '@vue/devtools-shared@8.0.0': + resolution: {integrity: sha512-jrKnbjshQCiOAJanoeJjTU7WaCg0Dz2BUal6SaR6VM/P3hiFdX5Q6Pxl73ZMnrhCxNK9nAg5hvvRGqs+6dtU1g==} '@vue/reactivity@3.5.18': resolution: {integrity: sha512-x0vPO5Imw+3sChLM5Y+B6G1zPjwdOri9e8V21NnTnlEvkxatHEH5B5KEAJcjuzQ7BsjGrKtfzuQ5eQwXh8HXBg==} @@ -2221,11 +2096,13 @@ packages: '@vue/shared@3.5.18': resolution: {integrity: sha512-cZy8Dq+uuIXbxCZpuLd2GJdeSO/lIzIspC2WtkqIpje5QyFbvLaI5wZtdUjLHjGZrlVX6GilejatWwVYYRc8tA==} - '@vueuse/core@12.8.2': - resolution: {integrity: sha512-HbvCmZdzAu3VGi/pWYm5Ut+Kd9mn1ZHnn4L5G8kOQTPs/IwIAmJoBrmYk2ckLArgMXZj0AW3n5CAejLUO+PhdQ==} + '@vueuse/core@13.7.0': + resolution: {integrity: sha512-myagn09+c6BmS6yHc1gTwwsdZilAovHslMjyykmZH3JNyzI5HoWhv114IIdytXiPipdHJ2gDUx0PB93jRduJYg==} + peerDependencies: + vue: ^3.5.0 - '@vueuse/integrations@12.8.2': - resolution: {integrity: sha512-fbGYivgK5uBTRt7p5F3zy6VrETlV9RtZjBqd1/HxGdjdckBgBM4ugP8LHpjolqTj14TXTxSK1ZfgPbHYyGuH7g==} + '@vueuse/integrations@13.7.0': + resolution: {integrity: sha512-Na5p0ONLepNV/xCBi8vBMuzCOZh9CFT/OHnrUlABWXgWTWSHM3wrVaLS1xvAijPLU5B1ysyJDDW/hKak80oLGA==} peerDependencies: async-validator: ^4 axios: ^1 @@ -2238,7 +2115,8 @@ packages: nprogress: ^0.2 qrcode: ^1.5 sortablejs: ^1 - universal-cookie: ^7 + universal-cookie: ^7 || ^8 + vue: ^3.5.0 peerDependenciesMeta: async-validator: optional: true @@ -2265,11 +2143,13 @@ packages: universal-cookie: optional: true - '@vueuse/metadata@12.8.2': - resolution: {integrity: sha512-rAyLGEuoBJ/Il5AmFHiziCPdQzRt88VxR+Y/A/QhJ1EWtWqPBBAxTAFaSkviwEuOEZNtW8pvkPgoCZQ+HxqW1A==} + '@vueuse/metadata@13.7.0': + resolution: {integrity: sha512-8okFhS/1ite8EwUdZZfvTYowNTfXmVCOrBFlA31O0HD8HKXhY+WtTRyF0LwbpJfoFPc+s9anNJIXMVrvP7UTZg==} - '@vueuse/shared@12.8.2': - resolution: {integrity: sha512-dznP38YzxZoNloI0qpEfpkms8knDtaoQ6Y/sfS0L7Yki4zh40LFHEhur0odJC6xTHG5dxWVPiUWBXn+wCG2s5w==} + '@vueuse/shared@13.7.0': + resolution: {integrity: sha512-Wi2LpJi4UA9kM0OZ0FCZslACp92HlVNw1KPaDY6RAzvQ+J1s7seOtcOpmkfbD5aBSmMn9NvOakc8ZxMxmDXTIg==} + peerDependencies: + vue: ^3.5.0 '@xterm/addon-clipboard@0.1.0': resolution: {integrity: sha512-zdoM7p53T5sv/HbRTyp4hY0kKmEQ3MZvAvEtiXqNIHc/JdpqwByCtsTaQF5DX2n4hYdXRPO4P/eOS0QEhX1nPw==} @@ -2308,10 +2188,6 @@ packages: resolution: {integrity: sha512-jRR5wdylq8CkOe6hei19GGZnxM6rBGwFl3Bg0YItGDimvjGtAvdZk4Pu6Cl4u4Igsws4a1fd1Vq3ezrhn4KmFw==} engines: {node: '>= 14'} - algoliasearch@5.35.0: - resolution: {integrity: sha512-Y+moNhsqgLmvJdgTsO4GZNgsaDWv8AOGAaPeIeHKlDn/XunoAqYbA+XNpBd1dW8GOXAUDyxC9Rxc7AV4kpFcIg==} - engines: {node: '>= 14.0.0'} - ansi-regex@5.0.1: resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} engines: {node: '>=8'} @@ -2679,9 +2555,6 @@ packages: electron-to-chromium@1.5.160: resolution: {integrity: sha512-8yQk54/CoCQT8GX3zuxqPBwMAQuIr6dWI/qO8Aah/JAZwB5XmCbEElsqb1n4pzc2vpkTdfc/kbyNPJOjswfbgg==} - emoji-regex-xs@1.0.0: - resolution: {integrity: sha512-LRlerrMYoIDrT6jgpeZ2YYl/L8EulRTt5hQcYjy5AInh7HWXKimpqx68aknBFpGL2+/IcogTcaydJEgaTmOpDg==} - emoji-regex@8.0.0: resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} @@ -2731,11 +2604,6 @@ packages: engines: {node: '>=12'} hasBin: true - esbuild@0.21.5: - resolution: {integrity: sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==} - engines: {node: '>=12'} - hasBin: true - esbuild@0.25.5: resolution: {integrity: sha512-P8OtKZRv/5J5hhz0cUAdu/cLuPIKXpQl1R9pZtvmHWQvrAUVd0UNIPT4IB4W3rNOqVO0rlqHmCIbSwxh/c9yUQ==} engines: {node: '>=18'} @@ -2774,6 +2642,15 @@ packages: picomatch: optional: true + fdir@6.5.0: + resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==} + engines: {node: '>=12.0.0'} + peerDependencies: + picomatch: ^3 || ^4 + peerDependenciesMeta: + picomatch: + optional: true + fetch-blob@3.2.0: resolution: {integrity: sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==} engines: {node: ^12.20 || >= 14.13} @@ -3275,8 +3152,11 @@ packages: once@1.4.0: resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} - oniguruma-to-es@3.1.1: - resolution: {integrity: sha512-bUH8SDvPkH3ho3dvwJwfonjlQ4R80vjyvrU8YpxuROddv55vAEJrTuCuCVUhhsHbtlD9tGGbaNApGQckXhS8iQ==} + oniguruma-parser@0.12.1: + resolution: {integrity: sha512-8Unqkvk1RYc6yq2WBYRj4hdnsAxVze8i7iPfQr8e4uSP3tRv0rpZcbGUDvxfQQcdwHt/e9PrMvGCsa8OqG9X3w==} + + oniguruma-to-es@4.3.3: + resolution: {integrity: sha512-rPiZhzC3wXwE59YQMRDodUwwT9FZ9nNBwQQfsd1wfdtlKEyCdRV0avrTcSZ5xlIvGRVPd/cx6ZN45ECmS39xvg==} openid-client@6.5.0: resolution: {integrity: sha512-fAfYaTnOYE2kQCqEJGX9KDObW2aw7IQy4jWpU/+3D3WoCFLbix5Hg6qIPQ6Js9r7f8jDUmsnnguRNCSw4wU/IQ==} @@ -3312,6 +3192,10 @@ packages: resolution: {integrity: sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==} engines: {node: '>=12'} + picomatch@4.0.3: + resolution: {integrity: sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==} + engines: {node: '>=12'} + playwright-core@1.52.0: resolution: {integrity: sha512-l2osTgLXSMeuLZOML9qYODUQoPPnUsKsb5/P6LJ2e6uPKXUdPK5WYhN4z03G+YNbWmGDY4YENauNu4ZKczreHg==} engines: {node: '>=18'} @@ -3549,15 +3433,17 @@ packages: engines: {node: '>=18.0.0', npm: '>=8.0.0'} hasBin: true + rollup@4.46.3: + resolution: {integrity: sha512-RZn2XTjXb8t5g13f5YclGoilU/kwT696DIkY3sywjdZidNSi3+vseaQov7D7BZXVJCPv3pDWUN69C78GGbXsKw==} + engines: {node: '>=18.0.0', npm: '>=8.0.0'} + hasBin: true + safe-buffer@5.2.1: resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} scheduler@0.26.0: resolution: {integrity: sha512-NlHwttCI/l5gCPR3D1nNXtWABUmBwvZpEQiD4IXSbIDq8BzLIK/7Ir5gTFSGZDUu37K5cMNp0hFtzO38sC7gWA==} - search-insights@2.17.3: - resolution: {integrity: sha512-RQPdCYTa8A68uM2jwxoY842xDhvx3E5LFL1LxvxCNMev4o5mLuokczhzjAgGwUZBAmOKZknArSxLKmXtIi2AxQ==} - semver@6.3.1: resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} hasBin: true @@ -3578,8 +3464,8 @@ packages: resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} engines: {node: '>=8'} - shiki@2.5.0: - resolution: {integrity: sha512-mI//trrsaiCIPsja5CNfsyNOqgAZUb6VpJA+340toL42UpzQlXpwRV9nch69X6gaUxrr9kaOOa6e3y3uAkGFxQ==} + shiki@3.9.2: + resolution: {integrity: sha512-t6NKl5e/zGTvw/IyftLcumolgOczhuroqwXngDeMqJ3h3EQiTY/7wmfgPlsmloD8oYfqkEDqxiaH37Pjm1zUhQ==} siginfo@2.0.0: resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==} @@ -3903,37 +3789,6 @@ packages: vite: optional: true - vite@5.4.19: - resolution: {integrity: sha512-qO3aKv3HoQC8QKiNSTuUM1l9o/XX3+c+VTgLHbJWHZGeTPVAg2XwazI9UWzoxjIJCGCV2zU60uqMzjeLZuULqA==} - engines: {node: ^18.0.0 || >=20.0.0} - hasBin: true - peerDependencies: - '@types/node': ^18.0.0 || >=20.0.0 - less: '*' - lightningcss: ^1.21.0 - sass: '*' - sass-embedded: '*' - stylus: '*' - sugarss: '*' - terser: ^5.4.0 - peerDependenciesMeta: - '@types/node': - optional: true - less: - optional: true - lightningcss: - optional: true - sass: - optional: true - sass-embedded: - optional: true - stylus: - optional: true - sugarss: - optional: true - terser: - optional: true - vite@6.3.5: resolution: {integrity: sha512-cZn6NDFE7wdTpINgs++ZJ4N49W2vRp8LCKrn3Ob1kYNtOo21vfDoaV5GzBfLU4MovSAB8uNRm4jgzVQZ+mBzPQ==} engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} @@ -3974,15 +3829,58 @@ packages: yaml: optional: true - vitepress@1.6.4: - resolution: {integrity: sha512-+2ym1/+0VVrbhNyRoFFesVvBvHAVMZMK0rw60E3X/5349M1GuVdKeazuksqopEdvkKwKGs21Q729jX81/bkBJg==} + vite@7.1.2: + resolution: {integrity: sha512-J0SQBPlQiEXAF7tajiH+rUooJPo0l8KQgyg4/aMunNtrOa7bwuZJsJbDWzeljqQpgftxuq5yNJxQ91O9ts29UQ==} + engines: {node: ^20.19.0 || >=22.12.0} + hasBin: true + 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 + + vitepress@2.0.0-alpha.11: + resolution: {integrity: sha512-l3FFkGtcB3u3iMlpnvkCR+MdOYqNaz2z+xPRlgZZnx8Xne4XLgQR0yfEfTqY/UyloTymXwxvRvu443Yo9Cr8pA==} hasBin: true peerDependencies: markdown-it-mathjax3: ^4 + oxc-minify: ^0.81.0 postcss: ^8 peerDependenciesMeta: markdown-it-mathjax3: optional: true + oxc-minify: + optional: true postcss: optional: true @@ -4108,118 +4006,6 @@ packages: snapshots: - '@algolia/abtesting@1.1.0': - dependencies: - '@algolia/client-common': 5.35.0 - '@algolia/requester-browser-xhr': 5.35.0 - '@algolia/requester-fetch': 5.35.0 - '@algolia/requester-node-http': 5.35.0 - - '@algolia/autocomplete-core@1.17.7(@algolia/client-search@5.35.0)(algoliasearch@5.35.0)(search-insights@2.17.3)': - dependencies: - '@algolia/autocomplete-plugin-algolia-insights': 1.17.7(@algolia/client-search@5.35.0)(algoliasearch@5.35.0)(search-insights@2.17.3) - '@algolia/autocomplete-shared': 1.17.7(@algolia/client-search@5.35.0)(algoliasearch@5.35.0) - transitivePeerDependencies: - - '@algolia/client-search' - - algoliasearch - - search-insights - - '@algolia/autocomplete-plugin-algolia-insights@1.17.7(@algolia/client-search@5.35.0)(algoliasearch@5.35.0)(search-insights@2.17.3)': - dependencies: - '@algolia/autocomplete-shared': 1.17.7(@algolia/client-search@5.35.0)(algoliasearch@5.35.0) - search-insights: 2.17.3 - transitivePeerDependencies: - - '@algolia/client-search' - - algoliasearch - - '@algolia/autocomplete-preset-algolia@1.17.7(@algolia/client-search@5.35.0)(algoliasearch@5.35.0)': - dependencies: - '@algolia/autocomplete-shared': 1.17.7(@algolia/client-search@5.35.0)(algoliasearch@5.35.0) - '@algolia/client-search': 5.35.0 - algoliasearch: 5.35.0 - - '@algolia/autocomplete-shared@1.17.7(@algolia/client-search@5.35.0)(algoliasearch@5.35.0)': - dependencies: - '@algolia/client-search': 5.35.0 - algoliasearch: 5.35.0 - - '@algolia/client-abtesting@5.35.0': - dependencies: - '@algolia/client-common': 5.35.0 - '@algolia/requester-browser-xhr': 5.35.0 - '@algolia/requester-fetch': 5.35.0 - '@algolia/requester-node-http': 5.35.0 - - '@algolia/client-analytics@5.35.0': - dependencies: - '@algolia/client-common': 5.35.0 - '@algolia/requester-browser-xhr': 5.35.0 - '@algolia/requester-fetch': 5.35.0 - '@algolia/requester-node-http': 5.35.0 - - '@algolia/client-common@5.35.0': {} - - '@algolia/client-insights@5.35.0': - dependencies: - '@algolia/client-common': 5.35.0 - '@algolia/requester-browser-xhr': 5.35.0 - '@algolia/requester-fetch': 5.35.0 - '@algolia/requester-node-http': 5.35.0 - - '@algolia/client-personalization@5.35.0': - dependencies: - '@algolia/client-common': 5.35.0 - '@algolia/requester-browser-xhr': 5.35.0 - '@algolia/requester-fetch': 5.35.0 - '@algolia/requester-node-http': 5.35.0 - - '@algolia/client-query-suggestions@5.35.0': - dependencies: - '@algolia/client-common': 5.35.0 - '@algolia/requester-browser-xhr': 5.35.0 - '@algolia/requester-fetch': 5.35.0 - '@algolia/requester-node-http': 5.35.0 - - '@algolia/client-search@5.35.0': - dependencies: - '@algolia/client-common': 5.35.0 - '@algolia/requester-browser-xhr': 5.35.0 - '@algolia/requester-fetch': 5.35.0 - '@algolia/requester-node-http': 5.35.0 - - '@algolia/ingestion@1.35.0': - dependencies: - '@algolia/client-common': 5.35.0 - '@algolia/requester-browser-xhr': 5.35.0 - '@algolia/requester-fetch': 5.35.0 - '@algolia/requester-node-http': 5.35.0 - - '@algolia/monitoring@1.35.0': - dependencies: - '@algolia/client-common': 5.35.0 - '@algolia/requester-browser-xhr': 5.35.0 - '@algolia/requester-fetch': 5.35.0 - '@algolia/requester-node-http': 5.35.0 - - '@algolia/recommend@5.35.0': - dependencies: - '@algolia/client-common': 5.35.0 - '@algolia/requester-browser-xhr': 5.35.0 - '@algolia/requester-fetch': 5.35.0 - '@algolia/requester-node-http': 5.35.0 - - '@algolia/requester-browser-xhr@5.35.0': - dependencies: - '@algolia/client-common': 5.35.0 - - '@algolia/requester-fetch@5.35.0': - dependencies: - '@algolia/client-common': 5.35.0 - - '@algolia/requester-node-http@5.35.0': - dependencies: - '@algolia/client-common': 5.35.0 - '@ampproject/remapping@2.3.0': dependencies: '@jridgewell/gen-mapping': 0.3.8 @@ -4569,32 +4355,9 @@ snapshots: react: 19.1.0 tslib: 2.6.2 - '@docsearch/css@3.8.2': {} + '@docsearch/css@4.0.0-beta.7': {} - '@docsearch/js@3.8.2(@algolia/client-search@5.35.0)(@types/react@19.1.6)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(search-insights@2.17.3)': - dependencies: - '@docsearch/react': 3.8.2(@algolia/client-search@5.35.0)(@types/react@19.1.6)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(search-insights@2.17.3) - preact: 10.26.7 - transitivePeerDependencies: - - '@algolia/client-search' - - '@types/react' - - react - - react-dom - - search-insights - - '@docsearch/react@3.8.2(@algolia/client-search@5.35.0)(@types/react@19.1.6)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(search-insights@2.17.3)': - dependencies: - '@algolia/autocomplete-core': 1.17.7(@algolia/client-search@5.35.0)(algoliasearch@5.35.0)(search-insights@2.17.3) - '@algolia/autocomplete-preset-algolia': 1.17.7(@algolia/client-search@5.35.0)(algoliasearch@5.35.0) - '@docsearch/css': 3.8.2 - algoliasearch: 5.35.0 - optionalDependencies: - '@types/react': 19.1.6 - react: 19.1.0 - react-dom: 19.1.0(react@19.1.0) - search-insights: 2.17.3 - transitivePeerDependencies: - - '@algolia/client-search' + '@docsearch/js@4.0.0-beta.7': {} '@drizzle-team/brocli@0.10.2': {} @@ -4610,153 +4373,102 @@ snapshots: '@esbuild-kit/core-utils': 3.3.2 get-tsconfig: 4.10.1 - '@esbuild/aix-ppc64@0.21.5': - optional: true - '@esbuild/aix-ppc64@0.25.5': optional: true '@esbuild/android-arm64@0.18.20': optional: true - '@esbuild/android-arm64@0.21.5': - optional: true - '@esbuild/android-arm64@0.25.5': optional: true '@esbuild/android-arm@0.18.20': optional: true - '@esbuild/android-arm@0.21.5': - optional: true - '@esbuild/android-arm@0.25.5': optional: true '@esbuild/android-x64@0.18.20': optional: true - '@esbuild/android-x64@0.21.5': - optional: true - '@esbuild/android-x64@0.25.5': optional: true '@esbuild/darwin-arm64@0.18.20': optional: true - '@esbuild/darwin-arm64@0.21.5': - optional: true - '@esbuild/darwin-arm64@0.25.5': optional: true '@esbuild/darwin-x64@0.18.20': optional: true - '@esbuild/darwin-x64@0.21.5': - optional: true - '@esbuild/darwin-x64@0.25.5': optional: true '@esbuild/freebsd-arm64@0.18.20': optional: true - '@esbuild/freebsd-arm64@0.21.5': - optional: true - '@esbuild/freebsd-arm64@0.25.5': optional: true '@esbuild/freebsd-x64@0.18.20': optional: true - '@esbuild/freebsd-x64@0.21.5': - optional: true - '@esbuild/freebsd-x64@0.25.5': optional: true '@esbuild/linux-arm64@0.18.20': optional: true - '@esbuild/linux-arm64@0.21.5': - optional: true - '@esbuild/linux-arm64@0.25.5': optional: true '@esbuild/linux-arm@0.18.20': optional: true - '@esbuild/linux-arm@0.21.5': - optional: true - '@esbuild/linux-arm@0.25.5': optional: true '@esbuild/linux-ia32@0.18.20': optional: true - '@esbuild/linux-ia32@0.21.5': - optional: true - '@esbuild/linux-ia32@0.25.5': optional: true '@esbuild/linux-loong64@0.18.20': optional: true - '@esbuild/linux-loong64@0.21.5': - optional: true - '@esbuild/linux-loong64@0.25.5': optional: true '@esbuild/linux-mips64el@0.18.20': optional: true - '@esbuild/linux-mips64el@0.21.5': - optional: true - '@esbuild/linux-mips64el@0.25.5': optional: true '@esbuild/linux-ppc64@0.18.20': optional: true - '@esbuild/linux-ppc64@0.21.5': - optional: true - '@esbuild/linux-ppc64@0.25.5': optional: true '@esbuild/linux-riscv64@0.18.20': optional: true - '@esbuild/linux-riscv64@0.21.5': - optional: true - '@esbuild/linux-riscv64@0.25.5': optional: true '@esbuild/linux-s390x@0.18.20': optional: true - '@esbuild/linux-s390x@0.21.5': - optional: true - '@esbuild/linux-s390x@0.25.5': optional: true '@esbuild/linux-x64@0.18.20': optional: true - '@esbuild/linux-x64@0.21.5': - optional: true - '@esbuild/linux-x64@0.25.5': optional: true @@ -4766,9 +4478,6 @@ snapshots: '@esbuild/netbsd-x64@0.18.20': optional: true - '@esbuild/netbsd-x64@0.21.5': - optional: true - '@esbuild/netbsd-x64@0.25.5': optional: true @@ -4778,45 +4487,30 @@ snapshots: '@esbuild/openbsd-x64@0.18.20': optional: true - '@esbuild/openbsd-x64@0.21.5': - optional: true - '@esbuild/openbsd-x64@0.25.5': optional: true '@esbuild/sunos-x64@0.18.20': optional: true - '@esbuild/sunos-x64@0.21.5': - optional: true - '@esbuild/sunos-x64@0.25.5': optional: true '@esbuild/win32-arm64@0.18.20': optional: true - '@esbuild/win32-arm64@0.21.5': - optional: true - '@esbuild/win32-arm64@0.25.5': optional: true '@esbuild/win32-ia32@0.18.20': optional: true - '@esbuild/win32-ia32@0.21.5': - optional: true - '@esbuild/win32-ia32@0.25.5': optional: true '@esbuild/win32-x64@0.18.20': optional: true - '@esbuild/win32-x64@0.21.5': - optional: true - '@esbuild/win32-x64@0.25.5': optional: true @@ -4869,7 +4563,7 @@ snapshots: hono: 4.7.10 minimatch: 9.0.5 - '@iconify-json/simple-icons@1.2.46': + '@iconify-json/simple-icons@1.2.48': dependencies: '@iconify/types': 2.0.0 @@ -6111,105 +5805,165 @@ snapshots: '@react-types/shared': 3.29.1(react@19.1.0) react: 19.1.0 - '@rollup/pluginutils@5.1.4(rollup@4.36.0)': + '@rolldown/pluginutils@1.0.0-beta.29': {} + + '@rollup/pluginutils@5.1.4(rollup@4.46.3)': dependencies: '@types/estree': 1.0.7 estree-walker: 2.0.2 picomatch: 4.0.2 optionalDependencies: - rollup: 4.36.0 + rollup: 4.46.3 '@rollup/rollup-android-arm-eabi@4.36.0': optional: true + '@rollup/rollup-android-arm-eabi@4.46.3': + optional: true + '@rollup/rollup-android-arm64@4.36.0': optional: true + '@rollup/rollup-android-arm64@4.46.3': + optional: true + '@rollup/rollup-darwin-arm64@4.36.0': optional: true + '@rollup/rollup-darwin-arm64@4.46.3': + optional: true + '@rollup/rollup-darwin-x64@4.36.0': optional: true + '@rollup/rollup-darwin-x64@4.46.3': + optional: true + '@rollup/rollup-freebsd-arm64@4.36.0': optional: true + '@rollup/rollup-freebsd-arm64@4.46.3': + optional: true + '@rollup/rollup-freebsd-x64@4.36.0': optional: true + '@rollup/rollup-freebsd-x64@4.46.3': + optional: true + '@rollup/rollup-linux-arm-gnueabihf@4.36.0': optional: true + '@rollup/rollup-linux-arm-gnueabihf@4.46.3': + optional: true + '@rollup/rollup-linux-arm-musleabihf@4.36.0': optional: true + '@rollup/rollup-linux-arm-musleabihf@4.46.3': + optional: true + '@rollup/rollup-linux-arm64-gnu@4.36.0': optional: true + '@rollup/rollup-linux-arm64-gnu@4.46.3': + optional: true + '@rollup/rollup-linux-arm64-musl@4.36.0': optional: true + '@rollup/rollup-linux-arm64-musl@4.46.3': + optional: true + '@rollup/rollup-linux-loongarch64-gnu@4.36.0': optional: true + '@rollup/rollup-linux-loongarch64-gnu@4.46.3': + optional: true + '@rollup/rollup-linux-powerpc64le-gnu@4.36.0': optional: true + '@rollup/rollup-linux-ppc64-gnu@4.46.3': + optional: true + '@rollup/rollup-linux-riscv64-gnu@4.36.0': optional: true + '@rollup/rollup-linux-riscv64-gnu@4.46.3': + optional: true + + '@rollup/rollup-linux-riscv64-musl@4.46.3': + optional: true + '@rollup/rollup-linux-s390x-gnu@4.36.0': optional: true + '@rollup/rollup-linux-s390x-gnu@4.46.3': + optional: true + '@rollup/rollup-linux-x64-gnu@4.36.0': optional: true + '@rollup/rollup-linux-x64-gnu@4.46.3': + optional: true + '@rollup/rollup-linux-x64-musl@4.36.0': optional: true + '@rollup/rollup-linux-x64-musl@4.46.3': + optional: true + '@rollup/rollup-win32-arm64-msvc@4.36.0': optional: true + '@rollup/rollup-win32-arm64-msvc@4.46.3': + optional: true + '@rollup/rollup-win32-ia32-msvc@4.36.0': optional: true + '@rollup/rollup-win32-ia32-msvc@4.46.3': + optional: true + '@rollup/rollup-win32-x64-msvc@4.36.0': optional: true - '@shikijs/core@2.5.0': + '@rollup/rollup-win32-x64-msvc@4.46.3': + optional: true + + '@shikijs/core@3.9.2': dependencies: - '@shikijs/engine-javascript': 2.5.0 - '@shikijs/engine-oniguruma': 2.5.0 - '@shikijs/types': 2.5.0 + '@shikijs/types': 3.9.2 '@shikijs/vscode-textmate': 10.0.2 '@types/hast': 3.0.4 hast-util-to-html: 9.0.5 - '@shikijs/engine-javascript@2.5.0': + '@shikijs/engine-javascript@3.9.2': dependencies: - '@shikijs/types': 2.5.0 + '@shikijs/types': 3.9.2 '@shikijs/vscode-textmate': 10.0.2 - oniguruma-to-es: 3.1.1 + oniguruma-to-es: 4.3.3 - '@shikijs/engine-oniguruma@2.5.0': + '@shikijs/engine-oniguruma@3.9.2': dependencies: - '@shikijs/types': 2.5.0 + '@shikijs/types': 3.9.2 '@shikijs/vscode-textmate': 10.0.2 - '@shikijs/langs@2.5.0': + '@shikijs/langs@3.9.2': dependencies: - '@shikijs/types': 2.5.0 + '@shikijs/types': 3.9.2 - '@shikijs/themes@2.5.0': + '@shikijs/themes@3.9.2': dependencies: - '@shikijs/types': 2.5.0 + '@shikijs/types': 3.9.2 - '@shikijs/transformers@2.5.0': + '@shikijs/transformers@3.9.2': dependencies: - '@shikijs/core': 2.5.0 - '@shikijs/types': 2.5.0 + '@shikijs/core': 3.9.2 + '@shikijs/types': 3.9.2 - '@shikijs/types@2.5.0': + '@shikijs/types@3.9.2': dependencies: '@shikijs/vscode-textmate': 10.0.2 '@types/hast': 3.0.4 @@ -6311,6 +6065,8 @@ snapshots: '@types/estree@1.0.7': {} + '@types/estree@1.0.8': {} + '@types/hast@3.0.4': dependencies: '@types/unist': 3.0.3 @@ -6422,9 +6178,10 @@ snapshots: '@ungap/structured-clone@1.3.0': {} - '@vitejs/plugin-vue@5.2.4(vite@5.4.19(@types/node@22.15.24)(lightningcss@1.30.1)(terser@5.39.0))(vue@3.5.18(typescript@5.8.3))': + '@vitejs/plugin-vue@6.0.1(vite@7.1.2(@types/node@22.15.24)(jiti@2.4.2)(lightningcss@1.30.1)(terser@5.39.0)(tsx@4.19.4)(yaml@2.8.0))(vue@3.5.18(typescript@5.8.3))': dependencies: - vite: 5.4.19(@types/node@22.15.24)(lightningcss@1.30.1)(terser@5.39.0) + '@rolldown/pluginutils': 1.0.0-beta.29 + vite: 7.1.2(@types/node@22.15.24)(jiti@2.4.2)(lightningcss@1.30.1)(terser@5.39.0)(tsx@4.19.4)(yaml@2.8.0) vue: 3.5.18(typescript@5.8.3) '@vitest/expect@3.2.4': @@ -6499,13 +6256,13 @@ snapshots: '@vue/compiler-dom': 3.5.18 '@vue/shared': 3.5.18 - '@vue/devtools-api@7.7.7': + '@vue/devtools-api@8.0.0': dependencies: - '@vue/devtools-kit': 7.7.7 + '@vue/devtools-kit': 8.0.0 - '@vue/devtools-kit@7.7.7': + '@vue/devtools-kit@8.0.0': dependencies: - '@vue/devtools-shared': 7.7.7 + '@vue/devtools-shared': 8.0.0 birpc: 2.5.0 hookable: 5.5.3 mitt: 3.0.1 @@ -6513,7 +6270,7 @@ snapshots: speakingurl: 14.0.1 superjson: 2.2.2 - '@vue/devtools-shared@7.7.7': + '@vue/devtools-shared@8.0.0': dependencies: rfdc: 1.4.1 @@ -6541,32 +6298,26 @@ snapshots: '@vue/shared@3.5.18': {} - '@vueuse/core@12.8.2(typescript@5.8.3)': + '@vueuse/core@13.7.0(vue@3.5.18(typescript@5.8.3))': dependencies: '@types/web-bluetooth': 0.0.21 - '@vueuse/metadata': 12.8.2 - '@vueuse/shared': 12.8.2(typescript@5.8.3) + '@vueuse/metadata': 13.7.0 + '@vueuse/shared': 13.7.0(vue@3.5.18(typescript@5.8.3)) vue: 3.5.18(typescript@5.8.3) - transitivePeerDependencies: - - typescript - '@vueuse/integrations@12.8.2(focus-trap@7.6.5)(typescript@5.8.3)': + '@vueuse/integrations@13.7.0(focus-trap@7.6.5)(vue@3.5.18(typescript@5.8.3))': dependencies: - '@vueuse/core': 12.8.2(typescript@5.8.3) - '@vueuse/shared': 12.8.2(typescript@5.8.3) + '@vueuse/core': 13.7.0(vue@3.5.18(typescript@5.8.3)) + '@vueuse/shared': 13.7.0(vue@3.5.18(typescript@5.8.3)) vue: 3.5.18(typescript@5.8.3) optionalDependencies: focus-trap: 7.6.5 - transitivePeerDependencies: - - typescript - '@vueuse/metadata@12.8.2': {} + '@vueuse/metadata@13.7.0': {} - '@vueuse/shared@12.8.2(typescript@5.8.3)': + '@vueuse/shared@13.7.0(vue@3.5.18(typescript@5.8.3))': dependencies: vue: 3.5.18(typescript@5.8.3) - transitivePeerDependencies: - - typescript '@xterm/addon-clipboard@0.1.0(@xterm/xterm@5.5.0)': dependencies: @@ -6596,23 +6347,6 @@ snapshots: agent-base@7.1.3: {} - algoliasearch@5.35.0: - dependencies: - '@algolia/abtesting': 1.1.0 - '@algolia/client-abtesting': 5.35.0 - '@algolia/client-analytics': 5.35.0 - '@algolia/client-common': 5.35.0 - '@algolia/client-insights': 5.35.0 - '@algolia/client-personalization': 5.35.0 - '@algolia/client-query-suggestions': 5.35.0 - '@algolia/client-search': 5.35.0 - '@algolia/ingestion': 1.35.0 - '@algolia/monitoring': 1.35.0 - '@algolia/recommend': 5.35.0 - '@algolia/requester-browser-xhr': 5.35.0 - '@algolia/requester-fetch': 5.35.0 - '@algolia/requester-node-http': 5.35.0 - ansi-regex@5.0.1: {} ansi-regex@6.0.1: {} @@ -6872,8 +6606,6 @@ snapshots: electron-to-chromium@1.5.160: {} - emoji-regex-xs@1.0.0: {} - emoji-regex@8.0.0: {} emoji-regex@9.2.2: {} @@ -6940,32 +6672,6 @@ snapshots: '@esbuild/win32-ia32': 0.18.20 '@esbuild/win32-x64': 0.18.20 - esbuild@0.21.5: - optionalDependencies: - '@esbuild/aix-ppc64': 0.21.5 - '@esbuild/android-arm': 0.21.5 - '@esbuild/android-arm64': 0.21.5 - '@esbuild/android-x64': 0.21.5 - '@esbuild/darwin-arm64': 0.21.5 - '@esbuild/darwin-x64': 0.21.5 - '@esbuild/freebsd-arm64': 0.21.5 - '@esbuild/freebsd-x64': 0.21.5 - '@esbuild/linux-arm': 0.21.5 - '@esbuild/linux-arm64': 0.21.5 - '@esbuild/linux-ia32': 0.21.5 - '@esbuild/linux-loong64': 0.21.5 - '@esbuild/linux-mips64el': 0.21.5 - '@esbuild/linux-ppc64': 0.21.5 - '@esbuild/linux-riscv64': 0.21.5 - '@esbuild/linux-s390x': 0.21.5 - '@esbuild/linux-x64': 0.21.5 - '@esbuild/netbsd-x64': 0.21.5 - '@esbuild/openbsd-x64': 0.21.5 - '@esbuild/sunos-x64': 0.21.5 - '@esbuild/win32-arm64': 0.21.5 - '@esbuild/win32-ia32': 0.21.5 - '@esbuild/win32-x64': 0.21.5 - esbuild@0.25.5: optionalDependencies: '@esbuild/aix-ppc64': 0.25.5 @@ -7015,6 +6721,10 @@ snapshots: optionalDependencies: picomatch: 4.0.2 + fdir@6.5.0(picomatch@4.0.3): + optionalDependencies: + picomatch: 4.0.3 + fetch-blob@3.2.0: dependencies: node-domexception: 1.0.0 @@ -7481,9 +7191,11 @@ snapshots: dependencies: wrappy: 1.0.2 - oniguruma-to-es@3.1.1: + oniguruma-parser@0.12.1: {} + + oniguruma-to-es@4.3.3: dependencies: - emoji-regex-xs: 1.0.0 + oniguruma-parser: 0.12.1 regex: 6.0.1 regex-recursion: 6.0.2 @@ -7513,6 +7225,8 @@ snapshots: picomatch@4.0.2: {} + picomatch@4.0.3: {} + playwright-core@1.52.0: {} playwright@1.52.0: @@ -7688,7 +7402,7 @@ snapshots: optionalDependencies: react-dom: 19.1.0(react@19.1.0) - react-scan@0.3.4(@types/react@19.1.6)(react-dom@19.1.0(react@19.1.0))(react-router-dom@7.6.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react-router@7.6.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react@19.1.0)(rollup@4.36.0): + react-scan@0.3.4(@types/react@19.1.6)(react-dom@19.1.0(react@19.1.0))(react-router-dom@7.6.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react-router@7.6.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react@19.1.0)(rollup@4.46.3): dependencies: '@babel/core': 7.27.3 '@babel/generator': 7.27.3 @@ -7697,7 +7411,7 @@ snapshots: '@clack/prompts': 0.8.2 '@pivanov/utils': 0.0.2(react-dom@19.1.0(react@19.1.0))(react@19.1.0) '@preact/signals': 1.3.2(preact@10.26.7) - '@rollup/pluginutils': 5.1.4(rollup@4.36.0) + '@rollup/pluginutils': 5.1.4(rollup@4.46.3) '@types/node': 20.17.52 bippy: 0.3.14(@types/react@19.1.6)(react@19.1.0) esbuild: 0.25.5 @@ -7814,13 +7528,37 @@ snapshots: '@rollup/rollup-win32-x64-msvc': 4.36.0 fsevents: 2.3.3 + rollup@4.46.3: + dependencies: + '@types/estree': 1.0.8 + optionalDependencies: + '@rollup/rollup-android-arm-eabi': 4.46.3 + '@rollup/rollup-android-arm64': 4.46.3 + '@rollup/rollup-darwin-arm64': 4.46.3 + '@rollup/rollup-darwin-x64': 4.46.3 + '@rollup/rollup-freebsd-arm64': 4.46.3 + '@rollup/rollup-freebsd-x64': 4.46.3 + '@rollup/rollup-linux-arm-gnueabihf': 4.46.3 + '@rollup/rollup-linux-arm-musleabihf': 4.46.3 + '@rollup/rollup-linux-arm64-gnu': 4.46.3 + '@rollup/rollup-linux-arm64-musl': 4.46.3 + '@rollup/rollup-linux-loongarch64-gnu': 4.46.3 + '@rollup/rollup-linux-ppc64-gnu': 4.46.3 + '@rollup/rollup-linux-riscv64-gnu': 4.46.3 + '@rollup/rollup-linux-riscv64-musl': 4.46.3 + '@rollup/rollup-linux-s390x-gnu': 4.46.3 + '@rollup/rollup-linux-x64-gnu': 4.46.3 + '@rollup/rollup-linux-x64-musl': 4.46.3 + '@rollup/rollup-win32-arm64-msvc': 4.46.3 + '@rollup/rollup-win32-ia32-msvc': 4.46.3 + '@rollup/rollup-win32-x64-msvc': 4.46.3 + fsevents: 2.3.3 + safe-buffer@5.2.1: optional: true scheduler@0.26.0: {} - search-insights@2.17.3: {} - semver@6.3.1: {} semver@7.7.2: {} @@ -7833,14 +7571,14 @@ snapshots: shebang-regex@3.0.0: {} - shiki@2.5.0: + shiki@3.9.2: dependencies: - '@shikijs/core': 2.5.0 - '@shikijs/engine-javascript': 2.5.0 - '@shikijs/engine-oniguruma': 2.5.0 - '@shikijs/langs': 2.5.0 - '@shikijs/themes': 2.5.0 - '@shikijs/types': 2.5.0 + '@shikijs/core': 3.9.2 + '@shikijs/engine-javascript': 3.9.2 + '@shikijs/engine-oniguruma': 3.9.2 + '@shikijs/langs': 3.9.2 + '@shikijs/themes': 3.9.2 + '@shikijs/types': 3.9.2 '@shikijs/vscode-textmate': 10.0.2 '@types/hast': 3.0.4 @@ -8211,17 +7949,6 @@ snapshots: - supports-color - typescript - vite@5.4.19(@types/node@22.15.24)(lightningcss@1.30.1)(terser@5.39.0): - dependencies: - esbuild: 0.21.5 - postcss: 8.5.4 - rollup: 4.36.0 - optionalDependencies: - '@types/node': 22.15.24 - fsevents: 2.3.3 - lightningcss: 1.30.1 - terser: 5.39.0 - vite@6.3.5(@types/node@22.15.24)(jiti@2.4.2)(lightningcss@1.30.1)(terser@5.39.0)(tsx@4.19.4)(yaml@2.8.0): dependencies: esbuild: 0.25.5 @@ -8239,54 +7966,69 @@ snapshots: tsx: 4.19.4 yaml: 2.8.0 - vitepress@1.6.4(@algolia/client-search@5.35.0)(@types/node@22.15.24)(@types/react@19.1.6)(lightningcss@1.30.1)(postcss@8.5.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(search-insights@2.17.3)(terser@5.39.0)(typescript@5.8.3): + vite@7.1.2(@types/node@22.15.24)(jiti@2.4.2)(lightningcss@1.30.1)(terser@5.39.0)(tsx@4.19.4)(yaml@2.8.0): dependencies: - '@docsearch/css': 3.8.2 - '@docsearch/js': 3.8.2(@algolia/client-search@5.35.0)(@types/react@19.1.6)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(search-insights@2.17.3) - '@iconify-json/simple-icons': 1.2.46 - '@shikijs/core': 2.5.0 - '@shikijs/transformers': 2.5.0 - '@shikijs/types': 2.5.0 + esbuild: 0.25.5 + fdir: 6.5.0(picomatch@4.0.3) + picomatch: 4.0.3 + postcss: 8.5.6 + rollup: 4.46.3 + tinyglobby: 0.2.14 + optionalDependencies: + '@types/node': 22.15.24 + fsevents: 2.3.3 + jiti: 2.4.2 + lightningcss: 1.30.1 + terser: 5.39.0 + tsx: 4.19.4 + yaml: 2.8.0 + + vitepress@2.0.0-alpha.11(@types/node@22.15.24)(jiti@2.4.2)(lightningcss@1.30.1)(postcss@8.5.4)(terser@5.39.0)(tsx@4.19.4)(typescript@5.8.3)(yaml@2.8.0): + dependencies: + '@docsearch/css': 4.0.0-beta.7 + '@docsearch/js': 4.0.0-beta.7 + '@iconify-json/simple-icons': 1.2.48 + '@shikijs/core': 3.9.2 + '@shikijs/transformers': 3.9.2 + '@shikijs/types': 3.9.2 '@types/markdown-it': 14.1.2 - '@vitejs/plugin-vue': 5.2.4(vite@5.4.19(@types/node@22.15.24)(lightningcss@1.30.1)(terser@5.39.0))(vue@3.5.18(typescript@5.8.3)) - '@vue/devtools-api': 7.7.7 + '@vitejs/plugin-vue': 6.0.1(vite@7.1.2(@types/node@22.15.24)(jiti@2.4.2)(lightningcss@1.30.1)(terser@5.39.0)(tsx@4.19.4)(yaml@2.8.0))(vue@3.5.18(typescript@5.8.3)) + '@vue/devtools-api': 8.0.0 '@vue/shared': 3.5.18 - '@vueuse/core': 12.8.2(typescript@5.8.3) - '@vueuse/integrations': 12.8.2(focus-trap@7.6.5)(typescript@5.8.3) + '@vueuse/core': 13.7.0(vue@3.5.18(typescript@5.8.3)) + '@vueuse/integrations': 13.7.0(focus-trap@7.6.5)(vue@3.5.18(typescript@5.8.3)) focus-trap: 7.6.5 mark.js: 8.11.1 minisearch: 7.1.2 - shiki: 2.5.0 - vite: 5.4.19(@types/node@22.15.24)(lightningcss@1.30.1)(terser@5.39.0) + shiki: 3.9.2 + vite: 7.1.2(@types/node@22.15.24)(jiti@2.4.2)(lightningcss@1.30.1)(terser@5.39.0)(tsx@4.19.4)(yaml@2.8.0) vue: 3.5.18(typescript@5.8.3) optionalDependencies: postcss: 8.5.4 transitivePeerDependencies: - - '@algolia/client-search' - '@types/node' - - '@types/react' - async-validator - axios - change-case - drauu - fuse.js - idb-keyval + - jiti - jwt-decode - less - lightningcss - nprogress - qrcode - - react - - react-dom - sass - sass-embedded - - search-insights - sortablejs - stylus - sugarss - terser + - tsx - typescript - universal-cookie + - yaml vitest@3.2.4(@types/node@22.15.24)(jiti@2.4.2)(lightningcss@1.30.1)(terser@5.39.0)(tsx@4.19.4)(yaml@2.8.0): dependencies: From 616b63bb713cd587b5bd5517d90bd01194250385 Mon Sep 17 00:00:00 2001 From: Aarnav Tale Date: Mon, 18 Aug 2025 13:37:53 -0400 Subject: [PATCH 71/97] chore: add wrangler.toml --- wrangler.toml | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 wrangler.toml diff --git a/wrangler.toml b/wrangler.toml new file mode 100644 index 0000000..2931343 --- /dev/null +++ b/wrangler.toml @@ -0,0 +1,6 @@ +name = "headplane" +compatibility_date = "2025-08-18" + +[assets] +directory = "docs/.vitepress/dist" +not_found_handling = "404-page" From 17712cbb1c920bb8deea751c3c3652b614f79b2f Mon Sep 17 00:00:00 2001 From: Aarnav Tale Date: Mon, 18 Aug 2025 16:04:30 -0400 Subject: [PATCH 72/97] feat: upgrade react-router + hono-server --- package.json | 23 +- patches/react-router-hono-server.patch | 34 - pnpm-lock.yaml | 2215 +++++++++++++----------- vite.config.ts | 3 + 4 files changed, 1179 insertions(+), 1096 deletions(-) delete mode 100644 patches/react-router-hono-server.patch diff --git a/package.json b/package.json index d2daa95..82da29b 100644 --- a/package.json +++ b/package.json @@ -26,11 +26,11 @@ "@libsql/client": "^0.15.9", "@primer/octicons-react": "^19.15.2", "@react-aria/toast": "3.0.3", - "@react-router/node": "^7.6.1", + "@react-router/node": "^7.8.1", "@react-stately/toast": "3.1.0", "@shopify/lang-jsonc": "^1.0.1", - "@types/react": "^19.1.6", - "@types/react-dom": "^19.1.5", + "@types/react": "^19.1.10", + "@types/react-dom": "^19.1.7", "@uiw/codemirror-theme-github": "^4.23.12", "@uiw/codemirror-theme-xcode": "^4.23.12", "@uiw/react-codemirror": "^4.23.12", @@ -48,16 +48,16 @@ "lucide-react": "^0.511.0", "mime": "^4.0.7", "openid-client": "^6.5.0", - "react": "19.1.0", + "react": "19.1.1", "react-aria": "^3.40.0", "react-codemirror-merge": "^4.23.12", - "react-dom": "19.1.0", + "react-dom": "19.1.1", "react-error-boundary": "^6.0.0", "react-icons": "^5.5.0", - "react-router": "^7.6.1", - "react-router-hono-server": "^2.13.0", + "react-router": "^7.8.1", + "react-router-hono-server": "2.21.0", "react-stately": "^3.38.0", - "remix-utils": "^8.7.0", + "remix-utils": "^8.8.0", "tailwind-merge": "^3.3.0", "undici": "^7.10.0", "usehooks-ts": "^3.1.1", @@ -65,7 +65,7 @@ }, "devDependencies": { "@biomejs/biome": "^1.9.4", - "@react-router/dev": "^7.6.1", + "@react-router/dev": "^7.8.1", "@tailwindcss/vite": "^4.1.8", "@types/node": "^22.15.17", "@types/websocket": "^1.0.10", @@ -73,7 +73,7 @@ "js-yaml": "^4.1.0", "lefthook": "^1.11.13", "postcss": "^8.5.4", - "react-router-dom": "^7.6.1", + "react-router-dom": "^7.8.1", "react-scan": "^0.3.4", "tailwindcss": "^4.1.8", "tailwindcss-animate": "^1.0.7", @@ -90,9 +90,6 @@ "pnpm": ">=10.4 <11" }, "pnpm": { - "patchedDependencies": { - "react-router-hono-server": "patches/react-router-hono-server.patch" - }, "supportedArchitectures": { "os": ["current", "linux"], "cpu": ["x64", "arm64"] diff --git a/patches/react-router-hono-server.patch b/patches/react-router-hono-server.patch deleted file mode 100644 index c2c6862..0000000 --- a/patches/react-router-hono-server.patch +++ /dev/null @@ -1,34 +0,0 @@ -diff --git a/dist/adapters/node.js b/dist/adapters/node.js -index 966604f94ca8528b684ef95fe7891c2e6352561b..8222cf31333668f8c2ebe65986b6ab9a3711b587 100644 ---- a/dist/adapters/node.js -+++ b/dist/adapters/node.js -@@ -46,16 +46,25 @@ async function createHonoServer(options) { - } - await mergedOptions.beforeAll?.(app); - app.use( -- `/${import.meta.env.REACT_ROUTER_HONO_SERVER_ASSETS_DIR}/*`, -+ `/${__PREFIX__}${import.meta.env.REACT_ROUTER_HONO_SERVER_ASSETS_DIR}/*`, - cache(60 * 60 * 24 * 365), - // 1 year -- serveStatic({ root: clientBuildPath, ...mergedOptions.serveStaticOptions?.clientAssets }) -+ serveStatic({ -+ root: clientBuildPath, -+ ...mergedOptions.serveStaticOptions?.clientAssets, -+ rewriteRequestPath: path => path.replace(__PREFIX__, "/") -+ }) - ); -+ app.use(__PREFIX__, (c) => c.redirect(`${__PREFIX__}/`)); - app.use( -- "*", -+ `${__PREFIX__}/*`, - cache(60 * 60), - // 1 hour -- serveStatic({ root: PRODUCTION ? clientBuildPath : "./public", ...mergedOptions.serveStaticOptions?.publicAssets }) -+ serveStatic({ -+ root: PRODUCTION ? clientBuildPath : "./public", -+ ...mergedOptions.serveStaticOptions?.publicAssets, -+ rewriteRequestPath: path => path.replace(__PREFIX__, "/") -+ }) - ); - if (mergedOptions.defaultLogger) { - app.use("*", logger()); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 26f179a..62c7736 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -4,27 +4,22 @@ settings: autoInstallPeers: true excludeLinksFromLockfile: false -patchedDependencies: - react-router-hono-server: - hash: 6549978df41006e07f1335bfe4ca86224ea36ed40d3f08dfef33143bad54005c - path: patches/react-router-hono-server.patch - importers: .: dependencies: '@dnd-kit/core': specifier: ^6.3.1 - version: 6.3.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + version: 6.3.1(react-dom@19.1.1(react@19.1.1))(react@19.1.1) '@dnd-kit/modifiers': specifier: ^7.0.0 - version: 7.0.0(@dnd-kit/core@6.3.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react@19.1.0) + version: 7.0.0(@dnd-kit/core@6.3.1(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(react@19.1.1) '@dnd-kit/sortable': specifier: ^8.0.0 - version: 8.0.0(@dnd-kit/core@6.3.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react@19.1.0) + version: 8.0.0(@dnd-kit/core@6.3.1(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(react@19.1.1) '@dnd-kit/utilities': specifier: ^3.2.2 - version: 3.2.2(react@19.1.0) + version: 3.2.2(react@19.1.1) '@faker-js/faker': specifier: ^9.8.0 version: 9.8.0 @@ -39,25 +34,25 @@ importers: version: 0.15.9(bufferutil@4.0.9)(utf-8-validate@5.0.10) '@primer/octicons-react': specifier: ^19.15.2 - version: 19.15.2(react@19.1.0) + version: 19.15.2(react@19.1.1) '@react-aria/toast': specifier: 3.0.3 - version: 3.0.3(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + version: 3.0.3(react-dom@19.1.1(react@19.1.1))(react@19.1.1) '@react-router/node': - specifier: ^7.6.1 - version: 7.6.1(react-router@7.6.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(typescript@5.8.3) + specifier: ^7.8.1 + version: 7.8.1(react-router@7.8.1(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(typescript@5.8.3) '@react-stately/toast': specifier: 3.1.0 - version: 3.1.0(react@19.1.0) + version: 3.1.0(react@19.1.1) '@shopify/lang-jsonc': specifier: ^1.0.1 version: 1.0.1 '@types/react': - specifier: ^19.1.6 - version: 19.1.6 + specifier: ^19.1.10 + version: 19.1.10 '@types/react-dom': - specifier: ^19.1.5 - version: 19.1.5(@types/react@19.1.6) + specifier: ^19.1.7 + version: 19.1.7(@types/react@19.1.10) '@uiw/codemirror-theme-github': specifier: ^4.23.12 version: 4.23.12(@codemirror/language@6.11.0)(@codemirror/state@6.5.2)(@codemirror/view@6.36.8) @@ -66,7 +61,7 @@ importers: version: 4.23.12(@codemirror/language@6.11.0)(@codemirror/state@6.5.2)(@codemirror/view@6.36.8) '@uiw/react-codemirror': specifier: ^4.23.12 - version: 4.23.12(@babel/runtime@7.27.3)(@codemirror/autocomplete@6.18.2(@codemirror/language@6.11.0)(@codemirror/state@6.5.2)(@codemirror/view@6.36.8)(@lezer/common@1.2.3))(@codemirror/language@6.11.0)(@codemirror/lint@6.8.2)(@codemirror/search@6.5.7)(@codemirror/state@6.5.2)(@codemirror/theme-one-dark@6.1.2)(@codemirror/view@6.36.8)(codemirror@6.0.1(@lezer/common@1.2.3))(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + version: 4.23.12(@babel/runtime@7.27.3)(@codemirror/autocomplete@6.18.2(@codemirror/language@6.11.0)(@codemirror/state@6.5.2)(@codemirror/view@6.36.8)(@lezer/common@1.2.3))(@codemirror/language@6.11.0)(@codemirror/lint@6.8.2)(@codemirror/search@6.5.7)(@codemirror/state@6.5.2)(@codemirror/theme-one-dark@6.1.2)(@codemirror/view@6.36.8)(codemirror@6.0.1(@lezer/common@1.2.3))(react-dom@19.1.1(react@19.1.1))(react@19.1.1) '@xterm/addon-clipboard': specifier: ^0.1.0 version: 0.1.0(@xterm/xterm@5.5.0) @@ -102,7 +97,7 @@ importers: version: 5.1.28 lucide-react: specifier: ^0.511.0 - version: 0.511.0(react@19.1.0) + version: 0.511.0(react@19.1.1) mime: specifier: ^4.0.7 version: 4.0.7 @@ -110,35 +105,35 @@ importers: specifier: ^6.5.0 version: 6.5.0 react: - specifier: 19.1.0 - version: 19.1.0 + specifier: 19.1.1 + version: 19.1.1 react-aria: specifier: ^3.40.0 - version: 3.40.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + version: 3.40.0(react-dom@19.1.1(react@19.1.1))(react@19.1.1) react-codemirror-merge: specifier: ^4.23.12 - version: 4.23.12(@babel/runtime@7.27.3)(@codemirror/autocomplete@6.18.2(@codemirror/language@6.11.0)(@codemirror/state@6.5.2)(@codemirror/view@6.36.8)(@lezer/common@1.2.3))(@codemirror/language@6.11.0)(@codemirror/lint@6.8.2)(@codemirror/search@6.5.7)(@codemirror/state@6.5.2)(@codemirror/theme-one-dark@6.1.2)(@codemirror/view@6.36.8)(codemirror@6.0.1(@lezer/common@1.2.3))(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + version: 4.23.12(@babel/runtime@7.27.3)(@codemirror/autocomplete@6.18.2(@codemirror/language@6.11.0)(@codemirror/state@6.5.2)(@codemirror/view@6.36.8)(@lezer/common@1.2.3))(@codemirror/language@6.11.0)(@codemirror/lint@6.8.2)(@codemirror/search@6.5.7)(@codemirror/state@6.5.2)(@codemirror/theme-one-dark@6.1.2)(@codemirror/view@6.36.8)(codemirror@6.0.1(@lezer/common@1.2.3))(react-dom@19.1.1(react@19.1.1))(react@19.1.1) react-dom: - specifier: 19.1.0 - version: 19.1.0(react@19.1.0) + specifier: 19.1.1 + version: 19.1.1(react@19.1.1) react-error-boundary: specifier: ^6.0.0 - version: 6.0.0(react@19.1.0) + version: 6.0.0(react@19.1.1) react-icons: specifier: ^5.5.0 - version: 5.5.0(react@19.1.0) + version: 5.5.0(react@19.1.1) react-router: - specifier: ^7.6.1 - version: 7.6.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + specifier: ^7.8.1 + version: 7.8.1(react-dom@19.1.1(react@19.1.1))(react@19.1.1) react-router-hono-server: - specifier: ^2.13.0 - version: 2.13.0(patch_hash=6549978df41006e07f1335bfe4ca86224ea36ed40d3f08dfef33143bad54005c)(@react-router/dev@7.6.1(@types/node@22.15.24)(jiti@2.4.2)(lightningcss@1.30.1)(react-router@7.6.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(terser@5.39.0)(tsx@4.19.4)(typescript@5.8.3)(vite@6.3.5(@types/node@22.15.24)(jiti@2.4.2)(lightningcss@1.30.1)(terser@5.39.0)(tsx@4.19.4)(yaml@2.8.0))(yaml@2.8.0))(@types/react@19.1.6)(bufferutil@4.0.9)(react-router@7.6.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(utf-8-validate@5.0.10)(vite@6.3.5(@types/node@22.15.24)(jiti@2.4.2)(lightningcss@1.30.1)(terser@5.39.0)(tsx@4.19.4)(yaml@2.8.0)) + specifier: 2.21.0 + version: 2.21.0(@hono/node-server@1.19.0(hono@4.7.10))(@react-router/dev@7.8.1(@types/node@22.15.24)(jiti@2.4.2)(lightningcss@1.30.1)(react-dom@19.1.1(react@19.1.1))(react-router@7.8.1(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(react@19.1.1)(terser@5.39.0)(tsx@4.19.4)(typescript@5.8.3)(vite@6.3.5(@types/node@22.15.24)(jiti@2.4.2)(lightningcss@1.30.1)(terser@5.39.0)(tsx@4.19.4)(yaml@2.8.0))(yaml@2.8.0))(@types/react@19.1.10)(bufferutil@4.0.9)(react-router@7.8.1(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(utf-8-validate@5.0.10)(vite@6.3.5(@types/node@22.15.24)(jiti@2.4.2)(lightningcss@1.30.1)(terser@5.39.0)(tsx@4.19.4)(yaml@2.8.0)) react-stately: specifier: ^3.38.0 - version: 3.38.0(react@19.1.0) + version: 3.38.0(react@19.1.1) remix-utils: - specifier: ^8.7.0 - version: 8.7.0(react-router@7.6.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react@19.1.0)(zod@3.25.33) + specifier: ^8.8.0 + version: 8.8.0(react-router@7.8.1(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(react@19.1.1)(zod@3.25.33) tailwind-merge: specifier: ^3.3.0 version: 3.3.0 @@ -147,7 +142,7 @@ importers: version: 7.10.0 usehooks-ts: specifier: ^3.1.1 - version: 3.1.1(react@19.1.0) + version: 3.1.1(react@19.1.1) yaml: specifier: ^2.8.0 version: 2.8.0 @@ -156,8 +151,8 @@ importers: specifier: ^1.9.4 version: 1.9.4 '@react-router/dev': - specifier: ^7.6.1 - version: 7.6.1(@types/node@22.15.24)(jiti@2.4.2)(lightningcss@1.30.1)(react-router@7.6.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(terser@5.39.0)(tsx@4.19.4)(typescript@5.8.3)(vite@6.3.5(@types/node@22.15.24)(jiti@2.4.2)(lightningcss@1.30.1)(terser@5.39.0)(tsx@4.19.4)(yaml@2.8.0))(yaml@2.8.0) + specifier: ^7.8.1 + version: 7.8.1(@types/node@22.15.24)(jiti@2.4.2)(lightningcss@1.30.1)(react-dom@19.1.1(react@19.1.1))(react-router@7.8.1(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(react@19.1.1)(terser@5.39.0)(tsx@4.19.4)(typescript@5.8.3)(vite@6.3.5(@types/node@22.15.24)(jiti@2.4.2)(lightningcss@1.30.1)(terser@5.39.0)(tsx@4.19.4)(yaml@2.8.0))(yaml@2.8.0) '@tailwindcss/vite': specifier: ^4.1.8 version: 4.1.8(vite@6.3.5(@types/node@22.15.24)(jiti@2.4.2)(lightningcss@1.30.1)(terser@5.39.0)(tsx@4.19.4)(yaml@2.8.0)) @@ -180,11 +175,11 @@ importers: specifier: ^8.5.4 version: 8.5.4 react-router-dom: - specifier: ^7.6.1 - version: 7.6.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + specifier: ^7.8.1 + version: 7.8.1(react-dom@19.1.1(react@19.1.1))(react@19.1.1) react-scan: specifier: ^0.3.4 - version: 0.3.4(@types/react@19.1.6)(react-dom@19.1.0(react@19.1.0))(react-router-dom@7.6.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react-router@7.6.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react@19.1.0)(rollup@4.46.3) + version: 0.3.4(@types/react@19.1.10)(react-dom@19.1.1(react@19.1.1))(react-router-dom@7.8.1(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(react-router@7.8.1(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(react@19.1.1)(rollup@4.46.3) tailwindcss: specifier: ^4.1.8 version: 4.1.8 @@ -234,10 +229,18 @@ packages: resolution: {integrity: sha512-hyrN8ivxfvJ4i0fIJuV4EOlV0WDMz5Ui4StRTgVaAvWeiRCilXgwVvxJKtFQ3TKtHgJscB2YiXKGNJuVwhQMtA==} engines: {node: '>=6.9.0'} + '@babel/core@7.28.3': + resolution: {integrity: sha512-yDBHV9kQNcr2/sUr9jghVyz9C3Y5G2zUM2H2lo+9mKv4sFgbA8s8Z9t8D1jiTkGoO/NoIfKMyKWr4s6CN23ZwQ==} + engines: {node: '>=6.9.0'} + '@babel/generator@7.27.3': resolution: {integrity: sha512-xnlJYj5zepml8NXtjkG0WquFUv8RskFqyFcVgTBp5k+NaA/8uw/K+OSVf8AMGw5e9HKP2ETd5xpK5MLZQD6b4Q==} engines: {node: '>=6.9.0'} + '@babel/generator@7.28.3': + resolution: {integrity: sha512-3lSpxGgvnmZznmBkCRnVREPUFJv2wrv9iAoFDvADJc0ypmdOxdUtcLeBgBJ6zE0PMeTKnxeQzyk0xTBq4Ep7zw==} + engines: {node: '>=6.9.0'} + '@babel/helper-annotate-as-pure@7.27.3': resolution: {integrity: sha512-fXSwMQqitTGeHLBC08Eq5yXz2m37E4pJX1qAU1+2cNedz/ifv/bVXft90VeSav5nFO61EcNgwr0aJxbyPaWBPg==} engines: {node: '>=6.9.0'} @@ -246,12 +249,16 @@ packages: resolution: {integrity: sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==} engines: {node: '>=6.9.0'} - '@babel/helper-create-class-features-plugin@7.27.1': - resolution: {integrity: sha512-QwGAmuvM17btKU5VqXfb+Giw4JcN0hjuufz3DYnpeVDvZLAObloM77bhMXiqry3Iio+Ai4phVRDwl6WU10+r5A==} + '@babel/helper-create-class-features-plugin@7.28.3': + resolution: {integrity: sha512-V9f6ZFIYSLNEbuGA/92uOvYsGCJNsuA8ESZ4ldc09bWk/j8H8TKiPw8Mk1eG6olpnO0ALHJmYfZvF4MEE4gajg==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0 + '@babel/helper-globals@7.28.0': + resolution: {integrity: sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==} + engines: {node: '>=6.9.0'} + '@babel/helper-member-expression-to-functions@7.27.1': resolution: {integrity: sha512-E5chM8eWjTp/aNoVpcbfM7mLxu9XGLWYise2eBKGQomAk/Mb4XoxyqXTZbuTohbsl8EKqdlMhnDI2CCLfcs9wA==} engines: {node: '>=6.9.0'} @@ -266,6 +273,12 @@ packages: peerDependencies: '@babel/core': ^7.0.0 + '@babel/helper-module-transforms@7.28.3': + resolution: {integrity: sha512-gytXUbs8k2sXS9PnQptz5o0QnpLL51SwASIORY6XaBKF88nsOT0Zw9szLqlSGQDP/4TljBAD5y98p2U1fqkdsw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + '@babel/helper-optimise-call-expression@7.27.1': resolution: {integrity: sha512-URMGH08NzYFhubNSGJrpUEphGKQwMQYBySzat5cAByY1/YgIRkULnIy3tAMeszlL/so2HbeilYloUmSpd7GdVw==} engines: {node: '>=6.9.0'} @@ -300,6 +313,10 @@ packages: resolution: {integrity: sha512-h/eKy9agOya1IGuLaZ9tEUgz+uIRXcbtOhRtUyyMf8JFmn1iT13vnl/IGVWSkdOCG/pC57U4S1jnAabAavTMwg==} engines: {node: '>=6.9.0'} + '@babel/helpers@7.28.3': + resolution: {integrity: sha512-PTNtvUQihsAsDHMOP5pfobP8C6CM4JWXmP8DrEIt46c3r2bf87Ua1zoqevsMo9g+tWDwgWrFP5EIxuBx5RudAw==} + engines: {node: '>=6.9.0'} + '@babel/parser@7.27.3': resolution: {integrity: sha512-xyYxRj6+tLNDTWi0KCBcZ9V7yg3/lwL9DWh9Uwh/RIVlIfFidggcgxKX3GCXwCiswwcGRawBKbEg2LG/Y8eJhw==} engines: {node: '>=6.0.0'} @@ -310,11 +327,10 @@ packages: engines: {node: '>=6.0.0'} hasBin: true - '@babel/plugin-syntax-decorators@7.27.1': - resolution: {integrity: sha512-YMq8Z87Lhl8EGkmb0MwYkt36QnxC+fzCgrl66ereamPlYToRpIk5nUjKUY3QKLWq8mwUB1BgbeXcTJhZOCDg5A==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 + '@babel/parser@7.28.3': + resolution: {integrity: sha512-7+Ey1mAgYqFAx2h0RuoxcQT5+MlG3GTV0TQrgr7/ZliKsm/MNDxVVutlWaziMq7wJNAz8MTqz55XLpWvva6StA==} + engines: {node: '>=6.0.0'} + hasBin: true '@babel/plugin-syntax-jsx@7.27.1': resolution: {integrity: sha512-y8YTNIeKoyhGd9O0Jiyzyyqk8gdjnumGTQPsz0xOZOQ2RmkVJeZ1vmmfIvFEKqucBG6axJGBZDE/7iI5suUI/w==} @@ -334,8 +350,8 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-typescript@7.27.1': - resolution: {integrity: sha512-Q5sT5+O4QUebHdbwKedFBEwRLb02zJ7r4A5Gg2hUoLuU3FjdMcyqcywqUrLCaDsFCxzokf7u9kuy7qz51YUuAg==} + '@babel/plugin-transform-typescript@7.28.0': + resolution: {integrity: sha512-4AEiDEBPIZvLQaWlc9liCavE0xRM0dNca41WtBeM3jgFptfUOSG9z0uteLhq6+3rq+WB6jIvUwKDTpXEHPJ2Vg==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 @@ -358,6 +374,10 @@ packages: resolution: {integrity: sha512-lId/IfN/Ye1CIu8xG7oKBHXd2iNb2aW1ilPszzGcJug6M8RCKfVNcYhpI5+bMvFYjK7lXIM0R+a+6r8xhHp2FQ==} engines: {node: '>=6.9.0'} + '@babel/traverse@7.28.3': + resolution: {integrity: sha512-7w4kZYHneL3A6NP2nxzHvT3HCZ7puDZZjFMqDpBPECub79sTtSO5CGXDkKrTQq8ksAwfD/XI2MRFX23njdDaIQ==} + engines: {node: '>=6.9.0'} + '@babel/types@7.27.3': resolution: {integrity: sha512-Y1GkI4ktrtvmawoSq+4FCVHNryea6uR+qUQy0AGxLSsjCX0nVmkYQMBLHDkXZuo5hGx7eYdnIaslsdBFm7zbUw==} engines: {node: '>=6.9.0'} @@ -809,21 +829,21 @@ packages: '@formatjs/intl-localematcher@0.6.1': resolution: {integrity: sha512-ePEgLgVCqi2BBFnTMWPfIghu6FkbZnnBVhO2sSxvLfrdFw7wCHAHiDoM2h4NRgjbaY7+B7HgOLZGkK187pZTZg==} - '@hono/node-server@1.14.0': - resolution: {integrity: sha512-YUCxJwgHRKSqjrdTk9e4VMGKN27MK5r4+MGPyZTgKH+IYbK+KtYbHeOcPGJ91KGGD6RIQiz2dAHxvjauNhOS8g==} + '@hono/node-server@1.19.0': + resolution: {integrity: sha512-1k8/8OHf5VIymJEcJyVksFpT+AQ5euY0VA5hUkCnlKpD4mr8FSbvXaHblxeTTEr90OaqWzAkQaqD80qHZQKxBA==} engines: {node: '>=18.14.1'} peerDependencies: hono: ^4 - '@hono/node-ws@1.1.1': - resolution: {integrity: sha512-iFJrAw5GuBTstehBzLY2FyW5rRlXmO3Uwpijpm4Liv75owNP/UjZe3KExsLuEK4w+u+xhvHqOoQUyEKWUvyghw==} + '@hono/node-ws@1.2.0': + resolution: {integrity: sha512-OBPQ8OSHBw29mj00wT/xGYtB6HY54j0fNSdVZ7gZM3TUeq0So11GXaWtFf1xWxQNfumKIsj0wRuLKWfVsO5GgQ==} engines: {node: '>=18.14.1'} peerDependencies: '@hono/node-server': ^1.11.1 hono: ^4.6.0 - '@hono/vite-dev-server@0.19.0': - resolution: {integrity: sha512-myMc4Nm0nFQSPaeE6I/a1ODyDR5KpQ4EHodX4Tu/7qlB31GfUemhUH/WsO91HJjDEpRRpsT4Zbg+PleMlpTljw==} + '@hono/vite-dev-server@0.20.1': + resolution: {integrity: sha512-wXikV0C5tPqwH6udA68VTsmnFKWGhIeBuhuxgdGDQIbVNbv5gD0vqKLhx4nG1o4dhksaWPiiSqZV58sTKUzNxA==} engines: {node: '>=18.14.1'} peerDependencies: hono: '*' @@ -861,6 +881,9 @@ packages: resolution: {integrity: sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==} engines: {node: '>=18.0.0'} + '@jridgewell/gen-mapping@0.3.13': + resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==} + '@jridgewell/gen-mapping@0.3.8': resolution: {integrity: sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==} engines: {node: '>=6.0.0'} @@ -873,8 +896,8 @@ packages: resolution: {integrity: sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==} engines: {node: '>=6.0.0'} - '@jridgewell/source-map@0.3.6': - resolution: {integrity: sha512-1ZJTZebgqllO79ue2bm3rIGud/bOe0pP5BjSRCRxxYkEZS8STV7zN84UBbiYu7jy+eCKSnVIUgoWWE/tt+shMQ==} + '@jridgewell/source-map@0.3.11': + resolution: {integrity: sha512-ZMp1V8ZFcPG5dIWnQLr3NSI1MiCU7UETdS/A0G8V/XWHvJv3ZsFqutJn1Y5RPmAPX6F3BiE397OqveU/9NCuIA==} '@jridgewell/sourcemap-codec@1.4.15': resolution: {integrity: sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==} @@ -882,9 +905,15 @@ packages: '@jridgewell/sourcemap-codec@1.5.0': resolution: {integrity: sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==} + '@jridgewell/sourcemap-codec@1.5.5': + resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==} + '@jridgewell/trace-mapping@0.3.25': resolution: {integrity: sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==} + '@jridgewell/trace-mapping@0.3.30': + resolution: {integrity: sha512-GQ7Nw5G2lTu/BtHTKfXhKHok2WGetd4XYcVKGx00SjAk8GMwgJM3zr6zORiPGuOE+/vkc90KtTosSSvaCjKb2Q==} + '@jsep-plugin/assignment@1.3.0': resolution: {integrity: sha512-VVgV+CXrhbMI3aSusQyclHkenWSAm95WaiKrMxRFam3JSUiIaQjoMIw2sEs/OX4XifnqeQUN4DYbJjlA8EfktQ==} engines: {node: '>= 10.16.0'} @@ -976,6 +1005,9 @@ packages: '@mjackson/node-fetch-server@0.2.0': resolution: {integrity: sha512-EMlH1e30yzmTpGLQjlFmaDAjyOeZhng1/XCd7DExR8PNAnG/G1tyruZxEoUe11ClnwGhGrtsdnyyUx1frSzjng==} + '@mjackson/node-fetch-server@0.7.0': + resolution: {integrity: sha512-un8diyEBKU3BTVj3GzlTPA1kIjCkGdD+AMYQy31Gf9JCkfoZzwgJ79GUtHrF2BN3XPNMLpubbzPcxys+a3uZEw==} + '@neon-rs/load@0.0.4': resolution: {integrity: sha512-kTPhdZyTQxB+2wpiRcFWrDcejc4JI6tkPuS7UZCG4l6Zvc5kU/gGQ/ozvHTh1XR5tS+UlfAfGuPajjzQjCiHCw==} @@ -1288,15 +1320,15 @@ packages: react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - '@react-router/dev@7.6.1': - resolution: {integrity: sha512-E4pzxViSQ1Z4EPUz1p47ldm+qIbzfFbJtbXvxi+KSidpftf/ttjr+DtLEiTEdIqZTYv8trBASRtV6C5hn9GZQQ==} + '@react-router/dev@7.8.1': + resolution: {integrity: sha512-ESFe7DbMvCvl7e8N7L9NmI64VJGNCc60/VX1DUZYw/jFfzA5098/6D1aUojcxyVYBbMbVTfw0xmEvD4CsJzy1Q==} engines: {node: '>=20.0.0'} hasBin: true peerDependencies: - '@react-router/serve': ^7.6.1 - react-router: ^7.6.1 + '@react-router/serve': ^7.8.1 + react-router: ^7.8.1 typescript: ^5.1.0 - vite: ^5.1.0 || ^6.0.0 + vite: ^5.1.0 || ^6.0.0 || ^7.0.0 wrangler: ^3.28.2 || ^4.0.0 peerDependenciesMeta: '@react-router/serve': @@ -1306,11 +1338,11 @@ packages: wrangler: optional: true - '@react-router/node@7.6.1': - resolution: {integrity: sha512-RZ9IatEarjF1GSHV+OUHNaRKtfp27UXP2J8dVdax6K/UXHc45k3t9Zp6splqT88wob4CUt4loDQw/7srNvsQhQ==} + '@react-router/node@7.8.1': + resolution: {integrity: sha512-NC8eVQir2CRdcokzyyBsfxdq85Yu8B5XynDt581CzjBOreHAFfqIsNjGnqmg+aqBLiknQb2De9fH/TjyeYNeqw==} engines: {node: '>=20.0.0'} peerDependencies: - react-router: 7.6.1 + react-router: 7.8.1 typescript: ^5.1.0 peerDependenciesMeta: typescript: @@ -1956,8 +1988,11 @@ packages: '@types/node@22.15.24': resolution: {integrity: sha512-w9CZGm9RDjzTh/D+hFwlBJ3ziUaVw7oufKA3vOFSOZlzmW9AkZnfjPb+DLnrV6qtgL/LNmP0/2zBNCFHL3F0ng==} - '@types/react-dom@19.1.5': - resolution: {integrity: sha512-CMCjrWucUBZvohgZxkjd6S9h0nZxXjzus6yDfUb+xLxYM7VvjKNH1tQrE9GWLql1XoOP4/Ds3bwFqShHUYraGg==} + '@types/node@22.17.2': + resolution: {integrity: sha512-gL6z5N9Jm9mhY+U2KXZpteb+09zyffliRkZyZOHODGATyC5B1Jt/7TzuuiLkFsSUMLbS1OLmlj/E+/3KF4Q/4w==} + + '@types/react-dom@19.1.7': + resolution: {integrity: sha512-i5ZzwYpqjmrKenzkoLM2Ibzt6mAsM7pxB6BCIouEVVmgiqaMj1TjaK7hnA36hbW5aZv20kx7Lw6hWzPWg0Rurw==} peerDependencies: '@types/react': ^19.0.0 @@ -1966,8 +2001,8 @@ packages: peerDependencies: '@types/react': '*' - '@types/react@19.1.6': - resolution: {integrity: sha512-JeG0rEWak0N6Itr6QUx+X60uQmN+5t3j9r/OVDtWzFXKaj6kD1BwJzOksD0FF6iWxZlbE1kB0q9vtnU2ekqa1Q==} + '@types/react@19.1.10': + resolution: {integrity: sha512-EhBeSYX0Y6ye8pNebpKrwFJq7BoQ8J5SO6NlvNwwHjSj6adXJViPQrKlsyPw7hLBLvckEMO1yxeGdR82YBBlDg==} '@types/stream-buffers@3.0.7': resolution: {integrity: sha512-azOCy05sXVXrO+qklf0c/B07H/oHaIuDDAiHPVwlk3A9Ek+ksHyTeMajLZl3r76FxpPpxem//4Te61G1iW3Giw==} @@ -2022,6 +2057,13 @@ packages: '@ungap/structured-clone@1.3.0': resolution: {integrity: sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==} + '@vitejs/plugin-rsc@0.4.11': + resolution: {integrity: sha512-+4H4wLi+Y9yF58znBfKgGfX8zcqUGt8ngnmNgzrdGdF1SVz7EO0sg7WnhK5fFVHt6fUxsVEjmEabsCWHKPL1Tw==} + peerDependencies: + react: '*' + react-dom: '*' + vite: '*' + '@vitejs/plugin-vue@6.0.1': resolution: {integrity: sha512-+MaE752hU0wfPFJEUAIxqw18+20euHHdxVtMvbFcOEpjEyfqXH/5DCoTHiVJ0J29EhTJdoTkjEv5YBKU9dnoTw==} engines: {node: ^20.19.0 || >=22.12.0} @@ -2184,6 +2226,11 @@ packages: engines: {node: '>=0.4.0'} hasBin: true + acorn@8.15.0: + resolution: {integrity: sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==} + engines: {node: '>=0.4.0'} + hasBin: true + agent-base@7.1.3: resolution: {integrity: sha512-jRR5wdylq8CkOe6hei19GGZnxM6rBGwFl3Bg0YItGDimvjGtAvdZk4Pu6Cl4u4Igsws4a1fd1Vq3ezrhn4KmFw==} engines: {node: '>= 14'} @@ -2564,6 +2611,9 @@ packages: end-of-stream@1.4.4: resolution: {integrity: sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==} + end-of-stream@1.4.5: + resolution: {integrity: sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==} + enhanced-resolve@5.18.1: resolution: {integrity: sha512-ZSW3ma5GkcQBIpwZTSRAI8N71Uuwgs93IezB7mf7R60tC8ZbJideoDNKjHn2O9KIlx6rkGTTEk1xUCK2E1Y2Yg==} engines: {node: '>=10.13.0'} @@ -2676,10 +2726,6 @@ packages: fs-constants@1.0.0: resolution: {integrity: sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==} - fs-extra@10.1.0: - resolution: {integrity: sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==} - engines: {node: '>=12'} - fsevents@2.3.2: resolution: {integrity: sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==} engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} @@ -2789,6 +2835,9 @@ packages: resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} engines: {node: '>=8'} + is-reference@3.0.3: + resolution: {integrity: sha512-ixkJoqQvAP88E6wLydLGGqCJsrFUnqoH6HnaczB8XmDH1oaWU+xxdptvikTgaEhtZ53Ky6YXiBuUI2WXLMCwjw==} + is-what@4.1.16: resolution: {integrity: sha512-ZhMwEosbFJkA0YhFnNDgTM4ZxDRsS6HqTo7qsZM08fehyRYIYa0yHu5R6mgo1n/8MgaPBXiPimPD77baVFYg+A==} engines: {node: '>=12.13'} @@ -2849,9 +2898,6 @@ packages: engines: {node: '>=6'} hasBin: true - jsonfile@6.1.0: - resolution: {integrity: sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==} - jsonpath-plus@10.3.0: resolution: {integrity: sha512-8TNmfeTCk2Le33A3vRRwtuworG/L5RrgMvdjhKZxvyShO+mBu2fP50OWUjRLNtvw344DdDarFh9buFAZs5ujeA==} engines: {node: '>=18.0.0'} @@ -3185,6 +3231,9 @@ packages: perfect-debounce@1.0.0: resolution: {integrity: sha512-xCy9V055GLEqoFaHoC1SoLIaLmWctgCUaBaWxDZ7/Zx4CTyX7cJQLJOok/orfjZAh9kEYpjJa4d0KcJmCbctZA==} + periscopic@4.0.2: + resolution: {integrity: sha512-sqpQDUy8vgB7ycLkendSKS6HnVz1Rneoc3Rc+ZBUCe2pbqlVuCC5vF52l0NJ1aiMg/r1qfYF9/myz8CZeI2rjA==} + picocolors@1.1.1: resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} @@ -3222,9 +3271,9 @@ packages: engines: {node: '>=10'} hasBin: true - prettier@2.8.8: - resolution: {integrity: sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==} - engines: {node: '>=10.13.0'} + prettier@3.6.2: + resolution: {integrity: sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ==} + engines: {node: '>=14'} hasBin: true proc-log@3.0.0: @@ -3252,6 +3301,9 @@ packages: pump@3.0.2: resolution: {integrity: sha512-tUPXtzlGM8FE3P0ZL6DVs/3P58k9nk8/jZeQCurTJylQA8qFYzHFfhBJkuqyE0FifOsQ0uKWekiZ5g8wtr28cw==} + pump@3.0.3: + resolution: {integrity: sha512-todwxLMY7/heScKmntwQG8CXVkWUOdYxIvY2s0VWAAMh/nd8SoYiRaKjlr7+iCs984f2P8zvrfWcDDYVb73NfA==} + rc@1.2.8: resolution: {integrity: sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==} hasBin: true @@ -3273,10 +3325,10 @@ packages: react: '>=16.8.0' react-dom: '>=16.8.0' - react-dom@19.1.0: - resolution: {integrity: sha512-Xs1hdnE+DyKgeHJeJznQmYMIBG3TKIHJJT95Q58nHLSrElKlGQqDTR2HQ9fx5CN/Gk6Vh/kupBTDLU11/nDk/g==} + react-dom@19.1.1: + resolution: {integrity: sha512-Dlq/5LAZgF0Gaz6yiqZCf6VCcZs1ghAJyrsu84Q/GT0gV+mCxbfmKNoGRKBYMJ8IEdGPqu49YWXD02GCknEDkw==} peerDependencies: - react: ^19.1.0 + react: ^19.1.1 react-error-boundary@6.0.0: resolution: {integrity: sha512-gdlJjD7NWr0IfkPlaREN2d9uUZUlksrfOx7SX62VRerwXbMY6ftGCIZua1VG1aXFNOimhISsTq+Owp725b9SiA==} @@ -3292,24 +3344,25 @@ packages: resolution: {integrity: sha512-jCvmsr+1IUSMUyzOkRcvnVbX3ZYC6g9TDrDbFuFmRDq7PD4yaGbLKNQL6k2jnArV8hjYxh7hVhAZB6s9HDGpZA==} engines: {node: '>=0.10.0'} - react-router-dom@7.6.1: - resolution: {integrity: sha512-vxU7ei//UfPYQ3iZvHuO1D/5fX3/JOqhNTbRR+WjSBWxf9bIvpWK+ftjmdfJHzPOuMQKe2fiEdG+dZX6E8uUpA==} + react-router-dom@7.8.1: + resolution: {integrity: sha512-NkgBCF3sVgCiAWIlSt89GR2PLaksMpoo3HDCorpRfnCEfdtRPLiuTf+CNXvqZMI5SJLZCLpVCvcZrTdtGW64xQ==} engines: {node: '>=20.0.0'} peerDependencies: react: '>=18' react-dom: '>=18' - react-router-hono-server@2.13.0: - resolution: {integrity: sha512-YcxmFpphZL9Jc4CnOgsKw35wcurmiOpTg3vBzjOROIUhEo6rKvUHOg7AM2QJi2Fa8BRJK6MvHaN4haedYo1iiA==} + react-router-hono-server@2.21.0: + resolution: {integrity: sha512-Sq5W6Kv8ttNUScgE+zHPbJEGw4ZXdlmuGpkpeskqi0yv263joDwESIKYU47WMmz5CTpFoSL/QyQ/TzHn3n1Edw==} engines: {node: '>=22.12.0'} hasBin: true peerDependencies: '@cloudflare/workers-types': ^4.20250317.0 - '@react-router/dev': ^7.2.0 + '@hono/node-server': ^1.18.1 + '@react-router/dev': ^7.8.0 '@types/react': ^18.3.10 || ^19.0.0 miniflare: ^3.20241205.0 react-router: ^7.2.0 - vite: ^6.0.0 + vite: ^6.0.0 || ^7.0.0 wrangler: ^4.2.0 peerDependenciesMeta: '@cloudflare/workers-types': @@ -3319,8 +3372,8 @@ packages: wrangler: optional: true - react-router@7.6.1: - resolution: {integrity: sha512-hPJXXxHJZEsPFNVbtATH7+MMX43UDeOauz+EAU4cgqTn7ojdI9qQORqS8Z0qmDlL1TclO/6jLRYUEtbWidtdHQ==} + react-router@7.8.1: + resolution: {integrity: sha512-5cy/M8DHcG51/KUIka1nfZ2QeylS4PJRs6TT8I4PF5axVsI5JUxp0hC0NZ/AEEj8Vw7xsEoD7L/6FY+zoYaOGA==} engines: {node: '>=20.0.0'} peerDependencies: react: '>=18' @@ -3354,8 +3407,8 @@ packages: peerDependencies: react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - react@19.1.0: - resolution: {integrity: sha512-FS+XFBNvn3GTAWq26joslQgWNoFu08F4kl0J4CgdNKADkdSGXQyTCnKteIAJy96Br6YbpEU1LSzV5dYtjMkMDg==} + react@19.1.1: + resolution: {integrity: sha512-w8nqGImo45dmMIfljjMwOGtbmC/mk4CMYhWIicdSflH91J9TyCyczcPFXJzrZ/ZXcgGRFeP6BU0BEJTw6tZdfQ==} engines: {node: '>=0.10.0'} readable-stream@3.6.2: @@ -3375,8 +3428,8 @@ packages: regex@6.0.1: resolution: {integrity: sha512-uorlqlzAKjKQZ5P+kTJr3eeJGSVroLKoHmquUj4zHWuR+hEyNqlXsSKlYYF5F4NI6nl7tWCs0apKJ0lmfsXAPA==} - remix-utils@8.7.0: - resolution: {integrity: sha512-oRumofsYFaxHyPtqLuYe3g2nQi4SMYjCoebaeed0gYHIOKBiPPYdNP6cgmQbFjQQ5pwXV+uQiKLqO6pM9ep3VA==} + remix-utils@8.8.0: + resolution: {integrity: sha512-5CR4a3YwPaCoPUHg+O69UMek05tnFMrtQsoXTU4KzsyU7heDO94IFTjKVzEFhi+s+SSo+ebRMxWvjf/QiPpqiQ==} engines: {node: '>=20.0.0'} peerDependencies: '@edgefirst-dev/batcher': ^1.0.0 @@ -3384,6 +3437,7 @@ packages: '@edgefirst-dev/server-timing': ^0.0.1 '@oslojs/crypto': ^1.0.1 '@oslojs/encoding': ^1.1.0 + '@standard-schema/spec': ^1.0.0 intl-parse-accept-language: ^1.0.0 is-ip: ^5.0.1 react: ^18.0.0 || ^19.0.0 @@ -3400,6 +3454,8 @@ packages: optional: true '@oslojs/encoding': optional: true + '@standard-schema/spec': + optional: true intl-parse-accept-language: optional: true is-ip: @@ -3518,8 +3574,8 @@ packages: spdx-expression-parse@3.0.1: resolution: {integrity: sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==} - spdx-license-ids@3.0.21: - resolution: {integrity: sha512-Bvg/8F5XephndSK3JffaRqdT+gyhfqIPwDHpX80tJrF8QQRYMo8sNMeaZ2Dp5+jhwKnUmIOyFFQfHRkjJm5nXg==} + spdx-license-ids@3.0.22: + resolution: {integrity: sha512-4PRT4nh1EImPbt2jASOKHX7PB7I+e4IWNLvkKFDxNhJlfjbYlleYQh285Z/3mPTHSAK/AvdMmw5BNNuYH8ShgQ==} speakingurl@14.0.1: resolution: {integrity: sha512-1POYv7uv2gXoyGFpBCmpDVSNV74IfsWlDW216UPjbWufNf+bSU6GdbDsxdcxtfwb4xlI3yxzOTKClUosxARYrQ==} @@ -3538,9 +3594,6 @@ packages: resolution: {integrity: sha512-pqMqwQCso0PBJt2PQmDO0cFj0lyqmiwOMiMSkVtRokl7e+ZTRYgDHKnuZNbqjiJXgsg4nuqtD/zxuo9KqTp0Yw==} engines: {node: '>= 0.10.0'} - stream-slice@0.1.2: - resolution: {integrity: sha512-QzQxpoacatkreL6jsxnVb7X5R/pGw9OUv2qWTYWnmLpg4NdN31snPy/f3TdQE1ZUXaThRvj1Zw4/OGg0ZkaLMA==} - streamx@2.22.0: resolution: {integrity: sha512-sLh1evHOzBy/iWRiR6d1zRcLao4gGZr3C1kzNz4fopCOKJb6xD9ub8Mpi9Mr1R6id5o43S+d93fI48UC5uM9aw==} @@ -3680,6 +3733,9 @@ packages: tunnel-agent@0.6.0: resolution: {integrity: sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==} + turbo-stream@3.1.0: + resolution: {integrity: sha512-tVI25WEXl4fckNEmrq70xU1XumxUwEx/FZD5AgEcV8ri7Wvrg2o7GEq8U7htrNx3CajciGm+kDyhRf5JB6t7/A==} + type-fest@4.41.0: resolution: {integrity: sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==} engines: {node: '>=16'} @@ -3695,10 +3751,6 @@ packages: undici-types@6.21.0: resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==} - undici@6.21.3: - resolution: {integrity: sha512-gBLkYIlEnSp8pFbT64yFgGE6UIB9tAkhukC23PmMDCe5Nd+cRqKxSjw5y54MK2AZMgZfJWMaNE4nYUHgi1XEOw==} - engines: {node: '>=18.17'} - undici@7.10.0: resolution: {integrity: sha512-u5otvFBOBZvmdjWLVW+5DAc9Nkq8f24g0O9oY7qw2JVIF1VocIFoyz9JFkuVOS2j41AufeO0xnlweJ2RLT8nGw==} engines: {node: '>=20.18.1'} @@ -3718,10 +3770,6 @@ packages: unist-util-visit@5.0.0: resolution: {integrity: sha512-MR04uvD+07cwl/yhVuVWAtw+3GOR/knlL55Nd/wAdblk27GCVt3lqpTivy/tkJcZoNPzTwS1Y+KMojlLDhoTzg==} - universalify@2.0.1: - resolution: {integrity: sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==} - engines: {node: '>= 10.0.0'} - unplugin@2.1.0: resolution: {integrity: sha512-us4j03/499KhbGP8BU7Hrzrgseo+KdfJYWcbcajCOqsAyb8Gk0Yn2kiUIcZISYCb1JFaZfIuG3b42HmguVOKCQ==} engines: {node: '>=18.12.0'} @@ -3771,11 +3819,6 @@ packages: vfile@6.0.3: resolution: {integrity: sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==} - vite-node@3.0.0-beta.2: - resolution: {integrity: sha512-ofTf6cfRdL30Wbl9n/BX81EyIR5s4PReLmSurrxQ+koLaWUNOEo8E0lCM53OJkb8vpa2URM2nSrxZsIFyvY1rg==} - engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} - hasBin: true - vite-node@3.2.4: resolution: {integrity: sha512-EbKSKh+bh1E1IFxeO0pg1n4dvoOTt0UDiXMd/qn++r98+jPO1xtJilvXldeuQ8giIB5IkpjCgMleHMNEsGH6pg==} engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} @@ -3869,6 +3912,14 @@ packages: yaml: optional: true + vitefu@1.1.1: + resolution: {integrity: sha512-B/Fegf3i8zh0yFbpzZ21amWzHmuNlLlmJT6n7bu5e+pCHUKQIfXSYokrqOBGEMMe9UG2sostKQF9mml/vYaWJQ==} + peerDependencies: + vite: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0-beta.0 + peerDependenciesMeta: + vite: + optional: true + vitepress@2.0.0-alpha.11: resolution: {integrity: sha512-l3FFkGtcB3u3iMlpnvkCR+MdOYqNaz2z+xPRlgZZnx8Xne4XLgQR0yfEfTqY/UyloTymXwxvRvu443Yo9Cr8pA==} hasBin: true @@ -3962,18 +4013,6 @@ packages: wrappy@1.0.2: resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} - ws@8.18.1: - resolution: {integrity: sha512-RKW2aJZMXeMxVpnZ6bck+RswznaxmzdULiBr6KY7XkTnW8uvt0iT9H5DkHUChXrc+uurzwa0rVI16n/Xzjdz1w==} - engines: {node: '>=10.0.0'} - peerDependencies: - bufferutil: ^4.0.1 - utf-8-validate: '>=5.0.2' - peerDependenciesMeta: - bufferutil: - optional: true - utf-8-validate: - optional: true - ws@8.18.2: resolution: {integrity: sha512-DMricUmwGZUVr++AEAe2uiVM7UoO9MAVZMDu05UQOaUII0lp+zOzLLU4Xqh/JvTqklB1T4uELaaPBKyjE1r4fQ==} engines: {node: '>=10.0.0'} @@ -3998,6 +4037,9 @@ packages: engines: {node: '>= 14.6'} hasBin: true + zimmerframe@1.1.2: + resolution: {integrity: sha512-rAbqEGa8ovJy4pyBxZM70hg4pE6gDgaQ0Sl9M3enG3I0d6H4XSAM3GeNGLKnsBpuijUow064sf7ww1nutC5/3w==} + zod@3.25.33: resolution: {integrity: sha512-RnBGYCwJFrLi/hUmeqbYjSIrS/strWjN5PHWgUfyVq96nKycSa4gp4+p6hQGwvcabZs0DWRGzyLhEeQWl+5NhA==} @@ -4045,6 +4087,26 @@ snapshots: transitivePeerDependencies: - supports-color + '@babel/core@7.28.3': + dependencies: + '@ampproject/remapping': 2.3.0 + '@babel/code-frame': 7.27.1 + '@babel/generator': 7.28.3 + '@babel/helper-compilation-targets': 7.27.2 + '@babel/helper-module-transforms': 7.28.3(@babel/core@7.28.3) + '@babel/helpers': 7.28.3 + '@babel/parser': 7.28.3 + '@babel/template': 7.27.2 + '@babel/traverse': 7.28.3 + '@babel/types': 7.28.2 + convert-source-map: 2.0.0 + debug: 4.4.1 + gensync: 1.0.0-beta.2 + json5: 2.2.3 + semver: 6.3.1 + transitivePeerDependencies: + - supports-color + '@babel/generator@7.27.3': dependencies: '@babel/parser': 7.27.3 @@ -4053,9 +4115,17 @@ snapshots: '@jridgewell/trace-mapping': 0.3.25 jsesc: 3.0.2 + '@babel/generator@7.28.3': + dependencies: + '@babel/parser': 7.28.3 + '@babel/types': 7.28.2 + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.30 + jsesc: 3.0.2 + '@babel/helper-annotate-as-pure@7.27.3': dependencies: - '@babel/types': 7.27.3 + '@babel/types': 7.28.2 '@babel/helper-compilation-targets@7.27.2': dependencies: @@ -4065,23 +4135,25 @@ snapshots: lru-cache: 5.1.1 semver: 6.3.1 - '@babel/helper-create-class-features-plugin@7.27.1(@babel/core@7.27.3)': + '@babel/helper-create-class-features-plugin@7.28.3(@babel/core@7.28.3)': dependencies: - '@babel/core': 7.27.3 + '@babel/core': 7.28.3 '@babel/helper-annotate-as-pure': 7.27.3 '@babel/helper-member-expression-to-functions': 7.27.1 '@babel/helper-optimise-call-expression': 7.27.1 - '@babel/helper-replace-supers': 7.27.1(@babel/core@7.27.3) + '@babel/helper-replace-supers': 7.27.1(@babel/core@7.28.3) '@babel/helper-skip-transparent-expression-wrappers': 7.27.1 - '@babel/traverse': 7.27.3 + '@babel/traverse': 7.28.3 semver: 6.3.1 transitivePeerDependencies: - supports-color + '@babel/helper-globals@7.28.0': {} + '@babel/helper-member-expression-to-functions@7.27.1': dependencies: - '@babel/traverse': 7.27.3 - '@babel/types': 7.27.3 + '@babel/traverse': 7.28.3 + '@babel/types': 7.28.2 transitivePeerDependencies: - supports-color @@ -4101,25 +4173,34 @@ snapshots: transitivePeerDependencies: - supports-color + '@babel/helper-module-transforms@7.28.3(@babel/core@7.28.3)': + dependencies: + '@babel/core': 7.28.3 + '@babel/helper-module-imports': 7.27.1 + '@babel/helper-validator-identifier': 7.27.1 + '@babel/traverse': 7.28.3 + transitivePeerDependencies: + - supports-color + '@babel/helper-optimise-call-expression@7.27.1': dependencies: - '@babel/types': 7.27.3 + '@babel/types': 7.28.2 '@babel/helper-plugin-utils@7.27.1': {} - '@babel/helper-replace-supers@7.27.1(@babel/core@7.27.3)': + '@babel/helper-replace-supers@7.27.1(@babel/core@7.28.3)': dependencies: - '@babel/core': 7.27.3 + '@babel/core': 7.28.3 '@babel/helper-member-expression-to-functions': 7.27.1 '@babel/helper-optimise-call-expression': 7.27.1 - '@babel/traverse': 7.27.3 + '@babel/traverse': 7.28.3 transitivePeerDependencies: - supports-color '@babel/helper-skip-transparent-expression-wrappers@7.27.1': dependencies: - '@babel/traverse': 7.27.3 - '@babel/types': 7.27.3 + '@babel/traverse': 7.28.3 + '@babel/types': 7.28.2 transitivePeerDependencies: - supports-color @@ -4134,6 +4215,11 @@ snapshots: '@babel/template': 7.27.2 '@babel/types': 7.27.3 + '@babel/helpers@7.28.3': + dependencies: + '@babel/template': 7.27.2 + '@babel/types': 7.28.2 + '@babel/parser@7.27.3': dependencies: '@babel/types': 7.27.3 @@ -4142,48 +4228,47 @@ snapshots: dependencies: '@babel/types': 7.28.2 - '@babel/plugin-syntax-decorators@7.27.1(@babel/core@7.27.3)': + '@babel/parser@7.28.3': dependencies: - '@babel/core': 7.27.3 + '@babel/types': 7.28.2 + + '@babel/plugin-syntax-jsx@7.27.1(@babel/core@7.28.3)': + dependencies: + '@babel/core': 7.28.3 '@babel/helper-plugin-utils': 7.27.1 - '@babel/plugin-syntax-jsx@7.27.1(@babel/core@7.27.3)': + '@babel/plugin-syntax-typescript@7.27.1(@babel/core@7.28.3)': dependencies: - '@babel/core': 7.27.3 + '@babel/core': 7.28.3 '@babel/helper-plugin-utils': 7.27.1 - '@babel/plugin-syntax-typescript@7.27.1(@babel/core@7.27.3)': + '@babel/plugin-transform-modules-commonjs@7.27.1(@babel/core@7.28.3)': dependencies: - '@babel/core': 7.27.3 - '@babel/helper-plugin-utils': 7.27.1 - - '@babel/plugin-transform-modules-commonjs@7.27.1(@babel/core@7.27.3)': - dependencies: - '@babel/core': 7.27.3 - '@babel/helper-module-transforms': 7.27.3(@babel/core@7.27.3) + '@babel/core': 7.28.3 + '@babel/helper-module-transforms': 7.28.3(@babel/core@7.28.3) '@babel/helper-plugin-utils': 7.27.1 transitivePeerDependencies: - supports-color - '@babel/plugin-transform-typescript@7.27.1(@babel/core@7.27.3)': + '@babel/plugin-transform-typescript@7.28.0(@babel/core@7.28.3)': dependencies: - '@babel/core': 7.27.3 + '@babel/core': 7.28.3 '@babel/helper-annotate-as-pure': 7.27.3 - '@babel/helper-create-class-features-plugin': 7.27.1(@babel/core@7.27.3) + '@babel/helper-create-class-features-plugin': 7.28.3(@babel/core@7.28.3) '@babel/helper-plugin-utils': 7.27.1 '@babel/helper-skip-transparent-expression-wrappers': 7.27.1 - '@babel/plugin-syntax-typescript': 7.27.1(@babel/core@7.27.3) + '@babel/plugin-syntax-typescript': 7.27.1(@babel/core@7.28.3) transitivePeerDependencies: - supports-color - '@babel/preset-typescript@7.27.1(@babel/core@7.27.3)': + '@babel/preset-typescript@7.27.1(@babel/core@7.28.3)': dependencies: - '@babel/core': 7.27.3 + '@babel/core': 7.28.3 '@babel/helper-plugin-utils': 7.27.1 '@babel/helper-validator-option': 7.27.1 - '@babel/plugin-syntax-jsx': 7.27.1(@babel/core@7.27.3) - '@babel/plugin-transform-modules-commonjs': 7.27.1(@babel/core@7.27.3) - '@babel/plugin-transform-typescript': 7.27.1(@babel/core@7.27.3) + '@babel/plugin-syntax-jsx': 7.27.1(@babel/core@7.28.3) + '@babel/plugin-transform-modules-commonjs': 7.27.1(@babel/core@7.28.3) + '@babel/plugin-transform-typescript': 7.28.0(@babel/core@7.28.3) transitivePeerDependencies: - supports-color @@ -4207,6 +4292,18 @@ snapshots: transitivePeerDependencies: - supports-color + '@babel/traverse@7.28.3': + dependencies: + '@babel/code-frame': 7.27.1 + '@babel/generator': 7.28.3 + '@babel/helper-globals': 7.28.0 + '@babel/parser': 7.28.3 + '@babel/template': 7.27.2 + '@babel/types': 7.28.2 + debug: 4.4.1 + transitivePeerDependencies: + - supports-color + '@babel/types@7.27.3': dependencies: '@babel/helper-string-parser': 7.27.1 @@ -4323,36 +4420,36 @@ snapshots: style-mod: 4.1.2 w3c-keyname: 2.2.8 - '@dnd-kit/accessibility@3.1.1(react@19.1.0)': + '@dnd-kit/accessibility@3.1.1(react@19.1.1)': dependencies: - react: 19.1.0 + react: 19.1.1 tslib: 2.8.1 - '@dnd-kit/core@6.3.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': + '@dnd-kit/core@6.3.1(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': dependencies: - '@dnd-kit/accessibility': 3.1.1(react@19.1.0) - '@dnd-kit/utilities': 3.2.2(react@19.1.0) - react: 19.1.0 - react-dom: 19.1.0(react@19.1.0) + '@dnd-kit/accessibility': 3.1.1(react@19.1.1) + '@dnd-kit/utilities': 3.2.2(react@19.1.1) + react: 19.1.1 + react-dom: 19.1.1(react@19.1.1) tslib: 2.8.1 - '@dnd-kit/modifiers@7.0.0(@dnd-kit/core@6.3.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react@19.1.0)': + '@dnd-kit/modifiers@7.0.0(@dnd-kit/core@6.3.1(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(react@19.1.1)': dependencies: - '@dnd-kit/core': 6.3.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - '@dnd-kit/utilities': 3.2.2(react@19.1.0) - react: 19.1.0 + '@dnd-kit/core': 6.3.1(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@dnd-kit/utilities': 3.2.2(react@19.1.1) + react: 19.1.1 tslib: 2.8.1 - '@dnd-kit/sortable@8.0.0(@dnd-kit/core@6.3.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react@19.1.0)': + '@dnd-kit/sortable@8.0.0(@dnd-kit/core@6.3.1(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(react@19.1.1)': dependencies: - '@dnd-kit/core': 6.3.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - '@dnd-kit/utilities': 3.2.2(react@19.1.0) - react: 19.1.0 + '@dnd-kit/core': 6.3.1(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@dnd-kit/utilities': 3.2.2(react@19.1.1) + react: 19.1.1 tslib: 2.8.1 - '@dnd-kit/utilities@3.2.2(react@19.1.0)': + '@dnd-kit/utilities@3.2.2(react@19.1.1)': dependencies: - react: 19.1.0 + react: 19.1.1 tslib: 2.6.2 '@docsearch/css@4.0.0-beta.7': {} @@ -4544,22 +4641,22 @@ snapshots: dependencies: tslib: 2.8.1 - '@hono/node-server@1.14.0(hono@4.7.10)': + '@hono/node-server@1.19.0(hono@4.7.10)': dependencies: hono: 4.7.10 - '@hono/node-ws@1.1.1(@hono/node-server@1.14.0(hono@4.7.10))(bufferutil@4.0.9)(hono@4.7.10)(utf-8-validate@5.0.10)': + '@hono/node-ws@1.2.0(@hono/node-server@1.19.0(hono@4.7.10))(bufferutil@4.0.9)(hono@4.7.10)(utf-8-validate@5.0.10)': dependencies: - '@hono/node-server': 1.14.0(hono@4.7.10) + '@hono/node-server': 1.19.0(hono@4.7.10) hono: 4.7.10 - ws: 8.18.1(bufferutil@4.0.9)(utf-8-validate@5.0.10) + ws: 8.18.2(bufferutil@4.0.9)(utf-8-validate@5.0.10) transitivePeerDependencies: - bufferutil - utf-8-validate - '@hono/vite-dev-server@0.19.0(hono@4.7.10)': + '@hono/vite-dev-server@0.20.1(hono@4.7.10)': dependencies: - '@hono/node-server': 1.14.0(hono@4.7.10) + '@hono/node-server': 1.19.0(hono@4.7.10) hono: 4.7.10 minimatch: 9.0.5 @@ -4599,6 +4696,11 @@ snapshots: dependencies: minipass: 7.1.2 + '@jridgewell/gen-mapping@0.3.13': + dependencies: + '@jridgewell/sourcemap-codec': 1.5.5 + '@jridgewell/trace-mapping': 0.3.30 + '@jridgewell/gen-mapping@0.3.8': dependencies: '@jridgewell/set-array': 1.2.1 @@ -4609,21 +4711,28 @@ snapshots: '@jridgewell/set-array@1.2.1': {} - '@jridgewell/source-map@0.3.6': + '@jridgewell/source-map@0.3.11': dependencies: - '@jridgewell/gen-mapping': 0.3.8 - '@jridgewell/trace-mapping': 0.3.25 + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.30 optional: true '@jridgewell/sourcemap-codec@1.4.15': {} '@jridgewell/sourcemap-codec@1.5.0': {} + '@jridgewell/sourcemap-codec@1.5.5': {} + '@jridgewell/trace-mapping@0.3.25': dependencies: '@jridgewell/resolve-uri': 3.1.2 '@jridgewell/sourcemap-codec': 1.4.15 + '@jridgewell/trace-mapping@0.3.30': + dependencies: + '@jridgewell/resolve-uri': 3.1.2 + '@jridgewell/sourcemap-codec': 1.5.5 + '@jsep-plugin/assignment@1.3.0(jsep@1.4.0)': dependencies: jsep: 1.4.0 @@ -4733,6 +4842,8 @@ snapshots: '@mjackson/node-fetch-server@0.2.0': {} + '@mjackson/node-fetch-server@0.7.0': {} + '@neon-rs/load@0.0.4': {} '@npmcli/git@4.1.0': @@ -4764,10 +4875,10 @@ snapshots: dependencies: which: 3.0.1 - '@pivanov/utils@0.0.2(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': + '@pivanov/utils@0.0.2(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': dependencies: - react: 19.1.0 - react-dom: 19.1.0(react@19.1.0) + react: 19.1.1 + react-dom: 19.1.1(react@19.1.1) '@pkgjs/parseargs@0.11.0': optional: true @@ -4779,632 +4890,633 @@ snapshots: '@preact/signals-core': 1.8.0 preact: 10.26.7 - '@primer/octicons-react@19.15.2(react@19.1.0)': + '@primer/octicons-react@19.15.2(react@19.1.1)': dependencies: - react: 19.1.0 + react: 19.1.1 - '@react-aria/breadcrumbs@3.5.24(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': + '@react-aria/breadcrumbs@3.5.24(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': dependencies: - '@react-aria/i18n': 3.12.9(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - '@react-aria/link': 3.8.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - '@react-aria/utils': 3.29.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - '@react-types/breadcrumbs': 3.7.13(react@19.1.0) - '@react-types/shared': 3.29.1(react@19.1.0) + '@react-aria/i18n': 3.12.9(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-aria/link': 3.8.1(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-aria/utils': 3.29.0(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-types/breadcrumbs': 3.7.13(react@19.1.1) + '@react-types/shared': 3.29.1(react@19.1.1) '@swc/helpers': 0.5.17 - react: 19.1.0 - react-dom: 19.1.0(react@19.1.0) + react: 19.1.1 + react-dom: 19.1.1(react@19.1.1) - '@react-aria/button@3.13.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': + '@react-aria/button@3.13.1(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': dependencies: - '@react-aria/interactions': 3.25.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - '@react-aria/toolbar': 3.0.0-beta.16(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - '@react-aria/utils': 3.29.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - '@react-stately/toggle': 3.8.4(react@19.1.0) - '@react-types/button': 3.12.1(react@19.1.0) - '@react-types/shared': 3.29.1(react@19.1.0) + '@react-aria/interactions': 3.25.1(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-aria/toolbar': 3.0.0-beta.16(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-aria/utils': 3.29.0(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-stately/toggle': 3.8.4(react@19.1.1) + '@react-types/button': 3.12.1(react@19.1.1) + '@react-types/shared': 3.29.1(react@19.1.1) '@swc/helpers': 0.5.17 - react: 19.1.0 - react-dom: 19.1.0(react@19.1.0) + react: 19.1.1 + react-dom: 19.1.1(react@19.1.1) - '@react-aria/calendar@3.8.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': + '@react-aria/calendar@3.8.1(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': dependencies: '@internationalized/date': 3.8.1 - '@react-aria/i18n': 3.12.9(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - '@react-aria/interactions': 3.25.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@react-aria/i18n': 3.12.9(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-aria/interactions': 3.25.1(react-dom@19.1.1(react@19.1.1))(react@19.1.1) '@react-aria/live-announcer': 3.4.2 - '@react-aria/utils': 3.29.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - '@react-stately/calendar': 3.8.1(react@19.1.0) - '@react-types/button': 3.12.1(react@19.1.0) - '@react-types/calendar': 3.7.1(react@19.1.0) - '@react-types/shared': 3.29.1(react@19.1.0) + '@react-aria/utils': 3.29.0(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-stately/calendar': 3.8.1(react@19.1.1) + '@react-types/button': 3.12.1(react@19.1.1) + '@react-types/calendar': 3.7.1(react@19.1.1) + '@react-types/shared': 3.29.1(react@19.1.1) '@swc/helpers': 0.5.17 - react: 19.1.0 - react-dom: 19.1.0(react@19.1.0) + react: 19.1.1 + react-dom: 19.1.1(react@19.1.1) - '@react-aria/checkbox@3.15.5(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': + '@react-aria/checkbox@3.15.5(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': dependencies: - '@react-aria/form': 3.0.16(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - '@react-aria/interactions': 3.25.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - '@react-aria/label': 3.7.18(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - '@react-aria/toggle': 3.11.3(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - '@react-aria/utils': 3.29.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - '@react-stately/checkbox': 3.6.14(react@19.1.0) - '@react-stately/form': 3.1.4(react@19.1.0) - '@react-stately/toggle': 3.8.4(react@19.1.0) - '@react-types/checkbox': 3.9.4(react@19.1.0) - '@react-types/shared': 3.29.1(react@19.1.0) + '@react-aria/form': 3.0.16(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-aria/interactions': 3.25.1(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-aria/label': 3.7.18(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-aria/toggle': 3.11.3(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-aria/utils': 3.29.0(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-stately/checkbox': 3.6.14(react@19.1.1) + '@react-stately/form': 3.1.4(react@19.1.1) + '@react-stately/toggle': 3.8.4(react@19.1.1) + '@react-types/checkbox': 3.9.4(react@19.1.1) + '@react-types/shared': 3.29.1(react@19.1.1) '@swc/helpers': 0.5.17 - react: 19.1.0 - react-dom: 19.1.0(react@19.1.0) + react: 19.1.1 + react-dom: 19.1.1(react@19.1.1) - '@react-aria/color@3.0.7(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': + '@react-aria/color@3.0.7(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': dependencies: - '@react-aria/i18n': 3.12.9(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - '@react-aria/interactions': 3.25.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - '@react-aria/numberfield': 3.11.14(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - '@react-aria/slider': 3.7.19(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - '@react-aria/spinbutton': 3.6.15(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - '@react-aria/textfield': 3.17.3(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - '@react-aria/utils': 3.29.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - '@react-aria/visually-hidden': 3.8.23(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - '@react-stately/color': 3.8.5(react@19.1.0) - '@react-stately/form': 3.1.4(react@19.1.0) - '@react-types/color': 3.0.5(react@19.1.0) - '@react-types/shared': 3.29.1(react@19.1.0) + '@react-aria/i18n': 3.12.9(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-aria/interactions': 3.25.1(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-aria/numberfield': 3.11.14(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-aria/slider': 3.7.19(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-aria/spinbutton': 3.6.15(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-aria/textfield': 3.17.3(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-aria/utils': 3.29.0(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-aria/visually-hidden': 3.8.23(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-stately/color': 3.8.5(react@19.1.1) + '@react-stately/form': 3.1.4(react@19.1.1) + '@react-types/color': 3.0.5(react@19.1.1) + '@react-types/shared': 3.29.1(react@19.1.1) '@swc/helpers': 0.5.17 - react: 19.1.0 - react-dom: 19.1.0(react@19.1.0) + react: 19.1.1 + react-dom: 19.1.1(react@19.1.1) - '@react-aria/combobox@3.12.3(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': + '@react-aria/combobox@3.12.3(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': dependencies: - '@react-aria/focus': 3.20.3(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - '@react-aria/i18n': 3.12.9(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - '@react-aria/listbox': 3.14.4(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@react-aria/focus': 3.20.3(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-aria/i18n': 3.12.9(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-aria/listbox': 3.14.4(react-dom@19.1.1(react@19.1.1))(react@19.1.1) '@react-aria/live-announcer': 3.4.2 - '@react-aria/menu': 3.18.3(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - '@react-aria/overlays': 3.27.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - '@react-aria/selection': 3.24.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - '@react-aria/textfield': 3.17.3(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - '@react-aria/utils': 3.29.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - '@react-stately/collections': 3.12.4(react@19.1.0) - '@react-stately/combobox': 3.10.5(react@19.1.0) - '@react-stately/form': 3.1.4(react@19.1.0) - '@react-types/button': 3.12.1(react@19.1.0) - '@react-types/combobox': 3.13.5(react@19.1.0) - '@react-types/shared': 3.29.1(react@19.1.0) + '@react-aria/menu': 3.18.3(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-aria/overlays': 3.27.1(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-aria/selection': 3.24.1(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-aria/textfield': 3.17.3(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-aria/utils': 3.29.0(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-stately/collections': 3.12.4(react@19.1.1) + '@react-stately/combobox': 3.10.5(react@19.1.1) + '@react-stately/form': 3.1.4(react@19.1.1) + '@react-types/button': 3.12.1(react@19.1.1) + '@react-types/combobox': 3.13.5(react@19.1.1) + '@react-types/shared': 3.29.1(react@19.1.1) '@swc/helpers': 0.5.17 - react: 19.1.0 - react-dom: 19.1.0(react@19.1.0) + react: 19.1.1 + react-dom: 19.1.1(react@19.1.1) - '@react-aria/datepicker@3.14.3(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': + '@react-aria/datepicker@3.14.3(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': dependencies: '@internationalized/date': 3.8.1 '@internationalized/number': 3.6.2 '@internationalized/string': 3.2.6 - '@react-aria/focus': 3.20.3(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - '@react-aria/form': 3.0.16(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - '@react-aria/i18n': 3.12.9(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - '@react-aria/interactions': 3.25.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - '@react-aria/label': 3.7.18(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - '@react-aria/spinbutton': 3.6.15(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - '@react-aria/utils': 3.29.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - '@react-stately/datepicker': 3.14.1(react@19.1.0) - '@react-stately/form': 3.1.4(react@19.1.0) - '@react-types/button': 3.12.1(react@19.1.0) - '@react-types/calendar': 3.7.1(react@19.1.0) - '@react-types/datepicker': 3.12.1(react@19.1.0) - '@react-types/dialog': 3.5.18(react@19.1.0) - '@react-types/shared': 3.29.1(react@19.1.0) + '@react-aria/focus': 3.20.3(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-aria/form': 3.0.16(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-aria/i18n': 3.12.9(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-aria/interactions': 3.25.1(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-aria/label': 3.7.18(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-aria/spinbutton': 3.6.15(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-aria/utils': 3.29.0(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-stately/datepicker': 3.14.1(react@19.1.1) + '@react-stately/form': 3.1.4(react@19.1.1) + '@react-types/button': 3.12.1(react@19.1.1) + '@react-types/calendar': 3.7.1(react@19.1.1) + '@react-types/datepicker': 3.12.1(react@19.1.1) + '@react-types/dialog': 3.5.18(react@19.1.1) + '@react-types/shared': 3.29.1(react@19.1.1) '@swc/helpers': 0.5.17 - react: 19.1.0 - react-dom: 19.1.0(react@19.1.0) + react: 19.1.1 + react-dom: 19.1.1(react@19.1.1) - '@react-aria/dialog@3.5.25(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': + '@react-aria/dialog@3.5.25(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': dependencies: - '@react-aria/interactions': 3.25.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - '@react-aria/overlays': 3.27.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - '@react-aria/utils': 3.29.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - '@react-types/dialog': 3.5.18(react@19.1.0) - '@react-types/shared': 3.29.1(react@19.1.0) + '@react-aria/interactions': 3.25.1(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-aria/overlays': 3.27.1(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-aria/utils': 3.29.0(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-types/dialog': 3.5.18(react@19.1.1) + '@react-types/shared': 3.29.1(react@19.1.1) '@swc/helpers': 0.5.17 - react: 19.1.0 - react-dom: 19.1.0(react@19.1.0) + react: 19.1.1 + react-dom: 19.1.1(react@19.1.1) - '@react-aria/disclosure@3.0.5(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': + '@react-aria/disclosure@3.0.5(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': dependencies: - '@react-aria/ssr': 3.9.8(react@19.1.0) - '@react-aria/utils': 3.29.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - '@react-stately/disclosure': 3.0.4(react@19.1.0) - '@react-types/button': 3.12.1(react@19.1.0) + '@react-aria/ssr': 3.9.8(react@19.1.1) + '@react-aria/utils': 3.29.0(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-stately/disclosure': 3.0.4(react@19.1.1) + '@react-types/button': 3.12.1(react@19.1.1) '@swc/helpers': 0.5.17 - react: 19.1.0 - react-dom: 19.1.0(react@19.1.0) + react: 19.1.1 + react-dom: 19.1.1(react@19.1.1) - '@react-aria/dnd@3.9.3(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': + '@react-aria/dnd@3.9.3(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': dependencies: '@internationalized/string': 3.2.6 - '@react-aria/i18n': 3.12.9(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - '@react-aria/interactions': 3.25.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@react-aria/i18n': 3.12.9(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-aria/interactions': 3.25.1(react-dom@19.1.1(react@19.1.1))(react@19.1.1) '@react-aria/live-announcer': 3.4.2 - '@react-aria/overlays': 3.27.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - '@react-aria/utils': 3.29.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - '@react-stately/dnd': 3.5.4(react@19.1.0) - '@react-types/button': 3.12.1(react@19.1.0) - '@react-types/shared': 3.29.1(react@19.1.0) + '@react-aria/overlays': 3.27.1(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-aria/utils': 3.29.0(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-stately/dnd': 3.5.4(react@19.1.1) + '@react-types/button': 3.12.1(react@19.1.1) + '@react-types/shared': 3.29.1(react@19.1.1) '@swc/helpers': 0.5.17 - react: 19.1.0 - react-dom: 19.1.0(react@19.1.0) + react: 19.1.1 + react-dom: 19.1.1(react@19.1.1) - '@react-aria/focus@3.20.3(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': + '@react-aria/focus@3.20.3(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': dependencies: - '@react-aria/interactions': 3.25.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - '@react-aria/utils': 3.29.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - '@react-types/shared': 3.29.1(react@19.1.0) + '@react-aria/interactions': 3.25.1(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-aria/utils': 3.29.0(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-types/shared': 3.29.1(react@19.1.1) '@swc/helpers': 0.5.17 clsx: 2.1.1 - react: 19.1.0 - react-dom: 19.1.0(react@19.1.0) + react: 19.1.1 + react-dom: 19.1.1(react@19.1.1) - '@react-aria/form@3.0.16(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': + '@react-aria/form@3.0.16(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': dependencies: - '@react-aria/interactions': 3.25.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - '@react-aria/utils': 3.29.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - '@react-stately/form': 3.1.4(react@19.1.0) - '@react-types/shared': 3.29.1(react@19.1.0) + '@react-aria/interactions': 3.25.1(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-aria/utils': 3.29.0(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-stately/form': 3.1.4(react@19.1.1) + '@react-types/shared': 3.29.1(react@19.1.1) '@swc/helpers': 0.5.17 - react: 19.1.0 - react-dom: 19.1.0(react@19.1.0) + react: 19.1.1 + react-dom: 19.1.1(react@19.1.1) - '@react-aria/grid@3.14.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': + '@react-aria/grid@3.14.0(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': dependencies: - '@react-aria/focus': 3.20.3(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - '@react-aria/i18n': 3.12.9(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - '@react-aria/interactions': 3.25.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@react-aria/focus': 3.20.3(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-aria/i18n': 3.12.9(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-aria/interactions': 3.25.1(react-dom@19.1.1(react@19.1.1))(react@19.1.1) '@react-aria/live-announcer': 3.4.2 - '@react-aria/selection': 3.24.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - '@react-aria/utils': 3.29.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - '@react-stately/collections': 3.12.4(react@19.1.0) - '@react-stately/grid': 3.11.2(react@19.1.0) - '@react-stately/selection': 3.20.2(react@19.1.0) - '@react-types/checkbox': 3.9.4(react@19.1.0) - '@react-types/grid': 3.3.2(react@19.1.0) - '@react-types/shared': 3.29.1(react@19.1.0) + '@react-aria/selection': 3.24.1(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-aria/utils': 3.29.0(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-stately/collections': 3.12.4(react@19.1.1) + '@react-stately/grid': 3.11.2(react@19.1.1) + '@react-stately/selection': 3.20.2(react@19.1.1) + '@react-types/checkbox': 3.9.4(react@19.1.1) + '@react-types/grid': 3.3.2(react@19.1.1) + '@react-types/shared': 3.29.1(react@19.1.1) '@swc/helpers': 0.5.17 - react: 19.1.0 - react-dom: 19.1.0(react@19.1.0) + react: 19.1.1 + react-dom: 19.1.1(react@19.1.1) - '@react-aria/gridlist@3.13.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': + '@react-aria/gridlist@3.13.0(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': dependencies: - '@react-aria/focus': 3.20.3(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - '@react-aria/grid': 3.14.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - '@react-aria/i18n': 3.12.9(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - '@react-aria/interactions': 3.25.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - '@react-aria/selection': 3.24.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - '@react-aria/utils': 3.29.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - '@react-stately/collections': 3.12.4(react@19.1.0) - '@react-stately/list': 3.12.2(react@19.1.0) - '@react-stately/tree': 3.8.10(react@19.1.0) - '@react-types/shared': 3.29.1(react@19.1.0) + '@react-aria/focus': 3.20.3(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-aria/grid': 3.14.0(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-aria/i18n': 3.12.9(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-aria/interactions': 3.25.1(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-aria/selection': 3.24.1(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-aria/utils': 3.29.0(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-stately/collections': 3.12.4(react@19.1.1) + '@react-stately/list': 3.12.2(react@19.1.1) + '@react-stately/tree': 3.8.10(react@19.1.1) + '@react-types/shared': 3.29.1(react@19.1.1) '@swc/helpers': 0.5.17 - react: 19.1.0 - react-dom: 19.1.0(react@19.1.0) + react: 19.1.1 + react-dom: 19.1.1(react@19.1.1) - '@react-aria/i18n@3.12.9(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': + '@react-aria/i18n@3.12.9(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': dependencies: '@internationalized/date': 3.8.1 '@internationalized/message': 3.1.7 '@internationalized/number': 3.6.2 '@internationalized/string': 3.2.6 - '@react-aria/ssr': 3.9.8(react@19.1.0) - '@react-aria/utils': 3.29.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - '@react-types/shared': 3.29.1(react@19.1.0) + '@react-aria/ssr': 3.9.8(react@19.1.1) + '@react-aria/utils': 3.29.0(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-types/shared': 3.29.1(react@19.1.1) '@swc/helpers': 0.5.17 - react: 19.1.0 - react-dom: 19.1.0(react@19.1.0) + react: 19.1.1 + react-dom: 19.1.1(react@19.1.1) - '@react-aria/interactions@3.25.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': + '@react-aria/interactions@3.25.1(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': dependencies: - '@react-aria/ssr': 3.9.8(react@19.1.0) - '@react-aria/utils': 3.29.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@react-aria/ssr': 3.9.8(react@19.1.1) + '@react-aria/utils': 3.29.0(react-dom@19.1.1(react@19.1.1))(react@19.1.1) '@react-stately/flags': 3.1.1 - '@react-types/shared': 3.29.1(react@19.1.0) + '@react-types/shared': 3.29.1(react@19.1.1) '@swc/helpers': 0.5.17 - react: 19.1.0 - react-dom: 19.1.0(react@19.1.0) + react: 19.1.1 + react-dom: 19.1.1(react@19.1.1) - '@react-aria/label@3.7.18(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': + '@react-aria/label@3.7.18(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': dependencies: - '@react-aria/utils': 3.29.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - '@react-types/shared': 3.29.1(react@19.1.0) + '@react-aria/utils': 3.29.0(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-types/shared': 3.29.1(react@19.1.1) '@swc/helpers': 0.5.17 - react: 19.1.0 - react-dom: 19.1.0(react@19.1.0) + react: 19.1.1 + react-dom: 19.1.1(react@19.1.1) - '@react-aria/landmark@3.0.3(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': + '@react-aria/landmark@3.0.3(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': dependencies: - '@react-aria/utils': 3.29.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - '@react-types/shared': 3.29.1(react@19.1.0) + '@react-aria/utils': 3.29.0(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-types/shared': 3.29.1(react@19.1.1) '@swc/helpers': 0.5.17 - react: 19.1.0 - react-dom: 19.1.0(react@19.1.0) - use-sync-external-store: 1.5.0(react@19.1.0) + react: 19.1.1 + react-dom: 19.1.1(react@19.1.1) + use-sync-external-store: 1.5.0(react@19.1.1) - '@react-aria/link@3.8.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': + '@react-aria/link@3.8.1(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': dependencies: - '@react-aria/interactions': 3.25.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - '@react-aria/utils': 3.29.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - '@react-types/link': 3.6.1(react@19.1.0) - '@react-types/shared': 3.29.1(react@19.1.0) + '@react-aria/interactions': 3.25.1(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-aria/utils': 3.29.0(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-types/link': 3.6.1(react@19.1.1) + '@react-types/shared': 3.29.1(react@19.1.1) '@swc/helpers': 0.5.17 - react: 19.1.0 - react-dom: 19.1.0(react@19.1.0) + react: 19.1.1 + react-dom: 19.1.1(react@19.1.1) - '@react-aria/listbox@3.14.4(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': + '@react-aria/listbox@3.14.4(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': dependencies: - '@react-aria/interactions': 3.25.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - '@react-aria/label': 3.7.18(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - '@react-aria/selection': 3.24.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - '@react-aria/utils': 3.29.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - '@react-stately/collections': 3.12.4(react@19.1.0) - '@react-stately/list': 3.12.2(react@19.1.0) - '@react-types/listbox': 3.7.0(react@19.1.0) - '@react-types/shared': 3.29.1(react@19.1.0) + '@react-aria/interactions': 3.25.1(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-aria/label': 3.7.18(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-aria/selection': 3.24.1(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-aria/utils': 3.29.0(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-stately/collections': 3.12.4(react@19.1.1) + '@react-stately/list': 3.12.2(react@19.1.1) + '@react-types/listbox': 3.7.0(react@19.1.1) + '@react-types/shared': 3.29.1(react@19.1.1) '@swc/helpers': 0.5.17 - react: 19.1.0 - react-dom: 19.1.0(react@19.1.0) + react: 19.1.1 + react-dom: 19.1.1(react@19.1.1) '@react-aria/live-announcer@3.4.2': dependencies: '@swc/helpers': 0.5.17 - '@react-aria/menu@3.18.3(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': + '@react-aria/menu@3.18.3(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': dependencies: - '@react-aria/focus': 3.20.3(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - '@react-aria/i18n': 3.12.9(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - '@react-aria/interactions': 3.25.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - '@react-aria/overlays': 3.27.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - '@react-aria/selection': 3.24.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - '@react-aria/utils': 3.29.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - '@react-stately/collections': 3.12.4(react@19.1.0) - '@react-stately/menu': 3.9.4(react@19.1.0) - '@react-stately/selection': 3.20.2(react@19.1.0) - '@react-stately/tree': 3.8.10(react@19.1.0) - '@react-types/button': 3.12.1(react@19.1.0) - '@react-types/menu': 3.10.1(react@19.1.0) - '@react-types/shared': 3.29.1(react@19.1.0) + '@react-aria/focus': 3.20.3(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-aria/i18n': 3.12.9(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-aria/interactions': 3.25.1(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-aria/overlays': 3.27.1(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-aria/selection': 3.24.1(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-aria/utils': 3.29.0(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-stately/collections': 3.12.4(react@19.1.1) + '@react-stately/menu': 3.9.4(react@19.1.1) + '@react-stately/selection': 3.20.2(react@19.1.1) + '@react-stately/tree': 3.8.10(react@19.1.1) + '@react-types/button': 3.12.1(react@19.1.1) + '@react-types/menu': 3.10.1(react@19.1.1) + '@react-types/shared': 3.29.1(react@19.1.1) '@swc/helpers': 0.5.17 - react: 19.1.0 - react-dom: 19.1.0(react@19.1.0) + react: 19.1.1 + react-dom: 19.1.1(react@19.1.1) - '@react-aria/meter@3.4.23(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': + '@react-aria/meter@3.4.23(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': dependencies: - '@react-aria/progress': 3.4.23(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - '@react-types/meter': 3.4.9(react@19.1.0) - '@react-types/shared': 3.29.1(react@19.1.0) + '@react-aria/progress': 3.4.23(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-types/meter': 3.4.9(react@19.1.1) + '@react-types/shared': 3.29.1(react@19.1.1) '@swc/helpers': 0.5.17 - react: 19.1.0 - react-dom: 19.1.0(react@19.1.0) + react: 19.1.1 + react-dom: 19.1.1(react@19.1.1) - '@react-aria/numberfield@3.11.14(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': + '@react-aria/numberfield@3.11.14(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': dependencies: - '@react-aria/i18n': 3.12.9(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - '@react-aria/interactions': 3.25.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - '@react-aria/spinbutton': 3.6.15(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - '@react-aria/textfield': 3.17.3(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - '@react-aria/utils': 3.29.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - '@react-stately/form': 3.1.4(react@19.1.0) - '@react-stately/numberfield': 3.9.12(react@19.1.0) - '@react-types/button': 3.12.1(react@19.1.0) - '@react-types/numberfield': 3.8.11(react@19.1.0) - '@react-types/shared': 3.29.1(react@19.1.0) + '@react-aria/i18n': 3.12.9(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-aria/interactions': 3.25.1(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-aria/spinbutton': 3.6.15(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-aria/textfield': 3.17.3(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-aria/utils': 3.29.0(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-stately/form': 3.1.4(react@19.1.1) + '@react-stately/numberfield': 3.9.12(react@19.1.1) + '@react-types/button': 3.12.1(react@19.1.1) + '@react-types/numberfield': 3.8.11(react@19.1.1) + '@react-types/shared': 3.29.1(react@19.1.1) '@swc/helpers': 0.5.17 - react: 19.1.0 - react-dom: 19.1.0(react@19.1.0) + react: 19.1.1 + react-dom: 19.1.1(react@19.1.1) - '@react-aria/overlays@3.27.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': + '@react-aria/overlays@3.27.1(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': dependencies: - '@react-aria/focus': 3.20.3(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - '@react-aria/i18n': 3.12.9(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - '@react-aria/interactions': 3.25.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - '@react-aria/ssr': 3.9.8(react@19.1.0) - '@react-aria/utils': 3.29.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - '@react-aria/visually-hidden': 3.8.23(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - '@react-stately/overlays': 3.6.16(react@19.1.0) - '@react-types/button': 3.12.1(react@19.1.0) - '@react-types/overlays': 3.8.15(react@19.1.0) - '@react-types/shared': 3.29.1(react@19.1.0) + '@react-aria/focus': 3.20.3(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-aria/i18n': 3.12.9(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-aria/interactions': 3.25.1(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-aria/ssr': 3.9.8(react@19.1.1) + '@react-aria/utils': 3.29.0(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-aria/visually-hidden': 3.8.23(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-stately/overlays': 3.6.16(react@19.1.1) + '@react-types/button': 3.12.1(react@19.1.1) + '@react-types/overlays': 3.8.15(react@19.1.1) + '@react-types/shared': 3.29.1(react@19.1.1) '@swc/helpers': 0.5.17 - react: 19.1.0 - react-dom: 19.1.0(react@19.1.0) + react: 19.1.1 + react-dom: 19.1.1(react@19.1.1) - '@react-aria/progress@3.4.23(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': + '@react-aria/progress@3.4.23(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': dependencies: - '@react-aria/i18n': 3.12.9(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - '@react-aria/label': 3.7.18(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - '@react-aria/utils': 3.29.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - '@react-types/progress': 3.5.12(react@19.1.0) - '@react-types/shared': 3.29.1(react@19.1.0) + '@react-aria/i18n': 3.12.9(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-aria/label': 3.7.18(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-aria/utils': 3.29.0(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-types/progress': 3.5.12(react@19.1.1) + '@react-types/shared': 3.29.1(react@19.1.1) '@swc/helpers': 0.5.17 - react: 19.1.0 - react-dom: 19.1.0(react@19.1.0) + react: 19.1.1 + react-dom: 19.1.1(react@19.1.1) - '@react-aria/radio@3.11.3(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': + '@react-aria/radio@3.11.3(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': dependencies: - '@react-aria/focus': 3.20.3(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - '@react-aria/form': 3.0.16(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - '@react-aria/i18n': 3.12.9(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - '@react-aria/interactions': 3.25.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - '@react-aria/label': 3.7.18(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - '@react-aria/utils': 3.29.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - '@react-stately/radio': 3.10.13(react@19.1.0) - '@react-types/radio': 3.8.9(react@19.1.0) - '@react-types/shared': 3.29.1(react@19.1.0) + '@react-aria/focus': 3.20.3(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-aria/form': 3.0.16(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-aria/i18n': 3.12.9(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-aria/interactions': 3.25.1(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-aria/label': 3.7.18(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-aria/utils': 3.29.0(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-stately/radio': 3.10.13(react@19.1.1) + '@react-types/radio': 3.8.9(react@19.1.1) + '@react-types/shared': 3.29.1(react@19.1.1) '@swc/helpers': 0.5.17 - react: 19.1.0 - react-dom: 19.1.0(react@19.1.0) + react: 19.1.1 + react-dom: 19.1.1(react@19.1.1) - '@react-aria/searchfield@3.8.4(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': + '@react-aria/searchfield@3.8.4(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': dependencies: - '@react-aria/i18n': 3.12.9(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - '@react-aria/textfield': 3.17.3(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - '@react-aria/utils': 3.29.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - '@react-stately/searchfield': 3.5.12(react@19.1.0) - '@react-types/button': 3.12.1(react@19.1.0) - '@react-types/searchfield': 3.6.2(react@19.1.0) - '@react-types/shared': 3.29.1(react@19.1.0) + '@react-aria/i18n': 3.12.9(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-aria/textfield': 3.17.3(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-aria/utils': 3.29.0(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-stately/searchfield': 3.5.12(react@19.1.1) + '@react-types/button': 3.12.1(react@19.1.1) + '@react-types/searchfield': 3.6.2(react@19.1.1) + '@react-types/shared': 3.29.1(react@19.1.1) '@swc/helpers': 0.5.17 - react: 19.1.0 - react-dom: 19.1.0(react@19.1.0) + react: 19.1.1 + react-dom: 19.1.1(react@19.1.1) - '@react-aria/select@3.15.5(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': + '@react-aria/select@3.15.5(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': dependencies: - '@react-aria/form': 3.0.16(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - '@react-aria/i18n': 3.12.9(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - '@react-aria/interactions': 3.25.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - '@react-aria/label': 3.7.18(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - '@react-aria/listbox': 3.14.4(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - '@react-aria/menu': 3.18.3(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - '@react-aria/selection': 3.24.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - '@react-aria/utils': 3.29.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - '@react-aria/visually-hidden': 3.8.23(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - '@react-stately/select': 3.6.13(react@19.1.0) - '@react-types/button': 3.12.1(react@19.1.0) - '@react-types/select': 3.9.12(react@19.1.0) - '@react-types/shared': 3.29.1(react@19.1.0) + '@react-aria/form': 3.0.16(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-aria/i18n': 3.12.9(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-aria/interactions': 3.25.1(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-aria/label': 3.7.18(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-aria/listbox': 3.14.4(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-aria/menu': 3.18.3(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-aria/selection': 3.24.1(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-aria/utils': 3.29.0(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-aria/visually-hidden': 3.8.23(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-stately/select': 3.6.13(react@19.1.1) + '@react-types/button': 3.12.1(react@19.1.1) + '@react-types/select': 3.9.12(react@19.1.1) + '@react-types/shared': 3.29.1(react@19.1.1) '@swc/helpers': 0.5.17 - react: 19.1.0 - react-dom: 19.1.0(react@19.1.0) + react: 19.1.1 + react-dom: 19.1.1(react@19.1.1) - '@react-aria/selection@3.24.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': + '@react-aria/selection@3.24.1(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': dependencies: - '@react-aria/focus': 3.20.3(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - '@react-aria/i18n': 3.12.9(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - '@react-aria/interactions': 3.25.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - '@react-aria/utils': 3.29.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - '@react-stately/selection': 3.20.2(react@19.1.0) - '@react-types/shared': 3.29.1(react@19.1.0) + '@react-aria/focus': 3.20.3(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-aria/i18n': 3.12.9(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-aria/interactions': 3.25.1(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-aria/utils': 3.29.0(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-stately/selection': 3.20.2(react@19.1.1) + '@react-types/shared': 3.29.1(react@19.1.1) '@swc/helpers': 0.5.17 - react: 19.1.0 - react-dom: 19.1.0(react@19.1.0) + react: 19.1.1 + react-dom: 19.1.1(react@19.1.1) - '@react-aria/separator@3.4.9(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': + '@react-aria/separator@3.4.9(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': dependencies: - '@react-aria/utils': 3.29.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - '@react-types/shared': 3.29.1(react@19.1.0) + '@react-aria/utils': 3.29.0(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-types/shared': 3.29.1(react@19.1.1) '@swc/helpers': 0.5.17 - react: 19.1.0 - react-dom: 19.1.0(react@19.1.0) + react: 19.1.1 + react-dom: 19.1.1(react@19.1.1) - '@react-aria/slider@3.7.19(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': + '@react-aria/slider@3.7.19(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': dependencies: - '@react-aria/i18n': 3.12.9(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - '@react-aria/interactions': 3.25.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - '@react-aria/label': 3.7.18(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - '@react-aria/utils': 3.29.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - '@react-stately/slider': 3.6.4(react@19.1.0) - '@react-types/shared': 3.29.1(react@19.1.0) - '@react-types/slider': 3.7.11(react@19.1.0) + '@react-aria/i18n': 3.12.9(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-aria/interactions': 3.25.1(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-aria/label': 3.7.18(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-aria/utils': 3.29.0(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-stately/slider': 3.6.4(react@19.1.1) + '@react-types/shared': 3.29.1(react@19.1.1) + '@react-types/slider': 3.7.11(react@19.1.1) '@swc/helpers': 0.5.17 - react: 19.1.0 - react-dom: 19.1.0(react@19.1.0) + react: 19.1.1 + react-dom: 19.1.1(react@19.1.1) - '@react-aria/spinbutton@3.6.15(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': + '@react-aria/spinbutton@3.6.15(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': dependencies: - '@react-aria/i18n': 3.12.9(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@react-aria/i18n': 3.12.9(react-dom@19.1.1(react@19.1.1))(react@19.1.1) '@react-aria/live-announcer': 3.4.2 - '@react-aria/utils': 3.29.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - '@react-types/button': 3.12.1(react@19.1.0) - '@react-types/shared': 3.29.1(react@19.1.0) + '@react-aria/utils': 3.29.0(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-types/button': 3.12.1(react@19.1.1) + '@react-types/shared': 3.29.1(react@19.1.1) '@swc/helpers': 0.5.17 - react: 19.1.0 - react-dom: 19.1.0(react@19.1.0) + react: 19.1.1 + react-dom: 19.1.1(react@19.1.1) - '@react-aria/ssr@3.9.8(react@19.1.0)': + '@react-aria/ssr@3.9.8(react@19.1.1)': dependencies: '@swc/helpers': 0.5.17 - react: 19.1.0 + react: 19.1.1 - '@react-aria/switch@3.7.3(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': + '@react-aria/switch@3.7.3(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': dependencies: - '@react-aria/toggle': 3.11.3(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - '@react-stately/toggle': 3.8.4(react@19.1.0) - '@react-types/shared': 3.29.1(react@19.1.0) - '@react-types/switch': 3.5.11(react@19.1.0) + '@react-aria/toggle': 3.11.3(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-stately/toggle': 3.8.4(react@19.1.1) + '@react-types/shared': 3.29.1(react@19.1.1) + '@react-types/switch': 3.5.11(react@19.1.1) '@swc/helpers': 0.5.17 - react: 19.1.0 - react-dom: 19.1.0(react@19.1.0) + react: 19.1.1 + react-dom: 19.1.1(react@19.1.1) - '@react-aria/table@3.17.3(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': + '@react-aria/table@3.17.3(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': dependencies: - '@react-aria/focus': 3.20.3(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - '@react-aria/grid': 3.14.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - '@react-aria/i18n': 3.12.9(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - '@react-aria/interactions': 3.25.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@react-aria/focus': 3.20.3(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-aria/grid': 3.14.0(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-aria/i18n': 3.12.9(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-aria/interactions': 3.25.1(react-dom@19.1.1(react@19.1.1))(react@19.1.1) '@react-aria/live-announcer': 3.4.2 - '@react-aria/utils': 3.29.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - '@react-aria/visually-hidden': 3.8.23(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - '@react-stately/collections': 3.12.4(react@19.1.0) + '@react-aria/utils': 3.29.0(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-aria/visually-hidden': 3.8.23(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-stately/collections': 3.12.4(react@19.1.1) '@react-stately/flags': 3.1.1 - '@react-stately/table': 3.14.2(react@19.1.0) - '@react-types/checkbox': 3.9.4(react@19.1.0) - '@react-types/grid': 3.3.2(react@19.1.0) - '@react-types/shared': 3.29.1(react@19.1.0) - '@react-types/table': 3.13.0(react@19.1.0) + '@react-stately/table': 3.14.2(react@19.1.1) + '@react-types/checkbox': 3.9.4(react@19.1.1) + '@react-types/grid': 3.3.2(react@19.1.1) + '@react-types/shared': 3.29.1(react@19.1.1) + '@react-types/table': 3.13.0(react@19.1.1) '@swc/helpers': 0.5.17 - react: 19.1.0 - react-dom: 19.1.0(react@19.1.0) + react: 19.1.1 + react-dom: 19.1.1(react@19.1.1) - '@react-aria/tabs@3.10.3(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': + '@react-aria/tabs@3.10.3(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': dependencies: - '@react-aria/focus': 3.20.3(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - '@react-aria/i18n': 3.12.9(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - '@react-aria/selection': 3.24.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - '@react-aria/utils': 3.29.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - '@react-stately/tabs': 3.8.2(react@19.1.0) - '@react-types/shared': 3.29.1(react@19.1.0) - '@react-types/tabs': 3.3.15(react@19.1.0) + '@react-aria/focus': 3.20.3(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-aria/i18n': 3.12.9(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-aria/selection': 3.24.1(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-aria/utils': 3.29.0(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-stately/tabs': 3.8.2(react@19.1.1) + '@react-types/shared': 3.29.1(react@19.1.1) + '@react-types/tabs': 3.3.15(react@19.1.1) '@swc/helpers': 0.5.17 - react: 19.1.0 - react-dom: 19.1.0(react@19.1.0) + react: 19.1.1 + react-dom: 19.1.1(react@19.1.1) - '@react-aria/tag@3.6.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': + '@react-aria/tag@3.6.0(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': dependencies: - '@react-aria/gridlist': 3.13.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - '@react-aria/i18n': 3.12.9(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - '@react-aria/interactions': 3.25.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - '@react-aria/label': 3.7.18(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - '@react-aria/selection': 3.24.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - '@react-aria/utils': 3.29.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - '@react-stately/list': 3.12.2(react@19.1.0) - '@react-types/button': 3.12.1(react@19.1.0) - '@react-types/shared': 3.29.1(react@19.1.0) + '@react-aria/gridlist': 3.13.0(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-aria/i18n': 3.12.9(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-aria/interactions': 3.25.1(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-aria/label': 3.7.18(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-aria/selection': 3.24.1(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-aria/utils': 3.29.0(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-stately/list': 3.12.2(react@19.1.1) + '@react-types/button': 3.12.1(react@19.1.1) + '@react-types/shared': 3.29.1(react@19.1.1) '@swc/helpers': 0.5.17 - react: 19.1.0 - react-dom: 19.1.0(react@19.1.0) + react: 19.1.1 + react-dom: 19.1.1(react@19.1.1) - '@react-aria/textfield@3.17.3(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': + '@react-aria/textfield@3.17.3(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': dependencies: - '@react-aria/form': 3.0.16(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - '@react-aria/interactions': 3.25.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - '@react-aria/label': 3.7.18(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - '@react-aria/utils': 3.29.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - '@react-stately/form': 3.1.4(react@19.1.0) - '@react-stately/utils': 3.10.6(react@19.1.0) - '@react-types/shared': 3.29.1(react@19.1.0) - '@react-types/textfield': 3.12.2(react@19.1.0) + '@react-aria/form': 3.0.16(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-aria/interactions': 3.25.1(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-aria/label': 3.7.18(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-aria/utils': 3.29.0(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-stately/form': 3.1.4(react@19.1.1) + '@react-stately/utils': 3.10.6(react@19.1.1) + '@react-types/shared': 3.29.1(react@19.1.1) + '@react-types/textfield': 3.12.2(react@19.1.1) '@swc/helpers': 0.5.17 - react: 19.1.0 - react-dom: 19.1.0(react@19.1.0) + react: 19.1.1 + react-dom: 19.1.1(react@19.1.1) - '@react-aria/toast@3.0.3(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': + '@react-aria/toast@3.0.3(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': dependencies: - '@react-aria/i18n': 3.12.9(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - '@react-aria/interactions': 3.25.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - '@react-aria/landmark': 3.0.3(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - '@react-aria/utils': 3.29.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - '@react-stately/toast': 3.1.0(react@19.1.0) - '@react-types/button': 3.12.1(react@19.1.0) - '@react-types/shared': 3.29.1(react@19.1.0) + '@react-aria/i18n': 3.12.9(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-aria/interactions': 3.25.1(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-aria/landmark': 3.0.3(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-aria/utils': 3.29.0(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-stately/toast': 3.1.0(react@19.1.1) + '@react-types/button': 3.12.1(react@19.1.1) + '@react-types/shared': 3.29.1(react@19.1.1) '@swc/helpers': 0.5.17 - react: 19.1.0 - react-dom: 19.1.0(react@19.1.0) + react: 19.1.1 + react-dom: 19.1.1(react@19.1.1) - '@react-aria/toggle@3.11.3(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': + '@react-aria/toggle@3.11.3(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': dependencies: - '@react-aria/interactions': 3.25.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - '@react-aria/utils': 3.29.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - '@react-stately/toggle': 3.8.4(react@19.1.0) - '@react-types/checkbox': 3.9.4(react@19.1.0) - '@react-types/shared': 3.29.1(react@19.1.0) + '@react-aria/interactions': 3.25.1(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-aria/utils': 3.29.0(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-stately/toggle': 3.8.4(react@19.1.1) + '@react-types/checkbox': 3.9.4(react@19.1.1) + '@react-types/shared': 3.29.1(react@19.1.1) '@swc/helpers': 0.5.17 - react: 19.1.0 - react-dom: 19.1.0(react@19.1.0) + react: 19.1.1 + react-dom: 19.1.1(react@19.1.1) - '@react-aria/toolbar@3.0.0-beta.16(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': + '@react-aria/toolbar@3.0.0-beta.16(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': dependencies: - '@react-aria/focus': 3.20.3(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - '@react-aria/i18n': 3.12.9(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - '@react-aria/utils': 3.29.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - '@react-types/shared': 3.29.1(react@19.1.0) + '@react-aria/focus': 3.20.3(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-aria/i18n': 3.12.9(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-aria/utils': 3.29.0(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-types/shared': 3.29.1(react@19.1.1) '@swc/helpers': 0.5.17 - react: 19.1.0 - react-dom: 19.1.0(react@19.1.0) + react: 19.1.1 + react-dom: 19.1.1(react@19.1.1) - '@react-aria/tooltip@3.8.3(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': + '@react-aria/tooltip@3.8.3(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': dependencies: - '@react-aria/interactions': 3.25.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - '@react-aria/utils': 3.29.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - '@react-stately/tooltip': 3.5.4(react@19.1.0) - '@react-types/shared': 3.29.1(react@19.1.0) - '@react-types/tooltip': 3.4.17(react@19.1.0) + '@react-aria/interactions': 3.25.1(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-aria/utils': 3.29.0(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-stately/tooltip': 3.5.4(react@19.1.1) + '@react-types/shared': 3.29.1(react@19.1.1) + '@react-types/tooltip': 3.4.17(react@19.1.1) '@swc/helpers': 0.5.17 - react: 19.1.0 - react-dom: 19.1.0(react@19.1.0) + react: 19.1.1 + react-dom: 19.1.1(react@19.1.1) - '@react-aria/tree@3.0.3(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': + '@react-aria/tree@3.0.3(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': dependencies: - '@react-aria/gridlist': 3.13.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - '@react-aria/i18n': 3.12.9(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - '@react-aria/selection': 3.24.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - '@react-aria/utils': 3.29.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - '@react-stately/tree': 3.8.10(react@19.1.0) - '@react-types/button': 3.12.1(react@19.1.0) - '@react-types/shared': 3.29.1(react@19.1.0) + '@react-aria/gridlist': 3.13.0(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-aria/i18n': 3.12.9(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-aria/selection': 3.24.1(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-aria/utils': 3.29.0(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-stately/tree': 3.8.10(react@19.1.1) + '@react-types/button': 3.12.1(react@19.1.1) + '@react-types/shared': 3.29.1(react@19.1.1) '@swc/helpers': 0.5.17 - react: 19.1.0 - react-dom: 19.1.0(react@19.1.0) + react: 19.1.1 + react-dom: 19.1.1(react@19.1.1) - '@react-aria/utils@3.29.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': + '@react-aria/utils@3.29.0(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': dependencies: - '@react-aria/ssr': 3.9.8(react@19.1.0) + '@react-aria/ssr': 3.9.8(react@19.1.1) '@react-stately/flags': 3.1.1 - '@react-stately/utils': 3.10.6(react@19.1.0) - '@react-types/shared': 3.29.1(react@19.1.0) + '@react-stately/utils': 3.10.6(react@19.1.1) + '@react-types/shared': 3.29.1(react@19.1.1) '@swc/helpers': 0.5.17 clsx: 2.1.1 - react: 19.1.0 - react-dom: 19.1.0(react@19.1.0) + react: 19.1.1 + react-dom: 19.1.1(react@19.1.1) - '@react-aria/visually-hidden@3.8.23(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': + '@react-aria/visually-hidden@3.8.23(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': dependencies: - '@react-aria/interactions': 3.25.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - '@react-aria/utils': 3.29.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - '@react-types/shared': 3.29.1(react@19.1.0) + '@react-aria/interactions': 3.25.1(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-aria/utils': 3.29.0(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-types/shared': 3.29.1(react@19.1.1) '@swc/helpers': 0.5.17 - react: 19.1.0 - react-dom: 19.1.0(react@19.1.0) + react: 19.1.1 + react-dom: 19.1.1(react@19.1.1) - '@react-router/dev@7.6.1(@types/node@22.15.24)(jiti@2.4.2)(lightningcss@1.30.1)(react-router@7.6.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(terser@5.39.0)(tsx@4.19.4)(typescript@5.8.3)(vite@6.3.5(@types/node@22.15.24)(jiti@2.4.2)(lightningcss@1.30.1)(terser@5.39.0)(tsx@4.19.4)(yaml@2.8.0))(yaml@2.8.0)': + '@react-router/dev@7.8.1(@types/node@22.15.24)(jiti@2.4.2)(lightningcss@1.30.1)(react-dom@19.1.1(react@19.1.1))(react-router@7.8.1(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(react@19.1.1)(terser@5.39.0)(tsx@4.19.4)(typescript@5.8.3)(vite@6.3.5(@types/node@22.15.24)(jiti@2.4.2)(lightningcss@1.30.1)(terser@5.39.0)(tsx@4.19.4)(yaml@2.8.0))(yaml@2.8.0)': dependencies: - '@babel/core': 7.27.3 - '@babel/generator': 7.27.3 - '@babel/parser': 7.27.3 - '@babel/plugin-syntax-decorators': 7.27.1(@babel/core@7.27.3) - '@babel/plugin-syntax-jsx': 7.27.1(@babel/core@7.27.3) - '@babel/preset-typescript': 7.27.1(@babel/core@7.27.3) - '@babel/traverse': 7.27.3 - '@babel/types': 7.27.3 + '@babel/core': 7.28.3 + '@babel/generator': 7.28.3 + '@babel/parser': 7.28.3 + '@babel/plugin-syntax-jsx': 7.27.1(@babel/core@7.28.3) + '@babel/preset-typescript': 7.27.1(@babel/core@7.28.3) + '@babel/traverse': 7.28.3 + '@babel/types': 7.28.2 '@npmcli/package-json': 4.0.1 - '@react-router/node': 7.6.1(react-router@7.6.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(typescript@5.8.3) + '@react-router/node': 7.8.1(react-router@7.8.1(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(typescript@5.8.3) + '@vitejs/plugin-rsc': 0.4.11(react-dom@19.1.1(react@19.1.1))(react@19.1.1)(vite@6.3.5(@types/node@22.15.24)(jiti@2.4.2)(lightningcss@1.30.1)(terser@5.39.0)(tsx@4.19.4)(yaml@2.8.0)) arg: 5.0.2 babel-dead-code-elimination: 1.0.10 chokidar: 4.0.3 dedent: 1.6.0 es-module-lexer: 1.7.0 exit-hook: 2.2.1 - fs-extra: 10.1.0 + isbot: 5.1.28 jsesc: 3.0.2 lodash: 4.17.21 pathe: 1.1.2 picocolors: 1.1.1 - prettier: 2.8.8 + prettier: 3.6.2 react-refresh: 0.14.2 - react-router: 7.6.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + react-router: 7.8.1(react-dom@19.1.1(react@19.1.1))(react@19.1.1) semver: 7.7.2 set-cookie-parser: 2.7.1 + tinyglobby: 0.2.14 valibot: 0.41.0(typescript@5.8.3) vite: 6.3.5(@types/node@22.15.24)(jiti@2.4.2)(lightningcss@1.30.1)(terser@5.39.0)(tsx@4.19.4)(yaml@2.8.0) - vite-node: 3.0.0-beta.2(@types/node@22.15.24)(jiti@2.4.2)(lightningcss@1.30.1)(terser@5.39.0)(tsx@4.19.4)(yaml@2.8.0) + vite-node: 3.2.4(@types/node@22.15.24)(jiti@2.4.2)(lightningcss@1.30.1)(terser@5.39.0)(tsx@4.19.4)(yaml@2.8.0) optionalDependencies: typescript: 5.8.3 transitivePeerDependencies: @@ -5414,6 +5526,8 @@ snapshots: - jiti - less - lightningcss + - react + - react-dom - sass - sass-embedded - stylus @@ -5423,387 +5537,384 @@ snapshots: - tsx - yaml - '@react-router/node@7.6.1(react-router@7.6.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(typescript@5.8.3)': + '@react-router/node@7.8.1(react-router@7.8.1(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(typescript@5.8.3)': dependencies: '@mjackson/node-fetch-server': 0.2.0 - react-router: 7.6.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - source-map-support: 0.5.21 - stream-slice: 0.1.2 - undici: 6.21.3 + react-router: 7.8.1(react-dom@19.1.1(react@19.1.1))(react@19.1.1) optionalDependencies: typescript: 5.8.3 - '@react-stately/calendar@3.8.1(react@19.1.0)': + '@react-stately/calendar@3.8.1(react@19.1.1)': dependencies: '@internationalized/date': 3.8.1 - '@react-stately/utils': 3.10.6(react@19.1.0) - '@react-types/calendar': 3.7.1(react@19.1.0) - '@react-types/shared': 3.29.1(react@19.1.0) + '@react-stately/utils': 3.10.6(react@19.1.1) + '@react-types/calendar': 3.7.1(react@19.1.1) + '@react-types/shared': 3.29.1(react@19.1.1) '@swc/helpers': 0.5.17 - react: 19.1.0 + react: 19.1.1 - '@react-stately/checkbox@3.6.14(react@19.1.0)': + '@react-stately/checkbox@3.6.14(react@19.1.1)': dependencies: - '@react-stately/form': 3.1.4(react@19.1.0) - '@react-stately/utils': 3.10.6(react@19.1.0) - '@react-types/checkbox': 3.9.4(react@19.1.0) - '@react-types/shared': 3.29.1(react@19.1.0) + '@react-stately/form': 3.1.4(react@19.1.1) + '@react-stately/utils': 3.10.6(react@19.1.1) + '@react-types/checkbox': 3.9.4(react@19.1.1) + '@react-types/shared': 3.29.1(react@19.1.1) '@swc/helpers': 0.5.17 - react: 19.1.0 + react: 19.1.1 - '@react-stately/collections@3.12.4(react@19.1.0)': + '@react-stately/collections@3.12.4(react@19.1.1)': dependencies: - '@react-types/shared': 3.29.1(react@19.1.0) + '@react-types/shared': 3.29.1(react@19.1.1) '@swc/helpers': 0.5.17 - react: 19.1.0 + react: 19.1.1 - '@react-stately/color@3.8.5(react@19.1.0)': + '@react-stately/color@3.8.5(react@19.1.1)': dependencies: '@internationalized/number': 3.6.2 '@internationalized/string': 3.2.6 - '@react-stately/form': 3.1.4(react@19.1.0) - '@react-stately/numberfield': 3.9.12(react@19.1.0) - '@react-stately/slider': 3.6.4(react@19.1.0) - '@react-stately/utils': 3.10.6(react@19.1.0) - '@react-types/color': 3.0.5(react@19.1.0) - '@react-types/shared': 3.29.1(react@19.1.0) + '@react-stately/form': 3.1.4(react@19.1.1) + '@react-stately/numberfield': 3.9.12(react@19.1.1) + '@react-stately/slider': 3.6.4(react@19.1.1) + '@react-stately/utils': 3.10.6(react@19.1.1) + '@react-types/color': 3.0.5(react@19.1.1) + '@react-types/shared': 3.29.1(react@19.1.1) '@swc/helpers': 0.5.17 - react: 19.1.0 + react: 19.1.1 - '@react-stately/combobox@3.10.5(react@19.1.0)': + '@react-stately/combobox@3.10.5(react@19.1.1)': dependencies: - '@react-stately/collections': 3.12.4(react@19.1.0) - '@react-stately/form': 3.1.4(react@19.1.0) - '@react-stately/list': 3.12.2(react@19.1.0) - '@react-stately/overlays': 3.6.16(react@19.1.0) - '@react-stately/select': 3.6.13(react@19.1.0) - '@react-stately/utils': 3.10.6(react@19.1.0) - '@react-types/combobox': 3.13.5(react@19.1.0) - '@react-types/shared': 3.29.1(react@19.1.0) + '@react-stately/collections': 3.12.4(react@19.1.1) + '@react-stately/form': 3.1.4(react@19.1.1) + '@react-stately/list': 3.12.2(react@19.1.1) + '@react-stately/overlays': 3.6.16(react@19.1.1) + '@react-stately/select': 3.6.13(react@19.1.1) + '@react-stately/utils': 3.10.6(react@19.1.1) + '@react-types/combobox': 3.13.5(react@19.1.1) + '@react-types/shared': 3.29.1(react@19.1.1) '@swc/helpers': 0.5.17 - react: 19.1.0 + react: 19.1.1 - '@react-stately/data@3.13.0(react@19.1.0)': + '@react-stately/data@3.13.0(react@19.1.1)': dependencies: - '@react-types/shared': 3.29.1(react@19.1.0) + '@react-types/shared': 3.29.1(react@19.1.1) '@swc/helpers': 0.5.17 - react: 19.1.0 + react: 19.1.1 - '@react-stately/datepicker@3.14.1(react@19.1.0)': + '@react-stately/datepicker@3.14.1(react@19.1.1)': dependencies: '@internationalized/date': 3.8.1 '@internationalized/string': 3.2.6 - '@react-stately/form': 3.1.4(react@19.1.0) - '@react-stately/overlays': 3.6.16(react@19.1.0) - '@react-stately/utils': 3.10.6(react@19.1.0) - '@react-types/datepicker': 3.12.1(react@19.1.0) - '@react-types/shared': 3.29.1(react@19.1.0) + '@react-stately/form': 3.1.4(react@19.1.1) + '@react-stately/overlays': 3.6.16(react@19.1.1) + '@react-stately/utils': 3.10.6(react@19.1.1) + '@react-types/datepicker': 3.12.1(react@19.1.1) + '@react-types/shared': 3.29.1(react@19.1.1) '@swc/helpers': 0.5.17 - react: 19.1.0 + react: 19.1.1 - '@react-stately/disclosure@3.0.4(react@19.1.0)': + '@react-stately/disclosure@3.0.4(react@19.1.1)': dependencies: - '@react-stately/utils': 3.10.6(react@19.1.0) - '@react-types/shared': 3.29.1(react@19.1.0) + '@react-stately/utils': 3.10.6(react@19.1.1) + '@react-types/shared': 3.29.1(react@19.1.1) '@swc/helpers': 0.5.17 - react: 19.1.0 + react: 19.1.1 - '@react-stately/dnd@3.5.4(react@19.1.0)': + '@react-stately/dnd@3.5.4(react@19.1.1)': dependencies: - '@react-stately/selection': 3.20.2(react@19.1.0) - '@react-types/shared': 3.29.1(react@19.1.0) + '@react-stately/selection': 3.20.2(react@19.1.1) + '@react-types/shared': 3.29.1(react@19.1.1) '@swc/helpers': 0.5.17 - react: 19.1.0 + react: 19.1.1 '@react-stately/flags@3.1.1': dependencies: '@swc/helpers': 0.5.17 - '@react-stately/form@3.1.4(react@19.1.0)': + '@react-stately/form@3.1.4(react@19.1.1)': dependencies: - '@react-types/shared': 3.29.1(react@19.1.0) + '@react-types/shared': 3.29.1(react@19.1.1) '@swc/helpers': 0.5.17 - react: 19.1.0 + react: 19.1.1 - '@react-stately/grid@3.11.2(react@19.1.0)': + '@react-stately/grid@3.11.2(react@19.1.1)': dependencies: - '@react-stately/collections': 3.12.4(react@19.1.0) - '@react-stately/selection': 3.20.2(react@19.1.0) - '@react-types/grid': 3.3.2(react@19.1.0) - '@react-types/shared': 3.29.1(react@19.1.0) + '@react-stately/collections': 3.12.4(react@19.1.1) + '@react-stately/selection': 3.20.2(react@19.1.1) + '@react-types/grid': 3.3.2(react@19.1.1) + '@react-types/shared': 3.29.1(react@19.1.1) '@swc/helpers': 0.5.17 - react: 19.1.0 + react: 19.1.1 - '@react-stately/list@3.12.2(react@19.1.0)': + '@react-stately/list@3.12.2(react@19.1.1)': dependencies: - '@react-stately/collections': 3.12.4(react@19.1.0) - '@react-stately/selection': 3.20.2(react@19.1.0) - '@react-stately/utils': 3.10.6(react@19.1.0) - '@react-types/shared': 3.29.1(react@19.1.0) + '@react-stately/collections': 3.12.4(react@19.1.1) + '@react-stately/selection': 3.20.2(react@19.1.1) + '@react-stately/utils': 3.10.6(react@19.1.1) + '@react-types/shared': 3.29.1(react@19.1.1) '@swc/helpers': 0.5.17 - react: 19.1.0 + react: 19.1.1 - '@react-stately/menu@3.9.4(react@19.1.0)': + '@react-stately/menu@3.9.4(react@19.1.1)': dependencies: - '@react-stately/overlays': 3.6.16(react@19.1.0) - '@react-types/menu': 3.10.1(react@19.1.0) - '@react-types/shared': 3.29.1(react@19.1.0) + '@react-stately/overlays': 3.6.16(react@19.1.1) + '@react-types/menu': 3.10.1(react@19.1.1) + '@react-types/shared': 3.29.1(react@19.1.1) '@swc/helpers': 0.5.17 - react: 19.1.0 + react: 19.1.1 - '@react-stately/numberfield@3.9.12(react@19.1.0)': + '@react-stately/numberfield@3.9.12(react@19.1.1)': dependencies: '@internationalized/number': 3.6.2 - '@react-stately/form': 3.1.4(react@19.1.0) - '@react-stately/utils': 3.10.6(react@19.1.0) - '@react-types/numberfield': 3.8.11(react@19.1.0) + '@react-stately/form': 3.1.4(react@19.1.1) + '@react-stately/utils': 3.10.6(react@19.1.1) + '@react-types/numberfield': 3.8.11(react@19.1.1) '@swc/helpers': 0.5.17 - react: 19.1.0 + react: 19.1.1 - '@react-stately/overlays@3.6.16(react@19.1.0)': + '@react-stately/overlays@3.6.16(react@19.1.1)': dependencies: - '@react-stately/utils': 3.10.6(react@19.1.0) - '@react-types/overlays': 3.8.15(react@19.1.0) + '@react-stately/utils': 3.10.6(react@19.1.1) + '@react-types/overlays': 3.8.15(react@19.1.1) '@swc/helpers': 0.5.17 - react: 19.1.0 + react: 19.1.1 - '@react-stately/radio@3.10.13(react@19.1.0)': + '@react-stately/radio@3.10.13(react@19.1.1)': dependencies: - '@react-stately/form': 3.1.4(react@19.1.0) - '@react-stately/utils': 3.10.6(react@19.1.0) - '@react-types/radio': 3.8.9(react@19.1.0) - '@react-types/shared': 3.29.1(react@19.1.0) + '@react-stately/form': 3.1.4(react@19.1.1) + '@react-stately/utils': 3.10.6(react@19.1.1) + '@react-types/radio': 3.8.9(react@19.1.1) + '@react-types/shared': 3.29.1(react@19.1.1) '@swc/helpers': 0.5.17 - react: 19.1.0 + react: 19.1.1 - '@react-stately/searchfield@3.5.12(react@19.1.0)': + '@react-stately/searchfield@3.5.12(react@19.1.1)': dependencies: - '@react-stately/utils': 3.10.6(react@19.1.0) - '@react-types/searchfield': 3.6.2(react@19.1.0) + '@react-stately/utils': 3.10.6(react@19.1.1) + '@react-types/searchfield': 3.6.2(react@19.1.1) '@swc/helpers': 0.5.17 - react: 19.1.0 + react: 19.1.1 - '@react-stately/select@3.6.13(react@19.1.0)': + '@react-stately/select@3.6.13(react@19.1.1)': dependencies: - '@react-stately/form': 3.1.4(react@19.1.0) - '@react-stately/list': 3.12.2(react@19.1.0) - '@react-stately/overlays': 3.6.16(react@19.1.0) - '@react-types/select': 3.9.12(react@19.1.0) - '@react-types/shared': 3.29.1(react@19.1.0) + '@react-stately/form': 3.1.4(react@19.1.1) + '@react-stately/list': 3.12.2(react@19.1.1) + '@react-stately/overlays': 3.6.16(react@19.1.1) + '@react-types/select': 3.9.12(react@19.1.1) + '@react-types/shared': 3.29.1(react@19.1.1) '@swc/helpers': 0.5.17 - react: 19.1.0 + react: 19.1.1 - '@react-stately/selection@3.20.2(react@19.1.0)': + '@react-stately/selection@3.20.2(react@19.1.1)': dependencies: - '@react-stately/collections': 3.12.4(react@19.1.0) - '@react-stately/utils': 3.10.6(react@19.1.0) - '@react-types/shared': 3.29.1(react@19.1.0) + '@react-stately/collections': 3.12.4(react@19.1.1) + '@react-stately/utils': 3.10.6(react@19.1.1) + '@react-types/shared': 3.29.1(react@19.1.1) '@swc/helpers': 0.5.17 - react: 19.1.0 + react: 19.1.1 - '@react-stately/slider@3.6.4(react@19.1.0)': + '@react-stately/slider@3.6.4(react@19.1.1)': dependencies: - '@react-stately/utils': 3.10.6(react@19.1.0) - '@react-types/shared': 3.29.1(react@19.1.0) - '@react-types/slider': 3.7.11(react@19.1.0) + '@react-stately/utils': 3.10.6(react@19.1.1) + '@react-types/shared': 3.29.1(react@19.1.1) + '@react-types/slider': 3.7.11(react@19.1.1) '@swc/helpers': 0.5.17 - react: 19.1.0 + react: 19.1.1 - '@react-stately/table@3.14.2(react@19.1.0)': + '@react-stately/table@3.14.2(react@19.1.1)': dependencies: - '@react-stately/collections': 3.12.4(react@19.1.0) + '@react-stately/collections': 3.12.4(react@19.1.1) '@react-stately/flags': 3.1.1 - '@react-stately/grid': 3.11.2(react@19.1.0) - '@react-stately/selection': 3.20.2(react@19.1.0) - '@react-stately/utils': 3.10.6(react@19.1.0) - '@react-types/grid': 3.3.2(react@19.1.0) - '@react-types/shared': 3.29.1(react@19.1.0) - '@react-types/table': 3.13.0(react@19.1.0) + '@react-stately/grid': 3.11.2(react@19.1.1) + '@react-stately/selection': 3.20.2(react@19.1.1) + '@react-stately/utils': 3.10.6(react@19.1.1) + '@react-types/grid': 3.3.2(react@19.1.1) + '@react-types/shared': 3.29.1(react@19.1.1) + '@react-types/table': 3.13.0(react@19.1.1) '@swc/helpers': 0.5.17 - react: 19.1.0 + react: 19.1.1 - '@react-stately/tabs@3.8.2(react@19.1.0)': + '@react-stately/tabs@3.8.2(react@19.1.1)': dependencies: - '@react-stately/list': 3.12.2(react@19.1.0) - '@react-types/shared': 3.29.1(react@19.1.0) - '@react-types/tabs': 3.3.15(react@19.1.0) + '@react-stately/list': 3.12.2(react@19.1.1) + '@react-types/shared': 3.29.1(react@19.1.1) + '@react-types/tabs': 3.3.15(react@19.1.1) '@swc/helpers': 0.5.17 - react: 19.1.0 + react: 19.1.1 - '@react-stately/toast@3.1.0(react@19.1.0)': + '@react-stately/toast@3.1.0(react@19.1.1)': dependencies: '@swc/helpers': 0.5.17 - react: 19.1.0 - use-sync-external-store: 1.5.0(react@19.1.0) + react: 19.1.1 + use-sync-external-store: 1.5.0(react@19.1.1) - '@react-stately/toggle@3.8.4(react@19.1.0)': + '@react-stately/toggle@3.8.4(react@19.1.1)': dependencies: - '@react-stately/utils': 3.10.6(react@19.1.0) - '@react-types/checkbox': 3.9.4(react@19.1.0) - '@react-types/shared': 3.29.1(react@19.1.0) + '@react-stately/utils': 3.10.6(react@19.1.1) + '@react-types/checkbox': 3.9.4(react@19.1.1) + '@react-types/shared': 3.29.1(react@19.1.1) '@swc/helpers': 0.5.17 - react: 19.1.0 + react: 19.1.1 - '@react-stately/tooltip@3.5.4(react@19.1.0)': + '@react-stately/tooltip@3.5.4(react@19.1.1)': dependencies: - '@react-stately/overlays': 3.6.16(react@19.1.0) - '@react-types/tooltip': 3.4.17(react@19.1.0) + '@react-stately/overlays': 3.6.16(react@19.1.1) + '@react-types/tooltip': 3.4.17(react@19.1.1) '@swc/helpers': 0.5.17 - react: 19.1.0 + react: 19.1.1 - '@react-stately/tree@3.8.10(react@19.1.0)': + '@react-stately/tree@3.8.10(react@19.1.1)': dependencies: - '@react-stately/collections': 3.12.4(react@19.1.0) - '@react-stately/selection': 3.20.2(react@19.1.0) - '@react-stately/utils': 3.10.6(react@19.1.0) - '@react-types/shared': 3.29.1(react@19.1.0) + '@react-stately/collections': 3.12.4(react@19.1.1) + '@react-stately/selection': 3.20.2(react@19.1.1) + '@react-stately/utils': 3.10.6(react@19.1.1) + '@react-types/shared': 3.29.1(react@19.1.1) '@swc/helpers': 0.5.17 - react: 19.1.0 + react: 19.1.1 - '@react-stately/utils@3.10.6(react@19.1.0)': + '@react-stately/utils@3.10.6(react@19.1.1)': dependencies: '@swc/helpers': 0.5.17 - react: 19.1.0 + react: 19.1.1 - '@react-types/breadcrumbs@3.7.13(react@19.1.0)': + '@react-types/breadcrumbs@3.7.13(react@19.1.1)': dependencies: - '@react-types/link': 3.6.1(react@19.1.0) - '@react-types/shared': 3.29.1(react@19.1.0) - react: 19.1.0 + '@react-types/link': 3.6.1(react@19.1.1) + '@react-types/shared': 3.29.1(react@19.1.1) + react: 19.1.1 - '@react-types/button@3.12.1(react@19.1.0)': + '@react-types/button@3.12.1(react@19.1.1)': dependencies: - '@react-types/shared': 3.29.1(react@19.1.0) - react: 19.1.0 + '@react-types/shared': 3.29.1(react@19.1.1) + react: 19.1.1 - '@react-types/calendar@3.7.1(react@19.1.0)': + '@react-types/calendar@3.7.1(react@19.1.1)': dependencies: '@internationalized/date': 3.8.1 - '@react-types/shared': 3.29.1(react@19.1.0) - react: 19.1.0 + '@react-types/shared': 3.29.1(react@19.1.1) + react: 19.1.1 - '@react-types/checkbox@3.9.4(react@19.1.0)': + '@react-types/checkbox@3.9.4(react@19.1.1)': dependencies: - '@react-types/shared': 3.29.1(react@19.1.0) - react: 19.1.0 + '@react-types/shared': 3.29.1(react@19.1.1) + react: 19.1.1 - '@react-types/color@3.0.5(react@19.1.0)': + '@react-types/color@3.0.5(react@19.1.1)': dependencies: - '@react-types/shared': 3.29.1(react@19.1.0) - '@react-types/slider': 3.7.11(react@19.1.0) - react: 19.1.0 + '@react-types/shared': 3.29.1(react@19.1.1) + '@react-types/slider': 3.7.11(react@19.1.1) + react: 19.1.1 - '@react-types/combobox@3.13.5(react@19.1.0)': + '@react-types/combobox@3.13.5(react@19.1.1)': dependencies: - '@react-types/shared': 3.29.1(react@19.1.0) - react: 19.1.0 + '@react-types/shared': 3.29.1(react@19.1.1) + react: 19.1.1 - '@react-types/datepicker@3.12.1(react@19.1.0)': + '@react-types/datepicker@3.12.1(react@19.1.1)': dependencies: '@internationalized/date': 3.8.1 - '@react-types/calendar': 3.7.1(react@19.1.0) - '@react-types/overlays': 3.8.15(react@19.1.0) - '@react-types/shared': 3.29.1(react@19.1.0) - react: 19.1.0 + '@react-types/calendar': 3.7.1(react@19.1.1) + '@react-types/overlays': 3.8.15(react@19.1.1) + '@react-types/shared': 3.29.1(react@19.1.1) + react: 19.1.1 - '@react-types/dialog@3.5.18(react@19.1.0)': + '@react-types/dialog@3.5.18(react@19.1.1)': dependencies: - '@react-types/overlays': 3.8.15(react@19.1.0) - '@react-types/shared': 3.29.1(react@19.1.0) - react: 19.1.0 + '@react-types/overlays': 3.8.15(react@19.1.1) + '@react-types/shared': 3.29.1(react@19.1.1) + react: 19.1.1 - '@react-types/grid@3.3.2(react@19.1.0)': + '@react-types/grid@3.3.2(react@19.1.1)': dependencies: - '@react-types/shared': 3.29.1(react@19.1.0) - react: 19.1.0 + '@react-types/shared': 3.29.1(react@19.1.1) + react: 19.1.1 - '@react-types/link@3.6.1(react@19.1.0)': + '@react-types/link@3.6.1(react@19.1.1)': dependencies: - '@react-types/shared': 3.29.1(react@19.1.0) - react: 19.1.0 + '@react-types/shared': 3.29.1(react@19.1.1) + react: 19.1.1 - '@react-types/listbox@3.7.0(react@19.1.0)': + '@react-types/listbox@3.7.0(react@19.1.1)': dependencies: - '@react-types/shared': 3.29.1(react@19.1.0) - react: 19.1.0 + '@react-types/shared': 3.29.1(react@19.1.1) + react: 19.1.1 - '@react-types/menu@3.10.1(react@19.1.0)': + '@react-types/menu@3.10.1(react@19.1.1)': dependencies: - '@react-types/overlays': 3.8.15(react@19.1.0) - '@react-types/shared': 3.29.1(react@19.1.0) - react: 19.1.0 + '@react-types/overlays': 3.8.15(react@19.1.1) + '@react-types/shared': 3.29.1(react@19.1.1) + react: 19.1.1 - '@react-types/meter@3.4.9(react@19.1.0)': + '@react-types/meter@3.4.9(react@19.1.1)': dependencies: - '@react-types/progress': 3.5.12(react@19.1.0) - react: 19.1.0 + '@react-types/progress': 3.5.12(react@19.1.1) + react: 19.1.1 - '@react-types/numberfield@3.8.11(react@19.1.0)': + '@react-types/numberfield@3.8.11(react@19.1.1)': dependencies: - '@react-types/shared': 3.29.1(react@19.1.0) - react: 19.1.0 + '@react-types/shared': 3.29.1(react@19.1.1) + react: 19.1.1 - '@react-types/overlays@3.8.15(react@19.1.0)': + '@react-types/overlays@3.8.15(react@19.1.1)': dependencies: - '@react-types/shared': 3.29.1(react@19.1.0) - react: 19.1.0 + '@react-types/shared': 3.29.1(react@19.1.1) + react: 19.1.1 - '@react-types/progress@3.5.12(react@19.1.0)': + '@react-types/progress@3.5.12(react@19.1.1)': dependencies: - '@react-types/shared': 3.29.1(react@19.1.0) - react: 19.1.0 + '@react-types/shared': 3.29.1(react@19.1.1) + react: 19.1.1 - '@react-types/radio@3.8.9(react@19.1.0)': + '@react-types/radio@3.8.9(react@19.1.1)': dependencies: - '@react-types/shared': 3.29.1(react@19.1.0) - react: 19.1.0 + '@react-types/shared': 3.29.1(react@19.1.1) + react: 19.1.1 - '@react-types/searchfield@3.6.2(react@19.1.0)': + '@react-types/searchfield@3.6.2(react@19.1.1)': dependencies: - '@react-types/shared': 3.29.1(react@19.1.0) - '@react-types/textfield': 3.12.2(react@19.1.0) - react: 19.1.0 + '@react-types/shared': 3.29.1(react@19.1.1) + '@react-types/textfield': 3.12.2(react@19.1.1) + react: 19.1.1 - '@react-types/select@3.9.12(react@19.1.0)': + '@react-types/select@3.9.12(react@19.1.1)': dependencies: - '@react-types/shared': 3.29.1(react@19.1.0) - react: 19.1.0 + '@react-types/shared': 3.29.1(react@19.1.1) + react: 19.1.1 - '@react-types/shared@3.29.1(react@19.1.0)': + '@react-types/shared@3.29.1(react@19.1.1)': dependencies: - react: 19.1.0 + react: 19.1.1 - '@react-types/slider@3.7.11(react@19.1.0)': + '@react-types/slider@3.7.11(react@19.1.1)': dependencies: - '@react-types/shared': 3.29.1(react@19.1.0) - react: 19.1.0 + '@react-types/shared': 3.29.1(react@19.1.1) + react: 19.1.1 - '@react-types/switch@3.5.11(react@19.1.0)': + '@react-types/switch@3.5.11(react@19.1.1)': dependencies: - '@react-types/shared': 3.29.1(react@19.1.0) - react: 19.1.0 + '@react-types/shared': 3.29.1(react@19.1.1) + react: 19.1.1 - '@react-types/table@3.13.0(react@19.1.0)': + '@react-types/table@3.13.0(react@19.1.1)': dependencies: - '@react-types/grid': 3.3.2(react@19.1.0) - '@react-types/shared': 3.29.1(react@19.1.0) - react: 19.1.0 + '@react-types/grid': 3.3.2(react@19.1.1) + '@react-types/shared': 3.29.1(react@19.1.1) + react: 19.1.1 - '@react-types/tabs@3.3.15(react@19.1.0)': + '@react-types/tabs@3.3.15(react@19.1.1)': dependencies: - '@react-types/shared': 3.29.1(react@19.1.0) - react: 19.1.0 + '@react-types/shared': 3.29.1(react@19.1.1) + react: 19.1.1 - '@react-types/textfield@3.12.2(react@19.1.0)': + '@react-types/textfield@3.12.2(react@19.1.1)': dependencies: - '@react-types/shared': 3.29.1(react@19.1.0) - react: 19.1.0 + '@react-types/shared': 3.29.1(react@19.1.1) + react: 19.1.1 - '@react-types/tooltip@3.4.17(react@19.1.0)': + '@react-types/tooltip@3.4.17(react@19.1.1)': dependencies: - '@react-types/overlays': 3.8.15(react@19.1.0) - '@react-types/shared': 3.29.1(react@19.1.0) - react: 19.1.0 + '@react-types/overlays': 3.8.15(react@19.1.1) + '@react-types/shared': 3.29.1(react@19.1.1) + react: 19.1.1 '@rolldown/pluginutils@1.0.0-beta.29': {} @@ -6052,7 +6163,7 @@ snapshots: '@types/better-sqlite3@7.6.13': dependencies: - '@types/node': 22.15.24 + '@types/node': 22.17.2 optional: true '@types/chai@5.2.2': @@ -6099,15 +6210,20 @@ snapshots: dependencies: undici-types: 6.21.0 - '@types/react-dom@19.1.5(@types/react@19.1.6)': + '@types/node@22.17.2': dependencies: - '@types/react': 19.1.6 + undici-types: 6.21.0 + optional: true - '@types/react-reconciler@0.28.9(@types/react@19.1.6)': + '@types/react-dom@19.1.7(@types/react@19.1.10)': dependencies: - '@types/react': 19.1.6 + '@types/react': 19.1.10 - '@types/react@19.1.6': + '@types/react-reconciler@0.28.9(@types/react@19.1.10)': + dependencies: + '@types/react': 19.1.10 + + '@types/react@19.1.10': dependencies: csstype: 3.1.3 @@ -6159,7 +6275,7 @@ snapshots: '@codemirror/state': 6.5.2 '@codemirror/view': 6.36.8 - '@uiw/react-codemirror@4.23.12(@babel/runtime@7.27.3)(@codemirror/autocomplete@6.18.2(@codemirror/language@6.11.0)(@codemirror/state@6.5.2)(@codemirror/view@6.36.8)(@lezer/common@1.2.3))(@codemirror/language@6.11.0)(@codemirror/lint@6.8.2)(@codemirror/search@6.5.7)(@codemirror/state@6.5.2)(@codemirror/theme-one-dark@6.1.2)(@codemirror/view@6.36.8)(codemirror@6.0.1(@lezer/common@1.2.3))(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': + '@uiw/react-codemirror@4.23.12(@babel/runtime@7.27.3)(@codemirror/autocomplete@6.18.2(@codemirror/language@6.11.0)(@codemirror/state@6.5.2)(@codemirror/view@6.36.8)(@lezer/common@1.2.3))(@codemirror/language@6.11.0)(@codemirror/lint@6.8.2)(@codemirror/search@6.5.7)(@codemirror/state@6.5.2)(@codemirror/theme-one-dark@6.1.2)(@codemirror/view@6.36.8)(codemirror@6.0.1(@lezer/common@1.2.3))(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': dependencies: '@babel/runtime': 7.27.3 '@codemirror/commands': 6.8.1 @@ -6168,8 +6284,8 @@ snapshots: '@codemirror/view': 6.36.8 '@uiw/codemirror-extensions-basic-setup': 4.23.12(@codemirror/autocomplete@6.18.2(@codemirror/language@6.11.0)(@codemirror/state@6.5.2)(@codemirror/view@6.36.8)(@lezer/common@1.2.3))(@codemirror/commands@6.8.1)(@codemirror/language@6.11.0)(@codemirror/lint@6.8.2)(@codemirror/search@6.5.7)(@codemirror/state@6.5.2)(@codemirror/view@6.36.8) codemirror: 6.0.1(@lezer/common@1.2.3) - react: 19.1.0 - react-dom: 19.1.0(react@19.1.0) + react: 19.1.1 + react-dom: 19.1.1(react@19.1.1) transitivePeerDependencies: - '@codemirror/autocomplete' - '@codemirror/language' @@ -6178,6 +6294,19 @@ snapshots: '@ungap/structured-clone@1.3.0': {} + '@vitejs/plugin-rsc@0.4.11(react-dom@19.1.1(react@19.1.1))(react@19.1.1)(vite@6.3.5(@types/node@22.15.24)(jiti@2.4.2)(lightningcss@1.30.1)(terser@5.39.0)(tsx@4.19.4)(yaml@2.8.0))': + dependencies: + '@mjackson/node-fetch-server': 0.7.0 + es-module-lexer: 1.7.0 + estree-walker: 3.0.3 + magic-string: 0.30.17 + periscopic: 4.0.2 + react: 19.1.1 + react-dom: 19.1.1(react@19.1.1) + turbo-stream: 3.1.0 + vite: 6.3.5(@types/node@22.15.24)(jiti@2.4.2)(lightningcss@1.30.1)(terser@5.39.0)(tsx@4.19.4)(yaml@2.8.0) + vitefu: 1.1.1(vite@6.3.5(@types/node@22.15.24)(jiti@2.4.2)(lightningcss@1.30.1)(terser@5.39.0)(tsx@4.19.4)(yaml@2.8.0)) + '@vitejs/plugin-vue@6.0.1(vite@7.1.2(@types/node@22.15.24)(jiti@2.4.2)(lightningcss@1.30.1)(terser@5.39.0)(tsx@4.19.4)(yaml@2.8.0))(vue@3.5.18(typescript@5.8.3))': dependencies: '@rolldown/pluginutils': 1.0.0-beta.29 @@ -6345,6 +6474,9 @@ snapshots: acorn@8.14.1: optional: true + acorn@8.15.0: + optional: true + agent-base@7.1.3: {} ansi-regex@5.0.1: {} @@ -6374,10 +6506,10 @@ snapshots: babel-dead-code-elimination@1.0.10: dependencies: - '@babel/core': 7.27.3 - '@babel/parser': 7.27.3 - '@babel/traverse': 7.27.3 - '@babel/types': 7.27.3 + '@babel/core': 7.28.3 + '@babel/parser': 7.28.3 + '@babel/traverse': 7.28.3 + '@babel/types': 7.28.2 transitivePeerDependencies: - supports-color @@ -6422,10 +6554,10 @@ snapshots: file-uri-to-path: 1.0.0 optional: true - bippy@0.3.14(@types/react@19.1.6)(react@19.1.0): + bippy@0.3.14(@types/react@19.1.10)(react@19.1.1): dependencies: - '@types/react-reconciler': 0.28.9(@types/react@19.1.6) - react: 19.1.0 + '@types/react-reconciler': 0.28.9(@types/react@19.1.10) + react: 19.1.1 transitivePeerDependencies: - '@types/react' @@ -6614,6 +6746,11 @@ snapshots: dependencies: once: 1.4.0 + end-of-stream@1.4.5: + dependencies: + once: 1.4.0 + optional: true + enhanced-resolve@5.18.1: dependencies: graceful-fs: 4.2.11 @@ -6756,12 +6893,6 @@ snapshots: fs-constants@1.0.0: optional: true - fs-extra@10.1.0: - dependencies: - graceful-fs: 4.2.11 - jsonfile: 6.1.0 - universalify: 2.0.1 - fsevents@2.3.2: optional: true @@ -6881,6 +7012,10 @@ snapshots: is-fullwidth-code-point@3.0.0: {} + is-reference@3.0.3: + dependencies: + '@types/estree': 1.0.8 + is-what@4.1.16: {} isbot@5.1.28: {} @@ -6921,12 +7056,6 @@ snapshots: json5@2.2.3: {} - jsonfile@6.1.0: - dependencies: - universalify: 2.0.1 - optionalDependencies: - graceful-fs: 4.2.11 - jsonpath-plus@10.3.0: dependencies: '@jsep-plugin/assignment': 1.3.0(jsep@1.4.0) @@ -7052,9 +7181,9 @@ snapshots: lru-cache@7.18.3: {} - lucide-react@0.511.0(react@19.1.0): + lucide-react@0.511.0(react@19.1.1): dependencies: - react: 19.1.0 + react: 19.1.1 magic-string@0.30.17: dependencies: @@ -7221,6 +7350,12 @@ snapshots: perfect-debounce@1.0.0: {} + periscopic@4.0.2: + dependencies: + '@types/estree': 1.0.8 + is-reference: 3.0.3 + zimmerframe: 1.1.2 + picocolors@1.1.1: {} picomatch@4.0.2: {} @@ -7258,14 +7393,14 @@ snapshots: mkdirp-classic: 0.5.3 napi-build-utils: 2.0.0 node-abi: 3.75.0 - pump: 3.0.2 + pump: 3.0.3 rc: 1.2.8 simple-get: 4.0.1 tar-fs: 2.1.3 tunnel-agent: 0.6.0 optional: true - prettier@2.8.8: {} + prettier@3.6.2: {} proc-log@3.0.0: {} @@ -7285,6 +7420,12 @@ snapshots: end-of-stream: 1.4.4 once: 1.4.0 + pump@3.0.3: + dependencies: + end-of-stream: 1.4.5 + once: 1.4.0 + optional: true + rc@1.2.8: dependencies: deep-extend: 0.6.0 @@ -7293,176 +7434,176 @@ snapshots: strip-json-comments: 2.0.1 optional: true - react-aria@3.40.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0): + react-aria@3.40.0(react-dom@19.1.1(react@19.1.1))(react@19.1.1): dependencies: '@internationalized/string': 3.2.6 - '@react-aria/breadcrumbs': 3.5.24(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - '@react-aria/button': 3.13.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - '@react-aria/calendar': 3.8.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - '@react-aria/checkbox': 3.15.5(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - '@react-aria/color': 3.0.7(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - '@react-aria/combobox': 3.12.3(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - '@react-aria/datepicker': 3.14.3(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - '@react-aria/dialog': 3.5.25(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - '@react-aria/disclosure': 3.0.5(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - '@react-aria/dnd': 3.9.3(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - '@react-aria/focus': 3.20.3(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - '@react-aria/gridlist': 3.13.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - '@react-aria/i18n': 3.12.9(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - '@react-aria/interactions': 3.25.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - '@react-aria/label': 3.7.18(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - '@react-aria/landmark': 3.0.3(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - '@react-aria/link': 3.8.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - '@react-aria/listbox': 3.14.4(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - '@react-aria/menu': 3.18.3(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - '@react-aria/meter': 3.4.23(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - '@react-aria/numberfield': 3.11.14(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - '@react-aria/overlays': 3.27.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - '@react-aria/progress': 3.4.23(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - '@react-aria/radio': 3.11.3(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - '@react-aria/searchfield': 3.8.4(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - '@react-aria/select': 3.15.5(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - '@react-aria/selection': 3.24.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - '@react-aria/separator': 3.4.9(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - '@react-aria/slider': 3.7.19(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - '@react-aria/ssr': 3.9.8(react@19.1.0) - '@react-aria/switch': 3.7.3(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - '@react-aria/table': 3.17.3(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - '@react-aria/tabs': 3.10.3(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - '@react-aria/tag': 3.6.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - '@react-aria/textfield': 3.17.3(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - '@react-aria/toast': 3.0.3(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - '@react-aria/tooltip': 3.8.3(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - '@react-aria/tree': 3.0.3(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - '@react-aria/utils': 3.29.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - '@react-aria/visually-hidden': 3.8.23(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - '@react-types/shared': 3.29.1(react@19.1.0) - react: 19.1.0 - react-dom: 19.1.0(react@19.1.0) + '@react-aria/breadcrumbs': 3.5.24(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-aria/button': 3.13.1(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-aria/calendar': 3.8.1(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-aria/checkbox': 3.15.5(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-aria/color': 3.0.7(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-aria/combobox': 3.12.3(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-aria/datepicker': 3.14.3(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-aria/dialog': 3.5.25(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-aria/disclosure': 3.0.5(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-aria/dnd': 3.9.3(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-aria/focus': 3.20.3(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-aria/gridlist': 3.13.0(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-aria/i18n': 3.12.9(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-aria/interactions': 3.25.1(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-aria/label': 3.7.18(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-aria/landmark': 3.0.3(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-aria/link': 3.8.1(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-aria/listbox': 3.14.4(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-aria/menu': 3.18.3(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-aria/meter': 3.4.23(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-aria/numberfield': 3.11.14(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-aria/overlays': 3.27.1(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-aria/progress': 3.4.23(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-aria/radio': 3.11.3(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-aria/searchfield': 3.8.4(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-aria/select': 3.15.5(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-aria/selection': 3.24.1(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-aria/separator': 3.4.9(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-aria/slider': 3.7.19(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-aria/ssr': 3.9.8(react@19.1.1) + '@react-aria/switch': 3.7.3(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-aria/table': 3.17.3(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-aria/tabs': 3.10.3(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-aria/tag': 3.6.0(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-aria/textfield': 3.17.3(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-aria/toast': 3.0.3(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-aria/tooltip': 3.8.3(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-aria/tree': 3.0.3(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-aria/utils': 3.29.0(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-aria/visually-hidden': 3.8.23(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-types/shared': 3.29.1(react@19.1.1) + react: 19.1.1 + react-dom: 19.1.1(react@19.1.1) - react-codemirror-merge@4.23.12(@babel/runtime@7.27.3)(@codemirror/autocomplete@6.18.2(@codemirror/language@6.11.0)(@codemirror/state@6.5.2)(@codemirror/view@6.36.8)(@lezer/common@1.2.3))(@codemirror/language@6.11.0)(@codemirror/lint@6.8.2)(@codemirror/search@6.5.7)(@codemirror/state@6.5.2)(@codemirror/theme-one-dark@6.1.2)(@codemirror/view@6.36.8)(codemirror@6.0.1(@lezer/common@1.2.3))(react-dom@19.1.0(react@19.1.0))(react@19.1.0): + react-codemirror-merge@4.23.12(@babel/runtime@7.27.3)(@codemirror/autocomplete@6.18.2(@codemirror/language@6.11.0)(@codemirror/state@6.5.2)(@codemirror/view@6.36.8)(@lezer/common@1.2.3))(@codemirror/language@6.11.0)(@codemirror/lint@6.8.2)(@codemirror/search@6.5.7)(@codemirror/state@6.5.2)(@codemirror/theme-one-dark@6.1.2)(@codemirror/view@6.36.8)(codemirror@6.0.1(@lezer/common@1.2.3))(react-dom@19.1.1(react@19.1.1))(react@19.1.1): dependencies: '@babel/runtime': 7.27.3 '@codemirror/merge': 6.10.1 '@codemirror/state': 6.5.2 '@codemirror/theme-one-dark': 6.1.2 '@codemirror/view': 6.36.8 - '@uiw/react-codemirror': 4.23.12(@babel/runtime@7.27.3)(@codemirror/autocomplete@6.18.2(@codemirror/language@6.11.0)(@codemirror/state@6.5.2)(@codemirror/view@6.36.8)(@lezer/common@1.2.3))(@codemirror/language@6.11.0)(@codemirror/lint@6.8.2)(@codemirror/search@6.5.7)(@codemirror/state@6.5.2)(@codemirror/theme-one-dark@6.1.2)(@codemirror/view@6.36.8)(codemirror@6.0.1(@lezer/common@1.2.3))(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@uiw/react-codemirror': 4.23.12(@babel/runtime@7.27.3)(@codemirror/autocomplete@6.18.2(@codemirror/language@6.11.0)(@codemirror/state@6.5.2)(@codemirror/view@6.36.8)(@lezer/common@1.2.3))(@codemirror/language@6.11.0)(@codemirror/lint@6.8.2)(@codemirror/search@6.5.7)(@codemirror/state@6.5.2)(@codemirror/theme-one-dark@6.1.2)(@codemirror/view@6.36.8)(codemirror@6.0.1(@lezer/common@1.2.3))(react-dom@19.1.1(react@19.1.1))(react@19.1.1) codemirror: 6.0.1(@lezer/common@1.2.3) - react: 19.1.0 - react-dom: 19.1.0(react@19.1.0) + react: 19.1.1 + react-dom: 19.1.1(react@19.1.1) transitivePeerDependencies: - '@codemirror/autocomplete' - '@codemirror/language' - '@codemirror/lint' - '@codemirror/search' - react-dom@19.1.0(react@19.1.0): + react-dom@19.1.1(react@19.1.1): dependencies: - react: 19.1.0 + react: 19.1.1 scheduler: 0.26.0 - react-error-boundary@6.0.0(react@19.1.0): + react-error-boundary@6.0.0(react@19.1.1): dependencies: '@babel/runtime': 7.27.3 - react: 19.1.0 + react: 19.1.1 - react-icons@5.5.0(react@19.1.0): + react-icons@5.5.0(react@19.1.1): dependencies: - react: 19.1.0 + react: 19.1.1 react-refresh@0.14.2: {} - react-router-dom@7.6.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0): + react-router-dom@7.8.1(react-dom@19.1.1(react@19.1.1))(react@19.1.1): dependencies: - react: 19.1.0 - react-dom: 19.1.0(react@19.1.0) - react-router: 7.6.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + react: 19.1.1 + react-dom: 19.1.1(react@19.1.1) + react-router: 7.8.1(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - react-router-hono-server@2.13.0(patch_hash=6549978df41006e07f1335bfe4ca86224ea36ed40d3f08dfef33143bad54005c)(@react-router/dev@7.6.1(@types/node@22.15.24)(jiti@2.4.2)(lightningcss@1.30.1)(react-router@7.6.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(terser@5.39.0)(tsx@4.19.4)(typescript@5.8.3)(vite@6.3.5(@types/node@22.15.24)(jiti@2.4.2)(lightningcss@1.30.1)(terser@5.39.0)(tsx@4.19.4)(yaml@2.8.0))(yaml@2.8.0))(@types/react@19.1.6)(bufferutil@4.0.9)(react-router@7.6.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(utf-8-validate@5.0.10)(vite@6.3.5(@types/node@22.15.24)(jiti@2.4.2)(lightningcss@1.30.1)(terser@5.39.0)(tsx@4.19.4)(yaml@2.8.0)): + react-router-hono-server@2.21.0(@hono/node-server@1.19.0(hono@4.7.10))(@react-router/dev@7.8.1(@types/node@22.15.24)(jiti@2.4.2)(lightningcss@1.30.1)(react-dom@19.1.1(react@19.1.1))(react-router@7.8.1(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(react@19.1.1)(terser@5.39.0)(tsx@4.19.4)(typescript@5.8.3)(vite@6.3.5(@types/node@22.15.24)(jiti@2.4.2)(lightningcss@1.30.1)(terser@5.39.0)(tsx@4.19.4)(yaml@2.8.0))(yaml@2.8.0))(@types/react@19.1.10)(bufferutil@4.0.9)(react-router@7.8.1(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(utf-8-validate@5.0.10)(vite@6.3.5(@types/node@22.15.24)(jiti@2.4.2)(lightningcss@1.30.1)(terser@5.39.0)(tsx@4.19.4)(yaml@2.8.0)): dependencies: '@drizzle-team/brocli': 0.11.0 - '@hono/node-server': 1.14.0(hono@4.7.10) - '@hono/node-ws': 1.1.1(@hono/node-server@1.14.0(hono@4.7.10))(bufferutil@4.0.9)(hono@4.7.10)(utf-8-validate@5.0.10) - '@hono/vite-dev-server': 0.19.0(hono@4.7.10) - '@react-router/dev': 7.6.1(@types/node@22.15.24)(jiti@2.4.2)(lightningcss@1.30.1)(react-router@7.6.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(terser@5.39.0)(tsx@4.19.4)(typescript@5.8.3)(vite@6.3.5(@types/node@22.15.24)(jiti@2.4.2)(lightningcss@1.30.1)(terser@5.39.0)(tsx@4.19.4)(yaml@2.8.0))(yaml@2.8.0) - '@types/react': 19.1.6 + '@hono/node-server': 1.19.0(hono@4.7.10) + '@hono/node-ws': 1.2.0(@hono/node-server@1.19.0(hono@4.7.10))(bufferutil@4.0.9)(hono@4.7.10)(utf-8-validate@5.0.10) + '@hono/vite-dev-server': 0.20.1(hono@4.7.10) + '@react-router/dev': 7.8.1(@types/node@22.15.24)(jiti@2.4.2)(lightningcss@1.30.1)(react-dom@19.1.1(react@19.1.1))(react-router@7.8.1(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(react@19.1.1)(terser@5.39.0)(tsx@4.19.4)(typescript@5.8.3)(vite@6.3.5(@types/node@22.15.24)(jiti@2.4.2)(lightningcss@1.30.1)(terser@5.39.0)(tsx@4.19.4)(yaml@2.8.0))(yaml@2.8.0) + '@types/react': 19.1.10 hono: 4.7.10 - react-router: 7.6.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + react-router: 7.8.1(react-dom@19.1.1(react@19.1.1))(react@19.1.1) vite: 6.3.5(@types/node@22.15.24)(jiti@2.4.2)(lightningcss@1.30.1)(terser@5.39.0)(tsx@4.19.4)(yaml@2.8.0) transitivePeerDependencies: - bufferutil - utf-8-validate - react-router@7.6.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0): + react-router@7.8.1(react-dom@19.1.1(react@19.1.1))(react@19.1.1): dependencies: cookie: 1.0.2 - react: 19.1.0 + react: 19.1.1 set-cookie-parser: 2.7.1 optionalDependencies: - react-dom: 19.1.0(react@19.1.0) + react-dom: 19.1.1(react@19.1.1) - react-scan@0.3.4(@types/react@19.1.6)(react-dom@19.1.0(react@19.1.0))(react-router-dom@7.6.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react-router@7.6.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react@19.1.0)(rollup@4.46.3): + react-scan@0.3.4(@types/react@19.1.10)(react-dom@19.1.1(react@19.1.1))(react-router-dom@7.8.1(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(react-router@7.8.1(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(react@19.1.1)(rollup@4.46.3): dependencies: '@babel/core': 7.27.3 '@babel/generator': 7.27.3 '@babel/types': 7.27.3 '@clack/core': 0.3.5 '@clack/prompts': 0.8.2 - '@pivanov/utils': 0.0.2(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@pivanov/utils': 0.0.2(react-dom@19.1.1(react@19.1.1))(react@19.1.1) '@preact/signals': 1.3.2(preact@10.26.7) '@rollup/pluginutils': 5.1.4(rollup@4.46.3) '@types/node': 20.17.52 - bippy: 0.3.14(@types/react@19.1.6)(react@19.1.0) + bippy: 0.3.14(@types/react@19.1.10)(react@19.1.1) esbuild: 0.25.5 estree-walker: 3.0.3 kleur: 4.1.5 mri: 1.2.0 playwright: 1.52.0 preact: 10.26.7 - react: 19.1.0 - react-dom: 19.1.0(react@19.1.0) + react: 19.1.1 + react-dom: 19.1.1(react@19.1.1) tsx: 4.19.4 optionalDependencies: - react-router: 7.6.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - react-router-dom: 7.6.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + react-router: 7.8.1(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + react-router-dom: 7.8.1(react-dom@19.1.1(react@19.1.1))(react@19.1.1) unplugin: 2.1.0 transitivePeerDependencies: - '@types/react' - rollup - supports-color - react-stately@3.38.0(react@19.1.0): + react-stately@3.38.0(react@19.1.1): dependencies: - '@react-stately/calendar': 3.8.1(react@19.1.0) - '@react-stately/checkbox': 3.6.14(react@19.1.0) - '@react-stately/collections': 3.12.4(react@19.1.0) - '@react-stately/color': 3.8.5(react@19.1.0) - '@react-stately/combobox': 3.10.5(react@19.1.0) - '@react-stately/data': 3.13.0(react@19.1.0) - '@react-stately/datepicker': 3.14.1(react@19.1.0) - '@react-stately/disclosure': 3.0.4(react@19.1.0) - '@react-stately/dnd': 3.5.4(react@19.1.0) - '@react-stately/form': 3.1.4(react@19.1.0) - '@react-stately/list': 3.12.2(react@19.1.0) - '@react-stately/menu': 3.9.4(react@19.1.0) - '@react-stately/numberfield': 3.9.12(react@19.1.0) - '@react-stately/overlays': 3.6.16(react@19.1.0) - '@react-stately/radio': 3.10.13(react@19.1.0) - '@react-stately/searchfield': 3.5.12(react@19.1.0) - '@react-stately/select': 3.6.13(react@19.1.0) - '@react-stately/selection': 3.20.2(react@19.1.0) - '@react-stately/slider': 3.6.4(react@19.1.0) - '@react-stately/table': 3.14.2(react@19.1.0) - '@react-stately/tabs': 3.8.2(react@19.1.0) - '@react-stately/toast': 3.1.0(react@19.1.0) - '@react-stately/toggle': 3.8.4(react@19.1.0) - '@react-stately/tooltip': 3.5.4(react@19.1.0) - '@react-stately/tree': 3.8.10(react@19.1.0) - '@react-types/shared': 3.29.1(react@19.1.0) - react: 19.1.0 + '@react-stately/calendar': 3.8.1(react@19.1.1) + '@react-stately/checkbox': 3.6.14(react@19.1.1) + '@react-stately/collections': 3.12.4(react@19.1.1) + '@react-stately/color': 3.8.5(react@19.1.1) + '@react-stately/combobox': 3.10.5(react@19.1.1) + '@react-stately/data': 3.13.0(react@19.1.1) + '@react-stately/datepicker': 3.14.1(react@19.1.1) + '@react-stately/disclosure': 3.0.4(react@19.1.1) + '@react-stately/dnd': 3.5.4(react@19.1.1) + '@react-stately/form': 3.1.4(react@19.1.1) + '@react-stately/list': 3.12.2(react@19.1.1) + '@react-stately/menu': 3.9.4(react@19.1.1) + '@react-stately/numberfield': 3.9.12(react@19.1.1) + '@react-stately/overlays': 3.6.16(react@19.1.1) + '@react-stately/radio': 3.10.13(react@19.1.1) + '@react-stately/searchfield': 3.5.12(react@19.1.1) + '@react-stately/select': 3.6.13(react@19.1.1) + '@react-stately/selection': 3.20.2(react@19.1.1) + '@react-stately/slider': 3.6.4(react@19.1.1) + '@react-stately/table': 3.14.2(react@19.1.1) + '@react-stately/tabs': 3.8.2(react@19.1.1) + '@react-stately/toast': 3.1.0(react@19.1.1) + '@react-stately/toggle': 3.8.4(react@19.1.1) + '@react-stately/tooltip': 3.5.4(react@19.1.1) + '@react-stately/tree': 3.8.10(react@19.1.1) + '@react-types/shared': 3.29.1(react@19.1.1) + react: 19.1.1 - react@19.1.0: {} + react@19.1.1: {} readable-stream@3.6.2: dependencies: @@ -7483,12 +7624,12 @@ snapshots: dependencies: regex-utilities: 2.3.0 - remix-utils@8.7.0(react-router@7.6.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react@19.1.0)(zod@3.25.33): + remix-utils@8.8.0(react-router@7.8.1(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(react@19.1.1)(zod@3.25.33): dependencies: type-fest: 4.41.0 optionalDependencies: - react: 19.1.0 - react-router: 7.6.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + react: 19.1.1 + react-router: 7.8.1(react-dom@19.1.1(react@19.1.1))(react@19.1.1) zod: 3.25.33 resolve-pkg-maps@1.0.0: {} @@ -7627,16 +7768,16 @@ snapshots: spdx-correct@3.2.0: dependencies: spdx-expression-parse: 3.0.1 - spdx-license-ids: 3.0.21 + spdx-license-ids: 3.0.22 spdx-exceptions@2.5.0: {} spdx-expression-parse@3.0.1: dependencies: spdx-exceptions: 2.5.0 - spdx-license-ids: 3.0.21 + spdx-license-ids: 3.0.22 - spdx-license-ids@3.0.21: {} + spdx-license-ids@3.0.22: {} speakingurl@14.0.1: {} @@ -7648,8 +7789,6 @@ snapshots: stream-buffers@3.0.3: {} - stream-slice@0.1.2: {} - streamx@2.22.0: dependencies: fast-fifo: 1.3.2 @@ -7720,7 +7859,7 @@ snapshots: dependencies: chownr: 1.1.4 mkdirp-classic: 0.5.3 - pump: 3.0.2 + pump: 3.0.3 tar-stream: 2.2.0 optional: true @@ -7737,7 +7876,7 @@ snapshots: tar-stream@2.2.0: dependencies: bl: 4.1.0 - end-of-stream: 1.4.4 + end-of-stream: 1.4.5 fs-constants: 1.0.0 inherits: 2.0.4 readable-stream: 3.6.2 @@ -7760,8 +7899,8 @@ snapshots: terser@5.39.0: dependencies: - '@jridgewell/source-map': 0.3.6 - acorn: 8.14.1 + '@jridgewell/source-map': 0.3.11 + acorn: 8.15.0 commander: 2.20.3 source-map-support: 0.5.21 optional: true @@ -7809,6 +7948,8 @@ snapshots: safe-buffer: 5.2.1 optional: true + turbo-stream@3.1.0: {} + type-fest@4.41.0: {} typescript@5.8.3: {} @@ -7817,8 +7958,6 @@ snapshots: undici-types@6.21.0: {} - undici@6.21.3: {} - undici@7.10.0: {} unist-util-is@6.0.0: @@ -7844,8 +7983,6 @@ snapshots: unist-util-is: 6.0.0 unist-util-visit-parents: 6.0.1 - universalify@2.0.1: {} - unplugin@2.1.0: dependencies: acorn: 8.14.1 @@ -7858,14 +7995,14 @@ snapshots: escalade: 3.2.0 picocolors: 1.1.1 - use-sync-external-store@1.5.0(react@19.1.0): + use-sync-external-store@1.5.0(react@19.1.1): dependencies: - react: 19.1.0 + react: 19.1.1 - usehooks-ts@3.1.1(react@19.1.0): + usehooks-ts@3.1.1(react@19.1.1): dependencies: lodash.debounce: 4.0.8 - react: 19.1.0 + react: 19.1.1 utf-8-validate@5.0.10: dependencies: @@ -7896,27 +8033,6 @@ snapshots: '@types/unist': 3.0.3 vfile-message: 4.0.3 - vite-node@3.0.0-beta.2(@types/node@22.15.24)(jiti@2.4.2)(lightningcss@1.30.1)(terser@5.39.0)(tsx@4.19.4)(yaml@2.8.0): - dependencies: - cac: 6.7.14 - debug: 4.4.1 - es-module-lexer: 1.7.0 - pathe: 1.1.2 - vite: 6.3.5(@types/node@22.15.24)(jiti@2.4.2)(lightningcss@1.30.1)(terser@5.39.0)(tsx@4.19.4)(yaml@2.8.0) - transitivePeerDependencies: - - '@types/node' - - jiti - - less - - lightningcss - - sass - - sass-embedded - - stylus - - sugarss - - supports-color - - terser - - tsx - - yaml - vite-node@3.2.4(@types/node@22.15.24)(jiti@2.4.2)(lightningcss@1.30.1)(terser@5.39.0)(tsx@4.19.4)(yaml@2.8.0): dependencies: cac: 6.7.14 @@ -7983,6 +8099,10 @@ snapshots: tsx: 4.19.4 yaml: 2.8.0 + vitefu@1.1.1(vite@6.3.5(@types/node@22.15.24)(jiti@2.4.2)(lightningcss@1.30.1)(terser@5.39.0)(tsx@4.19.4)(yaml@2.8.0)): + optionalDependencies: + vite: 6.3.5(@types/node@22.15.24)(jiti@2.4.2)(lightningcss@1.30.1)(terser@5.39.0)(tsx@4.19.4)(yaml@2.8.0) + vitepress@2.0.0-alpha.11(@types/node@22.15.24)(jiti@2.4.2)(lightningcss@1.30.1)(postcss@8.5.4)(terser@5.39.0)(tsx@4.19.4)(typescript@5.8.3)(yaml@2.8.0): dependencies: '@docsearch/css': 4.0.0-beta.7 @@ -8122,11 +8242,6 @@ snapshots: wrappy@1.0.2: {} - ws@8.18.1(bufferutil@4.0.9)(utf-8-validate@5.0.10): - optionalDependencies: - bufferutil: 4.0.9 - utf-8-validate: 5.0.10 - ws@8.18.2(bufferutil@4.0.9)(utf-8-validate@5.0.10): optionalDependencies: bufferutil: 4.0.9 @@ -8138,6 +8253,8 @@ snapshots: yaml@2.8.0: {} + zimmerframe@1.1.2: {} + zod@3.25.33: optional: true diff --git a/vite.config.ts b/vite.config.ts index 48e4f03..4f5469f 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -35,6 +35,9 @@ export default defineConfig(({ isSsrBuild }) => ({ host: server.host, port: server.port, }, + build: { + target: 'esnext', + }, ssr: { target: 'node', noExternal: isSsrBuild ? ['@libsql/client'] : undefined, From a4a037ed6815b5f04f4e54c0d3096c1dba988216 Mon Sep 17 00:00:00 2001 From: Aarnav Tale Date: Mon, 18 Aug 2025 16:18:08 -0400 Subject: [PATCH 73/97] feat: update to rolldown-vite and typescript-go --- app/routes/settings/local-agent.tsx | 42 - app/server/index.ts | 7 + package.json | 17 +- patches/react-router-hono-server.patch | 13 + pnpm-lock.yaml | 1217 ++++++++++++++++-------- tsconfig.json | 13 +- 6 files changed, 833 insertions(+), 476 deletions(-) delete mode 100644 app/routes/settings/local-agent.tsx create mode 100644 patches/react-router-hono-server.patch diff --git a/app/routes/settings/local-agent.tsx b/app/routes/settings/local-agent.tsx deleted file mode 100644 index a322246..0000000 --- a/app/routes/settings/local-agent.tsx +++ /dev/null @@ -1,42 +0,0 @@ -import { useMemo } from 'react'; -import { type LoaderFunctionArgs, useLoaderData } from 'react-router'; -import { commitSession, getSession } from '~/utils/sessions.server'; -import { queryAgent } from '~/utils/ws-agent'; -import AgentManagement from './components/agent/manage'; - -export async function loader({ request, context }: LoaderFunctionArgs) { - const { ws, wsAuthKey } = context; - const session = await getSession(request.headers.get('Cookie')); - const onboarding = session.get('agent_onboarding') ?? false; - - const nodeKey = - 'nodekey:542dad28354eb8d51e240aada7adf0222ba3ecc74af0bbd56123f03eefdb391b'; - const stats = await queryAgent([nodeKey]); - - return { - configured: wsAuthKey !== undefined, - onboarding, - stats: stats?.[nodeKey], - }; -} - -export default function Page() { - const data = useLoaderData(); - - // Whether we show the onboarding or management UI - const management = useMemo(() => { - return data.configured && data.onboarding === false; - }, [data.configured, data.onboarding]); - - return ( -
- {management ? ( - - ) : ( -
-

Local Agent Coming Soon

-
- )} -
- ); -} diff --git a/app/server/index.ts b/app/server/index.ts index 1235b12..e169e95 100644 --- a/app/server/index.ts +++ b/app/server/index.ts @@ -78,6 +78,13 @@ export default createHonoServer({ overrideGlobalObjects: true, port: config.server.port, hostname: config.server.host, + serveStaticOptions: { + clientAssets: { + // This is part of our monkey-patch for react-router-hono-server + // To see the first part, go to the patches/ directory. + rewriteRequestPath: (path) => path.replace(`${__PREFIX__}`, ''), + }, + }, // Only log in development mode defaultLogger: import.meta.env.DEV, diff --git a/package.json b/package.json index 82da29b..7600d17 100644 --- a/package.json +++ b/package.json @@ -9,7 +9,7 @@ "build": "react-router build", "dev": "HEADPLANE_LOAD_ENV_OVERRIDES=true HEADPLANE_CONFIG_PATH=./config.example.yaml react-router dev", "start": "node build/server/index.js", - "typecheck": "tsc", + "typecheck": "react-router typegen && tsgo", "test": "vitest run", "docs:dev": "vitepress dev docs", "docs:build": "vitepress build docs", @@ -66,9 +66,10 @@ "devDependencies": { "@biomejs/biome": "^1.9.4", "@react-router/dev": "^7.8.1", - "@tailwindcss/vite": "^4.1.8", - "@types/node": "^22.15.17", + "@tailwindcss/vite": "^4.1.12", + "@types/node": "^24.3.0", "@types/websocket": "^1.0.10", + "@typescript/native-preview": "7.0.0-dev.20250818.1", "drizzle-kit": "^0.31.1", "js-yaml": "^4.1.0", "lefthook": "^1.11.13", @@ -78,8 +79,8 @@ "tailwindcss": "^4.1.8", "tailwindcss-animate": "^1.0.7", "tailwindcss-react-aria-components": "^2.0.0", - "typescript": "^5.8.3", - "vite": "^6.3.5", + "typescript": "^5.9.2", + "vite": "npm:rolldown-vite@latest", "vite-tsconfig-paths": "^5.1.4", "vitepress": "next", "vitest": "^3.1.3" @@ -96,9 +97,13 @@ }, "onlyBuiltDependencies": [ "@biomejs/biome", + "@tailwindcss/oxide", "better-sqlite3", "esbuild", "lefthook" - ] + ], + "patchedDependencies": { + "react-router-hono-server": "patches/react-router-hono-server.patch" + } } } diff --git a/patches/react-router-hono-server.patch b/patches/react-router-hono-server.patch new file mode 100644 index 0000000..328e9e4 --- /dev/null +++ b/patches/react-router-hono-server.patch @@ -0,0 +1,13 @@ +diff --git a/dist/adapters/node.js b/dist/adapters/node.js +index ec81622898d583ddd8c2f4d9eaf5c0ed23776475..047ef1d45ad82e4b476a882b4ab5c8962faa0df1 100644 +--- a/dist/adapters/node.js ++++ b/dist/adapters/node.js +@@ -49,7 +49,7 @@ async function createHonoServer(options) { + } + await mergedOptions.beforeAll?.(app); + app.use( +- `/${import.meta.env.REACT_ROUTER_HONO_SERVER_ASSETS_DIR}/*`, ++ `${import.meta.env.REACT_ROUTER_HONO_SERVER_BASENAME}${import.meta.env.REACT_ROUTER_HONO_SERVER_ASSETS_DIR}/*`, + cache(60 * 60 * 24 * 365), + // 1 year + serveStatic({ root: clientBuildPath, ...mergedOptions.serveStaticOptions?.clientAssets }) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 62c7736..4a2a9e7 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -4,6 +4,11 @@ settings: autoInstallPeers: true excludeLinksFromLockfile: false +patchedDependencies: + react-router-hono-server: + hash: b68723a36649e2c3bd9e9edcfff3a8bc0fdb48e1cf6d64eba37c04f7bfbb1417 + path: patches/react-router-hono-server.patch + importers: .: @@ -40,7 +45,7 @@ importers: version: 3.0.3(react-dom@19.1.1(react@19.1.1))(react@19.1.1) '@react-router/node': specifier: ^7.8.1 - version: 7.8.1(react-router@7.8.1(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(typescript@5.8.3) + version: 7.8.1(react-router@7.8.1(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(typescript@5.9.2) '@react-stately/toast': specifier: 3.1.0 version: 3.1.0(react@19.1.1) @@ -127,7 +132,7 @@ importers: version: 7.8.1(react-dom@19.1.1(react@19.1.1))(react@19.1.1) react-router-hono-server: specifier: 2.21.0 - version: 2.21.0(@hono/node-server@1.19.0(hono@4.7.10))(@react-router/dev@7.8.1(@types/node@22.15.24)(jiti@2.4.2)(lightningcss@1.30.1)(react-dom@19.1.1(react@19.1.1))(react-router@7.8.1(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(react@19.1.1)(terser@5.39.0)(tsx@4.19.4)(typescript@5.8.3)(vite@6.3.5(@types/node@22.15.24)(jiti@2.4.2)(lightningcss@1.30.1)(terser@5.39.0)(tsx@4.19.4)(yaml@2.8.0))(yaml@2.8.0))(@types/react@19.1.10)(bufferutil@4.0.9)(react-router@7.8.1(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(utf-8-validate@5.0.10)(vite@6.3.5(@types/node@22.15.24)(jiti@2.4.2)(lightningcss@1.30.1)(terser@5.39.0)(tsx@4.19.4)(yaml@2.8.0)) + version: 2.21.0(patch_hash=b68723a36649e2c3bd9e9edcfff3a8bc0fdb48e1cf6d64eba37c04f7bfbb1417)(@hono/node-server@1.19.0(hono@4.7.10))(@react-router/dev@7.8.1(@types/node@24.3.0)(jiti@2.5.1)(lightningcss@1.30.1)(react-dom@19.1.1(react@19.1.1))(react-router@7.8.1(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(react@19.1.1)(rolldown-vite@7.1.3(@types/node@24.3.0)(esbuild@0.25.5)(jiti@2.5.1)(terser@5.39.0)(tsx@4.19.4)(yaml@2.8.0))(terser@5.39.0)(tsx@4.19.4)(typescript@5.9.2)(yaml@2.8.0))(@types/react@19.1.10)(bufferutil@4.0.9)(react-router@7.8.1(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(rolldown-vite@7.1.3(@types/node@24.3.0)(esbuild@0.25.5)(jiti@2.5.1)(terser@5.39.0)(tsx@4.19.4)(yaml@2.8.0))(utf-8-validate@5.0.10) react-stately: specifier: ^3.38.0 version: 3.38.0(react@19.1.1) @@ -152,16 +157,19 @@ importers: version: 1.9.4 '@react-router/dev': specifier: ^7.8.1 - version: 7.8.1(@types/node@22.15.24)(jiti@2.4.2)(lightningcss@1.30.1)(react-dom@19.1.1(react@19.1.1))(react-router@7.8.1(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(react@19.1.1)(terser@5.39.0)(tsx@4.19.4)(typescript@5.8.3)(vite@6.3.5(@types/node@22.15.24)(jiti@2.4.2)(lightningcss@1.30.1)(terser@5.39.0)(tsx@4.19.4)(yaml@2.8.0))(yaml@2.8.0) + version: 7.8.1(@types/node@24.3.0)(jiti@2.5.1)(lightningcss@1.30.1)(react-dom@19.1.1(react@19.1.1))(react-router@7.8.1(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(react@19.1.1)(rolldown-vite@7.1.3(@types/node@24.3.0)(esbuild@0.25.5)(jiti@2.5.1)(terser@5.39.0)(tsx@4.19.4)(yaml@2.8.0))(terser@5.39.0)(tsx@4.19.4)(typescript@5.9.2)(yaml@2.8.0) '@tailwindcss/vite': - specifier: ^4.1.8 - version: 4.1.8(vite@6.3.5(@types/node@22.15.24)(jiti@2.4.2)(lightningcss@1.30.1)(terser@5.39.0)(tsx@4.19.4)(yaml@2.8.0)) + specifier: ^4.1.12 + version: 4.1.12(rolldown-vite@7.1.3(@types/node@24.3.0)(esbuild@0.25.5)(jiti@2.5.1)(terser@5.39.0)(tsx@4.19.4)(yaml@2.8.0)) '@types/node': - specifier: ^22.15.17 - version: 22.15.24 + specifier: ^24.3.0 + version: 24.3.0 '@types/websocket': specifier: ^1.0.10 version: 1.0.10 + '@typescript/native-preview': + specifier: 7.0.0-dev.20250818.1 + version: 7.0.0-dev.20250818.1 drizzle-kit: specifier: ^0.31.1 version: 0.31.1 @@ -190,20 +198,20 @@ importers: specifier: ^2.0.0 version: 2.0.0(tailwindcss@4.1.8) typescript: - specifier: ^5.8.3 - version: 5.8.3 + specifier: ^5.9.2 + version: 5.9.2 vite: - specifier: ^6.3.5 - version: 6.3.5(@types/node@22.15.24)(jiti@2.4.2)(lightningcss@1.30.1)(terser@5.39.0)(tsx@4.19.4)(yaml@2.8.0) + specifier: npm:rolldown-vite@latest + version: rolldown-vite@7.1.3(@types/node@24.3.0)(esbuild@0.25.5)(jiti@2.5.1)(terser@5.39.0)(tsx@4.19.4)(yaml@2.8.0) vite-tsconfig-paths: specifier: ^5.1.4 - version: 5.1.4(typescript@5.8.3)(vite@6.3.5(@types/node@22.15.24)(jiti@2.4.2)(lightningcss@1.30.1)(terser@5.39.0)(tsx@4.19.4)(yaml@2.8.0)) + version: 5.1.4(rolldown-vite@7.1.3(@types/node@24.3.0)(esbuild@0.25.5)(jiti@2.5.1)(terser@5.39.0)(tsx@4.19.4)(yaml@2.8.0))(typescript@5.9.2) vitepress: specifier: next - version: 2.0.0-alpha.11(@types/node@22.15.24)(jiti@2.4.2)(lightningcss@1.30.1)(postcss@8.5.4)(terser@5.39.0)(tsx@4.19.4)(typescript@5.8.3)(yaml@2.8.0) + version: 2.0.0-alpha.11(@types/node@24.3.0)(jiti@2.5.1)(lightningcss@1.30.1)(postcss@8.5.4)(terser@5.39.0)(tsx@4.19.4)(typescript@5.9.2)(yaml@2.8.0) vitest: specifier: ^3.1.3 - version: 3.2.4(@types/node@22.15.24)(jiti@2.4.2)(lightningcss@1.30.1)(terser@5.39.0)(tsx@4.19.4)(yaml@2.8.0) + version: 3.2.4(@types/node@24.3.0)(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.39.0)(tsx@4.19.4)(yaml@2.8.0) packages: @@ -517,6 +525,15 @@ packages: '@drizzle-team/brocli@0.11.0': resolution: {integrity: sha512-hD3pekGiPg0WPCCGAZmusBBJsDqGUR66Y452YgQsZOnkdQ7ViEPKuyP4huUGEZQefp8g34RRodXYmJ2TbCH+tg==} + '@emnapi/core@1.4.5': + resolution: {integrity: sha512-XsLw1dEOpkSX/WucdqUhPWP7hDxSvZiY+fsUC14h+FtQ2Ifni4znbBt8punRX+Uj2JG/uDb8nEHVKvrVlvdZ5Q==} + + '@emnapi/runtime@1.4.5': + resolution: {integrity: sha512-++LApOtY0pEEz1zrd9vy1/zXVaVJJ/EbAF3u0fXIzPJEDtnITsBGbbK0EkM72amhl/R5b+5xx0Y/QhcVOpuulg==} + + '@emnapi/wasi-threads@1.0.4': + resolution: {integrity: sha512-PJR+bOmMOPH8AtcTGAyYNiuJ3/Fcoj2XN/gBEWzDIKh254XO+mM9XoXHk5GNEhodxeMznbg7BlRojVbKN+gC6g==} + '@esbuild-kit/core-utils@3.3.2': resolution: {integrity: sha512-sPRAnw9CdSsRmEtnsl2WXWdyquogVpB3yZ3dgwJfe8zrOzTsV7cJvmwrKVa+0ma5BoiGJ+BoqkMvawbayKUsqQ==} deprecated: 'Merged into tsx: https://tsx.is' @@ -531,6 +548,12 @@ packages: cpu: [ppc64] os: [aix] + '@esbuild/aix-ppc64@0.25.9': + resolution: {integrity: sha512-OaGtL73Jck6pBKjNIe24BnFE6agGl+6KxDtTfHhy1HmhthfKouEcOhqpSL64K4/0WCtbKFLOdzD/44cJ4k9opA==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [aix] + '@esbuild/android-arm64@0.18.20': resolution: {integrity: sha512-Nz4rJcchGDtENV0eMKUNa6L12zz2zBDXuhj/Vjh18zGqB44Bi7MBMSXjgunJgjRhCmKOjnPuZp4Mb6OKqtMHLQ==} engines: {node: '>=12'} @@ -543,6 +566,12 @@ packages: cpu: [arm64] os: [android] + '@esbuild/android-arm64@0.25.9': + resolution: {integrity: sha512-IDrddSmpSv51ftWslJMvl3Q2ZT98fUSL2/rlUXuVqRXHCs5EUF1/f+jbjF5+NG9UffUDMCiTyh8iec7u8RlTLg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [android] + '@esbuild/android-arm@0.18.20': resolution: {integrity: sha512-fyi7TDI/ijKKNZTUJAQqiG5T7YjJXgnzkURqmGj13C6dCqckZBLdl4h7bkhHt/t0WP+zO9/zwroDvANaOqO5Sw==} engines: {node: '>=12'} @@ -555,6 +584,12 @@ packages: cpu: [arm] os: [android] + '@esbuild/android-arm@0.25.9': + resolution: {integrity: sha512-5WNI1DaMtxQ7t7B6xa572XMXpHAaI/9Hnhk8lcxF4zVN4xstUgTlvuGDorBguKEnZO70qwEcLpfifMLoxiPqHQ==} + engines: {node: '>=18'} + cpu: [arm] + os: [android] + '@esbuild/android-x64@0.18.20': resolution: {integrity: sha512-8GDdlePJA8D6zlZYJV/jnrRAi6rOiNaCC/JclcXpB+KIuvfBN4owLtgzY2bsxnx666XjJx2kDPUmnTtR8qKQUg==} engines: {node: '>=12'} @@ -567,6 +602,12 @@ packages: cpu: [x64] os: [android] + '@esbuild/android-x64@0.25.9': + resolution: {integrity: sha512-I853iMZ1hWZdNllhVZKm34f4wErd4lMyeV7BLzEExGEIZYsOzqDWDf+y082izYUE8gtJnYHdeDpN/6tUdwvfiw==} + engines: {node: '>=18'} + cpu: [x64] + os: [android] + '@esbuild/darwin-arm64@0.18.20': resolution: {integrity: sha512-bxRHW5kHU38zS2lPTPOyuyTm+S+eobPUnTNkdJEfAddYgEcll4xkT8DB9d2008DtTbl7uJag2HuE5NZAZgnNEA==} engines: {node: '>=12'} @@ -579,6 +620,12 @@ packages: cpu: [arm64] os: [darwin] + '@esbuild/darwin-arm64@0.25.9': + resolution: {integrity: sha512-XIpIDMAjOELi/9PB30vEbVMs3GV1v2zkkPnuyRRURbhqjyzIINwj+nbQATh4H9GxUgH1kFsEyQMxwiLFKUS6Rg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [darwin] + '@esbuild/darwin-x64@0.18.20': resolution: {integrity: sha512-pc5gxlMDxzm513qPGbCbDukOdsGtKhfxD1zJKXjCCcU7ju50O7MeAZ8c4krSJcOIJGFR+qx21yMMVYwiQvyTyQ==} engines: {node: '>=12'} @@ -591,6 +638,12 @@ packages: cpu: [x64] os: [darwin] + '@esbuild/darwin-x64@0.25.9': + resolution: {integrity: sha512-jhHfBzjYTA1IQu8VyrjCX4ApJDnH+ez+IYVEoJHeqJm9VhG9Dh2BYaJritkYK3vMaXrf7Ogr/0MQ8/MeIefsPQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [darwin] + '@esbuild/freebsd-arm64@0.18.20': resolution: {integrity: sha512-yqDQHy4QHevpMAaxhhIwYPMv1NECwOvIpGCZkECn8w2WFHXjEwrBn3CeNIYsibZ/iZEUemj++M26W3cNR5h+Tw==} engines: {node: '>=12'} @@ -603,6 +656,12 @@ packages: cpu: [arm64] os: [freebsd] + '@esbuild/freebsd-arm64@0.25.9': + resolution: {integrity: sha512-z93DmbnY6fX9+KdD4Ue/H6sYs+bhFQJNCPZsi4XWJoYblUqT06MQUdBCpcSfuiN72AbqeBFu5LVQTjfXDE2A6Q==} + engines: {node: '>=18'} + cpu: [arm64] + os: [freebsd] + '@esbuild/freebsd-x64@0.18.20': resolution: {integrity: sha512-tgWRPPuQsd3RmBZwarGVHZQvtzfEBOreNuxEMKFcd5DaDn2PbBxfwLcj4+aenoh7ctXcbXmOQIn8HI6mCSw5MQ==} engines: {node: '>=12'} @@ -615,6 +674,12 @@ packages: cpu: [x64] os: [freebsd] + '@esbuild/freebsd-x64@0.25.9': + resolution: {integrity: sha512-mrKX6H/vOyo5v71YfXWJxLVxgy1kyt1MQaD8wZJgJfG4gq4DpQGpgTB74e5yBeQdyMTbgxp0YtNj7NuHN0PoZg==} + engines: {node: '>=18'} + cpu: [x64] + os: [freebsd] + '@esbuild/linux-arm64@0.18.20': resolution: {integrity: sha512-2YbscF+UL7SQAVIpnWvYwM+3LskyDmPhe31pE7/aoTMFKKzIc9lLbyGUpmmb8a8AixOL61sQ/mFh3jEjHYFvdA==} engines: {node: '>=12'} @@ -627,6 +692,12 @@ packages: cpu: [arm64] os: [linux] + '@esbuild/linux-arm64@0.25.9': + resolution: {integrity: sha512-BlB7bIcLT3G26urh5Dmse7fiLmLXnRlopw4s8DalgZ8ef79Jj4aUcYbk90g8iCa2467HX8SAIidbL7gsqXHdRw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [linux] + '@esbuild/linux-arm@0.18.20': resolution: {integrity: sha512-/5bHkMWnq1EgKr1V+Ybz3s1hWXok7mDFUMQ4cG10AfW3wL02PSZi5kFpYKrptDsgb2WAJIvRcDm+qIvXf/apvg==} engines: {node: '>=12'} @@ -639,6 +710,12 @@ packages: cpu: [arm] os: [linux] + '@esbuild/linux-arm@0.25.9': + resolution: {integrity: sha512-HBU2Xv78SMgaydBmdor38lg8YDnFKSARg1Q6AT0/y2ezUAKiZvc211RDFHlEZRFNRVhcMamiToo7bDx3VEOYQw==} + engines: {node: '>=18'} + cpu: [arm] + os: [linux] + '@esbuild/linux-ia32@0.18.20': resolution: {integrity: sha512-P4etWwq6IsReT0E1KHU40bOnzMHoH73aXp96Fs8TIT6z9Hu8G6+0SHSw9i2isWrD2nbx2qo5yUqACgdfVGx7TA==} engines: {node: '>=12'} @@ -651,6 +728,12 @@ packages: cpu: [ia32] os: [linux] + '@esbuild/linux-ia32@0.25.9': + resolution: {integrity: sha512-e7S3MOJPZGp2QW6AK6+Ly81rC7oOSerQ+P8L0ta4FhVi+/j/v2yZzx5CqqDaWjtPFfYz21Vi1S0auHrap3Ma3A==} + engines: {node: '>=18'} + cpu: [ia32] + os: [linux] + '@esbuild/linux-loong64@0.18.20': resolution: {integrity: sha512-nXW8nqBTrOpDLPgPY9uV+/1DjxoQ7DoB2N8eocyq8I9XuqJ7BiAMDMf9n1xZM9TgW0J8zrquIb/A7s3BJv7rjg==} engines: {node: '>=12'} @@ -663,6 +746,12 @@ packages: cpu: [loong64] os: [linux] + '@esbuild/linux-loong64@0.25.9': + resolution: {integrity: sha512-Sbe10Bnn0oUAB2AalYztvGcK+o6YFFA/9829PhOCUS9vkJElXGdphz0A3DbMdP8gmKkqPmPcMJmJOrI3VYB1JQ==} + engines: {node: '>=18'} + cpu: [loong64] + os: [linux] + '@esbuild/linux-mips64el@0.18.20': resolution: {integrity: sha512-d5NeaXZcHp8PzYy5VnXV3VSd2D328Zb+9dEq5HE6bw6+N86JVPExrA6O68OPwobntbNJ0pzCpUFZTo3w0GyetQ==} engines: {node: '>=12'} @@ -675,6 +764,12 @@ packages: cpu: [mips64el] os: [linux] + '@esbuild/linux-mips64el@0.25.9': + resolution: {integrity: sha512-YcM5br0mVyZw2jcQeLIkhWtKPeVfAerES5PvOzaDxVtIyZ2NUBZKNLjC5z3/fUlDgT6w89VsxP2qzNipOaaDyA==} + engines: {node: '>=18'} + cpu: [mips64el] + os: [linux] + '@esbuild/linux-ppc64@0.18.20': resolution: {integrity: sha512-WHPyeScRNcmANnLQkq6AfyXRFr5D6N2sKgkFo2FqguP44Nw2eyDlbTdZwd9GYk98DZG9QItIiTlFLHJHjxP3FA==} engines: {node: '>=12'} @@ -687,6 +782,12 @@ packages: cpu: [ppc64] os: [linux] + '@esbuild/linux-ppc64@0.25.9': + resolution: {integrity: sha512-++0HQvasdo20JytyDpFvQtNrEsAgNG2CY1CLMwGXfFTKGBGQT3bOeLSYE2l1fYdvML5KUuwn9Z8L1EWe2tzs1w==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [linux] + '@esbuild/linux-riscv64@0.18.20': resolution: {integrity: sha512-WSxo6h5ecI5XH34KC7w5veNnKkju3zBRLEQNY7mv5mtBmrP/MjNBCAlsM2u5hDBlS3NGcTQpoBvRzqBcRtpq1A==} engines: {node: '>=12'} @@ -699,6 +800,12 @@ packages: cpu: [riscv64] os: [linux] + '@esbuild/linux-riscv64@0.25.9': + resolution: {integrity: sha512-uNIBa279Y3fkjV+2cUjx36xkx7eSjb8IvnL01eXUKXez/CBHNRw5ekCGMPM0BcmqBxBcdgUWuUXmVWwm4CH9kg==} + engines: {node: '>=18'} + cpu: [riscv64] + os: [linux] + '@esbuild/linux-s390x@0.18.20': resolution: {integrity: sha512-+8231GMs3mAEth6Ja1iK0a1sQ3ohfcpzpRLH8uuc5/KVDFneH6jtAJLFGafpzpMRO6DzJ6AvXKze9LfFMrIHVQ==} engines: {node: '>=12'} @@ -711,6 +818,12 @@ packages: cpu: [s390x] os: [linux] + '@esbuild/linux-s390x@0.25.9': + resolution: {integrity: sha512-Mfiphvp3MjC/lctb+7D287Xw1DGzqJPb/J2aHHcHxflUo+8tmN/6d4k6I2yFR7BVo5/g7x2Monq4+Yew0EHRIA==} + engines: {node: '>=18'} + cpu: [s390x] + os: [linux] + '@esbuild/linux-x64@0.18.20': resolution: {integrity: sha512-UYqiqemphJcNsFEskc73jQ7B9jgwjWrSayxawS6UVFZGWrAAtkzjxSqnoclCXxWtfwLdzU+vTpcNYhpn43uP1w==} engines: {node: '>=12'} @@ -723,12 +836,24 @@ packages: cpu: [x64] os: [linux] + '@esbuild/linux-x64@0.25.9': + resolution: {integrity: sha512-iSwByxzRe48YVkmpbgoxVzn76BXjlYFXC7NvLYq+b+kDjyyk30J0JY47DIn8z1MO3K0oSl9fZoRmZPQI4Hklzg==} + engines: {node: '>=18'} + cpu: [x64] + os: [linux] + '@esbuild/netbsd-arm64@0.25.5': resolution: {integrity: sha512-pwHtMP9viAy1oHPvgxtOv+OkduK5ugofNTVDilIzBLpoWAM16r7b/mxBvfpuQDpRQFMfuVr5aLcn4yveGvBZvw==} engines: {node: '>=18'} cpu: [arm64] os: [netbsd] + '@esbuild/netbsd-arm64@0.25.9': + resolution: {integrity: sha512-9jNJl6FqaUG+COdQMjSCGW4QiMHH88xWbvZ+kRVblZsWrkXlABuGdFJ1E9L7HK+T0Yqd4akKNa/lO0+jDxQD4Q==} + engines: {node: '>=18'} + cpu: [arm64] + os: [netbsd] + '@esbuild/netbsd-x64@0.18.20': resolution: {integrity: sha512-iO1c++VP6xUBUmltHZoMtCUdPlnPGdBom6IrO4gyKPFFVBKioIImVooR5I83nTew5UOYrk3gIJhbZh8X44y06A==} engines: {node: '>=12'} @@ -741,12 +866,24 @@ packages: cpu: [x64] os: [netbsd] + '@esbuild/netbsd-x64@0.25.9': + resolution: {integrity: sha512-RLLdkflmqRG8KanPGOU7Rpg829ZHu8nFy5Pqdi9U01VYtG9Y0zOG6Vr2z4/S+/3zIyOxiK6cCeYNWOFR9QP87g==} + engines: {node: '>=18'} + cpu: [x64] + os: [netbsd] + '@esbuild/openbsd-arm64@0.25.5': resolution: {integrity: sha512-7A208+uQKgTxHd0G0uqZO8UjK2R0DDb4fDmERtARjSHWxqMTye4Erz4zZafx7Di9Cv+lNHYuncAkiGFySoD+Mw==} engines: {node: '>=18'} cpu: [arm64] os: [openbsd] + '@esbuild/openbsd-arm64@0.25.9': + resolution: {integrity: sha512-YaFBlPGeDasft5IIM+CQAhJAqS3St3nJzDEgsgFixcfZeyGPCd6eJBWzke5piZuZ7CtL656eOSYKk4Ls2C0FRQ==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openbsd] + '@esbuild/openbsd-x64@0.18.20': resolution: {integrity: sha512-e5e4YSsuQfX4cxcygw/UCPIEP6wbIL+se3sxPdCiMbFLBWu0eiZOJ7WoD+ptCLrmjZBK1Wk7I6D/I3NglUGOxg==} engines: {node: '>=12'} @@ -759,6 +896,18 @@ packages: cpu: [x64] os: [openbsd] + '@esbuild/openbsd-x64@0.25.9': + resolution: {integrity: sha512-1MkgTCuvMGWuqVtAvkpkXFmtL8XhWy+j4jaSO2wxfJtilVCi0ZE37b8uOdMItIHz4I6z1bWWtEX4CJwcKYLcuA==} + engines: {node: '>=18'} + cpu: [x64] + os: [openbsd] + + '@esbuild/openharmony-arm64@0.25.9': + resolution: {integrity: sha512-4Xd0xNiMVXKh6Fa7HEJQbrpP3m3DDn43jKxMjxLLRjWnRsfxjORYJlXPO4JNcXtOyfajXorRKY9NkOpTHptErg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openharmony] + '@esbuild/sunos-x64@0.18.20': resolution: {integrity: sha512-kDbFRFp0YpTQVVrqUd5FTYmWo45zGaXe0X8E1G/LKFC0v8x0vWrhOWSLITcCn63lmZIxfOMXtCfti/RxN/0wnQ==} engines: {node: '>=12'} @@ -771,6 +920,12 @@ packages: cpu: [x64] os: [sunos] + '@esbuild/sunos-x64@0.25.9': + resolution: {integrity: sha512-WjH4s6hzo00nNezhp3wFIAfmGZ8U7KtrJNlFMRKxiI9mxEK1scOMAaa9i4crUtu+tBr+0IN6JCuAcSBJZfnphw==} + engines: {node: '>=18'} + cpu: [x64] + os: [sunos] + '@esbuild/win32-arm64@0.18.20': resolution: {integrity: sha512-ddYFR6ItYgoaq4v4JmQQaAI5s7npztfV4Ag6NrhiaW0RrnOXqBkgwZLofVTlq1daVTQNhtI5oieTvkRPfZrePg==} engines: {node: '>=12'} @@ -783,6 +938,12 @@ packages: cpu: [arm64] os: [win32] + '@esbuild/win32-arm64@0.25.9': + resolution: {integrity: sha512-mGFrVJHmZiRqmP8xFOc6b84/7xa5y5YvR1x8djzXpJBSv/UsNK6aqec+6JDjConTgvvQefdGhFDAs2DLAds6gQ==} + engines: {node: '>=18'} + cpu: [arm64] + os: [win32] + '@esbuild/win32-ia32@0.18.20': resolution: {integrity: sha512-Wv7QBi3ID/rROT08SABTS7eV4hX26sVduqDOTe1MvGMjNd3EjOz4b7zeexIR62GTIEKrfJXKL9LFxTYgkyeu7g==} engines: {node: '>=12'} @@ -795,6 +956,12 @@ packages: cpu: [ia32] os: [win32] + '@esbuild/win32-ia32@0.25.9': + resolution: {integrity: sha512-b33gLVU2k11nVx1OhX3C8QQP6UHQK4ZtN56oFWvVXvz2VkDoe6fbG8TOgHFxEvqeqohmRnIHe5A1+HADk4OQww==} + engines: {node: '>=18'} + cpu: [ia32] + os: [win32] + '@esbuild/win32-x64@0.18.20': resolution: {integrity: sha512-kTdfRcSiDfQca/y9QIkng02avJ+NCaQvrMejlsB3RRv5sE9rRoeBPISaZpKxHELzRxZyLvNts1P27W3wV+8geQ==} engines: {node: '>=12'} @@ -807,6 +974,12 @@ packages: cpu: [x64] os: [win32] + '@esbuild/win32-x64@0.25.9': + resolution: {integrity: sha512-PPOl1mi6lpLNQxnGoyAfschAodRFYXJ+9fs6WHXz7CSWKbOqiMZsubC+BQsVKuul+3vKLuwTHsS2c2y9EoKwxQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [win32] + '@faker-js/faker@9.8.0': resolution: {integrity: sha512-U9wpuSrJC93jZBxx/Qq2wPjCuYISBueyVUGK7qqdmj7r/nxaxwW8AQDCLeRO7wZnjj94sh3p246cAYjUKuqgfg==} engines: {node: '>=18.0.0', npm: '>=9.0.0'} @@ -888,6 +1061,9 @@ packages: resolution: {integrity: sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==} engines: {node: '>=6.0.0'} + '@jridgewell/remapping@2.3.5': + resolution: {integrity: sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==} + '@jridgewell/resolve-uri@3.1.2': resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} engines: {node: '>=6.0.0'} @@ -1008,6 +1184,9 @@ packages: '@mjackson/node-fetch-server@0.7.0': resolution: {integrity: sha512-un8diyEBKU3BTVj3GzlTPA1kIjCkGdD+AMYQy31Gf9JCkfoZzwgJ79GUtHrF2BN3XPNMLpubbzPcxys+a3uZEw==} + '@napi-rs/wasm-runtime@1.0.3': + resolution: {integrity: sha512-rZxtMsLwjdXkMUGC3WwsPwLNVqVqnTJT6MNIB6e+5fhMcSCPP0AOsNWuMQ5mdCq6HNjs/ZeWAEchpqeprqBD2Q==} + '@neon-rs/load@0.0.4': resolution: {integrity: sha512-kTPhdZyTQxB+2wpiRcFWrDcejc4JI6tkPuS7UZCG4l6Zvc5kU/gGQ/ozvHTh1XR5tS+UlfAfGuPajjzQjCiHCw==} @@ -1023,6 +1202,13 @@ packages: resolution: {integrity: sha512-gGq0NJkIGSwdbUt4yhdF8ZrmkGKVz9vAdVzpOfnom+V8PLSmSOVhZwbNvZZS1EYcJN5hzzKBxmmVVAInM6HQLg==} engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} + '@oxc-project/runtime@0.82.2': + resolution: {integrity: sha512-cYxcj5CPn/vo5QSpCZcYzBiLidU5+GlFSqIeNaMgBDtcVRBsBJHZg3pHw999W6nHamFQ1EHuPPByB26tjaJiJw==} + engines: {node: '>=6.9.0'} + + '@oxc-project/types@0.82.2': + resolution: {integrity: sha512-WMGSwd9FsNBs/WfqIOH0h3k1LBdjZJQGYjGnC+vla/fh6HUsu5HzGPerRljiq1hgMQ6gs031YJR12VyP57b/hQ==} + '@pivanov/utils@0.0.2': resolution: {integrity: sha512-q9CN0bFWxWgMY5hVVYyBgez1jGiLBa6I+LkG37ycylPhFvEGOOeaADGtUSu46CaZasPnlY8fCdVJZmrgKb1EPA==} peerDependencies: @@ -1616,9 +1802,82 @@ packages: peerDependencies: react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + '@rolldown/binding-android-arm64@1.0.0-beta.33': + resolution: {integrity: sha512-xhDQXKftRkEULIxCddrKMR8y0YO/Y+6BKk/XrQP2B29YjV2wr8DByoEz+AHX9BfLHb2srfpdN46UquBW2QXWpQ==} + cpu: [arm64] + os: [android] + + '@rolldown/binding-darwin-arm64@1.0.0-beta.33': + resolution: {integrity: sha512-7lhhY08v5ZtRq8JJQaJ49fnJombAPnqllKKCDLU/UvaqNAOEyTGC8J1WVOLC4EA4zbXO5U3CCRgVGyAFNH2VtQ==} + cpu: [arm64] + os: [darwin] + + '@rolldown/binding-darwin-x64@1.0.0-beta.33': + resolution: {integrity: sha512-U2iGjcDV7NWyYyhap8YuY0nwrLX6TvX/9i7gBtdEMPm9z3wIUVGNMVdGlA43uqg7xDpRGpEqGnxbeDgiEwYdnA==} + cpu: [x64] + os: [darwin] + + '@rolldown/binding-freebsd-x64@1.0.0-beta.33': + resolution: {integrity: sha512-gd6ASromVHFLlzrjJWMG5CXHkS7/36DEZ8HhvGt2NN8eZALCIuyEx8HMMLqvKA7z4EAztVkdToVrdxpGMsKZxw==} + cpu: [x64] + os: [freebsd] + + '@rolldown/binding-linux-arm-gnueabihf@1.0.0-beta.33': + resolution: {integrity: sha512-xmeLfkfGthuynO1EpCdyTVr0r4G+wqvnKCuyR6rXOet+hLrq5HNAC2XtP/jU2TB4Bc6aiLYxl868B8CGtFDhcw==} + cpu: [arm] + os: [linux] + + '@rolldown/binding-linux-arm64-gnu@1.0.0-beta.33': + resolution: {integrity: sha512-cHGp8yfHL4pes6uaLbO5L58ceFkUK4efd8iE86jClD1QPPDLKiqEXJCFYeuK3OfODuF5EBOmf0SlcUZNEYGdmw==} + cpu: [arm64] + os: [linux] + + '@rolldown/binding-linux-arm64-musl@1.0.0-beta.33': + resolution: {integrity: sha512-wZ1t7JAvVeFgskH1L9y7c47ITitPytpL0s8FmAT8pVfXcaTmS58ZyoXT+y6cz8uCkQnETjrX3YezTGI18u3ecg==} + cpu: [arm64] + os: [linux] + + '@rolldown/binding-linux-x64-gnu@1.0.0-beta.33': + resolution: {integrity: sha512-cDndWo3VEYbm7yeujOV6Ie2XHz0K8YX/R/vbNmMo03m1QwtBKKvbYNSyJb3B9+8igltDjd8zNM9mpiNNrq/ekQ==} + cpu: [x64] + os: [linux] + + '@rolldown/binding-linux-x64-musl@1.0.0-beta.33': + resolution: {integrity: sha512-bl7uzi6es/l6LT++NZcBpiX43ldLyKXCPwEZGY1rZJ99HQ7m1g3KxWwYCcGxtKjlb2ExVvDZicF6k+96vxOJKg==} + cpu: [x64] + os: [linux] + + '@rolldown/binding-openharmony-arm64@1.0.0-beta.33': + resolution: {integrity: sha512-TrgzQanpLgcmmzolCbYA9BPZgF1gYxkIGZhU/HROnJPsq67gcyaYw/JBLioqQLjIwMipETkn25YY799D2OZzJA==} + cpu: [arm64] + os: [openharmony] + + '@rolldown/binding-wasm32-wasi@1.0.0-beta.33': + resolution: {integrity: sha512-z0LltdUfvoKak9SuaLz/M9AVSg+RTOZjFksbZXzC6Svl1odyW4ai21VHhZy3m2Faeeb/rl/9efVLayj+qYEGxw==} + engines: {node: '>=14.0.0'} + cpu: [wasm32] + + '@rolldown/binding-win32-arm64-msvc@1.0.0-beta.33': + resolution: {integrity: sha512-CpvOHyqDNOYx9riD4giyXQDIu72bWRU2Dwt1xFSPlBudk6NumK0OJl6Ch+LPnkp5podQHcQg0mMauAXPVKct7g==} + cpu: [arm64] + os: [win32] + + '@rolldown/binding-win32-ia32-msvc@1.0.0-beta.33': + resolution: {integrity: sha512-/tNTvZTWHz6HiVuwpR3zR0kGIyCNb+/tFhnJmti+Aw2fAXs3l7Aj0DcXd0646eFKMX8L2w5hOW9H08FXTUkN0g==} + cpu: [ia32] + os: [win32] + + '@rolldown/binding-win32-x64-msvc@1.0.0-beta.33': + resolution: {integrity: sha512-Bb2qK3z7g2mf4zaKRvkohHzweaP1lLbaoBmXZFkY6jJWMm0Z8Pfnh8cOoRlH1IVM1Ufbo8ZZ1WXp1LbOpRMtXw==} + cpu: [x64] + os: [win32] + '@rolldown/pluginutils@1.0.0-beta.29': resolution: {integrity: sha512-NIJgOsMjbxAXvoGq/X0gD7VPMQ8j9g0BiDaNjVNVjvl+iKXxL3Jre0v31RmBYeLEmkbj2s02v8vFTbUXi5XS2Q==} + '@rolldown/pluginutils@1.0.0-beta.33': + resolution: {integrity: sha512-she25NCG6NoEPC/SEB4pHs5STcnfI4VBFOzjeI63maSPrWME5J2XC8ogrBgp8NaE/xzj28/kbpSaebiMvFRj+w==} + '@rollup/pluginutils@5.1.4': resolution: {integrity: sha512-USm05zrsFxYLPdWWq+K3STlWiT/3ELn3RcV5hJMghpeAIhxfsUIg6mt12CBJBInWMV4VneoV7SfGv8xIwo2qNQ==} engines: {node: '>=14.0.0'} @@ -1628,131 +1887,66 @@ packages: rollup: optional: true - '@rollup/rollup-android-arm-eabi@4.36.0': - resolution: {integrity: sha512-jgrXjjcEwN6XpZXL0HUeOVGfjXhPyxAbbhD0BlXUB+abTOpbPiN5Wb3kOT7yb+uEtATNYF5x5gIfwutmuBA26w==} - cpu: [arm] - os: [android] - '@rollup/rollup-android-arm-eabi@4.46.3': resolution: {integrity: sha512-UmTdvXnLlqQNOCJnyksjPs1G4GqXNGW1LrzCe8+8QoaLhhDeTXYBgJ3k6x61WIhlHX2U+VzEJ55TtIjR/HTySA==} cpu: [arm] os: [android] - '@rollup/rollup-android-arm64@4.36.0': - resolution: {integrity: sha512-NyfuLvdPdNUfUNeYKUwPwKsE5SXa2J6bCt2LdB/N+AxShnkpiczi3tcLJrm5mA+eqpy0HmaIY9F6XCa32N5yzg==} - cpu: [arm64] - os: [android] - '@rollup/rollup-android-arm64@4.46.3': resolution: {integrity: sha512-8NoxqLpXm7VyeI0ocidh335D6OKT0UJ6fHdnIxf3+6oOerZZc+O7r+UhvROji6OspyPm+rrIdb1gTXtVIqn+Sg==} cpu: [arm64] os: [android] - '@rollup/rollup-darwin-arm64@4.36.0': - resolution: {integrity: sha512-JQ1Jk5G4bGrD4pWJQzWsD8I1n1mgPXq33+/vP4sk8j/z/C2siRuxZtaUA7yMTf71TCZTZl/4e1bfzwUmFb3+rw==} - cpu: [arm64] - os: [darwin] - '@rollup/rollup-darwin-arm64@4.46.3': resolution: {integrity: sha512-csnNavqZVs1+7/hUKtgjMECsNG2cdB8F7XBHP6FfQjqhjF8rzMzb3SLyy/1BG7YSfQ+bG75Ph7DyedbUqwq1rA==} cpu: [arm64] os: [darwin] - '@rollup/rollup-darwin-x64@4.36.0': - resolution: {integrity: sha512-6c6wMZa1lrtiRsbDziCmjE53YbTkxMYhhnWnSW8R/yqsM7a6mSJ3uAVT0t8Y/DGt7gxUWYuFM4bwWk9XCJrFKA==} - cpu: [x64] - os: [darwin] - '@rollup/rollup-darwin-x64@4.46.3': resolution: {integrity: sha512-r2MXNjbuYabSIX5yQqnT8SGSQ26XQc8fmp6UhlYJd95PZJkQD1u82fWP7HqvGUf33IsOC6qsiV+vcuD4SDP6iw==} cpu: [x64] os: [darwin] - '@rollup/rollup-freebsd-arm64@4.36.0': - resolution: {integrity: sha512-KXVsijKeJXOl8QzXTsA+sHVDsFOmMCdBRgFmBb+mfEb/7geR7+C8ypAml4fquUt14ZyVXaw2o1FWhqAfOvA4sg==} - cpu: [arm64] - os: [freebsd] - '@rollup/rollup-freebsd-arm64@4.46.3': resolution: {integrity: sha512-uluObTmgPJDuJh9xqxyr7MV61Imq+0IvVsAlWyvxAaBSNzCcmZlhfYcRhCdMaCsy46ccZa7vtDDripgs9Jkqsw==} cpu: [arm64] os: [freebsd] - '@rollup/rollup-freebsd-x64@4.36.0': - resolution: {integrity: sha512-dVeWq1ebbvByI+ndz4IJcD4a09RJgRYmLccwlQ8bPd4olz3Y213uf1iwvc7ZaxNn2ab7bjc08PrtBgMu6nb4pQ==} - cpu: [x64] - os: [freebsd] - '@rollup/rollup-freebsd-x64@4.46.3': resolution: {integrity: sha512-AVJXEq9RVHQnejdbFvh1eWEoobohUYN3nqJIPI4mNTMpsyYN01VvcAClxflyk2HIxvLpRcRggpX1m9hkXkpC/A==} cpu: [x64] os: [freebsd] - '@rollup/rollup-linux-arm-gnueabihf@4.36.0': - resolution: {integrity: sha512-bvXVU42mOVcF4le6XSjscdXjqx8okv4n5vmwgzcmtvFdifQ5U4dXFYaCB87namDRKlUL9ybVtLQ9ztnawaSzvg==} - cpu: [arm] - os: [linux] - '@rollup/rollup-linux-arm-gnueabihf@4.46.3': resolution: {integrity: sha512-byyflM+huiwHlKi7VHLAYTKr67X199+V+mt1iRgJenAI594vcmGGddWlu6eHujmcdl6TqSNnvqaXJqZdnEWRGA==} cpu: [arm] os: [linux] - '@rollup/rollup-linux-arm-musleabihf@4.36.0': - resolution: {integrity: sha512-JFIQrDJYrxOnyDQGYkqnNBtjDwTgbasdbUiQvcU8JmGDfValfH1lNpng+4FWlhaVIR4KPkeddYjsVVbmJYvDcg==} - cpu: [arm] - os: [linux] - '@rollup/rollup-linux-arm-musleabihf@4.46.3': resolution: {integrity: sha512-aLm3NMIjr4Y9LklrH5cu7yybBqoVCdr4Nvnm8WB7PKCn34fMCGypVNpGK0JQWdPAzR/FnoEoFtlRqZbBBLhVoQ==} cpu: [arm] os: [linux] - '@rollup/rollup-linux-arm64-gnu@4.36.0': - resolution: {integrity: sha512-KqjYVh3oM1bj//5X7k79PSCZ6CvaVzb7Qs7VMWS+SlWB5M8p3FqufLP9VNp4CazJ0CsPDLwVD9r3vX7Ci4J56A==} - cpu: [arm64] - os: [linux] - '@rollup/rollup-linux-arm64-gnu@4.46.3': resolution: {integrity: sha512-VtilE6eznJRDIoFOzaagQodUksTEfLIsvXymS+UdJiSXrPW7Ai+WG4uapAc3F7Hgs791TwdGh4xyOzbuzIZrnw==} cpu: [arm64] os: [linux] - '@rollup/rollup-linux-arm64-musl@4.36.0': - resolution: {integrity: sha512-QiGnhScND+mAAtfHqeT+cB1S9yFnNQ/EwCg5yE3MzoaZZnIV0RV9O5alJAoJKX/sBONVKeZdMfO8QSaWEygMhw==} - cpu: [arm64] - os: [linux] - '@rollup/rollup-linux-arm64-musl@4.46.3': resolution: {integrity: sha512-dG3JuS6+cRAL0GQ925Vppafi0qwZnkHdPeuZIxIPXqkCLP02l7ka+OCyBoDEv8S+nKHxfjvjW4OZ7hTdHkx8/w==} cpu: [arm64] os: [linux] - '@rollup/rollup-linux-loongarch64-gnu@4.36.0': - resolution: {integrity: sha512-1ZPyEDWF8phd4FQtTzMh8FQwqzvIjLsl6/84gzUxnMNFBtExBtpL51H67mV9xipuxl1AEAerRBgBwFNpkw8+Lg==} - cpu: [loong64] - os: [linux] - '@rollup/rollup-linux-loongarch64-gnu@4.46.3': resolution: {integrity: sha512-iU8DxnxEKJptf8Vcx4XvAUdpkZfaz0KWfRrnIRrOndL0SvzEte+MTM7nDH4A2Now4FvTZ01yFAgj6TX/mZl8hQ==} cpu: [loong64] os: [linux] - '@rollup/rollup-linux-powerpc64le-gnu@4.36.0': - resolution: {integrity: sha512-VMPMEIUpPFKpPI9GZMhJrtu8rxnp6mJR3ZzQPykq4xc2GmdHj3Q4cA+7avMyegXy4n1v+Qynr9fR88BmyO74tg==} - cpu: [ppc64] - os: [linux] - '@rollup/rollup-linux-ppc64-gnu@4.46.3': resolution: {integrity: sha512-VrQZp9tkk0yozJoQvQcqlWiqaPnLM6uY1qPYXvukKePb0fqaiQtOdMJSxNFUZFsGw5oA5vvVokjHrx8a9Qsz2A==} cpu: [ppc64] os: [linux] - '@rollup/rollup-linux-riscv64-gnu@4.36.0': - resolution: {integrity: sha512-ttE6ayb/kHwNRJGYLpuAvB7SMtOeQnVXEIpMtAvx3kepFQeowVED0n1K9nAdraHUPJ5hydEMxBpIR7o4nrm8uA==} - cpu: [riscv64] - os: [linux] - '@rollup/rollup-linux-riscv64-gnu@4.46.3': resolution: {integrity: sha512-uf2eucWSUb+M7b0poZ/08LsbcRgaDYL8NCGjUeFMwCWFwOuFcZ8D9ayPl25P3pl+D2FH45EbHdfyUesQ2Lt9wA==} cpu: [riscv64] @@ -1763,61 +1957,31 @@ packages: cpu: [riscv64] os: [linux] - '@rollup/rollup-linux-s390x-gnu@4.36.0': - resolution: {integrity: sha512-4a5gf2jpS0AIe7uBjxDeUMNcFmaRTbNv7NxI5xOCs4lhzsVyGR/0qBXduPnoWf6dGC365saTiwag8hP1imTgag==} - cpu: [s390x] - os: [linux] - '@rollup/rollup-linux-s390x-gnu@4.46.3': resolution: {integrity: sha512-MUpAOallJim8CsJK+4Lc9tQzlfPbHxWDrGXZm2z6biaadNpvh3a5ewcdat478W+tXDoUiHwErX/dOql7ETcLqg==} cpu: [s390x] os: [linux] - '@rollup/rollup-linux-x64-gnu@4.36.0': - resolution: {integrity: sha512-5KtoW8UWmwFKQ96aQL3LlRXX16IMwyzMq/jSSVIIyAANiE1doaQsx/KRyhAvpHlPjPiSU/AYX/8m+lQ9VToxFQ==} - cpu: [x64] - os: [linux] - '@rollup/rollup-linux-x64-gnu@4.46.3': resolution: {integrity: sha512-F42IgZI4JicE2vM2PWCe0N5mR5vR0gIdORPqhGQ32/u1S1v3kLtbZ0C/mi9FFk7C5T0PgdeyWEPajPjaUpyoKg==} cpu: [x64] os: [linux] - '@rollup/rollup-linux-x64-musl@4.36.0': - resolution: {integrity: sha512-sycrYZPrv2ag4OCvaN5js+f01eoZ2U+RmT5as8vhxiFz+kxwlHrsxOwKPSA8WyS+Wc6Epid9QeI/IkQ9NkgYyQ==} - cpu: [x64] - os: [linux] - '@rollup/rollup-linux-x64-musl@4.46.3': resolution: {integrity: sha512-oLc+JrwwvbimJUInzx56Q3ujL3Kkhxehg7O1gWAYzm8hImCd5ld1F2Gry5YDjR21MNb5WCKhC9hXgU7rRlyegQ==} cpu: [x64] os: [linux] - '@rollup/rollup-win32-arm64-msvc@4.36.0': - resolution: {integrity: sha512-qbqt4N7tokFwwSVlWDsjfoHgviS3n/vZ8LK0h1uLG9TYIRuUTJC88E1xb3LM2iqZ/WTqNQjYrtmtGmrmmawB6A==} - cpu: [arm64] - os: [win32] - '@rollup/rollup-win32-arm64-msvc@4.46.3': resolution: {integrity: sha512-lOrQ+BVRstruD1fkWg9yjmumhowR0oLAAzavB7yFSaGltY8klttmZtCLvOXCmGE9mLIn8IBV/IFrQOWz5xbFPg==} cpu: [arm64] os: [win32] - '@rollup/rollup-win32-ia32-msvc@4.36.0': - resolution: {integrity: sha512-t+RY0JuRamIocMuQcfwYSOkmdX9dtkr1PbhKW42AMvaDQa+jOdpUYysroTF/nuPpAaQMWp7ye+ndlmmthieJrQ==} - cpu: [ia32] - os: [win32] - '@rollup/rollup-win32-ia32-msvc@4.46.3': resolution: {integrity: sha512-vvrVKPRS4GduGR7VMH8EylCBqsDcw6U+/0nPDuIjXQRbHJc6xOBj+frx8ksfZAh6+Fptw5wHrN7etlMmQnPQVg==} cpu: [ia32] os: [win32] - '@rollup/rollup-win32-x64-msvc@4.36.0': - resolution: {integrity: sha512-aRXd7tRZkWLqGbChgcMMDEHjOKudo1kChb1Jt1IfR8cY/KIpgNviLeJy5FUb9IpSuQj8dU2fAYNMPW/hLKOSTw==} - cpu: [x64] - os: [win32] - '@rollup/rollup-win32-x64-msvc@4.46.3': resolution: {integrity: sha512-fi3cPxCnu3ZeM3EwKZPgXbWoGzm2XHgB/WShKI81uj8wG0+laobmqy5wbgEwzstlbLu4MyO8C19FyhhWseYKNQ==} cpu: [x64] @@ -1853,65 +2017,65 @@ packages: '@swc/helpers@0.5.17': resolution: {integrity: sha512-5IKx/Y13RsYd+sauPb2x+U/xZikHjolzfuDgTAl/Tdf3Q8rslRvC19NKDLgAJQ6wsqADk10ntlv08nPFw/gO/A==} - '@tailwindcss/node@4.1.8': - resolution: {integrity: sha512-OWwBsbC9BFAJelmnNcrKuf+bka2ZxCE2A4Ft53Tkg4uoiE67r/PMEYwCsourC26E+kmxfwE0hVzMdxqeW+xu7Q==} + '@tailwindcss/node@4.1.12': + resolution: {integrity: sha512-3hm9brwvQkZFe++SBt+oLjo4OLDtkvlE8q2WalaD/7QWaeM7KEJbAiY/LJZUaCs7Xa8aUu4xy3uoyX4q54UVdQ==} - '@tailwindcss/oxide-android-arm64@4.1.8': - resolution: {integrity: sha512-Fbz7qni62uKYceWYvUjRqhGfZKwhZDQhlrJKGtnZfuNtHFqa8wmr+Wn74CTWERiW2hn3mN5gTpOoxWKk0jRxjg==} + '@tailwindcss/oxide-android-arm64@4.1.12': + resolution: {integrity: sha512-oNY5pq+1gc4T6QVTsZKwZaGpBb2N1H1fsc1GD4o7yinFySqIuRZ2E4NvGasWc6PhYJwGK2+5YT1f9Tp80zUQZQ==} engines: {node: '>= 10'} cpu: [arm64] os: [android] - '@tailwindcss/oxide-darwin-arm64@4.1.8': - resolution: {integrity: sha512-RdRvedGsT0vwVVDztvyXhKpsU2ark/BjgG0huo4+2BluxdXo8NDgzl77qh0T1nUxmM11eXwR8jA39ibvSTbi7A==} + '@tailwindcss/oxide-darwin-arm64@4.1.12': + resolution: {integrity: sha512-cq1qmq2HEtDV9HvZlTtrj671mCdGB93bVY6J29mwCyaMYCP/JaUBXxrQQQm7Qn33AXXASPUb2HFZlWiiHWFytw==} engines: {node: '>= 10'} cpu: [arm64] os: [darwin] - '@tailwindcss/oxide-darwin-x64@4.1.8': - resolution: {integrity: sha512-t6PgxjEMLp5Ovf7uMb2OFmb3kqzVTPPakWpBIFzppk4JE4ix0yEtbtSjPbU8+PZETpaYMtXvss2Sdkx8Vs4XRw==} + '@tailwindcss/oxide-darwin-x64@4.1.12': + resolution: {integrity: sha512-6UCsIeFUcBfpangqlXay9Ffty9XhFH1QuUFn0WV83W8lGdX8cD5/+2ONLluALJD5+yJ7k8mVtwy3zMZmzEfbLg==} engines: {node: '>= 10'} cpu: [x64] os: [darwin] - '@tailwindcss/oxide-freebsd-x64@4.1.8': - resolution: {integrity: sha512-g8C8eGEyhHTqwPStSwZNSrOlyx0bhK/V/+zX0Y+n7DoRUzyS8eMbVshVOLJTDDC+Qn9IJnilYbIKzpB9n4aBsg==} + '@tailwindcss/oxide-freebsd-x64@4.1.12': + resolution: {integrity: sha512-JOH/f7j6+nYXIrHobRYCtoArJdMJh5zy5lr0FV0Qu47MID/vqJAY3r/OElPzx1C/wdT1uS7cPq+xdYYelny1ww==} engines: {node: '>= 10'} cpu: [x64] os: [freebsd] - '@tailwindcss/oxide-linux-arm-gnueabihf@4.1.8': - resolution: {integrity: sha512-Jmzr3FA4S2tHhaC6yCjac3rGf7hG9R6Gf2z9i9JFcuyy0u79HfQsh/thifbYTF2ic82KJovKKkIB6Z9TdNhCXQ==} + '@tailwindcss/oxide-linux-arm-gnueabihf@4.1.12': + resolution: {integrity: sha512-v4Ghvi9AU1SYgGr3/j38PD8PEe6bRfTnNSUE3YCMIRrrNigCFtHZ2TCm8142X8fcSqHBZBceDx+JlFJEfNg5zQ==} engines: {node: '>= 10'} cpu: [arm] os: [linux] - '@tailwindcss/oxide-linux-arm64-gnu@4.1.8': - resolution: {integrity: sha512-qq7jXtO1+UEtCmCeBBIRDrPFIVI4ilEQ97qgBGdwXAARrUqSn/L9fUrkb1XP/mvVtoVeR2bt/0L77xx53bPZ/Q==} + '@tailwindcss/oxide-linux-arm64-gnu@4.1.12': + resolution: {integrity: sha512-YP5s1LmetL9UsvVAKusHSyPlzSRqYyRB0f+Kl/xcYQSPLEw/BvGfxzbH+ihUciePDjiXwHh+p+qbSP3SlJw+6g==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] - '@tailwindcss/oxide-linux-arm64-musl@4.1.8': - resolution: {integrity: sha512-O6b8QesPbJCRshsNApsOIpzKt3ztG35gfX9tEf4arD7mwNinsoCKxkj8TgEE0YRjmjtO3r9FlJnT/ENd9EVefQ==} + '@tailwindcss/oxide-linux-arm64-musl@4.1.12': + resolution: {integrity: sha512-V8pAM3s8gsrXcCv6kCHSuwyb/gPsd863iT+v1PGXC4fSL/OJqsKhfK//v8P+w9ThKIoqNbEnsZqNy+WDnwQqCA==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] - '@tailwindcss/oxide-linux-x64-gnu@4.1.8': - resolution: {integrity: sha512-32iEXX/pXwikshNOGnERAFwFSfiltmijMIAbUhnNyjFr3tmWmMJWQKU2vNcFX0DACSXJ3ZWcSkzNbaKTdngH6g==} + '@tailwindcss/oxide-linux-x64-gnu@4.1.12': + resolution: {integrity: sha512-xYfqYLjvm2UQ3TZggTGrwxjYaLB62b1Wiysw/YE3Yqbh86sOMoTn0feF98PonP7LtjsWOWcXEbGqDL7zv0uW8Q==} engines: {node: '>= 10'} cpu: [x64] os: [linux] - '@tailwindcss/oxide-linux-x64-musl@4.1.8': - resolution: {integrity: sha512-s+VSSD+TfZeMEsCaFaHTaY5YNj3Dri8rST09gMvYQKwPphacRG7wbuQ5ZJMIJXN/puxPcg/nU+ucvWguPpvBDg==} + '@tailwindcss/oxide-linux-x64-musl@4.1.12': + resolution: {integrity: sha512-ha0pHPamN+fWZY7GCzz5rKunlv9L5R8kdh+YNvP5awe3LtuXb5nRi/H27GeL2U+TdhDOptU7T6Is7mdwh5Ar3A==} engines: {node: '>= 10'} cpu: [x64] os: [linux] - '@tailwindcss/oxide-wasm32-wasi@4.1.8': - resolution: {integrity: sha512-CXBPVFkpDjM67sS1psWohZ6g/2/cd+cq56vPxK4JeawelxwK4YECgl9Y9TjkE2qfF+9/s1tHHJqrC4SS6cVvSg==} + '@tailwindcss/oxide-wasm32-wasi@4.1.12': + resolution: {integrity: sha512-4tSyu3dW+ktzdEpuk6g49KdEangu3eCYoqPhWNsZgUhyegEda3M9rG0/j1GV/JjVVsj+lG7jWAyrTlLzd/WEBg==} engines: {node: '>=14.0.0'} cpu: [wasm32] bundledDependencies: @@ -1922,26 +2086,29 @@ packages: - '@emnapi/wasi-threads' - tslib - '@tailwindcss/oxide-win32-arm64-msvc@4.1.8': - resolution: {integrity: sha512-7GmYk1n28teDHUjPlIx4Z6Z4hHEgvP5ZW2QS9ygnDAdI/myh3HTHjDqtSqgu1BpRoI4OiLx+fThAyA1JePoENA==} + '@tailwindcss/oxide-win32-arm64-msvc@4.1.12': + resolution: {integrity: sha512-iGLyD/cVP724+FGtMWslhcFyg4xyYyM+5F4hGvKA7eifPkXHRAUDFaimu53fpNg9X8dfP75pXx/zFt/jlNF+lg==} engines: {node: '>= 10'} cpu: [arm64] os: [win32] - '@tailwindcss/oxide-win32-x64-msvc@4.1.8': - resolution: {integrity: sha512-fou+U20j+Jl0EHwK92spoWISON2OBnCazIc038Xj2TdweYV33ZRkS9nwqiUi2d/Wba5xg5UoHfvynnb/UB49cQ==} + '@tailwindcss/oxide-win32-x64-msvc@4.1.12': + resolution: {integrity: sha512-NKIh5rzw6CpEodv/++r0hGLlfgT/gFN+5WNdZtvh6wpU2BpGNgdjvj6H2oFc8nCM839QM1YOhjpgbAONUb4IxA==} engines: {node: '>= 10'} cpu: [x64] os: [win32] - '@tailwindcss/oxide@4.1.8': - resolution: {integrity: sha512-d7qvv9PsM5N3VNKhwVUhpK6r4h9wtLkJ6lz9ZY9aeZgrUWk1Z8VPyqyDT9MZlem7GTGseRQHkeB1j3tC7W1P+A==} + '@tailwindcss/oxide@4.1.12': + resolution: {integrity: sha512-gM5EoKHW/ukmlEtphNwaGx45fGoEmP10v51t9unv55voWh6WrOL19hfuIdo2FjxIaZzw776/BUQg7Pck++cIVw==} engines: {node: '>= 10'} - '@tailwindcss/vite@4.1.8': - resolution: {integrity: sha512-CQ+I8yxNV5/6uGaJjiuymgw0kEQiNKRinYbZXPdx1fk5WgiyReG0VaUx/Xq6aVNSUNJFzxm6o8FNKS5aMaim5A==} + '@tailwindcss/vite@4.1.12': + resolution: {integrity: sha512-4pt0AMFDx7gzIrAOIYgYP0KCBuKWqyW8ayrdiLEjoJTT4pKTjrzG/e4uzWtTLDziC+66R9wbUqZBccJalSE5vQ==} peerDependencies: - vite: ^5.2.0 || ^6 + vite: ^5.2.0 || ^6 || ^7 + + '@tybys/wasm-util@0.10.0': + resolution: {integrity: sha512-VyyPYFlOMNylG45GoAe0xDoLwWuowvf92F9kySqzYh8vmYm7D2u4iUJKa1tOUpS70Ku13ASrOkS4ScXFsTaCNQ==} '@types/better-sqlite3@7.6.13': resolution: {integrity: sha512-NMv9ASNARoKksWtsq/SHakpYAYnhBrQgGD8zkLYk/jaK8jUGn08CfEdTRgYhMypUQAfzSP8W6gNLe0q19/t4VA==} @@ -1952,9 +2119,6 @@ packages: '@types/deep-eql@4.0.2': resolution: {integrity: sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==} - '@types/estree@1.0.6': - resolution: {integrity: sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==} - '@types/estree@1.0.7': resolution: {integrity: sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ==} @@ -1982,15 +2146,15 @@ packages: '@types/node-fetch@2.6.12': resolution: {integrity: sha512-8nneRWKCg3rMtF69nLQJnOYUcbafYeFSjqkw3jCRLsqkWFlHaoQrr5mXmofFGOx3DKn7UfmBMyov8ySvLRVldA==} - '@types/node@20.17.52': - resolution: {integrity: sha512-2aj++KfxubvW/Lc0YyXE3OEW7Es8TWn1MsRzYgcOGyTNQxi0L8rxQUCZ7ZbyOBWZQD5I63PV9egZWMsapVaklg==} - - '@types/node@22.15.24': - resolution: {integrity: sha512-w9CZGm9RDjzTh/D+hFwlBJ3ziUaVw7oufKA3vOFSOZlzmW9AkZnfjPb+DLnrV6qtgL/LNmP0/2zBNCFHL3F0ng==} + '@types/node@20.19.11': + resolution: {integrity: sha512-uug3FEEGv0r+jrecvUUpbY8lLisvIjg6AAic6a2bSP5OEOLeJsDSnvhCDov7ipFFMXS3orMpzlmi0ZcuGkBbow==} '@types/node@22.17.2': resolution: {integrity: sha512-gL6z5N9Jm9mhY+U2KXZpteb+09zyffliRkZyZOHODGATyC5B1Jt/7TzuuiLkFsSUMLbS1OLmlj/E+/3KF4Q/4w==} + '@types/node@24.3.0': + resolution: {integrity: sha512-aPTXCrfwnDLj4VvXrm+UUCQjNEvJgNA8s5F1cvwQU+3KNltTOkBm1j30uNLyqqPNe7gE3KFzImYoZEfLhp4Yow==} + '@types/react-dom@19.1.7': resolution: {integrity: sha512-i5ZzwYpqjmrKenzkoLM2Ibzt6mAsM7pxB6BCIouEVVmgiqaMj1TjaK7hnA36hbW5aZv20kx7Lw6hWzPWg0Rurw==} peerDependencies: @@ -2019,6 +2183,53 @@ packages: '@types/ws@8.18.1': resolution: {integrity: sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==} + '@typescript/native-preview-darwin-arm64@7.0.0-dev.20250818.1': + resolution: {integrity: sha512-LVYgDjaOHMxy2OKG9WKPgYKcUFB/VxbhXHwMjPGPZMpx96YwgeVnhVY4hTHE/rIfUm1tdeUXSSDxUMUv6KMJZw==} + engines: {node: '>=20.6.0'} + cpu: [arm64] + os: [darwin] + + '@typescript/native-preview-darwin-x64@7.0.0-dev.20250818.1': + resolution: {integrity: sha512-z9FMbVVnppjsES3kmzIekozyJLp0TwYwTfvP+f5BX/2W5KZ+06GKZf5rI7ZaEoWscFmD/QCnfCe/k8LZlNhmXg==} + engines: {node: '>=20.6.0'} + cpu: [x64] + os: [darwin] + + '@typescript/native-preview-linux-arm64@7.0.0-dev.20250818.1': + resolution: {integrity: sha512-80gfjzmvyJRPJAl7RSXona76xyrkh8343ebUcFzia/yFL9x1dij3i+jAA7LI0hrjcp5+27KtSTfB99mxdmuelA==} + engines: {node: '>=20.6.0'} + cpu: [arm64] + os: [linux] + + '@typescript/native-preview-linux-arm@7.0.0-dev.20250818.1': + resolution: {integrity: sha512-XVYgb2d2jtf/IRbK7b0PsIWCBDzylc27UXnZj3pGzSHdkBSral+sb3X3bZivWnDMy4jSKdSYq1MS/4YASug41w==} + engines: {node: '>=20.6.0'} + cpu: [arm] + os: [linux] + + '@typescript/native-preview-linux-x64@7.0.0-dev.20250818.1': + resolution: {integrity: sha512-mo7v4rkuwg239i8Rmyvjgv2XrWCIjMGn+L0fX765Yxbw9wSPlvT8IkmJOTGoxIGByjeId6AKTgYHuJZb3uUwxQ==} + engines: {node: '>=20.6.0'} + cpu: [x64] + os: [linux] + + '@typescript/native-preview-win32-arm64@7.0.0-dev.20250818.1': + resolution: {integrity: sha512-yeHHlhotR8q6OhBKLX6MSy6bNenE6pxuAUeXI5Ym5Mrm/ZxVcEd8iFusA3QWwa4Fx0a+WKNNOS+LUpGNAlVogQ==} + engines: {node: '>=20.6.0'} + cpu: [arm64] + os: [win32] + + '@typescript/native-preview-win32-x64@7.0.0-dev.20250818.1': + resolution: {integrity: sha512-E3XzKN0bLSjfMy7/gklqotdDN8Ih5czWiq1Zll8KfFEHSfmUJ1x3/SP76qF+zGk2n2b851b81lWj7VplHkk99A==} + engines: {node: '>=20.6.0'} + cpu: [x64] + os: [win32] + + '@typescript/native-preview@7.0.0-dev.20250818.1': + resolution: {integrity: sha512-fJ2mjyx0Sk0ylOzBTjA3ubsNDCLshNcGFmS4Ouv1wx4Sf/G/o2YpyGw+ImsG+pXumpVC3nuAlLjvqoM+VpSsyQ==} + engines: {node: '>=20.6.0'} + hasBin: true + '@uiw/codemirror-extensions-basic-setup@4.23.12': resolution: {integrity: sha512-l9vuiXOTFDBetYrRLDmz3jDxQHDsrVAZ2Y6dVfmrqi2AsulsDu+y7csW0JsvaMqo79rYkaIZg8yeqmDgMb7VyQ==} peerDependencies: @@ -2251,6 +2462,10 @@ packages: resolution: {integrity: sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==} engines: {node: '>=12'} + ansis@4.1.0: + resolution: {integrity: sha512-BGcItUBWSMRgOCe+SVZJ+S7yTRG0eGt9cXAHev72yuGcY23hnLA7Bky5L/xLyPINoSN95geovfBkqoTlNZYa7w==} + engines: {node: '>=14'} + arg@5.0.2: resolution: {integrity: sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==} @@ -2614,8 +2829,8 @@ packages: end-of-stream@1.4.5: resolution: {integrity: sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==} - enhanced-resolve@5.18.1: - resolution: {integrity: sha512-ZSW3ma5GkcQBIpwZTSRAI8N71Uuwgs93IezB7mf7R60tC8ZbJideoDNKjHn2O9KIlx6rkGTTEk1xUCK2E1Y2Yg==} + enhanced-resolve@5.18.3: + resolution: {integrity: sha512-d4lC8xfavMeBjzGr2vECC3fsGXziXZQyJxD868h2M/mBI3PwAuODxAkLkq5HYuvrPYcUtiLzsTo8U3PgX3Ocww==} engines: {node: '>=10.13.0'} entities@4.5.0: @@ -2659,6 +2874,11 @@ packages: engines: {node: '>=18'} hasBin: true + esbuild@0.25.9: + resolution: {integrity: sha512-CRbODhYyQx3qp7ZEwzxOk4JBqmD/seJrzPa/cGjY1VtIn5E09Oi9/dB4JwctnfZ8Q8iT7rioVv5k/FNT/uf54g==} + engines: {node: '>=18'} + hasBin: true + escalade@3.2.0: resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} engines: {node: '>=6'} @@ -2857,8 +3077,8 @@ packages: jackspeak@3.4.3: resolution: {integrity: sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==} - jiti@2.4.2: - resolution: {integrity: sha512-rg9zJN+G4n2nfJl5MW3BMygZX56zKPNVEYYqq7adpmMh4Jn2QNEwhvQlFy6jPVdcod7txZtKHWnyZiA3a0zP7A==} + jiti@2.5.1: + resolution: {integrity: sha512-twQoecYPiVA5K/h6SxtORw/Bs3ar+mLUtoPSc7iMXzQzK8d7eJ/R09wmTwAjiamETn1cXYPGfNnu7DMoHgu12w==} hasBin: true jose@6.0.11: @@ -3113,8 +3333,8 @@ packages: minisearch@7.1.2: resolution: {integrity: sha512-R1Pd9eF+MD5JYDDSPAp/q1ougKglm14uEkPMvQ/05RGmx6G9wvmLTrTI/Q5iPNJLYqNdsDQ7qTGIcNWR+FrHmA==} - minizlib@3.0.1: - resolution: {integrity: sha512-umcy022ILvb5/3Djuu8LWeqUa8D68JaBzlttKeMWen48SjabqS3iY5w/vzeMzMUNhLDifyhbOwKDSznB1vvrwg==} + minizlib@3.0.2: + resolution: {integrity: sha512-oG62iEk+CYt5Xj2YqI5Xi9xWUeZhDI8jjQmC5oThVH5JGCTgIjr7ciJDzC7MBzYd//WvR1OTmP5Q38Q8ShQtVA==} engines: {node: '>= 18'} mitt@3.0.1: @@ -3480,13 +3700,48 @@ packages: rfdc@1.4.1: resolution: {integrity: sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==} - rimraf@5.0.10: - resolution: {integrity: sha512-l0OE8wL34P4nJH/H2ffoaniAokM2qSmrtXHmlpvYr5AVVX8msAyW0l8NVJFDxlSK4u3Uh/f41cQheDVdnYijwQ==} + rolldown-vite@7.1.3: + resolution: {integrity: sha512-J22JlCJQbIRxR6KGwpf76Uq3R4wk6avQHCijXGPsXsbVpPt+Uavg5M5dvzP7XdlAKL8uZE5Xof9j4Y9yu+cxxg==} + engines: {node: ^20.19.0 || >=22.12.0} hasBin: true + peerDependencies: + '@types/node': ^20.19.0 || >=22.12.0 + esbuild: ^0.25.0 + jiti: '>=1.21.0' + less: ^4.0.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 + esbuild: + optional: true + jiti: + optional: true + less: + optional: true + sass: + optional: true + sass-embedded: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + tsx: + optional: true + yaml: + optional: true - rollup@4.36.0: - resolution: {integrity: sha512-zwATAXNQxUcd40zgtQG0ZafcRK4g004WtEl7kbuhTWPvf07PsfohXl39jVUvPF7jvNAIkKPQ2XrsDlWuxBd++Q==} - engines: {node: '>=18.0.0', npm: '>=8.0.0'} + rolldown@1.0.0-beta.33: + resolution: {integrity: sha512-mgu118ZuRguC8unhPCbdZbyRbjQfEMiWqlojBA5aRIncBelRaBomnHNpGKYkYWeK7twRz5Cql30xgqqrA3Xelw==} hasBin: true rollup@4.46.3: @@ -3649,6 +3904,9 @@ packages: peerDependencies: tailwindcss: ^4.0.0 + tailwindcss@4.1.12: + resolution: {integrity: sha512-DzFtxOi+7NsFf7DBtI3BJsynR+0Yp6etH+nRPTbpWnS2pZBaSksv/JGctNwSWzbFjp0vxSqknaUylseZqMDGrA==} + tailwindcss@4.1.8: resolution: {integrity: sha512-kjeW8gjdxasbmFKpVGrGd5T4i40mV5J2Rasw48QARfYeQ8YS9x02ON9SFWax3Qf616rt4Cp3nVNIj6Hd1mP3og==} @@ -3740,17 +3998,17 @@ packages: resolution: {integrity: sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==} engines: {node: '>=16'} - typescript@5.8.3: - resolution: {integrity: sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==} + typescript@5.9.2: + resolution: {integrity: sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A==} engines: {node: '>=14.17'} hasBin: true - undici-types@6.19.8: - resolution: {integrity: sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==} - undici-types@6.21.0: resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==} + undici-types@7.10.0: + resolution: {integrity: sha512-t5Fy/nfn+14LuOc2KNYg75vZqClpAiqscVvMygNnlsHBFpSXdJaYtXMcdNLpl/Qvc3P2cB3s6lOV51nqsFq4ag==} + undici@7.10.0: resolution: {integrity: sha512-u5otvFBOBZvmdjWLVW+5DAc9Nkq8f24g0O9oY7qw2JVIF1VocIFoyz9JFkuVOS2j41AufeO0xnlweJ2RLT8nGw==} engines: {node: '>=20.18.1'} @@ -3832,46 +4090,6 @@ packages: vite: optional: true - vite@6.3.5: - resolution: {integrity: sha512-cZn6NDFE7wdTpINgs++ZJ4N49W2vRp8LCKrn3Ob1kYNtOo21vfDoaV5GzBfLU4MovSAB8uNRm4jgzVQZ+mBzPQ==} - engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} - hasBin: true - peerDependencies: - '@types/node': ^18.0.0 || ^20.0.0 || >=22.0.0 - jiti: '>=1.21.0' - less: '*' - lightningcss: ^1.21.0 - sass: '*' - sass-embedded: '*' - stylus: '*' - sugarss: '*' - terser: ^5.16.0 - tsx: ^4.8.1 - yaml: ^2.4.2 - peerDependenciesMeta: - '@types/node': - optional: true - jiti: - optional: true - less: - optional: true - lightningcss: - optional: true - sass: - optional: true - sass-embedded: - optional: true - stylus: - optional: true - sugarss: - optional: true - terser: - optional: true - tsx: - optional: true - yaml: - optional: true - vite@7.1.2: resolution: {integrity: sha512-J0SQBPlQiEXAF7tajiH+rUooJPo0l8KQgyg4/aMunNtrOa7bwuZJsJbDWzeljqQpgftxuq5yNJxQ91O9ts29UQ==} engines: {node: ^20.19.0 || >=22.12.0} @@ -4460,6 +4678,22 @@ snapshots: '@drizzle-team/brocli@0.11.0': {} + '@emnapi/core@1.4.5': + dependencies: + '@emnapi/wasi-threads': 1.0.4 + tslib: 2.8.1 + optional: true + + '@emnapi/runtime@1.4.5': + dependencies: + tslib: 2.8.1 + optional: true + + '@emnapi/wasi-threads@1.0.4': + dependencies: + tslib: 2.8.1 + optional: true + '@esbuild-kit/core-utils@3.3.2': dependencies: esbuild: 0.18.20 @@ -4473,144 +4707,222 @@ snapshots: '@esbuild/aix-ppc64@0.25.5': optional: true + '@esbuild/aix-ppc64@0.25.9': + optional: true + '@esbuild/android-arm64@0.18.20': optional: true '@esbuild/android-arm64@0.25.5': optional: true + '@esbuild/android-arm64@0.25.9': + optional: true + '@esbuild/android-arm@0.18.20': optional: true '@esbuild/android-arm@0.25.5': optional: true + '@esbuild/android-arm@0.25.9': + optional: true + '@esbuild/android-x64@0.18.20': optional: true '@esbuild/android-x64@0.25.5': optional: true + '@esbuild/android-x64@0.25.9': + optional: true + '@esbuild/darwin-arm64@0.18.20': optional: true '@esbuild/darwin-arm64@0.25.5': optional: true + '@esbuild/darwin-arm64@0.25.9': + optional: true + '@esbuild/darwin-x64@0.18.20': optional: true '@esbuild/darwin-x64@0.25.5': optional: true + '@esbuild/darwin-x64@0.25.9': + optional: true + '@esbuild/freebsd-arm64@0.18.20': optional: true '@esbuild/freebsd-arm64@0.25.5': optional: true + '@esbuild/freebsd-arm64@0.25.9': + optional: true + '@esbuild/freebsd-x64@0.18.20': optional: true '@esbuild/freebsd-x64@0.25.5': optional: true + '@esbuild/freebsd-x64@0.25.9': + optional: true + '@esbuild/linux-arm64@0.18.20': optional: true '@esbuild/linux-arm64@0.25.5': optional: true + '@esbuild/linux-arm64@0.25.9': + optional: true + '@esbuild/linux-arm@0.18.20': optional: true '@esbuild/linux-arm@0.25.5': optional: true + '@esbuild/linux-arm@0.25.9': + optional: true + '@esbuild/linux-ia32@0.18.20': optional: true '@esbuild/linux-ia32@0.25.5': optional: true + '@esbuild/linux-ia32@0.25.9': + optional: true + '@esbuild/linux-loong64@0.18.20': optional: true '@esbuild/linux-loong64@0.25.5': optional: true + '@esbuild/linux-loong64@0.25.9': + optional: true + '@esbuild/linux-mips64el@0.18.20': optional: true '@esbuild/linux-mips64el@0.25.5': optional: true + '@esbuild/linux-mips64el@0.25.9': + optional: true + '@esbuild/linux-ppc64@0.18.20': optional: true '@esbuild/linux-ppc64@0.25.5': optional: true + '@esbuild/linux-ppc64@0.25.9': + optional: true + '@esbuild/linux-riscv64@0.18.20': optional: true '@esbuild/linux-riscv64@0.25.5': optional: true + '@esbuild/linux-riscv64@0.25.9': + optional: true + '@esbuild/linux-s390x@0.18.20': optional: true '@esbuild/linux-s390x@0.25.5': optional: true + '@esbuild/linux-s390x@0.25.9': + optional: true + '@esbuild/linux-x64@0.18.20': optional: true '@esbuild/linux-x64@0.25.5': optional: true + '@esbuild/linux-x64@0.25.9': + optional: true + '@esbuild/netbsd-arm64@0.25.5': optional: true + '@esbuild/netbsd-arm64@0.25.9': + optional: true + '@esbuild/netbsd-x64@0.18.20': optional: true '@esbuild/netbsd-x64@0.25.5': optional: true + '@esbuild/netbsd-x64@0.25.9': + optional: true + '@esbuild/openbsd-arm64@0.25.5': optional: true + '@esbuild/openbsd-arm64@0.25.9': + optional: true + '@esbuild/openbsd-x64@0.18.20': optional: true '@esbuild/openbsd-x64@0.25.5': optional: true + '@esbuild/openbsd-x64@0.25.9': + optional: true + + '@esbuild/openharmony-arm64@0.25.9': + optional: true + '@esbuild/sunos-x64@0.18.20': optional: true '@esbuild/sunos-x64@0.25.5': optional: true + '@esbuild/sunos-x64@0.25.9': + optional: true + '@esbuild/win32-arm64@0.18.20': optional: true '@esbuild/win32-arm64@0.25.5': optional: true + '@esbuild/win32-arm64@0.25.9': + optional: true + '@esbuild/win32-ia32@0.18.20': optional: true '@esbuild/win32-ia32@0.25.5': optional: true + '@esbuild/win32-ia32@0.25.9': + optional: true + '@esbuild/win32-x64@0.18.20': optional: true '@esbuild/win32-x64@0.25.5': optional: true + '@esbuild/win32-x64@0.25.9': + optional: true + '@faker-js/faker@9.8.0': {} '@fontsource-variable/inter@5.2.5': {} @@ -4707,6 +5019,11 @@ snapshots: '@jridgewell/sourcemap-codec': 1.5.0 '@jridgewell/trace-mapping': 0.3.25 + '@jridgewell/remapping@2.3.5': + dependencies: + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.30 + '@jridgewell/resolve-uri@3.1.2': {} '@jridgewell/set-array@1.2.1': {} @@ -4744,7 +5061,7 @@ snapshots: '@kubernetes/client-node@1.3.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)': dependencies: '@types/js-yaml': 4.0.9 - '@types/node': 22.15.24 + '@types/node': 22.17.2 '@types/node-fetch': 2.6.12 '@types/stream-buffers': 3.0.7 form-data: 4.0.2 @@ -4844,6 +5161,13 @@ snapshots: '@mjackson/node-fetch-server@0.7.0': {} + '@napi-rs/wasm-runtime@1.0.3': + dependencies: + '@emnapi/core': 1.4.5 + '@emnapi/runtime': 1.4.5 + '@tybys/wasm-util': 0.10.0 + optional: true + '@neon-rs/load@0.0.4': {} '@npmcli/git@4.1.0': @@ -4875,6 +5199,10 @@ snapshots: dependencies: which: 3.0.1 + '@oxc-project/runtime@0.82.2': {} + + '@oxc-project/types@0.82.2': {} + '@pivanov/utils@0.0.2(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': dependencies: react: 19.1.1 @@ -5485,7 +5813,7 @@ snapshots: react: 19.1.1 react-dom: 19.1.1(react@19.1.1) - '@react-router/dev@7.8.1(@types/node@22.15.24)(jiti@2.4.2)(lightningcss@1.30.1)(react-dom@19.1.1(react@19.1.1))(react-router@7.8.1(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(react@19.1.1)(terser@5.39.0)(tsx@4.19.4)(typescript@5.8.3)(vite@6.3.5(@types/node@22.15.24)(jiti@2.4.2)(lightningcss@1.30.1)(terser@5.39.0)(tsx@4.19.4)(yaml@2.8.0))(yaml@2.8.0)': + '@react-router/dev@7.8.1(@types/node@24.3.0)(jiti@2.5.1)(lightningcss@1.30.1)(react-dom@19.1.1(react@19.1.1))(react-router@7.8.1(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(react@19.1.1)(rolldown-vite@7.1.3(@types/node@24.3.0)(esbuild@0.25.5)(jiti@2.5.1)(terser@5.39.0)(tsx@4.19.4)(yaml@2.8.0))(terser@5.39.0)(tsx@4.19.4)(typescript@5.9.2)(yaml@2.8.0)': dependencies: '@babel/core': 7.28.3 '@babel/generator': 7.28.3 @@ -5495,8 +5823,8 @@ snapshots: '@babel/traverse': 7.28.3 '@babel/types': 7.28.2 '@npmcli/package-json': 4.0.1 - '@react-router/node': 7.8.1(react-router@7.8.1(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(typescript@5.8.3) - '@vitejs/plugin-rsc': 0.4.11(react-dom@19.1.1(react@19.1.1))(react@19.1.1)(vite@6.3.5(@types/node@22.15.24)(jiti@2.4.2)(lightningcss@1.30.1)(terser@5.39.0)(tsx@4.19.4)(yaml@2.8.0)) + '@react-router/node': 7.8.1(react-router@7.8.1(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(typescript@5.9.2) + '@vitejs/plugin-rsc': 0.4.11(react-dom@19.1.1(react@19.1.1))(react@19.1.1)(rolldown-vite@7.1.3(@types/node@24.3.0)(esbuild@0.25.5)(jiti@2.5.1)(terser@5.39.0)(tsx@4.19.4)(yaml@2.8.0)) arg: 5.0.2 babel-dead-code-elimination: 1.0.10 chokidar: 4.0.3 @@ -5514,11 +5842,11 @@ snapshots: semver: 7.7.2 set-cookie-parser: 2.7.1 tinyglobby: 0.2.14 - valibot: 0.41.0(typescript@5.8.3) - vite: 6.3.5(@types/node@22.15.24)(jiti@2.4.2)(lightningcss@1.30.1)(terser@5.39.0)(tsx@4.19.4)(yaml@2.8.0) - vite-node: 3.2.4(@types/node@22.15.24)(jiti@2.4.2)(lightningcss@1.30.1)(terser@5.39.0)(tsx@4.19.4)(yaml@2.8.0) + valibot: 0.41.0(typescript@5.9.2) + vite: rolldown-vite@7.1.3(@types/node@24.3.0)(esbuild@0.25.5)(jiti@2.5.1)(terser@5.39.0)(tsx@4.19.4)(yaml@2.8.0) + vite-node: 3.2.4(@types/node@24.3.0)(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.39.0)(tsx@4.19.4)(yaml@2.8.0) optionalDependencies: - typescript: 5.8.3 + typescript: 5.9.2 transitivePeerDependencies: - '@types/node' - babel-plugin-macros @@ -5537,12 +5865,12 @@ snapshots: - tsx - yaml - '@react-router/node@7.8.1(react-router@7.8.1(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(typescript@5.8.3)': + '@react-router/node@7.8.1(react-router@7.8.1(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(typescript@5.9.2)': dependencies: '@mjackson/node-fetch-server': 0.2.0 react-router: 7.8.1(react-dom@19.1.1(react@19.1.1))(react@19.1.1) optionalDependencies: - typescript: 5.8.3 + typescript: 5.9.2 '@react-stately/calendar@3.8.1(react@19.1.1)': dependencies: @@ -5916,8 +6244,54 @@ snapshots: '@react-types/shared': 3.29.1(react@19.1.1) react: 19.1.1 + '@rolldown/binding-android-arm64@1.0.0-beta.33': + optional: true + + '@rolldown/binding-darwin-arm64@1.0.0-beta.33': + optional: true + + '@rolldown/binding-darwin-x64@1.0.0-beta.33': + optional: true + + '@rolldown/binding-freebsd-x64@1.0.0-beta.33': + optional: true + + '@rolldown/binding-linux-arm-gnueabihf@1.0.0-beta.33': + optional: true + + '@rolldown/binding-linux-arm64-gnu@1.0.0-beta.33': + optional: true + + '@rolldown/binding-linux-arm64-musl@1.0.0-beta.33': + optional: true + + '@rolldown/binding-linux-x64-gnu@1.0.0-beta.33': + optional: true + + '@rolldown/binding-linux-x64-musl@1.0.0-beta.33': + optional: true + + '@rolldown/binding-openharmony-arm64@1.0.0-beta.33': + optional: true + + '@rolldown/binding-wasm32-wasi@1.0.0-beta.33': + dependencies: + '@napi-rs/wasm-runtime': 1.0.3 + optional: true + + '@rolldown/binding-win32-arm64-msvc@1.0.0-beta.33': + optional: true + + '@rolldown/binding-win32-ia32-msvc@1.0.0-beta.33': + optional: true + + '@rolldown/binding-win32-x64-msvc@1.0.0-beta.33': + optional: true + '@rolldown/pluginutils@1.0.0-beta.29': {} + '@rolldown/pluginutils@1.0.0-beta.33': {} + '@rollup/pluginutils@5.1.4(rollup@4.46.3)': dependencies: '@types/estree': 1.0.7 @@ -5926,120 +6300,63 @@ snapshots: optionalDependencies: rollup: 4.46.3 - '@rollup/rollup-android-arm-eabi@4.36.0': - optional: true - '@rollup/rollup-android-arm-eabi@4.46.3': optional: true - '@rollup/rollup-android-arm64@4.36.0': - optional: true - '@rollup/rollup-android-arm64@4.46.3': optional: true - '@rollup/rollup-darwin-arm64@4.36.0': - optional: true - '@rollup/rollup-darwin-arm64@4.46.3': optional: true - '@rollup/rollup-darwin-x64@4.36.0': - optional: true - '@rollup/rollup-darwin-x64@4.46.3': optional: true - '@rollup/rollup-freebsd-arm64@4.36.0': - optional: true - '@rollup/rollup-freebsd-arm64@4.46.3': optional: true - '@rollup/rollup-freebsd-x64@4.36.0': - optional: true - '@rollup/rollup-freebsd-x64@4.46.3': optional: true - '@rollup/rollup-linux-arm-gnueabihf@4.36.0': - optional: true - '@rollup/rollup-linux-arm-gnueabihf@4.46.3': optional: true - '@rollup/rollup-linux-arm-musleabihf@4.36.0': - optional: true - '@rollup/rollup-linux-arm-musleabihf@4.46.3': optional: true - '@rollup/rollup-linux-arm64-gnu@4.36.0': - optional: true - '@rollup/rollup-linux-arm64-gnu@4.46.3': optional: true - '@rollup/rollup-linux-arm64-musl@4.36.0': - optional: true - '@rollup/rollup-linux-arm64-musl@4.46.3': optional: true - '@rollup/rollup-linux-loongarch64-gnu@4.36.0': - optional: true - '@rollup/rollup-linux-loongarch64-gnu@4.46.3': optional: true - '@rollup/rollup-linux-powerpc64le-gnu@4.36.0': - optional: true - '@rollup/rollup-linux-ppc64-gnu@4.46.3': optional: true - '@rollup/rollup-linux-riscv64-gnu@4.36.0': - optional: true - '@rollup/rollup-linux-riscv64-gnu@4.46.3': optional: true '@rollup/rollup-linux-riscv64-musl@4.46.3': optional: true - '@rollup/rollup-linux-s390x-gnu@4.36.0': - optional: true - '@rollup/rollup-linux-s390x-gnu@4.46.3': optional: true - '@rollup/rollup-linux-x64-gnu@4.36.0': - optional: true - '@rollup/rollup-linux-x64-gnu@4.46.3': optional: true - '@rollup/rollup-linux-x64-musl@4.36.0': - optional: true - '@rollup/rollup-linux-x64-musl@4.46.3': optional: true - '@rollup/rollup-win32-arm64-msvc@4.36.0': - optional: true - '@rollup/rollup-win32-arm64-msvc@4.46.3': optional: true - '@rollup/rollup-win32-ia32-msvc@4.36.0': - optional: true - '@rollup/rollup-win32-ia32-msvc@4.46.3': optional: true - '@rollup/rollup-win32-x64-msvc@4.36.0': - optional: true - '@rollup/rollup-win32-x64-msvc@4.46.3': optional: true @@ -6090,80 +6407,85 @@ snapshots: dependencies: tslib: 2.8.1 - '@tailwindcss/node@4.1.8': + '@tailwindcss/node@4.1.12': dependencies: - '@ampproject/remapping': 2.3.0 - enhanced-resolve: 5.18.1 - jiti: 2.4.2 + '@jridgewell/remapping': 2.3.5 + enhanced-resolve: 5.18.3 + jiti: 2.5.1 lightningcss: 1.30.1 magic-string: 0.30.17 source-map-js: 1.2.1 - tailwindcss: 4.1.8 + tailwindcss: 4.1.12 - '@tailwindcss/oxide-android-arm64@4.1.8': + '@tailwindcss/oxide-android-arm64@4.1.12': optional: true - '@tailwindcss/oxide-darwin-arm64@4.1.8': + '@tailwindcss/oxide-darwin-arm64@4.1.12': optional: true - '@tailwindcss/oxide-darwin-x64@4.1.8': + '@tailwindcss/oxide-darwin-x64@4.1.12': optional: true - '@tailwindcss/oxide-freebsd-x64@4.1.8': + '@tailwindcss/oxide-freebsd-x64@4.1.12': optional: true - '@tailwindcss/oxide-linux-arm-gnueabihf@4.1.8': + '@tailwindcss/oxide-linux-arm-gnueabihf@4.1.12': optional: true - '@tailwindcss/oxide-linux-arm64-gnu@4.1.8': + '@tailwindcss/oxide-linux-arm64-gnu@4.1.12': optional: true - '@tailwindcss/oxide-linux-arm64-musl@4.1.8': + '@tailwindcss/oxide-linux-arm64-musl@4.1.12': optional: true - '@tailwindcss/oxide-linux-x64-gnu@4.1.8': + '@tailwindcss/oxide-linux-x64-gnu@4.1.12': optional: true - '@tailwindcss/oxide-linux-x64-musl@4.1.8': + '@tailwindcss/oxide-linux-x64-musl@4.1.12': optional: true - '@tailwindcss/oxide-wasm32-wasi@4.1.8': + '@tailwindcss/oxide-wasm32-wasi@4.1.12': optional: true - '@tailwindcss/oxide-win32-arm64-msvc@4.1.8': + '@tailwindcss/oxide-win32-arm64-msvc@4.1.12': optional: true - '@tailwindcss/oxide-win32-x64-msvc@4.1.8': + '@tailwindcss/oxide-win32-x64-msvc@4.1.12': optional: true - '@tailwindcss/oxide@4.1.8': + '@tailwindcss/oxide@4.1.12': dependencies: detect-libc: 2.0.4 tar: 7.4.3 optionalDependencies: - '@tailwindcss/oxide-android-arm64': 4.1.8 - '@tailwindcss/oxide-darwin-arm64': 4.1.8 - '@tailwindcss/oxide-darwin-x64': 4.1.8 - '@tailwindcss/oxide-freebsd-x64': 4.1.8 - '@tailwindcss/oxide-linux-arm-gnueabihf': 4.1.8 - '@tailwindcss/oxide-linux-arm64-gnu': 4.1.8 - '@tailwindcss/oxide-linux-arm64-musl': 4.1.8 - '@tailwindcss/oxide-linux-x64-gnu': 4.1.8 - '@tailwindcss/oxide-linux-x64-musl': 4.1.8 - '@tailwindcss/oxide-wasm32-wasi': 4.1.8 - '@tailwindcss/oxide-win32-arm64-msvc': 4.1.8 - '@tailwindcss/oxide-win32-x64-msvc': 4.1.8 + '@tailwindcss/oxide-android-arm64': 4.1.12 + '@tailwindcss/oxide-darwin-arm64': 4.1.12 + '@tailwindcss/oxide-darwin-x64': 4.1.12 + '@tailwindcss/oxide-freebsd-x64': 4.1.12 + '@tailwindcss/oxide-linux-arm-gnueabihf': 4.1.12 + '@tailwindcss/oxide-linux-arm64-gnu': 4.1.12 + '@tailwindcss/oxide-linux-arm64-musl': 4.1.12 + '@tailwindcss/oxide-linux-x64-gnu': 4.1.12 + '@tailwindcss/oxide-linux-x64-musl': 4.1.12 + '@tailwindcss/oxide-wasm32-wasi': 4.1.12 + '@tailwindcss/oxide-win32-arm64-msvc': 4.1.12 + '@tailwindcss/oxide-win32-x64-msvc': 4.1.12 - '@tailwindcss/vite@4.1.8(vite@6.3.5(@types/node@22.15.24)(jiti@2.4.2)(lightningcss@1.30.1)(terser@5.39.0)(tsx@4.19.4)(yaml@2.8.0))': + '@tailwindcss/vite@4.1.12(rolldown-vite@7.1.3(@types/node@24.3.0)(esbuild@0.25.5)(jiti@2.5.1)(terser@5.39.0)(tsx@4.19.4)(yaml@2.8.0))': dependencies: - '@tailwindcss/node': 4.1.8 - '@tailwindcss/oxide': 4.1.8 - tailwindcss: 4.1.8 - vite: 6.3.5(@types/node@22.15.24)(jiti@2.4.2)(lightningcss@1.30.1)(terser@5.39.0)(tsx@4.19.4)(yaml@2.8.0) + '@tailwindcss/node': 4.1.12 + '@tailwindcss/oxide': 4.1.12 + tailwindcss: 4.1.12 + vite: rolldown-vite@7.1.3(@types/node@24.3.0)(esbuild@0.25.5)(jiti@2.5.1)(terser@5.39.0)(tsx@4.19.4)(yaml@2.8.0) + + '@tybys/wasm-util@0.10.0': + dependencies: + tslib: 2.8.1 + optional: true '@types/better-sqlite3@7.6.13': dependencies: - '@types/node': 22.17.2 + '@types/node': 24.3.0 optional: true '@types/chai@5.2.2': @@ -6172,8 +6494,6 @@ snapshots: '@types/deep-eql@4.0.2': {} - '@types/estree@1.0.6': {} - '@types/estree@1.0.7': {} '@types/estree@1.0.8': {} @@ -6199,21 +6519,20 @@ snapshots: '@types/node-fetch@2.6.12': dependencies: - '@types/node': 22.15.24 + '@types/node': 24.3.0 form-data: 4.0.2 - '@types/node@20.17.52': - dependencies: - undici-types: 6.19.8 - - '@types/node@22.15.24': + '@types/node@20.19.11': dependencies: undici-types: 6.21.0 '@types/node@22.17.2': dependencies: undici-types: 6.21.0 - optional: true + + '@types/node@24.3.0': + dependencies: + undici-types: 7.10.0 '@types/react-dom@19.1.7(@types/react@19.1.10)': dependencies: @@ -6229,7 +6548,7 @@ snapshots: '@types/stream-buffers@3.0.7': dependencies: - '@types/node': 22.15.24 + '@types/node': 24.3.0 '@types/unist@3.0.3': {} @@ -6237,11 +6556,42 @@ snapshots: '@types/websocket@1.0.10': dependencies: - '@types/node': 22.15.24 + '@types/node': 24.3.0 '@types/ws@8.18.1': dependencies: - '@types/node': 22.15.24 + '@types/node': 24.3.0 + + '@typescript/native-preview-darwin-arm64@7.0.0-dev.20250818.1': + optional: true + + '@typescript/native-preview-darwin-x64@7.0.0-dev.20250818.1': + optional: true + + '@typescript/native-preview-linux-arm64@7.0.0-dev.20250818.1': + optional: true + + '@typescript/native-preview-linux-arm@7.0.0-dev.20250818.1': + optional: true + + '@typescript/native-preview-linux-x64@7.0.0-dev.20250818.1': + optional: true + + '@typescript/native-preview-win32-arm64@7.0.0-dev.20250818.1': + optional: true + + '@typescript/native-preview-win32-x64@7.0.0-dev.20250818.1': + optional: true + + '@typescript/native-preview@7.0.0-dev.20250818.1': + optionalDependencies: + '@typescript/native-preview-darwin-arm64': 7.0.0-dev.20250818.1 + '@typescript/native-preview-darwin-x64': 7.0.0-dev.20250818.1 + '@typescript/native-preview-linux-arm': 7.0.0-dev.20250818.1 + '@typescript/native-preview-linux-arm64': 7.0.0-dev.20250818.1 + '@typescript/native-preview-linux-x64': 7.0.0-dev.20250818.1 + '@typescript/native-preview-win32-arm64': 7.0.0-dev.20250818.1 + '@typescript/native-preview-win32-x64': 7.0.0-dev.20250818.1 '@uiw/codemirror-extensions-basic-setup@4.23.12(@codemirror/autocomplete@6.18.2(@codemirror/language@6.11.0)(@codemirror/state@6.5.2)(@codemirror/view@6.36.8)(@lezer/common@1.2.3))(@codemirror/commands@6.8.1)(@codemirror/language@6.11.0)(@codemirror/lint@6.8.2)(@codemirror/search@6.5.7)(@codemirror/state@6.5.2)(@codemirror/view@6.36.8)': dependencies: @@ -6294,7 +6644,7 @@ snapshots: '@ungap/structured-clone@1.3.0': {} - '@vitejs/plugin-rsc@0.4.11(react-dom@19.1.1(react@19.1.1))(react@19.1.1)(vite@6.3.5(@types/node@22.15.24)(jiti@2.4.2)(lightningcss@1.30.1)(terser@5.39.0)(tsx@4.19.4)(yaml@2.8.0))': + '@vitejs/plugin-rsc@0.4.11(react-dom@19.1.1(react@19.1.1))(react@19.1.1)(rolldown-vite@7.1.3(@types/node@24.3.0)(esbuild@0.25.5)(jiti@2.5.1)(terser@5.39.0)(tsx@4.19.4)(yaml@2.8.0))': dependencies: '@mjackson/node-fetch-server': 0.7.0 es-module-lexer: 1.7.0 @@ -6304,14 +6654,14 @@ snapshots: react: 19.1.1 react-dom: 19.1.1(react@19.1.1) turbo-stream: 3.1.0 - vite: 6.3.5(@types/node@22.15.24)(jiti@2.4.2)(lightningcss@1.30.1)(terser@5.39.0)(tsx@4.19.4)(yaml@2.8.0) - vitefu: 1.1.1(vite@6.3.5(@types/node@22.15.24)(jiti@2.4.2)(lightningcss@1.30.1)(terser@5.39.0)(tsx@4.19.4)(yaml@2.8.0)) + vite: rolldown-vite@7.1.3(@types/node@24.3.0)(esbuild@0.25.5)(jiti@2.5.1)(terser@5.39.0)(tsx@4.19.4)(yaml@2.8.0) + vitefu: 1.1.1(rolldown-vite@7.1.3(@types/node@24.3.0)(esbuild@0.25.5)(jiti@2.5.1)(terser@5.39.0)(tsx@4.19.4)(yaml@2.8.0)) - '@vitejs/plugin-vue@6.0.1(vite@7.1.2(@types/node@22.15.24)(jiti@2.4.2)(lightningcss@1.30.1)(terser@5.39.0)(tsx@4.19.4)(yaml@2.8.0))(vue@3.5.18(typescript@5.8.3))': + '@vitejs/plugin-vue@6.0.1(vite@7.1.2(@types/node@24.3.0)(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.39.0)(tsx@4.19.4)(yaml@2.8.0))(vue@3.5.18(typescript@5.9.2))': dependencies: '@rolldown/pluginutils': 1.0.0-beta.29 - vite: 7.1.2(@types/node@22.15.24)(jiti@2.4.2)(lightningcss@1.30.1)(terser@5.39.0)(tsx@4.19.4)(yaml@2.8.0) - vue: 3.5.18(typescript@5.8.3) + vite: 7.1.2(@types/node@24.3.0)(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.39.0)(tsx@4.19.4)(yaml@2.8.0) + vue: 3.5.18(typescript@5.9.2) '@vitest/expect@3.2.4': dependencies: @@ -6321,13 +6671,13 @@ snapshots: chai: 5.2.1 tinyrainbow: 2.0.0 - '@vitest/mocker@3.2.4(vite@6.3.5(@types/node@22.15.24)(jiti@2.4.2)(lightningcss@1.30.1)(terser@5.39.0)(tsx@4.19.4)(yaml@2.8.0))': + '@vitest/mocker@3.2.4(vite@7.1.2(@types/node@24.3.0)(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.39.0)(tsx@4.19.4)(yaml@2.8.0))': dependencies: '@vitest/spy': 3.2.4 estree-walker: 3.0.3 magic-string: 0.30.17 optionalDependencies: - vite: 6.3.5(@types/node@22.15.24)(jiti@2.4.2)(lightningcss@1.30.1)(terser@5.39.0)(tsx@4.19.4)(yaml@2.8.0) + vite: 7.1.2(@types/node@24.3.0)(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.39.0)(tsx@4.19.4)(yaml@2.8.0) '@vitest/pretty-format@3.2.4': dependencies: @@ -6419,34 +6769,34 @@ snapshots: '@vue/shared': 3.5.18 csstype: 3.1.3 - '@vue/server-renderer@3.5.18(vue@3.5.18(typescript@5.8.3))': + '@vue/server-renderer@3.5.18(vue@3.5.18(typescript@5.9.2))': dependencies: '@vue/compiler-ssr': 3.5.18 '@vue/shared': 3.5.18 - vue: 3.5.18(typescript@5.8.3) + vue: 3.5.18(typescript@5.9.2) '@vue/shared@3.5.18': {} - '@vueuse/core@13.7.0(vue@3.5.18(typescript@5.8.3))': + '@vueuse/core@13.7.0(vue@3.5.18(typescript@5.9.2))': dependencies: '@types/web-bluetooth': 0.0.21 '@vueuse/metadata': 13.7.0 - '@vueuse/shared': 13.7.0(vue@3.5.18(typescript@5.8.3)) - vue: 3.5.18(typescript@5.8.3) + '@vueuse/shared': 13.7.0(vue@3.5.18(typescript@5.9.2)) + vue: 3.5.18(typescript@5.9.2) - '@vueuse/integrations@13.7.0(focus-trap@7.6.5)(vue@3.5.18(typescript@5.8.3))': + '@vueuse/integrations@13.7.0(focus-trap@7.6.5)(vue@3.5.18(typescript@5.9.2))': dependencies: - '@vueuse/core': 13.7.0(vue@3.5.18(typescript@5.8.3)) - '@vueuse/shared': 13.7.0(vue@3.5.18(typescript@5.8.3)) - vue: 3.5.18(typescript@5.8.3) + '@vueuse/core': 13.7.0(vue@3.5.18(typescript@5.9.2)) + '@vueuse/shared': 13.7.0(vue@3.5.18(typescript@5.9.2)) + vue: 3.5.18(typescript@5.9.2) optionalDependencies: focus-trap: 7.6.5 '@vueuse/metadata@13.7.0': {} - '@vueuse/shared@13.7.0(vue@3.5.18(typescript@5.8.3))': + '@vueuse/shared@13.7.0(vue@3.5.18(typescript@5.9.2))': dependencies: - vue: 3.5.18(typescript@5.8.3) + vue: 3.5.18(typescript@5.9.2) '@xterm/addon-clipboard@0.1.0(@xterm/xterm@5.5.0)': dependencies: @@ -6489,6 +6839,8 @@ snapshots: ansi-styles@6.2.1: {} + ansis@4.1.0: {} + arg@5.0.2: {} argparse@2.0.1: {} @@ -6751,7 +7103,7 @@ snapshots: once: 1.4.0 optional: true - enhanced-resolve@5.18.1: + enhanced-resolve@5.18.3: dependencies: graceful-fs: 4.2.11 tapable: 2.2.2 @@ -6837,6 +7189,35 @@ snapshots: '@esbuild/win32-ia32': 0.25.5 '@esbuild/win32-x64': 0.25.5 + esbuild@0.25.9: + optionalDependencies: + '@esbuild/aix-ppc64': 0.25.9 + '@esbuild/android-arm': 0.25.9 + '@esbuild/android-arm64': 0.25.9 + '@esbuild/android-x64': 0.25.9 + '@esbuild/darwin-arm64': 0.25.9 + '@esbuild/darwin-x64': 0.25.9 + '@esbuild/freebsd-arm64': 0.25.9 + '@esbuild/freebsd-x64': 0.25.9 + '@esbuild/linux-arm': 0.25.9 + '@esbuild/linux-arm64': 0.25.9 + '@esbuild/linux-ia32': 0.25.9 + '@esbuild/linux-loong64': 0.25.9 + '@esbuild/linux-mips64el': 0.25.9 + '@esbuild/linux-ppc64': 0.25.9 + '@esbuild/linux-riscv64': 0.25.9 + '@esbuild/linux-s390x': 0.25.9 + '@esbuild/linux-x64': 0.25.9 + '@esbuild/netbsd-arm64': 0.25.9 + '@esbuild/netbsd-x64': 0.25.9 + '@esbuild/openbsd-arm64': 0.25.9 + '@esbuild/openbsd-x64': 0.25.9 + '@esbuild/openharmony-arm64': 0.25.9 + '@esbuild/sunos-x64': 0.25.9 + '@esbuild/win32-arm64': 0.25.9 + '@esbuild/win32-ia32': 0.25.9 + '@esbuild/win32-x64': 0.25.9 + escalade@3.2.0: {} estree-walker@2.0.2: {} @@ -7032,7 +7413,7 @@ snapshots: optionalDependencies: '@pkgjs/parseargs': 0.11.0 - jiti@2.4.2: {} + jiti@2.5.1: {} jose@6.0.11: {} @@ -7244,10 +7625,9 @@ snapshots: minisearch@7.1.2: {} - minizlib@3.0.1: + minizlib@3.0.2: dependencies: minipass: 7.1.2 - rimraf: 5.0.10 mitt@3.0.1: {} @@ -7520,17 +7900,17 @@ snapshots: react-dom: 19.1.1(react@19.1.1) react-router: 7.8.1(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - react-router-hono-server@2.21.0(@hono/node-server@1.19.0(hono@4.7.10))(@react-router/dev@7.8.1(@types/node@22.15.24)(jiti@2.4.2)(lightningcss@1.30.1)(react-dom@19.1.1(react@19.1.1))(react-router@7.8.1(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(react@19.1.1)(terser@5.39.0)(tsx@4.19.4)(typescript@5.8.3)(vite@6.3.5(@types/node@22.15.24)(jiti@2.4.2)(lightningcss@1.30.1)(terser@5.39.0)(tsx@4.19.4)(yaml@2.8.0))(yaml@2.8.0))(@types/react@19.1.10)(bufferutil@4.0.9)(react-router@7.8.1(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(utf-8-validate@5.0.10)(vite@6.3.5(@types/node@22.15.24)(jiti@2.4.2)(lightningcss@1.30.1)(terser@5.39.0)(tsx@4.19.4)(yaml@2.8.0)): + react-router-hono-server@2.21.0(patch_hash=b68723a36649e2c3bd9e9edcfff3a8bc0fdb48e1cf6d64eba37c04f7bfbb1417)(@hono/node-server@1.19.0(hono@4.7.10))(@react-router/dev@7.8.1(@types/node@24.3.0)(jiti@2.5.1)(lightningcss@1.30.1)(react-dom@19.1.1(react@19.1.1))(react-router@7.8.1(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(react@19.1.1)(rolldown-vite@7.1.3(@types/node@24.3.0)(esbuild@0.25.5)(jiti@2.5.1)(terser@5.39.0)(tsx@4.19.4)(yaml@2.8.0))(terser@5.39.0)(tsx@4.19.4)(typescript@5.9.2)(yaml@2.8.0))(@types/react@19.1.10)(bufferutil@4.0.9)(react-router@7.8.1(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(rolldown-vite@7.1.3(@types/node@24.3.0)(esbuild@0.25.5)(jiti@2.5.1)(terser@5.39.0)(tsx@4.19.4)(yaml@2.8.0))(utf-8-validate@5.0.10): dependencies: '@drizzle-team/brocli': 0.11.0 '@hono/node-server': 1.19.0(hono@4.7.10) '@hono/node-ws': 1.2.0(@hono/node-server@1.19.0(hono@4.7.10))(bufferutil@4.0.9)(hono@4.7.10)(utf-8-validate@5.0.10) '@hono/vite-dev-server': 0.20.1(hono@4.7.10) - '@react-router/dev': 7.8.1(@types/node@22.15.24)(jiti@2.4.2)(lightningcss@1.30.1)(react-dom@19.1.1(react@19.1.1))(react-router@7.8.1(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(react@19.1.1)(terser@5.39.0)(tsx@4.19.4)(typescript@5.8.3)(vite@6.3.5(@types/node@22.15.24)(jiti@2.4.2)(lightningcss@1.30.1)(terser@5.39.0)(tsx@4.19.4)(yaml@2.8.0))(yaml@2.8.0) + '@react-router/dev': 7.8.1(@types/node@24.3.0)(jiti@2.5.1)(lightningcss@1.30.1)(react-dom@19.1.1(react@19.1.1))(react-router@7.8.1(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(react@19.1.1)(rolldown-vite@7.1.3(@types/node@24.3.0)(esbuild@0.25.5)(jiti@2.5.1)(terser@5.39.0)(tsx@4.19.4)(yaml@2.8.0))(terser@5.39.0)(tsx@4.19.4)(typescript@5.9.2)(yaml@2.8.0) '@types/react': 19.1.10 hono: 4.7.10 react-router: 7.8.1(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - vite: 6.3.5(@types/node@22.15.24)(jiti@2.4.2)(lightningcss@1.30.1)(terser@5.39.0)(tsx@4.19.4)(yaml@2.8.0) + vite: rolldown-vite@7.1.3(@types/node@24.3.0)(esbuild@0.25.5)(jiti@2.5.1)(terser@5.39.0)(tsx@4.19.4)(yaml@2.8.0) transitivePeerDependencies: - bufferutil - utf-8-validate @@ -7553,7 +7933,7 @@ snapshots: '@pivanov/utils': 0.0.2(react-dom@19.1.1(react@19.1.1))(react@19.1.1) '@preact/signals': 1.3.2(preact@10.26.7) '@rollup/pluginutils': 5.1.4(rollup@4.46.3) - '@types/node': 20.17.52 + '@types/node': 20.19.11 bippy: 0.3.14(@types/react@19.1.10)(react@19.1.1) esbuild: 0.25.5 estree-walker: 3.0.3 @@ -7640,34 +8020,44 @@ snapshots: rfdc@1.4.1: {} - rimraf@5.0.10: + rolldown-vite@7.1.3(@types/node@24.3.0)(esbuild@0.25.5)(jiti@2.5.1)(terser@5.39.0)(tsx@4.19.4)(yaml@2.8.0): dependencies: - glob: 10.4.5 - - rollup@4.36.0: - dependencies: - '@types/estree': 1.0.6 + fdir: 6.5.0(picomatch@4.0.3) + lightningcss: 1.30.1 + picomatch: 4.0.3 + postcss: 8.5.6 + rolldown: 1.0.0-beta.33 + tinyglobby: 0.2.14 optionalDependencies: - '@rollup/rollup-android-arm-eabi': 4.36.0 - '@rollup/rollup-android-arm64': 4.36.0 - '@rollup/rollup-darwin-arm64': 4.36.0 - '@rollup/rollup-darwin-x64': 4.36.0 - '@rollup/rollup-freebsd-arm64': 4.36.0 - '@rollup/rollup-freebsd-x64': 4.36.0 - '@rollup/rollup-linux-arm-gnueabihf': 4.36.0 - '@rollup/rollup-linux-arm-musleabihf': 4.36.0 - '@rollup/rollup-linux-arm64-gnu': 4.36.0 - '@rollup/rollup-linux-arm64-musl': 4.36.0 - '@rollup/rollup-linux-loongarch64-gnu': 4.36.0 - '@rollup/rollup-linux-powerpc64le-gnu': 4.36.0 - '@rollup/rollup-linux-riscv64-gnu': 4.36.0 - '@rollup/rollup-linux-s390x-gnu': 4.36.0 - '@rollup/rollup-linux-x64-gnu': 4.36.0 - '@rollup/rollup-linux-x64-musl': 4.36.0 - '@rollup/rollup-win32-arm64-msvc': 4.36.0 - '@rollup/rollup-win32-ia32-msvc': 4.36.0 - '@rollup/rollup-win32-x64-msvc': 4.36.0 + '@types/node': 24.3.0 + esbuild: 0.25.5 fsevents: 2.3.3 + jiti: 2.5.1 + terser: 5.39.0 + tsx: 4.19.4 + yaml: 2.8.0 + + rolldown@1.0.0-beta.33: + dependencies: + '@oxc-project/runtime': 0.82.2 + '@oxc-project/types': 0.82.2 + '@rolldown/pluginutils': 1.0.0-beta.33 + ansis: 4.1.0 + optionalDependencies: + '@rolldown/binding-android-arm64': 1.0.0-beta.33 + '@rolldown/binding-darwin-arm64': 1.0.0-beta.33 + '@rolldown/binding-darwin-x64': 1.0.0-beta.33 + '@rolldown/binding-freebsd-x64': 1.0.0-beta.33 + '@rolldown/binding-linux-arm-gnueabihf': 1.0.0-beta.33 + '@rolldown/binding-linux-arm64-gnu': 1.0.0-beta.33 + '@rolldown/binding-linux-arm64-musl': 1.0.0-beta.33 + '@rolldown/binding-linux-x64-gnu': 1.0.0-beta.33 + '@rolldown/binding-linux-x64-musl': 1.0.0-beta.33 + '@rolldown/binding-openharmony-arm64': 1.0.0-beta.33 + '@rolldown/binding-wasm32-wasi': 1.0.0-beta.33 + '@rolldown/binding-win32-arm64-msvc': 1.0.0-beta.33 + '@rolldown/binding-win32-ia32-msvc': 1.0.0-beta.33 + '@rolldown/binding-win32-x64-msvc': 1.0.0-beta.33 rollup@4.46.3: dependencies: @@ -7851,6 +8241,8 @@ snapshots: dependencies: tailwindcss: 4.1.8 + tailwindcss@4.1.12: {} + tailwindcss@4.1.8: {} tapable@2.2.2: {} @@ -7893,7 +8285,7 @@ snapshots: '@isaacs/fs-minipass': 4.0.1 chownr: 3.0.0 minipass: 7.1.2 - minizlib: 3.0.1 + minizlib: 3.0.2 mkdirp: 3.0.1 yallist: 5.0.0 @@ -7928,9 +8320,9 @@ snapshots: trim-lines@3.0.1: {} - tsconfck@3.1.4(typescript@5.8.3): + tsconfck@3.1.4(typescript@5.9.2): optionalDependencies: - typescript: 5.8.3 + typescript: 5.9.2 tslib@2.6.2: {} @@ -7952,12 +8344,12 @@ snapshots: type-fest@4.41.0: {} - typescript@5.8.3: {} - - undici-types@6.19.8: {} + typescript@5.9.2: {} undici-types@6.21.0: {} + undici-types@7.10.0: {} + undici@7.10.0: {} unist-util-is@6.0.0: @@ -8012,9 +8404,9 @@ snapshots: util-deprecate@1.0.2: optional: true - valibot@0.41.0(typescript@5.8.3): + valibot@0.41.0(typescript@5.9.2): optionalDependencies: - typescript: 5.8.3 + typescript: 5.9.2 validate-npm-package-license@3.0.4: dependencies: @@ -8033,13 +8425,13 @@ snapshots: '@types/unist': 3.0.3 vfile-message: 4.0.3 - vite-node@3.2.4(@types/node@22.15.24)(jiti@2.4.2)(lightningcss@1.30.1)(terser@5.39.0)(tsx@4.19.4)(yaml@2.8.0): + vite-node@3.2.4(@types/node@24.3.0)(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.39.0)(tsx@4.19.4)(yaml@2.8.0): dependencies: cac: 6.7.14 debug: 4.4.1 es-module-lexer: 1.7.0 pathe: 2.0.3 - vite: 6.3.5(@types/node@22.15.24)(jiti@2.4.2)(lightningcss@1.30.1)(terser@5.39.0)(tsx@4.19.4)(yaml@2.8.0) + vite: 7.1.2(@types/node@24.3.0)(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.39.0)(tsx@4.19.4)(yaml@2.8.0) transitivePeerDependencies: - '@types/node' - jiti @@ -8054,56 +8446,39 @@ snapshots: - tsx - yaml - vite-tsconfig-paths@5.1.4(typescript@5.8.3)(vite@6.3.5(@types/node@22.15.24)(jiti@2.4.2)(lightningcss@1.30.1)(terser@5.39.0)(tsx@4.19.4)(yaml@2.8.0)): + vite-tsconfig-paths@5.1.4(rolldown-vite@7.1.3(@types/node@24.3.0)(esbuild@0.25.5)(jiti@2.5.1)(terser@5.39.0)(tsx@4.19.4)(yaml@2.8.0))(typescript@5.9.2): dependencies: debug: 4.4.0 globrex: 0.1.2 - tsconfck: 3.1.4(typescript@5.8.3) + tsconfck: 3.1.4(typescript@5.9.2) optionalDependencies: - vite: 6.3.5(@types/node@22.15.24)(jiti@2.4.2)(lightningcss@1.30.1)(terser@5.39.0)(tsx@4.19.4)(yaml@2.8.0) + vite: rolldown-vite@7.1.3(@types/node@24.3.0)(esbuild@0.25.5)(jiti@2.5.1)(terser@5.39.0)(tsx@4.19.4)(yaml@2.8.0) transitivePeerDependencies: - supports-color - typescript - vite@6.3.5(@types/node@22.15.24)(jiti@2.4.2)(lightningcss@1.30.1)(terser@5.39.0)(tsx@4.19.4)(yaml@2.8.0): + vite@7.1.2(@types/node@24.3.0)(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.39.0)(tsx@4.19.4)(yaml@2.8.0): dependencies: - esbuild: 0.25.5 - fdir: 6.4.5(picomatch@4.0.2) - picomatch: 4.0.2 - postcss: 8.5.4 - rollup: 4.36.0 - tinyglobby: 0.2.14 - optionalDependencies: - '@types/node': 22.15.24 - fsevents: 2.3.3 - jiti: 2.4.2 - lightningcss: 1.30.1 - terser: 5.39.0 - tsx: 4.19.4 - yaml: 2.8.0 - - vite@7.1.2(@types/node@22.15.24)(jiti@2.4.2)(lightningcss@1.30.1)(terser@5.39.0)(tsx@4.19.4)(yaml@2.8.0): - dependencies: - esbuild: 0.25.5 + esbuild: 0.25.9 fdir: 6.5.0(picomatch@4.0.3) picomatch: 4.0.3 postcss: 8.5.6 rollup: 4.46.3 tinyglobby: 0.2.14 optionalDependencies: - '@types/node': 22.15.24 + '@types/node': 24.3.0 fsevents: 2.3.3 - jiti: 2.4.2 + jiti: 2.5.1 lightningcss: 1.30.1 terser: 5.39.0 tsx: 4.19.4 yaml: 2.8.0 - vitefu@1.1.1(vite@6.3.5(@types/node@22.15.24)(jiti@2.4.2)(lightningcss@1.30.1)(terser@5.39.0)(tsx@4.19.4)(yaml@2.8.0)): + vitefu@1.1.1(rolldown-vite@7.1.3(@types/node@24.3.0)(esbuild@0.25.5)(jiti@2.5.1)(terser@5.39.0)(tsx@4.19.4)(yaml@2.8.0)): optionalDependencies: - vite: 6.3.5(@types/node@22.15.24)(jiti@2.4.2)(lightningcss@1.30.1)(terser@5.39.0)(tsx@4.19.4)(yaml@2.8.0) + vite: rolldown-vite@7.1.3(@types/node@24.3.0)(esbuild@0.25.5)(jiti@2.5.1)(terser@5.39.0)(tsx@4.19.4)(yaml@2.8.0) - vitepress@2.0.0-alpha.11(@types/node@22.15.24)(jiti@2.4.2)(lightningcss@1.30.1)(postcss@8.5.4)(terser@5.39.0)(tsx@4.19.4)(typescript@5.8.3)(yaml@2.8.0): + vitepress@2.0.0-alpha.11(@types/node@24.3.0)(jiti@2.5.1)(lightningcss@1.30.1)(postcss@8.5.4)(terser@5.39.0)(tsx@4.19.4)(typescript@5.9.2)(yaml@2.8.0): dependencies: '@docsearch/css': 4.0.0-beta.7 '@docsearch/js': 4.0.0-beta.7 @@ -8112,17 +8487,17 @@ snapshots: '@shikijs/transformers': 3.9.2 '@shikijs/types': 3.9.2 '@types/markdown-it': 14.1.2 - '@vitejs/plugin-vue': 6.0.1(vite@7.1.2(@types/node@22.15.24)(jiti@2.4.2)(lightningcss@1.30.1)(terser@5.39.0)(tsx@4.19.4)(yaml@2.8.0))(vue@3.5.18(typescript@5.8.3)) + '@vitejs/plugin-vue': 6.0.1(vite@7.1.2(@types/node@24.3.0)(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.39.0)(tsx@4.19.4)(yaml@2.8.0))(vue@3.5.18(typescript@5.9.2)) '@vue/devtools-api': 8.0.0 '@vue/shared': 3.5.18 - '@vueuse/core': 13.7.0(vue@3.5.18(typescript@5.8.3)) - '@vueuse/integrations': 13.7.0(focus-trap@7.6.5)(vue@3.5.18(typescript@5.8.3)) + '@vueuse/core': 13.7.0(vue@3.5.18(typescript@5.9.2)) + '@vueuse/integrations': 13.7.0(focus-trap@7.6.5)(vue@3.5.18(typescript@5.9.2)) focus-trap: 7.6.5 mark.js: 8.11.1 minisearch: 7.1.2 shiki: 3.9.2 - vite: 7.1.2(@types/node@22.15.24)(jiti@2.4.2)(lightningcss@1.30.1)(terser@5.39.0)(tsx@4.19.4)(yaml@2.8.0) - vue: 3.5.18(typescript@5.8.3) + vite: 7.1.2(@types/node@24.3.0)(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.39.0)(tsx@4.19.4)(yaml@2.8.0) + vue: 3.5.18(typescript@5.9.2) optionalDependencies: postcss: 8.5.4 transitivePeerDependencies: @@ -8150,11 +8525,11 @@ snapshots: - universal-cookie - yaml - vitest@3.2.4(@types/node@22.15.24)(jiti@2.4.2)(lightningcss@1.30.1)(terser@5.39.0)(tsx@4.19.4)(yaml@2.8.0): + vitest@3.2.4(@types/node@24.3.0)(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.39.0)(tsx@4.19.4)(yaml@2.8.0): dependencies: '@types/chai': 5.2.2 '@vitest/expect': 3.2.4 - '@vitest/mocker': 3.2.4(vite@6.3.5(@types/node@22.15.24)(jiti@2.4.2)(lightningcss@1.30.1)(terser@5.39.0)(tsx@4.19.4)(yaml@2.8.0)) + '@vitest/mocker': 3.2.4(vite@7.1.2(@types/node@24.3.0)(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.39.0)(tsx@4.19.4)(yaml@2.8.0)) '@vitest/pretty-format': 3.2.4 '@vitest/runner': 3.2.4 '@vitest/snapshot': 3.2.4 @@ -8172,11 +8547,11 @@ snapshots: tinyglobby: 0.2.14 tinypool: 1.1.1 tinyrainbow: 2.0.0 - vite: 6.3.5(@types/node@22.15.24)(jiti@2.4.2)(lightningcss@1.30.1)(terser@5.39.0)(tsx@4.19.4)(yaml@2.8.0) - vite-node: 3.2.4(@types/node@22.15.24)(jiti@2.4.2)(lightningcss@1.30.1)(terser@5.39.0)(tsx@4.19.4)(yaml@2.8.0) + vite: 7.1.2(@types/node@24.3.0)(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.39.0)(tsx@4.19.4)(yaml@2.8.0) + vite-node: 3.2.4(@types/node@24.3.0)(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.39.0)(tsx@4.19.4)(yaml@2.8.0) why-is-node-running: 2.3.0 optionalDependencies: - '@types/node': 22.15.24 + '@types/node': 24.3.0 transitivePeerDependencies: - jiti - less @@ -8191,15 +8566,15 @@ snapshots: - tsx - yaml - vue@3.5.18(typescript@5.8.3): + vue@3.5.18(typescript@5.9.2): dependencies: '@vue/compiler-dom': 3.5.18 '@vue/compiler-sfc': 3.5.18 '@vue/runtime-dom': 3.5.18 - '@vue/server-renderer': 3.5.18(vue@3.5.18(typescript@5.8.3)) + '@vue/server-renderer': 3.5.18(vue@3.5.18(typescript@5.9.2)) '@vue/shared': 3.5.18 optionalDependencies: - typescript: 5.8.3 + typescript: 5.9.2 w3c-keyname@2.2.8: {} diff --git a/tsconfig.json b/tsconfig.json index 3e2b733..3fc47aa 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,11 +1,11 @@ { "include": [ - "**/*.ts", - "**/*.tsx", - "**/.server/**/*.ts", - "**/.server/**/*.tsx", - "**/.client/**/*.ts", - "**/.client/**/*.tsx" + "**/*.ts", + "**/*.tsx", + "**/.server/**/*.ts", + "**/.server/**/*.tsx", + "**/.client/**/*.ts", + "**/.client/**/*.tsx" ], "compilerOptions": { "lib": ["DOM", "DOM.Iterable", "ES2022"], @@ -21,7 +21,6 @@ "allowJs": true, "skipLibCheck": true, "forceConsistentCasingInFileNames": true, - "baseUrl": ".", "paths": { "~/*": ["./app/*"], "~server/*": ["./server/*"] From 8cb91cd45b437b791e7543482e88893e2b231f06 Mon Sep 17 00:00:00 2001 From: Aarnav Tale Date: Tue, 19 Aug 2025 00:10:06 -0400 Subject: [PATCH 74/97] feat: overhaul hp_agent lifecycle handling * Added backoff and liveness probes for better management * Switched IPC to a simple text based system * Lookups don't directly touch the agent now * Use the database as a source of truth --- CHANGELOG.md | 3 +- app/server/db/schema.ts | 14 +- app/server/hp-agent.ts | 400 ++++++++++++++++++++++++++++ app/server/index.ts | 10 +- app/server/web/agent.ts | 447 -------------------------------- cmd/hp_agent/hp_agent.go | 3 + drizzle/0001_naive_lilith.sql | 5 + drizzle/meta/0001_snapshot.json | 73 ++++++ drizzle/meta/_journal.json | 7 + internal/hpagent/handler.go | 84 ++---- internal/tsnet/peers.go | 82 ++++++ mise.toml | 3 + 12 files changed, 619 insertions(+), 512 deletions(-) create mode 100644 app/server/hp-agent.ts delete mode 100644 app/server/web/agent.ts create mode 100644 drizzle/0001_naive_lilith.sql create mode 100644 drizzle/meta/0001_snapshot.json diff --git a/CHANGELOG.md b/CHANGELOG.md index c8f4e8b..8e01999 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,7 @@ - This is built on top of a Go binary that runs in WebAssembly, using Xterm.js for the terminal interface. - Begin using a new SQLite database file in `/var/lib/headplane/hp_persist.db`. - The database is created automatically if it does not exist. - - It currently stores SSH connection details and will migrate older data. + - It currently stores SSH connection details and HostInfo for the agent. - The docker container now runs in a distroless image (closes [#255](https://github.com/tale/headplane/issues/255)). - A debug version of the container that runs as root and has a shell is available as `ghcr.io/tale/headplane:-shell`. - Removing a Split DNS record will no longer make the split domain unresolvable by clients (closes [#231](https://github.com/tale/headplane/issues/231)). @@ -18,6 +18,7 @@ - Environment variables are interpolatable into these paths - See the full reference in the [docs](https://github.com/tale/headplane/blob/main/docs/Configuration.md#sensitive-values) - The nix overlay build is fixed for the SSH module (via [#282](https://github.com/tale/headplane/pull/282)) +- Switch our build processes to use TypeScript Go and Rolldown Vite for better build and type-check performance. ### 0.6.0 (May 25, 2025) - Headplane 0.6.0 now requires **Headscale 0.26.0** or newer. diff --git a/app/server/db/schema.ts b/app/server/db/schema.ts index b0034fe..9aa6f69 100644 --- a/app/server/db/schema.ts +++ b/app/server/db/schema.ts @@ -1,4 +1,5 @@ -import { sqliteTable, text } from 'drizzle-orm/sqlite-core'; +import { integer, sqliteTable, text } from 'drizzle-orm/sqlite-core'; +import { HostInfo } from '~/types'; export const ephemeralNodes = sqliteTable('ephemeral_nodes', { auth_key: text('auth_key').primaryKey(), @@ -7,3 +8,14 @@ export const ephemeralNodes = sqliteTable('ephemeral_nodes', { export type EphemeralNode = typeof ephemeralNodes.$inferSelect; export type EphemeralNodeInsert = typeof ephemeralNodes.$inferInsert; + +export const hostInfo = sqliteTable('host_info', { + host_id: text('host_id').primaryKey(), + payload: text('payload', { mode: 'json' }).$type(), + updated_at: integer('updated_at', { mode: 'timestamp' }).$default( + () => new Date(), + ), +}); + +export type HostInfoRecord = typeof hostInfo.$inferSelect; +export type HostInfoInsert = typeof hostInfo.$inferInsert; diff --git a/app/server/hp-agent.ts b/app/server/hp-agent.ts new file mode 100644 index 0000000..c8050d8 --- /dev/null +++ b/app/server/hp-agent.ts @@ -0,0 +1,400 @@ +import { ChildProcessWithoutNullStreams, spawn } from 'node:child_process'; +import EventEmitter from 'node:events'; +import { constants, access, mkdir, open } from 'node:fs/promises'; +import { getegid, geteuid } from 'node:process'; +import { Interface, createInterface } from 'node:readline'; +import { inArray } from 'drizzle-orm'; +import { LibSQLDatabase } from 'drizzle-orm/libsql/driver-core'; +import { HostInfo } from '~/types'; +import log from '~/utils/log'; +import { HeadplaneConfig } from './config/schema'; +import { hostInfo } from './db/schema'; + +export async function createHeadplaneAgent( + config: NonNullable['agent'] | undefined, + headscaleUrl: string, + db: LibSQLDatabase, +) { + if (!config?.enabled) { + return; + } + + if (!config.pre_authkey) { + log.error('agent', 'Agent `pre_authkey` is not set'); + log.warn('agent', 'The agent will not run until resolved'); + return; + } + + try { + await access(config.work_dir, constants.R_OK | constants.W_OK); + log.debug('config', 'Using agent work dir at %s', config.work_dir); + } catch (error) { + // Try to create the directory just in case + try { + await mkdir(config.work_dir, { recursive: true }); + log.debug('config', 'Created agent work dir at %s', config.work_dir); + log.info( + 'config', + 'Created missing agent work dir at %s', + config.work_dir, + ); + + return; + } catch (innerError) { + log.error( + 'config', + 'Failed to create agent work dir at %s', + config.work_dir, + ); + log.info( + 'config', + 'Agent work dir not accessible at %s', + config.work_dir, + ); + log.debug('config', 'Error details: %s', error); + log.debug('config', 'Create error details: %s', innerError); + return; + } + } + + try { + const handle = await open(config.cache_path, 'a+'); + log.info('agent', 'Using agent cache file at %s', config.cache_path); + await handle.close(); + } catch (error) { + log.info( + 'agent', + 'Agent cache file not accessible at %s', + config.cache_path, + ); + log.debug('agent', 'Error details: %s', error); + return; + } + + const agent = new HeadplaneAgent({ + ...config, + headscaleUrl, + }); + + agent.on('spawn', () => { + log.info('agent', 'Headplane agent started'); + }); + + agent.on('ready', () => { + log.info('agent', 'Headplane agent is ready and serving queries'); + }); + + agent.on('error', (err) => { + log.warn('agent', 'Headplane agent experienced an error: %s', err.message); + log.debug('agent', 'Error details: %o', err); + }); + + agent.on('exit', ({ code, signal }) => { + log.warn( + 'agent', + 'Headplane agent exited with code %s and signal %s', + code, + signal, + ); + }); + + agent.on('restart', ({ delay, attempt }) => { + log.warn( + 'agent', + 'Headplane agent will restart in %f seconds (attempt %d)', + delay / 1000, + attempt, + ); + }); + + agent.on('stderr', (data) => { + log.error('agent', 'Headplane agent stderr:', data); + }); + + agent.on('info', async ({ id, info }) => { + log.debug('agent', 'Received HostInfo for %s', id); + try { + const parsedInfo = JSON.parse(info) as HostInfo; + await db + .insert(hostInfo) + .values({ + host_id: id, + payload: parsedInfo, + updated_at: new Date(), + }) + .onConflictDoUpdate({ + target: hostInfo.host_id, + set: { + payload: parsedInfo, + updated_at: new Date(), + }, + }); + } catch (error) { + log.error( + 'agent', + 'Failed to parse HostInfo for %s: %s', + id, + error instanceof Error ? error.message : String(error), + ); + return; + } + }); + + agent.start(); + + process.on('SIGTERM', () => agent.shutdown()); + process.on('SIGINT', () => agent.shutdown()); + + return { + agentID: () => agent.agentID(), + lookup: async (nodes: string[]) => { + const results = await db + .select() + .from(hostInfo) + .where(inArray(hostInfo.host_id, nodes)); + + return Object.fromEntries( + results.filter((r) => r.payload).map((r) => [r.host_id, r.payload]), + ) as Record; + }, + }; +} + +type AgentOptions = NonNullable< + NonNullable['agent'] +> & { + headscaleUrl: string; +}; + +interface AgentEvents { + ready: []; + spawn: []; + error: [Error]; + exit: [{ code?: number; signal?: NodeJS.Signals }]; + restart: [{ delay: number; attempt: number }]; + stderr: [string]; + info: [{ id: string; info: string }]; +} + +/** + * A custom class that turns the lifecycle of the agent into an event emitter. + * It has many different responsibilities ensuring that: + * - The agent is spawned with the correct configuration + * - The agent is ready and still running (ping/heartbeat) + * - The agent is restarted on a backoff strategy + */ +class HeadplaneAgent extends EventEmitter { + private child?: ChildProcessWithoutNullStreams; + private readline?: Interface; + + private options: AgentOptions; + + private hbInterval?: NodeJS.Timeout; + private hbDeadline?: NodeJS.Timeout; + private restartTimer?: NodeJS.Timeout; + private isWaitingForAck = false; + private isShuttingDown = false; + private backoffAttempt = 0; + private agentId?: string; + + private BASE_BACKOFF_MS = 1.5 * 1000; // 1.5 seconds + private MAX_BACKOFF_MS = 30 * 1000; // 30 seconds + private PROBE_COOLDOWN_MS = 5 * 60_000; // 5 minutes + private PROBE_ATTEMPT_INTERVAL = 10; // Every 10th attempt + + private HEARTBEAT_INTERVAL_MS = 5 * 1000; // 5 seconds + private HEARTBEAT_TIMEOUT_MS = 3 * 1000; // 3 seconds + + constructor(options: AgentOptions) { + super(); + this.options = options; + } + + agentID() { + return this.agentId; + } + + start() { + this.isShuttingDown = false; + this.spawnInternalChild(); + } + + shutdown() { + this.isShuttingDown = true; + this.agentId = undefined; + + clearTimeout(this.restartTimer); + clearInterval(this.hbInterval); + clearTimeout(this.hbDeadline); + this.isWaitingForAck = false; + + this.send('SHUTDOWN'); + this.child?.kill('SIGTERM'); + this.readline?.close(); + } + + private spawnInternalChild() { + this.child = spawn(this.options.executable_path, { + stdio: ['pipe', 'pipe', 'pipe'], + uid: geteuid?.() ?? undefined, + gid: getegid?.() ?? undefined, + env: { + HOME: process.env.HOME, + HEADPLANE_EMBEDDED: 'true', + HEADPLANE_AGENT_WORK_DIR: this.options.work_dir, + HEADPLANE_AGENT_DEBUG: log.debugEnabled ? 'true' : 'false', + HEADPLANE_AGENT_HOSTNAME: this.options.host_name, + HEADPLANE_AGENT_TS_SERVER: this.options.headscaleUrl, + HEADPLANE_AGENT_TS_AUTHKEY: this.options.pre_authkey, + }, + }); + + this.emit('spawn'); + this.child.on('error', (err) => this.emit('error', err)); + this.child.stderr.on('data', (data) => + this.emit('stderr', data.toString()), + ); + + this.child.on('exit', (code, signal) => { + this.agentId = undefined; + this.emit('exit', { + code: code ?? undefined, + signal: signal ?? undefined, + }); + + this.readline?.close(); + clearInterval(this.hbInterval); + clearTimeout(this.hbDeadline); + this.isWaitingForAck = false; + + if (this.isShuttingDown) { + log.info('agent', 'Child process exited gracefully'); + return; + } + + this.backoffAttempt++; + const delay = this.calculateBackoff(); + this.emit('restart', { delay, attempt: this.backoffAttempt }); + this.restartTimer = setTimeout(() => this.spawnInternalChild(), delay); + }); + + this.readline = createInterface({ input: this.child.stdout }); + this.readline.on('line', (line) => this.readlineHandler(line)); + this.send('START'); + + // Start the heartbeat loop with our custom interval + this.hbInterval = setInterval(() => { + if (!this.child || this.child.killed) return; + + // If we get here, we missed the last PONG response and can die + if (this.isWaitingForAck) { + this.agentId = undefined; + this.emit('error', new Error('Agent heartbeat missed')); + this.child.kill('SIGTERM'); + return; + } + + this.isWaitingForAck = true; + this.send('PING'); + + clearTimeout(this.hbDeadline); + this.hbDeadline = setTimeout(() => { + if (this.isWaitingForAck) { + this.agentId = undefined; + this.emit('error', new Error('Agent heartbeat timeout')); + this.child?.kill('SIGTERM'); + } + }, this.HEARTBEAT_TIMEOUT_MS); + }, this.HEARTBEAT_INTERVAL_MS); + } + + private send(s: string) { + if (!this.child || this.child.killed) return; + const ok = this.child.stdin.write(`${s}\n`); + if (!ok) this.child.stdin.once('drain', () => {}); + } + + /** + * Calculates a backoff time based on the current attempt. + * Supports a randomized jitter to avoid thundering herd problems. + * + * @param min The minimum backoff time in milliseconds. + * @param max The maximum backoff time in milliseconds. + * @returns The calculated backoff time in milliseconds. + */ + private calculateBackoff() { + const attempt = this.backoffAttempt; + if (attempt > 0 && attempt % this.PROBE_ATTEMPT_INTERVAL === 0) { + const jitter = Math.floor(Math.random() * (this.MAX_BACKOFF_MS + 1)); + const sign = Math.random() < 0.5 ? -1 : 1; + + return Math.max(0, this.PROBE_COOLDOWN_MS + jitter * sign); + } + + const cap = Math.min( + this.MAX_BACKOFF_MS, + this.BASE_BACKOFF_MS * 2 ** attempt, + ); + + return Math.floor(Math.random() * (cap + 1)); + } + + /** + * Processes and dispatches the appropriate response based on the message. + * @param line The message to process (piped straight from readline) + */ + private readlineHandler(line: string) { + // When we are ready we force a refresh so that the UI has the most + // up-to-date information and will gracefully handle new info being sent + if (line.startsWith('READY')) { + this.backoffAttempt = 0; + this.send('REFRESH'); + this.emit('ready'); + + const agentId = line.slice(5).trim(); + if (this.agentId && this.agentId !== agentId) { + log.warn( + 'agent', + 'Agent ID changed from %s to %s', + this.agentId, + agentId, + ); + } + + this.agentId = agentId; + return; + } + + if (line.startsWith('PONG')) { + this.isWaitingForAck = false; + clearTimeout(this.hbDeadline); + + const agentId = line.slice(5).trim(); + if (this.agentId && this.agentId !== agentId) { + log.warn( + 'agent', + 'Agent ID changed from %s to %s', + this.agentId, + agentId, + ); + } + + this.agentId = agentId; + return; + } + + if (line.startsWith('HOSTINFO')) { + const data = line.slice(9).trim(); + const [id, ...infoParts] = data.split(' '); + const info = infoParts.join(' '); + this.emit('info', { id, info }); + return; + } + + if (line.startsWith('ERROR')) { + const error = line.slice(6).trim(); + this.emit('error', new Error(error)); + return; + } + } +} diff --git a/app/server/index.ts b/app/server/index.ts index e169e95..acb6fdd 100644 --- a/app/server/index.ts +++ b/app/server/index.ts @@ -8,7 +8,7 @@ import { loadConfig } from './config/loader'; import { createDbClient } from './db/client.server'; import { createApiClient } from './headscale/api-client'; import { loadHeadscaleConfig } from './headscale/config-loader'; -import { loadAgentSocket } from './web/agent'; +import { createHeadplaneAgent } from './hp-agent'; import { createOidcClient } from './web/oidc'; import { createSessionStorage } from './web/sessions'; @@ -29,13 +29,13 @@ const config = await loadConfig( }), ); -const agentManager = await loadAgentSocket( +const db = await createDbClient(join(config.server.data_path, 'hp_persist.db')); +const agents = await createHeadplaneAgent( config.integration?.agent, config.headscale.url, + db, ); -const db = await createDbClient(join(config.server.data_path, 'hp_persist.db')); - // We also use this file to load anything needed by the react router code. // These are usually per-request things that we need access to, like the // helper that can issue and revoke cookies. @@ -64,7 +64,7 @@ const appLoadContext = { config.headscale.tls_cert_path, ), - agents: agentManager, + agents, integration: await loadIntegration(config.integration), oidc: config.oidc ? await createOidcClient(config.oidc) : undefined, db, diff --git a/app/server/web/agent.ts b/app/server/web/agent.ts deleted file mode 100644 index d090373..0000000 --- a/app/server/web/agent.ts +++ /dev/null @@ -1,447 +0,0 @@ -import { ChildProcess, spawn } from 'node:child_process'; -import { createHash } from 'node:crypto'; -import { - constants, - access, - mkdir, - open, - readFile, - writeFile, -} from 'node:fs/promises'; -import { exit, geteuid, getegid } from 'node:process'; -import { createInterface } from 'node:readline'; -import { setTimeout } from 'node:timers/promises'; -import { type } from 'arktype'; -import { HostInfo } from '~/types'; -import log from '~/utils/log'; -import type { HeadplaneConfig } from '../config/schema'; - -interface LogResponse { - Level: 'info' | 'debug' | 'error' | 'fatal'; - Message: string; -} - -interface RegisterMessage { - Type: 'register'; - ID: string; -} - -interface StatusMessage { - Type: 'status'; - Data: Record; -} - -interface MessageResponse { - Level: 'msg'; - Message: RegisterMessage | StatusMessage; -} - -type AgentResponse = LogResponse | MessageResponse; - -export async function loadAgentSocket( - config: NonNullable['agent'] | undefined, - headscaleUrl: string, -) { - if (!config?.enabled) { - return; - } - - if (config.pre_authkey.trim().length === 0) { - log.error('agent', 'Agent `pre_authkey` is not set'); - log.warn('agent', 'The agent will not run until resolved'); - return; - } - - try { - await access(config.work_dir, constants.R_OK | constants.W_OK); - log.debug('config', 'Using agent work dir at %s', config.work_dir); - } catch (error) { - // Try to create the directory just in case - try { - await mkdir(config.work_dir, { recursive: true }); - log.debug('config', 'Created agent work dir at %s', config.work_dir); - log.info( - 'config', - 'Created missing agent work dir at %s', - config.work_dir, - ); - - return; - } catch (innerError) { - log.error( - 'config', - 'Failed to create agent work dir at %s', - config.work_dir, - ); - log.info( - 'config', - 'Agent work dir not accessible at %s', - config.work_dir, - ); - log.debug('config', 'Error details: %s', error); - log.debug('config', 'Create error details: %s', innerError); - return; - } - } - - try { - const handle = await open(config.cache_path, 'a+'); - log.info('agent', 'Using agent cache file at %s', config.cache_path); - await handle.close(); - } catch (error) { - log.info( - 'agent', - 'Agent cache file not accessible at %s', - config.cache_path, - ); - log.debug('agent', 'Error details: %s', error); - return; - } - - const cache = new TimedCache(config.cache_ttl, config.cache_path); - return new AgentManager(cache, config, headscaleUrl); -} - -class AgentManager { - private static readonly MAX_RESTARTS = 5; - private restartCounter = 0; - - private cache: TimedCache; - private headscaleUrl: string; - private config: NonNullable< - NonNullable['agent'] - >; - - private spawnProcess: ChildProcess | null; - private agentId: string | null; - private uid: number | null; - private gid: number | null; - - constructor( - cache: TimedCache, - config: NonNullable['agent']>, - headscaleUrl: string, - ) { - this.cache = cache; - this.config = config; - this.headscaleUrl = headscaleUrl; - this.spawnProcess = null; - this.agentId = null; - this.startAgent(); - this.uid = geteuid ? geteuid() : null; - this.gid = getegid ? getegid() : null; - - process.on('SIGINT', () => { - this.spawnProcess?.kill(); - exit(0); - }); - - process.on('SIGTERM', () => { - this.spawnProcess?.kill(); - exit(0); - }); - } - - /** - * Used by the UI to indicate why the agent is not running. - * Exhaustion requires a manual restart of the agent. - * (Which can be invoked via the UI) - * @returns true if the agent is exhausted - */ - exhausted() { - return this.restartCounter >= AgentManager.MAX_RESTARTS; - } - - /* - * Called by the UI to manually force a restart of the agent. - */ - deExhaust() { - this.restartCounter = 0; - this.startAgent(); - } - - /* - * Stored agent ID for the current process. This is caught by the agent - * when parsing the stdout on agent startup. - */ - agentID() { - return this.agentId; - } - - private startAgent() { - if (this.spawnProcess) { - log.debug('agent', 'Agent already running'); - return; - } - - if (this.exhausted()) { - log.error('agent', 'Agent is exhausted, cannot start'); - return; - } - - // Cannot be detached since we want to follow our process lifecycle - // We also need to be able to send data to the process by using stdin - log.info( - 'agent', - 'Starting agent process (attempt %d)', - this.restartCounter, - ); - this.spawnProcess = spawn(this.config.executable_path, [], { - ...(this.uid ? { uid: this.uid } : {}), - ...(this.gid ? { gid: this.gid } : {}), - detached: false, - stdio: ['pipe', 'pipe', 'pipe', 'pipe', 'pipe'], - env: { - HOME: process.env.HOME, - HEADPLANE_EMBEDDED: 'true', - HEADPLANE_AGENT_WORK_DIR: this.config.work_dir, - HEADPLANE_AGENT_DEBUG: log.debugEnabled ? 'true' : 'false', - HEADPLANE_AGENT_HOSTNAME: this.config.host_name, - HEADPLANE_AGENT_TS_SERVER: this.headscaleUrl, - HEADPLANE_AGENT_TS_AUTHKEY: this.config.pre_authkey, - }, - }); - - if (!this.spawnProcess?.pid) { - log.error('agent', 'Failed to start agent process'); - this.restartCounter++; - global.setTimeout(() => this.startAgent(), 1000); - return; - } - - if (this.spawnProcess.stdin === null || this.spawnProcess.stdout === null) { - log.error('agent', 'Failed to connect to agent process'); - this.restartCounter++; - global.setTimeout(() => this.startAgent(), 1000); - return; - } - - const rlStdout = createInterface({ - input: this.spawnProcess.stdout, - crlfDelay: Number.POSITIVE_INFINITY, - }); - - rlStdout.on('line', (line) => { - try { - const parsed = JSON.parse(line) as AgentResponse; - if (parsed.Level === 'msg') { - switch (parsed.Message.Type) { - case 'register': - this.agentId = parsed.Message.ID; - break; - case 'status': - for (const [key, value] of Object.entries(parsed.Message.Data)) { - // Mark the agent as the one that is running - // We store it in the cache so that it shows - // itself later - if (key === this.agentId) { - value.HeadplaneAgent = true; - } - - this.cache.set(key, value); - } - - break; - } - - return; - } - - switch (parsed.Level) { - case 'info': - case 'debug': - case 'error': - log[parsed.Level]('agent', parsed.Message); - break; - case 'fatal': - log.error('agent', parsed.Message); - break; - default: - log.debug('agent', 'Unknown agent response: %s', line); - break; - } - } catch (error) { - log.debug('agent', 'Failed to parse agent response: %s', error); - log.debug('agent', 'Raw data: %s', line); - } - }); - - this.spawnProcess.on('error', (error) => { - log.error('agent', 'Failed to start agent process: %s', error); - this.restartCounter++; - this.spawnProcess = null; - global.setTimeout(() => this.startAgent(), 1000); - }); - - this.spawnProcess.on('exit', (code) => { - log.error('agent', 'Agent process exited with code %d', code ?? -1); - this.restartCounter++; - this.spawnProcess = null; - global.setTimeout(() => this.startAgent(), 1000); - }); - } - - async lookup(nodeIds: string[]) { - const entries = this.cache.toJSON(); - const missing = nodeIds.filter((nodeId) => !entries[nodeId]); - if (missing.length > 0) { - await this.requestData(missing); - } - - return Object.entries(entries).reduce>( - (acc, [key, value]) => { - if (nodeIds.includes(key)) { - acc[key] = value; - } - - return acc; - }, - {}, - ); - } - - // Request data from the internal agent by sending a message to the process - // via stdin. This is a blocking call, so it will wait for the agent to - // respond before returning. - private async requestData(nodeList: string[]) { - if (this.exhausted()) { - return; - } - - // Wait for the process to be spawned, busy waiting is gross - while (this.spawnProcess === null) { - await setTimeout(100); - } - - // Send the request to the agent, without waiting for a response. - // The live data invalidator will re-request the data if it is not - // available in the cache anyways. - const data = JSON.stringify({ NodeIDs: nodeList }); - this.spawnProcess.stdin?.write(`${data}\n`); - } -} - -const diskSchema = type({ - key: 'string', - value: 'unknown', - expires: 'number?', -}).array(); - -// A persistent HashMap with a TTL for each key -class TimedCache { - private _cache = new Map(); - private _timings = new Map(); - - // Default TTL is 1 minute - private defaultTTL: number; - private filePath: string; - private writeLock = false; - - // Last flush ID is essentially a hash of the flush contents - // Prevents unnecessary flushing if nothing has changed - private lastFlushId = ''; - - constructor(defaultTTL: number, filePath: string) { - this.defaultTTL = defaultTTL; - this.filePath = filePath; - - // Load the cache from disk and then queue flushes every 10 seconds - this.load().then(() => { - setInterval(() => this.flush(), 10000); - }); - } - - set(key: string, value: V, ttl: number = this.defaultTTL) { - this._cache.set(key, value); - this._timings.set(key, Date.now() + ttl); - } - - get(key: string) { - const value = this._cache.get(key); - if (!value) { - return; - } - - const expires = this._timings.get(key); - if (!expires || expires < Date.now()) { - this._cache.delete(key); - this._timings.delete(key); - return; - } - - return value; - } - - // Map into a Record without any TTLs - toJSON() { - const result: Record = {}; - for (const [key, value] of this._cache.entries()) { - result[key] = value; - } - - return result; - } - - // WARNING: This function expects that this.filePath is NOT ENOENT - private async load() { - const data = await readFile(this.filePath, 'utf-8'); - const cache = () => { - try { - return JSON.parse(data); - } catch (e) { - return undefined; - } - }; - - const diskData = cache(); - if (diskData === undefined) { - log.error('agent', 'Failed to load cache at %s', this.filePath); - return; - } - - const cacheData = diskSchema(diskData); - if (cacheData instanceof type.errors) { - log.debug('agent', 'Failed to load cache at %s', this.filePath); - log.debug('agent', 'Error details: %s', cacheData.toString()); - - // Skip loading the cache (it should be overwritten soon) - return; - } - - for (const { key, value, expires } of diskData) { - this._cache.set(key, value); - this._timings.set(key, expires); - } - - log.info('agent', 'Loaded cache from %s', this.filePath); - } - - private async flush() { - const data = Array.from(this._cache.entries()).map(([key, value]) => { - return { key, value, expires: this._timings.get(key) }; - }); - - if (data.length === 0) { - return; - } - - // Calculate the hash of the data - const dumpData = JSON.stringify(data); - const sha = createHash('sha256').update(dumpData).digest('hex'); - if (sha === this.lastFlushId) { - return; - } - - // We need to lock the writeLock so that we don't try to write - // to the file while we're already writing to it - while (this.writeLock) { - await setTimeout(100); - } - - this.writeLock = true; - await writeFile(this.filePath, dumpData, 'utf-8'); - log.debug('agent', 'Flushed cache to %s', this.filePath); - this.lastFlushId = sha; - this.writeLock = false; - } -} diff --git a/cmd/hp_agent/hp_agent.go b/cmd/hp_agent/hp_agent.go index b20c34d..c1400be 100644 --- a/cmd/hp_agent/hp_agent.go +++ b/cmd/hp_agent/hp_agent.go @@ -1,6 +1,8 @@ package main import ( + "fmt" + _ "github.com/joho/godotenv/autoload" "github.com/tale/headplane/internal/config" "github.com/tale/headplane/internal/hpagent" @@ -31,5 +33,6 @@ func main() { ID: agent.ID, }) + fmt.Println("READY") hpagent.FollowMaster(agent) } diff --git a/drizzle/0001_naive_lilith.sql b/drizzle/0001_naive_lilith.sql new file mode 100644 index 0000000..aaa61db --- /dev/null +++ b/drizzle/0001_naive_lilith.sql @@ -0,0 +1,5 @@ +CREATE TABLE `host_info` ( + `host_id` text PRIMARY KEY NOT NULL, + `payload` text, + `updated_at` integer +); diff --git a/drizzle/meta/0001_snapshot.json b/drizzle/meta/0001_snapshot.json new file mode 100644 index 0000000..4980bd6 --- /dev/null +++ b/drizzle/meta/0001_snapshot.json @@ -0,0 +1,73 @@ +{ + "version": "6", + "dialect": "sqlite", + "id": "16f780a3-a6e7-4810-94bb-fad5c6446ab4", + "prevId": "ab03ffcd-9aa5-4b4b-9f38-322acc6899a3", + "tables": { + "ephemeral_nodes": { + "name": "ephemeral_nodes", + "columns": { + "auth_key": { + "name": "auth_key", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "node_key": { + "name": "node_key", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "host_info": { + "name": "host_info", + "columns": { + "host_id": { + "name": "host_id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "payload": { + "name": "payload", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + } + }, + "views": {}, + "enums": {}, + "_meta": { + "schemas": {}, + "tables": {}, + "columns": {} + }, + "internal": { + "indexes": {} + } +} diff --git a/drizzle/meta/_journal.json b/drizzle/meta/_journal.json index dc3d0f8..e7c8c17 100644 --- a/drizzle/meta/_journal.json +++ b/drizzle/meta/_journal.json @@ -8,6 +8,13 @@ "when": 1750355487927, "tag": "0000_spicy_bloodscream", "breakpoints": true + }, + { + "idx": 1, + "version": "6", + "when": 1755554742267, + "tag": "0001_naive_lilith", + "breakpoints": true } ] } diff --git a/internal/hpagent/handler.go b/internal/hpagent/handler.go index 826b74f..c6d5327 100644 --- a/internal/hpagent/handler.go +++ b/internal/hpagent/handler.go @@ -2,26 +2,15 @@ package hpagent import ( "bufio" + "context" + "fmt" - "encoding/json" "os" - "sync" "github.com/tale/headplane/internal/tsnet" "github.com/tale/headplane/internal/util" - "tailscale.com/tailcfg" ) -// Represents messages from the Headplane master -type RecvMessage struct { - NodeIDs []string -} - -type SendMessage struct { - Type string - Data any -} - // Starts listening for messages from stdin func FollowMaster(agent *tsnet.TSAgent) { log := util.GetLogger() @@ -30,55 +19,34 @@ func FollowMaster(agent *tsnet.TSAgent) { for scanner.Scan() { line := scanner.Bytes() - var msg RecvMessage - err := json.Unmarshal(line, &msg) - if err != nil { - log.Error("Unable to decode message from master: %s", err) + directive := string(line) + + log.Debug("Received directive from master: %s", directive) + switch directive { + case "SHUTDOWN": + log.Debug("Received SHUTDOWN directive from master, shutting down agent") + agent.Shutdown() + return + + case "START": + log.Debug("Received START directive from master, starting agent") + // TODO: Start the agent here instead of in main + fmt.Println("READY " + agent.ID) continue - } - log.Debug("Recieved message from master: %v", line) - - if len(msg.NodeIDs) == 0 { - log.Debug("Message recieved had no node IDs") - log.Debug("Full message: %s", line) + case "PING": + log.Debug("Received PING directive from master, responding with PONG") + fmt.Println("PONG " + agent.ID) continue + + case "REFRESH": + log.Debug("Received REFRESH directive from master, refreshing status for all nodes") + err := agent.DispatchHostInfo(context.Background()) + if err != nil { + log.Error("Error refreshing host info: %s", err) + fmt.Println("ERR " + err.Error()) + } } - - // Accumulate the results since we invoke via gofunc - results := make(map[string]*tailcfg.HostinfoView) - mu := sync.Mutex{} - wg := sync.WaitGroup{} - - for _, nodeID := range msg.NodeIDs { - wg.Add(1) - go func(nodeID string) { - defer wg.Done() - result, err := agent.GetStatusForPeer(nodeID) - if err != nil { - log.Error("Unable to get status for node %s: %s", nodeID, err) - return - } - - if result == nil { - log.Debug("No status for node %s", nodeID) - return - } - - mu.Lock() - results[nodeID] = result - mu.Unlock() - }(nodeID) - } - - wg.Wait() - - // Send the results back to the Headplane master - log.Debug("Sending status back to master: %v", results) - log.Msg(&SendMessage{ - Type: "status", - Data: results, - }) } if err := scanner.Err(); err != nil { diff --git a/internal/tsnet/peers.go b/internal/tsnet/peers.go index 1ce2c2b..74a1ec1 100644 --- a/internal/tsnet/peers.go +++ b/internal/tsnet/peers.go @@ -3,10 +3,14 @@ package tsnet import ( "context" "encoding/hex" + "encoding/json" "fmt" "strings" + "sync" + "time" "github.com/tale/headplane/internal/util" + "tailscale.com/ipn/ipnstate" "tailscale.com/tailcfg" "tailscale.com/types/key" @@ -64,3 +68,81 @@ func (s *TSAgent) GetStatusForPeer(id string) (*tailcfg.HostinfoView, error) { log.Debug("Got whois for peer %s: %v", id, whois) return &whois.Node.Hostinfo, nil } + +// Dispatches ALL the HostInfo entries in our Tailnet to the master +func (s *TSAgent) DispatchHostInfo(ctx context.Context) error { + log := util.GetLogger() + + stat, err := s.Lc.Status(ctx) + if err != nil { + log.Debug("Failed to get status: %s", err) + return fmt.Errorf("failed to get status: %w", err) + } + + // Do lookups for all peers with a hint of parallelism for speed! + const maxParallel = 8 + sema := make(chan struct{}, maxParallel) + var wg sync.WaitGroup + var mu sync.Mutex + + nodeMap := make(map[key.NodePublic]*ipnstate.PeerStatus) + nodeMap[stat.Self.PublicKey] = stat.Self + for nodeKey, peer := range stat.Peer { + if peer == nil { + log.Debug("Skipping nil peer for node key: %s", nodeKey) + continue + } + + nodeMap[nodeKey] = peer + } + + for nodeKey, peer := range nodeMap { + idBytes, err := nodeKey.MarshalText() + if err != nil { + log.Debug("Failed to marshal node key: %s", err) + continue + } + + nodeID := string(idBytes) + wg.Add(1) + sema <- struct{}{} + + go func() { + defer wg.Done() + defer func() { <-sema }() + + wctx, cancel := context.WithTimeout(ctx, 3*time.Second) + defer cancel() + + ip := peer.TailscaleIPs[0].String() + if len(ip) == 0 { + log.Debug("Peer %s has no Tailscale IPs", nodeID) + return + } + + whois, err := s.Lc.WhoIs(wctx, ip) + if err != nil { + log.Debug("WhoIs failed for %s (%s): %s", nodeID, ip, err) + return + } + + if whois == nil || whois.Node == nil { + log.Debug("WhoIs returned nil node for %s (%s)", nodeID, ip) + return + } + + data, err := json.Marshal(whois.Node.Hostinfo) + if err != nil { + log.Debug("Failed to marshal hostinfo for %s (%s): %s", nodeID, ip, err) + return + } + + mu.Lock() + fmt.Println("HOSTINFO " + nodeID + " " + string(data)) + mu.Unlock() + }() + } + + wg.Wait() + return nil +} diff --git a/mise.toml b/mise.toml index a158439..345ecdf 100644 --- a/mise.toml +++ b/mise.toml @@ -23,6 +23,9 @@ run = "go build -o app/hp_ssh.wasm ./cmd/hp_ssh" alias = ["agent"] description = "Builds the Go agent for HostInfo" run = "go build -o build/hp_agent ./cmd/hp_agent" +# env = { CGO_ENABLED = "1" } +# run = "go build -o build/hp_agent.so -buildmode=c-shared ./cmd/hp_agent" + [tasks.build-fake-shell] alias = ["fake-shell"] From d2c4f5eb2bff3d623497189931bd1c9c9311283e Mon Sep 17 00:00:00 2001 From: Aarnav Tale Date: Tue, 19 Aug 2025 17:49:32 -0400 Subject: [PATCH 75/97] feat: completely overhaul the auth model * Cookies are now encrypted JWTs (GHSA-wrqq-v7qw-r5w7) * Authentication is stored in the SQLite database (auto-migrated) * Session logic is much cleaner --- CHANGELOG.md | 2 + app/layouts/dashboard.tsx | 12 +- app/layouts/shell.tsx | 89 +---- app/routes/acls/acl-action.ts | 2 +- app/routes/acls/acl-loader.ts | 2 +- app/routes/auth/login/action.ts | 24 +- app/routes/auth/login/page.tsx | 16 +- app/routes/auth/logout.ts | 9 +- app/routes/auth/oidc-callback.ts | 63 +++- app/routes/auth/oidc-start.ts | 34 +- app/routes/machines/machine-actions.ts | 4 +- app/routes/machines/machine.tsx | 42 +-- app/routes/machines/overview.tsx | 23 +- app/routes/settings/auth-keys/actions.ts | 2 +- app/routes/settings/auth-keys/overview.tsx | 21 +- app/routes/ssh/console.tsx | 34 +- app/routes/users/onboarding-skip.tsx | 19 +- app/routes/users/onboarding.tsx | 62 ++-- app/routes/users/overview.tsx | 57 ++- app/routes/users/user-actions.ts | 5 +- app/server/db/pruner.ts | 7 +- app/server/db/schema.ts | 10 + app/server/index.ts | 16 +- app/server/web/sessions.ts | 405 ++++++++++----------- drizzle/0002_square_bloodstorm.sql | 8 + drizzle/meta/0002_snapshot.json | 119 ++++++ drizzle/meta/_journal.json | 7 + package.json | 12 +- pnpm-lock.yaml | 27 +- 29 files changed, 628 insertions(+), 505 deletions(-) create mode 100644 drizzle/0002_square_bloodstorm.sql create mode 100644 drizzle/meta/0002_snapshot.json diff --git a/CHANGELOG.md b/CHANGELOG.md index 8e01999..124fb56 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ - Begin using a new SQLite database file in `/var/lib/headplane/hp_persist.db`. - The database is created automatically if it does not exist. - It currently stores SSH connection details and HostInfo for the agent. + - User information is automatically migrated from the previous database. - The docker container now runs in a distroless image (closes [#255](https://github.com/tale/headplane/issues/255)). - A debug version of the container that runs as root and has a shell is available as `ghcr.io/tale/headplane:-shell`. - Removing a Split DNS record will no longer make the split domain unresolvable by clients (closes [#231](https://github.com/tale/headplane/issues/231)). @@ -19,6 +20,7 @@ - See the full reference in the [docs](https://github.com/tale/headplane/blob/main/docs/Configuration.md#sensitive-values) - The nix overlay build is fixed for the SSH module (via [#282](https://github.com/tale/headplane/pull/282)) - Switch our build processes to use TypeScript Go and Rolldown Vite for better build and type-check performance. +- Cookies are now encrypted JWTs, preserving API key secrets (*GHSA-wrqq-v7qw-r5w7*) ### 0.6.0 (May 25, 2025) - Headplane 0.6.0 now requires **Headscale 0.26.0** or newer. diff --git a/app/layouts/dashboard.tsx b/app/layouts/dashboard.tsx index 5e93233..ffd8aea 100644 --- a/app/layouts/dashboard.tsx +++ b/app/layouts/dashboard.tsx @@ -18,13 +18,13 @@ export async function loader({ // TODO: Notify in the logs or the UI that OIDC auth key is wrong if enabled if (healthy) { try { - await context.client.get('v1/apikey', session.get('api_key')!); + await context.client.get('v1/apikey', session.api_key); } catch (error) { if (error instanceof ResponseError) { log.debug('api', 'API Key validation failed %o', error); return redirect('/login', { headers: { - 'Set-Cookie': await context.sessions.destroy(session), + 'Set-Cookie': await context.sessions.destroySession(), }, }); } @@ -38,11 +38,9 @@ export async function loader({ export default function Layout() { return ( - <> -
- -
- +
+ +
); } diff --git a/app/layouts/shell.tsx b/app/layouts/shell.tsx index 7fb7a17..0b2d247 100644 --- a/app/layouts/shell.tsx +++ b/app/layouts/shell.tsx @@ -1,3 +1,4 @@ +import { eq } from 'drizzle-orm'; import { CircleCheckIcon } from 'lucide-react'; import { LoaderFunctionArgs, @@ -10,9 +11,8 @@ import Card from '~/components/Card'; import Footer from '~/components/Footer'; import Header from '~/components/Header'; import type { LoadContext } from '~/server'; +import { users } from '~/server/db/schema'; import { Capabilities } from '~/server/web/roles'; -import { User } from '~/types'; -import log from '~/utils/log'; import toast from '~/utils/toast'; // This loads the bare minimum for the application to function @@ -23,72 +23,18 @@ export async function loader({ }: LoaderFunctionArgs) { try { const session = await context.sessions.auth(request); - if (!session.has('api_key')) { - // There is a session, but it's not valid - return redirect('/login', { - headers: { - 'Set-Cookie': await context.sessions.destroy(session), - }, - }); - } + if ( + context.oidc && + session.user.subject !== 'unknown-non-oauth' && + !request.url.endsWith('/onboarding') + ) { + const [user] = await context.db + .select() + .from(users) + .where(eq(users.sub, session.user.subject)) + .limit(1); - // Onboarding is only a feature of the OIDC flow - if (context.oidc && !request.url.endsWith('/onboarding')) { - let onboarded = false; - - const sessionUser = session.get('user'); - if (sessionUser) { - if (context.sessions.onboardForSubject(sessionUser.subject)) { - // Assume onboarded - onboarded = true; - } else { - try { - const { users } = await context.client.get<{ users: User[] }>( - 'v1/user', - session.get('api_key')!, - ); - - if (users.length === 0) { - onboarded = false; - } - - const user = users.find((u) => { - if (u.provider !== 'oidc') { - return false; - } - - // For some reason, headscale makes providerID a url where the - // last component is the subject, so we need to strip that out - const subject = u.providerId?.split('/').pop(); - if (!subject) { - return false; - } - - const sessionUser = session.get('user'); - if (!sessionUser) { - return false; - } - - if (context.sessions.onboardForSubject(sessionUser.subject)) { - // Assume onboarded - return true; - } - - return subject === sessionUser.subject; - }); - - if (user) { - onboarded = true; - } - } catch (e) { - // If we cannot lookup users, just assume our user is onboarded - log.debug('api', 'Failed to lookup users %o', e); - onboarded = true; - } - } - } - - if (!onboarded) { + if (!user?.onboarded) { return redirect('/onboarding'); } } @@ -99,7 +45,7 @@ export async function loader({ url: context.config.headscale.public_url ?? context.config.headscale.url, configAvailable: context.hs.readable(), debug: context.config.debug, - user: session.get('user'), + user: session.user, uiAccess: check, access: { ui: await context.sessions.check(request, Capabilities.ui_access), @@ -119,8 +65,11 @@ export async function loader({ healthy: await context.client.healthcheck(), }; } catch { - // No session, so we can just return - return redirect('/login'); + return redirect('/login', { + headers: { + 'Set-Cookie': await context.sessions.destroySession(), + }, + }); } } diff --git a/app/routes/acls/acl-action.ts b/app/routes/acls/acl-action.ts index 11e4bab..fc319cf 100644 --- a/app/routes/acls/acl-action.ts +++ b/app/routes/acls/acl-action.ts @@ -31,7 +31,7 @@ export async function aclAction({ const { policy, updatedAt } = await context.client.put<{ policy: string; updatedAt: string; - }>('v1/policy', session.get('api_key')!, { + }>('v1/policy', session.api_key, { policy: policyData, }); diff --git a/app/routes/acls/acl-loader.ts b/app/routes/acls/acl-loader.ts index 4b7ce56..19ffd3c 100644 --- a/app/routes/acls/acl-loader.ts +++ b/app/routes/acls/acl-loader.ts @@ -34,7 +34,7 @@ export async function aclLoader({ const { policy, updatedAt } = await context.client.get<{ policy: string; updatedAt: string | null; - }>('v1/policy', session.get('api_key')!); + }>('v1/policy', session.api_key); // Successfully loaded the policy, mark it as readable // If `updatedAt` is null, it means the policy is in file mode. diff --git a/app/routes/auth/login/action.ts b/app/routes/auth/login/action.ts index ad5c2dd..10f728d 100644 --- a/app/routes/auth/login/action.ts +++ b/app/routes/auth/login/action.ts @@ -60,25 +60,23 @@ export async function loginAction({ }; } - // Set the session - const session = await context.sessions.getOrCreate(request); const expiresDays = Math.round( (expiry.getTime() - Date.now()) / 1000 / 60 / 60 / 24, ); - session.set('state', 'auth'); - session.set('api_key', apiKey); - session.set('user', { - subject: 'unknown-non-oauth', - name: `${lookup.prefix}...`, - email: `expires@${expiresDays.toString()}-days`, - }); - return redirect('/machines', { headers: { - 'Set-Cookie': await context.sessions.commit(session, { - maxAge: expiry.getTime() - Date.now(), - }), + 'Set-Cookie': await context.sessions.createSession( + { + api_key: apiKey, + user: { + subject: 'unknown-non-oauth', + name: `${lookup.prefix}...`, + email: `expires@${expiresDays.toString()}-days`, + }, + }, + expiry.getTime() - Date.now(), + ), }, }); } catch (error) { diff --git a/app/routes/auth/login/page.tsx b/app/routes/auth/login/page.tsx index 3f8cb61..8ed5370 100644 --- a/app/routes/auth/login/page.tsx +++ b/app/routes/auth/login/page.tsx @@ -1,10 +1,10 @@ import { useEffect } from 'react'; import { ActionFunctionArgs, + data, Form, LoaderFunctionArgs, Link as RemixLink, - data, redirect, useActionData, useLoaderData, @@ -24,10 +24,8 @@ export async function loader({ context, }: LoaderFunctionArgs) { try { - const session = await context.sessions.auth(request); - if (session.has('api_key')) { - return redirect('/machines'); - } + await context.sessions.auth(request); + return redirect('/machines'); } catch {} const qp = new URL(request.url).searchParams; @@ -104,26 +102,26 @@ export default function Page() { terminal. {formData?.success === false ? ( {formData.message} ) : undefined} - {oidc ? ( - diff --git a/app/routes/auth/logout.ts b/app/routes/auth/logout.ts index 0713052..e46e7a2 100644 --- a/app/routes/auth/logout.ts +++ b/app/routes/auth/logout.ts @@ -9,9 +9,10 @@ export async function action({ request, context, }: ActionFunctionArgs) { - const session = await context.sessions.auth(request); - if (!session.has('api_key')) { - return redirect('/login'); + try { + await context.sessions.auth(request); + } catch { + redirect('/login'); } // When API key is disabled, we need to explicitly redirect @@ -22,7 +23,7 @@ export async function action({ return redirect(url, { headers: { - 'Set-Cookie': await context.sessions.destroy(session), + 'Set-Cookie': await context.sessions.destroySession(), }, }); } diff --git a/app/routes/auth/oidc-callback.ts b/app/routes/auth/oidc-callback.ts index 342beca..eec883b 100644 --- a/app/routes/auth/oidc-callback.ts +++ b/app/routes/auth/oidc-callback.ts @@ -1,9 +1,19 @@ -import { type LoaderFunctionArgs, Session, redirect } from 'react-router'; +import { count } from 'drizzle-orm'; +import { createCookie, type LoaderFunctionArgs, redirect } from 'react-router'; +import { ulid } from 'ulidx'; import type { LoadContext } from '~/server'; -import type { AuthSession, OidcFlowSession } from '~/server/web/sessions'; +import { users } from '~/server/db/schema'; +import { Roles } from '~/server/web/roles'; import { finishAuthFlow, formatError } from '~/utils/oidc'; import { send } from '~/utils/res'; +interface OidcFlowSession { + state: string; + nonce: string; + code_verifier: string; + redirect_uri: string; +} + export async function loader({ request, context, @@ -18,13 +28,21 @@ export async function loader({ return redirect('/login'); } - const session = await context.sessions.getOrCreate(request); - if (session.get('state') !== 'flow') { - return redirect('/login'); // Haven't started an OIDC flow + const cookie = createCookie('__oidc_auth_flow', { + httpOnly: true, + maxAge: 300, // 5 minutes + }); + + const data: OidcFlowSession | null = await cookie.parse( + request.headers.get('Cookie'), + ); + + if (data === null) { + console.warn('OIDC flow session not found'); + return redirect('/login'); } - const payload = session.get('oidc')!; - const { code_verifier, state, nonce, redirect_uri } = payload; + const { code_verifier, state, nonce, redirect_uri } = data; if (!code_verifier || !state || !nonce || !redirect_uri) { return send({ error: 'Missing OIDC state' }, { status: 400 }); } @@ -43,19 +61,30 @@ export async function loader({ try { const user = await finishAuthFlow(context.oidc, flowOptions); - session.unset('oidc'); - const userSession = session as Session; - // TODO: This is breaking, to stop the "over-generation" of API - // keys because they are currently non-deletable in the headscale - // database. Look at this in the future once we have a solution - // or we have permissioned API keys. - userSession.set('user', user); - userSession.set('api_key', context.config.oidc?.headscale_api_key!); - userSession.set('state', 'auth'); + const [{ count: userCount }] = await context.db + .select({ count: count() }) + .from(users); + + await context.db + .insert(users) + .values({ + id: ulid(), + sub: user.subject, + caps: userCount === 0 ? Roles.owner : Roles.member, + }) + .onConflictDoNothing(); + return redirect('/machines', { headers: { - 'Set-Cookie': await context.sessions.commit(userSession), + 'Set-Cookie': await context.sessions.createSession({ + // TODO: This is breaking, to stop the "over-generation" of API + // keys because they are currently non-deletable in the headscale + // database. Look at this in the future once we have a solution + // or we have permissioned API keys. + api_key: context.config.oidc?.headscale_api_key!, + user, + }), }, }); } catch (error) { diff --git a/app/routes/auth/oidc-start.ts b/app/routes/auth/oidc-start.ts index 89c6dfb..2b8d056 100644 --- a/app/routes/auth/oidc-start.ts +++ b/app/routes/auth/oidc-start.ts @@ -1,42 +1,42 @@ -import { type LoaderFunctionArgs, Session, redirect } from 'react-router'; +import { createCookie, type LoaderFunctionArgs, redirect } from 'react-router'; import type { LoadContext } from '~/server'; -import { AuthSession, OidcFlowSession } from '~/server/web/sessions'; import { beginAuthFlow, getRedirectUri } from '~/utils/oidc'; export async function loader({ request, context, }: LoaderFunctionArgs) { - const session = await context.sessions.getOrCreate(request); - if ((session as Session).has('api_key')) { + try { + await context.sessions.auth(request); return redirect('/machines'); - } + } catch {} - if (!context.oidc) { + if (!context.oidc || !context.config.oidc) { throw new Error('OIDC is not enabled'); } + const cookie = createCookie('__oidc_auth_flow', { + httpOnly: true, + maxAge: 300, // 5 minutes + }); + const redirectUri = context.config.oidc?.redirect_uri ?? getRedirectUri(request); const data = await beginAuthFlow( context.oidc, redirectUri, - // We can't get here without the OIDC config being defined - context.config.oidc!.token_endpoint_auth_method, + context.config.oidc.token_endpoint_auth_method, ); - session.set('state', 'flow'); - session.set('oidc', { - state: data.state, - nonce: data.nonce, - code_verifier: data.codeVerifier, - redirect_uri: redirectUri, - }); - return redirect(data.url, { status: 302, headers: { - 'Set-Cookie': await context.sessions.commit(session), + 'Set-Cookie': await cookie.serialize({ + state: data.state, + nonce: data.nonce, + code_verifier: data.codeVerifier, + redirect_uri: redirectUri, + }), }, }); } diff --git a/app/routes/machines/machine-actions.ts b/app/routes/machines/machine-actions.ts index d613cfb..791cb68 100644 --- a/app/routes/machines/machine-actions.ts +++ b/app/routes/machines/machine-actions.ts @@ -14,7 +14,7 @@ export async function machineAction({ ); const formData = await request.formData(); - const apiKey = session.get('api_key')!; + const apiKey = session.api_key; const action = formData.get('action_id')?.toString(); if (!action) { @@ -55,7 +55,7 @@ export async function machineAction({ } if ( - node.user.providerId?.split('/').pop() !== session.get('user')!.subject && + node.user.providerId?.split('/').pop() !== session.user.subject && !check ) { throw data('You do not have permission to act on this machine', { diff --git a/app/routes/machines/machine.tsx b/app/routes/machines/machine.tsx index 23b4a2a..5b8bcaa 100644 --- a/app/routes/machines/machine.tsx +++ b/app/routes/machines/machine.tsx @@ -39,9 +39,9 @@ export async function loader({ const [machine, { users }] = await Promise.all([ context.client.get<{ node: Machine }>( `v1/node/${params.id}`, - session.get('api_key')!, + session.api_key, ), - context.client.get<{ users: User[] }>('v1/user', session.get('api_key')!), + context.client.get<{ users: User[] }>('v1/user', session.api_key), ]); const lookup = await context.agents?.lookup([machine.node.nodeKey]); @@ -77,7 +77,7 @@ export default function Page() { return (

- + All Machines / @@ -91,9 +91,9 @@ export default function Page() { >

{node.givenName}

- + - +
@@ -123,14 +123,14 @@ export default function Page() {
- +

Subnets & Routing

Subnets let you expose physical network routes onto Tailscale.{' '} Learn More @@ -138,11 +138,11 @@ export default function Page() {

@@ -166,11 +166,11 @@ export default function Page() { )}
@@ -198,11 +198,11 @@ export default function Page() { )}
@@ -233,11 +233,11 @@ export default function Page() { )}
@@ -249,15 +249,15 @@ export default function Page() { issues.

{stats ? ( @@ -267,14 +267,14 @@ export default function Page() { ) : undefined} {magic ? ( ) : undefined} @@ -341,13 +341,13 @@ export default function Page() { Client Connectivity

) { const session = await context.sessions.auth(request); - const user = session.get('user'); + const user = session.user; if (!user) { throw new Error('Missing user session. Please log in again.'); } @@ -41,11 +41,8 @@ export async function loader({ ); const [{ nodes }, { users }] = await Promise.all([ - context.client.get<{ nodes: Machine[] }>( - 'v1/node', - session.get('api_key')!, - ), - context.client.get<{ users: User[] }>('v1/user', session.get('api_key')!), + context.client.get<{ nodes: Machine[] }>('v1/node', session.api_key), + context.client.get<{ users: User[] }>('v1/user', session.api_key), ]); let magic: string | undefined; @@ -90,18 +87,18 @@ export default function Page() {

Manage the devices connected to your Tailnet.{' '} Learn more

@@ -141,16 +138,16 @@ export default function Page() { > {data.populatedNodes.map((machine) => ( ))} diff --git a/app/routes/settings/auth-keys/actions.ts b/app/routes/settings/auth-keys/actions.ts index cdb4517..e503bc6 100644 --- a/app/routes/settings/auth-keys/actions.ts +++ b/app/routes/settings/auth-keys/actions.ts @@ -20,7 +20,7 @@ export async function authKeysAction({ } const formData = await request.formData(); - const apiKey = session.get('api_key')!; + const apiKey = session.api_key; const action = formData.get('action_id')?.toString(); if (!action) { throw data('Missing `action_id` in the form data.', { diff --git a/app/routes/settings/auth-keys/overview.tsx b/app/routes/settings/auth-keys/overview.tsx index a2457e7..9f328e2 100644 --- a/app/routes/settings/auth-keys/overview.tsx +++ b/app/routes/settings/auth-keys/overview.tsx @@ -1,8 +1,7 @@ import { FileKey2 } from 'lucide-react'; import { useMemo, useState } from 'react'; import type { ActionFunctionArgs, LoaderFunctionArgs } from 'react-router'; -import { useLoaderData } from 'react-router'; -import { Link as RemixLink } from 'react-router'; +import { Link as RemixLink, useLoaderData } from 'react-router'; import Code from '~/components/Code'; import Link from '~/components/Link'; import Notice from '~/components/Notice'; @@ -23,7 +22,7 @@ export async function loader({ const session = await context.sessions.auth(request); const { users } = await context.client.get<{ users: User[] }>( 'v1/user', - session.get('api_key')!, + session.api_key, ); const preAuthKeys = await Promise.all( @@ -36,7 +35,7 @@ export async function loader({ try { const { preAuthKeys } = await context.client.get<{ preAuthKeys: PreAuthKey[]; - }>(`v1/preauthkey?${qp.toString()}`, session.get('api_key')!); + }>(`v1/preauthkey?${qp.toString()}`, session.api_key); return { success: true, user, @@ -139,13 +138,15 @@ export default function Page() { return key.reusable; } + + return false; }); }, [keys, selectedUser, status]); return (

- + Settings / Pre-Auth Keys @@ -176,8 +177,8 @@ export default function Page() { devices to your Tailnet. To learn more about using pre-authentication keys, visit the{' '} Tailscale documentation @@ -185,14 +186,14 @@ export default function Page() {

@@ -128,8 +127,8 @@ export default function Page() { .map((user) => ( ))} diff --git a/app/routes/users/user-actions.ts b/app/routes/users/user-actions.ts index 821d121..1e94a12 100644 --- a/app/routes/users/user-actions.ts +++ b/app/routes/users/user-actions.ts @@ -1,7 +1,6 @@ -import { ActionFunctionArgs, Session, data } from 'react-router'; +import { ActionFunctionArgs, data } from 'react-router'; import type { LoadContext } from '~/server'; import { Capabilities, Roles } from '~/server/web/roles'; -import { AuthSession } from '~/server/web/sessions'; import { User } from '~/types'; import { data400, data403 } from '~/utils/res'; @@ -15,7 +14,7 @@ export async function userAction({ throw data403('You do not have permission to update users'); } - const apiKey = session.get('api_key')!; + const apiKey = session.api_key; const formData = await request.formData(); const action = formData.get('action_id')?.toString(); if (!action) { diff --git a/app/server/db/pruner.ts b/app/server/db/pruner.ts index 815e393..9491957 100644 --- a/app/server/db/pruner.ts +++ b/app/server/db/pruner.ts @@ -22,7 +22,7 @@ export async function pruneEphemeralNodes({ const { nodes } = await context.client.get<{ nodes: Machine[] }>( 'v1/node', - session.get('api_key')!, + session.api_key, ); const toPrune = nodes.filter((node) => { @@ -42,10 +42,7 @@ export async function pruneEphemeralNodes({ const promises = toPrune.map((node) => { return async () => { log.debug('api', `Pruning node ${node.name}`); - await context.client.delete( - `v1/node/${node.id}`, - session.get('api_key')!, - ); + await context.client.delete(`v1/node/${node.id}`, session.api_key); await context.db .delete(ephemeralNodes) diff --git a/app/server/db/schema.ts b/app/server/db/schema.ts index 9aa6f69..1f24d1f 100644 --- a/app/server/db/schema.ts +++ b/app/server/db/schema.ts @@ -19,3 +19,13 @@ export const hostInfo = sqliteTable('host_info', { export type HostInfoRecord = typeof hostInfo.$inferSelect; export type HostInfoInsert = typeof hostInfo.$inferInsert; + +export const users = sqliteTable('users', { + id: text('id').primaryKey(), + sub: text('sub').notNull().unique(), + caps: integer('caps').notNull().default(0), + onboarded: integer('onboarded', { mode: 'boolean' }).notNull().default(false), +}); + +export type User = typeof users.$inferSelect; +export type UserInsert = typeof users.$inferInsert; diff --git a/app/server/index.ts b/app/server/index.ts index acb6fdd..0e82e4f 100644 --- a/app/server/index.ts +++ b/app/server/index.ts @@ -49,15 +49,17 @@ const appLoadContext = { ), // TODO: Better cookie options in config - sessions: await createSessionStorage( - { - name: '_hp_session', - maxAge: 60 * 60 * 24, // 24 hours + sessions: await createSessionStorage({ + secret: config.server.cookie_secret, + db, + oidcUsersFile: config.oidc?.user_storage_file, + cookie: { + name: '_hp_auth', secure: config.server.cookie_secure, - secrets: [config.server.cookie_secret], + maxAge: 60 * 60 * 24, // 24 hours + // domain: config.server.cookie_domain, }, - config.oidc?.user_storage_file, - ), + }), client: await createApiClient( config.headscale.url, diff --git a/app/server/web/sessions.ts b/app/server/web/sessions.ts index 164dc33..88b5724 100644 --- a/app/server/web/sessions.ts +++ b/app/server/web/sessions.ts @@ -1,13 +1,13 @@ -import { open, readFile } from 'node:fs/promises'; +import { createHash } from 'node:crypto'; +import { open, readFile, rm } from 'node:fs/promises'; import { resolve } from 'node:path'; -import { exit } from 'node:process'; -import { - CookieSerializeOptions, - Session, - SessionStorage, - createCookieSessionStorage, -} from 'react-router'; +import { eq } from 'drizzle-orm'; +import { LibSQLDatabase } from 'drizzle-orm/libsql/driver'; +import { EncryptJWT, jwtDecrypt } from 'jose'; +import { createCookie } from 'react-router'; +import { ulid } from 'ulidx'; import log from '~/utils/log'; +import { users } from '../db/schema'; import { Capabilities, Roles } from './roles'; export interface AuthSession { @@ -22,6 +22,17 @@ export interface AuthSession { }; } +interface JWTSession { + api_key: string; + user: { + subject: string; + name: string; + email?: string; + username?: string; + picture?: string; + }; +} + export interface OidcFlowSession { state: 'flow'; oidc: { @@ -32,254 +43,207 @@ export interface OidcFlowSession { }; } -type JoinedSession = AuthSession | OidcFlowSession; -interface Error { - error: string; -} - -interface CookieOptions { - name: string; - secure: boolean; - maxAge: number; - secrets: string[]; - domain?: string; +interface AuthSessionOptions { + secret: string; + db: LibSQLDatabase; + oidcUsersFile?: string; + cookie: { + name: string; + secure: boolean; + maxAge: number; + domain?: string; + }; } class Sessionizer { - private storage: SessionStorage; - private caps: Record; - private capsPath?: string; + private options: AuthSessionOptions; - constructor( - options: CookieOptions, - caps: Record, - capsPath?: string, - ) { - this.caps = caps; - this.capsPath = capsPath; - this.storage = createCookieSessionStorage({ - cookie: { - ...options, - httpOnly: true, - path: __PREFIX__, // Only match on the prefix - sameSite: 'lax', // TODO: Strictify with Domain, - }, - }); + constructor(options: AuthSessionOptions) { + this.options = options; } // This throws on the assumption that auth is already checked correctly // on something that wraps the route calling auth. The top-level routes // that call this are wrapped with try/catch to handle the error. async auth(request: Request) { - const cookie = request.headers.get('cookie'); - const session = await this.storage.getSession(cookie); - const type = session.get('state'); - if (!type) { - throw new Error('Session state not found'); - } - - if (type !== 'auth') { - throw new Error('Session is not authenticated'); - } - - return session as Session; + return decodeSession(request, this.options); } - roleForSubject(subject: string): keyof typeof Roles | undefined { - const role = this.caps[subject]?.c; - if (!role) { + async createSession( + payload: JWTSession, + maxAge = this.options.cookie.maxAge, + ) { + // TODO: What the hell is this garbage + return createSession(payload, { + ...this.options, + cookie: { + ...this.options.cookie, + maxAge, + }, + }); + } + + async destroySession() { + return destroySession(this.options); + } + + async roleForSubject( + subject: string, + ): Promise { + const [user] = await this.options.db + .select() + .from(users) + .where(eq(users.sub, subject)) + .limit(1); + + if (!user) { return; } // We need this in string form based on Object.keys of the roles for (const [key, value] of Object.entries(Roles)) { - if (value === role) { + if (value === user.caps) { return key as keyof typeof Roles; } } } - onboardForSubject(subject: string) { - return this.caps[subject]?.oo ?? false; - } - // Given an OR of capabilities, check if the session has the required // capabilities. If not, return false. Can throw since it calls auth() async check(request: Request, capabilities: Capabilities) { const session = await this.auth(request); - const { subject } = session.get('user') ?? {}; - if (!subject) { + + // This is the subject we set on API key based sessions. API keys + // inherently imply admin access so we return true for all checks. + if (session.user.subject === 'unknown-non-oauth') { + return true; + } + + const [user] = await this.options.db + .select() + .from(users) + .where(eq(users.sub, session.user.subject)) + .limit(1); + + if (!user) { return false; } - // This is the subject we set on API key based sessions. API keys - // inherently imply admin access so we return true for all checks. - if (subject === 'unknown-non-oauth') { - return true; - } - - // If the role does not exist, then this is a new subject that we have - // not seen before. Since this is new, we set access to the lowest - // level by default which is the member role. - // - // This also allows us to avoid configuring preventing sign ups with - // OIDC, since the default sign up logic gives member which does not - // have access to the UI whatsoever. - const role = this.caps[subject]; - if (!role) { - const memberRole = await this.registerSubject(subject); - return (capabilities & memberRole.c) === capabilities; - } - - return (capabilities & role.c) === capabilities; - } - - async checkSubject(subject: string, capabilities: Capabilities) { - // This is the subject we set on API key based sessions. API keys - // inherently imply admin access so we return true for all checks. - if (subject === 'unknown-non-oauth') { - return true; - } - - // If the role does not exist, then this is a new subject that we have - // not seen before. Since this is new, we set access to the lowest - // level by default which is the member role. - // - // This also allows us to avoid configuring preventing sign ups with - // OIDC, since the default sign up logic gives member which does not - // have access to the UI whatsoever. - const role = this.caps[subject]; - if (!role) { - const memberRole = await this.registerSubject(subject); - return (capabilities & memberRole.c) === capabilities; - } - - return (capabilities & role.c) === capabilities; - } - - // This code is very simple, if the user does not exist in the database - // file then we register it with the lowest level of access. If the user - // database is empty, the first user to sign in will be given the owner - // role. - private async registerSubject(subject: string) { - if (this.caps[subject]) { - return this.caps[subject]; - } - - if (Object.keys(this.caps).length === 0) { - log.debug('auth', 'First user registered as owner: %s', subject); - this.caps[subject] = { c: Roles.owner }; - await this.flushUserDatabase(); - return this.caps[subject]; - } - - log.debug('auth', 'New user registered as member: %s', subject); - this.caps[subject] = { c: Roles.member }; - await this.flushUserDatabase(); - return this.caps[subject]; - } - - private async flushUserDatabase() { - if (!this.capsPath) { - return; - } - - const data = Object.entries(this.caps).map(([u, { c, oo }]) => ({ - u, - c, - oo, - })); - try { - const handle = await open(this.capsPath, 'w'); - await handle.write(JSON.stringify(data)); - await handle.close(); - } catch (error) { - log.error('config', 'Error writing user database file: %s', error); - } + return (capabilities & user.caps) === capabilities; } // Updates the capabilities and roles of a subject async reassignSubject(subject: string, role: keyof typeof Roles) { // Check if we are owner - if (this.roleForSubject(subject) === 'owner') { + const subjectRole = await this.roleForSubject(subject); + if (subjectRole === 'owner') { return false; } - this.caps[subject] = { - ...this.caps[subject], // Preserve the existing capabilities if any - c: Roles[role], - }; + await this.options.db + .update(users) + .set({ + caps: Roles[role], + }) + .where(eq(users.sub, subject)); - await this.flushUserDatabase(); return true; } - - // Overrides the onboarding status for a subject - async overrideOnboarding(subject: string, onboarding: boolean) { - this.caps[subject] = { - ...this.caps[subject], // Preserve the existing capabilities if any - oo: onboarding, - }; - await this.flushUserDatabase(); - } - - getOrCreate(request: Request) { - return this.storage.getSession(request.headers.get('cookie')) as Promise< - Session - >; - } - - destroy(session: Session) { - return this.storage.destroySession(session); - } - - commit(session: Session, options?: CookieSerializeOptions) { - return this.storage.commitSession(session, options); - } } -export async function createSessionStorage( - options: CookieOptions, - usersPath?: string, -) { - const map: Record< - string, - { - c: number; - oo?: boolean; - } - > = {}; - if (usersPath) { - // We need to load our users from the file (default to empty map) - // We then translate each user into a capability object using the helper - // method defined in the roles.ts file - const data = await loadUserFile(usersPath); - log.debug('config', 'Loaded %d users from database', data.length); +async function createSession(payload: JWTSession, options: AuthSessionOptions) { + const secret = createHash('sha256').update(options.secret, 'utf8').digest(); + const jwt = await new EncryptJWT({ + ...payload, + }) + .setProtectedHeader({ alg: 'dir', enc: 'A256GCM', typ: 'JWT' }) + .setIssuedAt() + .setExpirationTime('1d') + .setIssuer('urn:tale:headplane') + .setAudience('urn:tale:headplane') + .setJti(ulid()) + .encrypt(secret); - for (const user of data) { - map[user.u] = { - c: user.c, - oo: user.oo, - }; - } - } + const cookie = createCookie(options.cookie.name, { + ...options.cookie, + path: __PREFIX__, + }); - return new Sessionizer(options, map, usersPath); + return cookie.serialize(jwt); } -async function loadUserFile(path: string) { +async function decodeSession(request: Request, options: AuthSessionOptions) { + const cookieHeader = request.headers.get('cookie'); + if (cookieHeader === null) { + throw new Error('No session cookie found'); + } + + const cookie = createCookie(options.cookie.name, { + ...options.cookie, + path: __PREFIX__, + }); + + const cookieValue = (await cookie.parse(cookieHeader)) as string | null; + if (cookieValue === null) { + throw new Error('Session cookie is empty'); + } + + const secret = createHash('sha256').update(options.secret, 'utf8').digest(); + const { payload } = await jwtDecrypt(cookieValue, secret, { + issuer: 'urn:tale:headplane', + audience: 'urn:tale:headplane', + }); + + // Safe since we encode the session directly into the JWT + return payload as unknown as JWTSession; +} + +async function destroySession(options: AuthSessionOptions) { + const cookie = createCookie(options.cookie.name, { + ...options.cookie, + path: __PREFIX__, + }); + + return cookie.serialize('', { + expires: new Date(0), + }); +} + +export async function createSessionStorage(options: AuthSessionOptions) { + if (options.oidcUsersFile) { + await migrateUserDatabase(options.oidcUsersFile, options.db); + } + + return new Sessionizer(options); +} + +async function migrateUserDatabase(path: string, db: LibSQLDatabase) { + log.info('config', 'Migrating old user database from %s', path); const realPath = resolve(path); try { const handle = await open(realPath, 'a+'); - log.info('config', 'Using user database file at %s', realPath); await handle.close(); } catch (error) { - log.info('config', 'User database file not accessible at %s', realPath); + log.warn('config', 'Failed to migrate old user database at %s', realPath); + log.warn( + 'config', + 'This is not an error, but existing users will not be migrated', + ); + log.warn('config', 'Unable to open user database file: %s', error); log.debug('config', 'Error details: %s', error); - exit(1); + return; } + log.info('config', 'Found old user database file at %s', realPath); + log.info('config', 'Migrating user database to the new SQL database'); + + let migratableUsers: { + u: string; + c: number; + oo?: boolean; + }[]; + try { const data = await readFile(realPath, 'utf8'); const users = JSON.parse(data.trim()) as { @@ -288,8 +252,7 @@ async function loadUserFile(path: string) { oo?: boolean; }[]; - // Never trust user input - return users.filter( + migratableUsers = users.filter( (user) => user.u !== undefined && user.c !== undefined, ) as { u: string; @@ -297,8 +260,38 @@ async function loadUserFile(path: string) { oo?: boolean; }[]; } catch (error) { - log.debug('config', 'Error reading user database file: %s', error); - log.debug('config', 'Using empty user database'); - return []; + log.warn('config', 'Error reading old user database file: %s', error); + log.warn('config', 'Not migrating any users'); + return; } + + if (migratableUsers.length === 0) { + log.info('config', 'No users found in the old database to migrate'); + return; + } + + log.info( + 'config', + 'Migrating %d users from the old database', + migratableUsers.length, + ); + + const updated = await db + .insert(users) + .values( + migratableUsers.map((user) => ({ + id: ulid(), + sub: user.u, + caps: user.c, + onboarded: user.oo ?? false, + })), + ) + .onConflictDoNothing({ + target: users.sub, + }) + .returning(); + + log.info('config', 'Migrated %d users successfully', updated.length); + log.info('config', 'Removed old user database file %s', realPath); + await rm(realPath, { force: true }); } diff --git a/drizzle/0002_square_bloodstorm.sql b/drizzle/0002_square_bloodstorm.sql new file mode 100644 index 0000000..2c22aff --- /dev/null +++ b/drizzle/0002_square_bloodstorm.sql @@ -0,0 +1,8 @@ +CREATE TABLE `users` ( + `id` text PRIMARY KEY NOT NULL, + `sub` text NOT NULL, + `caps` integer DEFAULT 0 NOT NULL, + `onboarded` integer DEFAULT false NOT NULL +); +--> statement-breakpoint +CREATE UNIQUE INDEX `users_sub_unique` ON `users` (`sub`); diff --git a/drizzle/meta/0002_snapshot.json b/drizzle/meta/0002_snapshot.json new file mode 100644 index 0000000..9b09baf --- /dev/null +++ b/drizzle/meta/0002_snapshot.json @@ -0,0 +1,119 @@ +{ + "version": "6", + "dialect": "sqlite", + "id": "2c18fbcb-d5f5-47c0-962d-54121cbb2e71", + "prevId": "16f780a3-a6e7-4810-94bb-fad5c6446ab4", + "tables": { + "ephemeral_nodes": { + "name": "ephemeral_nodes", + "columns": { + "auth_key": { + "name": "auth_key", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "node_key": { + "name": "node_key", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "host_info": { + "name": "host_info", + "columns": { + "host_id": { + "name": "host_id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "payload": { + "name": "payload", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "users": { + "name": "users", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "sub": { + "name": "sub", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "caps": { + "name": "caps", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "onboarded": { + "name": "onboarded", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + } + }, + "indexes": { + "users_sub_unique": { + "name": "users_sub_unique", + "columns": ["sub"], + "isUnique": true + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + } + }, + "views": {}, + "enums": {}, + "_meta": { + "schemas": {}, + "tables": {}, + "columns": {} + }, + "internal": { + "indexes": {} + } +} diff --git a/drizzle/meta/_journal.json b/drizzle/meta/_journal.json index e7c8c17..78491b6 100644 --- a/drizzle/meta/_journal.json +++ b/drizzle/meta/_journal.json @@ -15,6 +15,13 @@ "when": 1755554742267, "tag": "0001_naive_lilith", "breakpoints": true + }, + { + "idx": 2, + "version": "6", + "when": 1755617607599, + "tag": "0002_square_bloodstorm", + "breakpoints": true } ] } diff --git a/package.json b/package.json index 7600d17..30d598a 100644 --- a/package.json +++ b/package.json @@ -45,6 +45,7 @@ "dotenv": "^16.5.0", "drizzle-orm": "^0.44.2", "isbot": "^5.1.28", + "jose": "6.0.12", "lucide-react": "^0.511.0", "mime": "^4.0.7", "openid-client": "^6.5.0", @@ -59,6 +60,7 @@ "react-stately": "^3.38.0", "remix-utils": "^8.8.0", "tailwind-merge": "^3.3.0", + "ulidx": "2.4.1", "undici": "^7.10.0", "usehooks-ts": "^3.1.1", "yaml": "^2.8.0" @@ -92,8 +94,14 @@ }, "pnpm": { "supportedArchitectures": { - "os": ["current", "linux"], - "cpu": ["x64", "arm64"] + "os": [ + "current", + "linux" + ], + "cpu": [ + "x64", + "arm64" + ] }, "onlyBuiltDependencies": [ "@biomejs/biome", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 4a2a9e7..e5c4dbb 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -100,6 +100,9 @@ importers: isbot: specifier: ^5.1.28 version: 5.1.28 + jose: + specifier: 6.0.12 + version: 6.0.12 lucide-react: specifier: ^0.511.0 version: 0.511.0(react@19.1.1) @@ -142,6 +145,9 @@ importers: tailwind-merge: specifier: ^3.3.0 version: 3.3.0 + ulidx: + specifier: 2.4.1 + version: 2.4.1 undici: specifier: ^7.10.0 version: 7.10.0 @@ -3081,8 +3087,8 @@ packages: resolution: {integrity: sha512-twQoecYPiVA5K/h6SxtORw/Bs3ar+mLUtoPSc7iMXzQzK8d7eJ/R09wmTwAjiamETn1cXYPGfNnu7DMoHgu12w==} hasBin: true - jose@6.0.11: - resolution: {integrity: sha512-QxG7EaliDARm1O1S8BGakqncGT9s25bKL1WSf6/oa17Tkqwi8D2ZNglqCF+DsYF88/rV66Q/Q2mFAy697E1DUg==} + jose@6.0.12: + resolution: {integrity: sha512-T8xypXs8CpmiIi78k0E+Lk7T2zlK4zDyg+o1CZ4AkOHgDg98ogdP2BeZ61lTFKFyoEwJ9RgAgN+SdM3iPgNonQ==} js-base64@3.7.7: resolution: {integrity: sha512-7rCnleh0z2CkXhH67J8K1Ytz0b2Y+yxTPL+/KOJoa20hfnVQ/3/T6W/KflYI4bRHRagNeXeU2bkNGI3v1oS/lw==} @@ -3127,6 +3133,9 @@ packages: resolution: {integrity: sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==} engines: {node: '>=6'} + layerr@3.0.0: + resolution: {integrity: sha512-tv754Ki2dXpPVApOrjTyRo4/QegVb9eVFq4mjqp4+NM5NaX7syQvN5BBNfV/ZpAHCEHV24XdUVrBAoka4jt3pA==} + lefthook-darwin-arm64@1.11.13: resolution: {integrity: sha512-gHwHofXupCtzNLN+8esdWfFTnAEkmBxE/WKA0EwxPPJXdZYa1GUsiG5ipq/CdG/0j8ekYyM9Hzyrrk5BqJ42xw==} cpu: [arm64] @@ -4003,6 +4012,10 @@ packages: engines: {node: '>=14.17'} hasBin: true + ulidx@2.4.1: + resolution: {integrity: sha512-xY7c8LPyzvhvew0Fn+Ek3wBC9STZAuDI/Y5andCKi9AX6/jvfaX45PhsDX8oxgPL0YFp0Jhr8qWMbS/p9375Xg==} + engines: {node: '>=16'} + undici-types@6.21.0: resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==} @@ -7415,7 +7428,7 @@ snapshots: jiti@2.5.1: {} - jose@6.0.11: {} + jose@6.0.12: {} js-base64@3.7.7: {} @@ -7445,6 +7458,8 @@ snapshots: kleur@4.1.5: {} + layerr@3.0.0: {} + lefthook-darwin-arm64@1.11.13: optional: true @@ -7710,7 +7725,7 @@ snapshots: openid-client@6.5.0: dependencies: - jose: 6.0.11 + jose: 6.0.12 oauth4webapi: 3.5.1 package-json-from-dist@1.0.1: {} @@ -8346,6 +8361,10 @@ snapshots: typescript@5.9.2: {} + ulidx@2.4.1: + dependencies: + layerr: 3.0.0 + undici-types@6.21.0: {} undici-types@7.10.0: {} From ae2bd35cbf789cf884f91ddad618a571012c626f Mon Sep 17 00:00:00 2001 From: Aarnav Tale Date: Tue, 19 Aug 2025 17:52:26 -0400 Subject: [PATCH 76/97] chore: update biome to v2 --- biome.json | 13 ++++++--- package.json | 2 +- pnpm-lock.yaml | 74 +++++++++++++++++++++++++------------------------- 3 files changed, 47 insertions(+), 42 deletions(-) diff --git a/biome.json b/biome.json index dfae710..eb8e0d2 100644 --- a/biome.json +++ b/biome.json @@ -1,5 +1,5 @@ { - "$schema": "https://biomejs.dev/schemas/1.9.4/schema.json", + "$schema": "https://biomejs.dev/schemas/2.2.0/schema.json", "vcs": { "enabled": false, "clientKind": "git", @@ -7,7 +7,7 @@ }, "files": { "ignoreUnknown": false, - "ignore": ["app/wasm_exec.js"] + "includes": ["**", "!app/wasm_exec.js"] }, "formatter": { "enabled": true, @@ -15,8 +15,13 @@ "lineWidth": 80, "lineEnding": "lf" }, - "organizeImports": { - "enabled": true + "assist": { + "actions": { + "source": { + "organizeImports": "on", + "useSortedAttributes": "on" + } + } }, "linter": { "enabled": true, diff --git a/package.json b/package.json index 30d598a..731f05c 100644 --- a/package.json +++ b/package.json @@ -66,7 +66,7 @@ "yaml": "^2.8.0" }, "devDependencies": { - "@biomejs/biome": "^1.9.4", + "@biomejs/biome": "^2.2.0", "@react-router/dev": "^7.8.1", "@tailwindcss/vite": "^4.1.12", "@types/node": "^24.3.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index e5c4dbb..c0188b7 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -159,8 +159,8 @@ importers: version: 2.8.0 devDependencies: '@biomejs/biome': - specifier: ^1.9.4 - version: 1.9.4 + specifier: ^2.2.0 + version: 2.2.0 '@react-router/dev': specifier: ^7.8.1 version: 7.8.1(@types/node@24.3.0)(jiti@2.5.1)(lightningcss@1.30.1)(react-dom@19.1.1(react@19.1.1))(react-router@7.8.1(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(react@19.1.1)(rolldown-vite@7.1.3(@types/node@24.3.0)(esbuild@0.25.5)(jiti@2.5.1)(terser@5.39.0)(tsx@4.19.4)(yaml@2.8.0))(terser@5.39.0)(tsx@4.19.4)(typescript@5.9.2)(yaml@2.8.0) @@ -400,55 +400,55 @@ packages: resolution: {integrity: sha512-ruv7Ae4J5dUYULmeXw1gmb7rYRz57OWCPM57pHojnLq/3Z1CK2lNSLTCVjxVk1F/TZHwOZZrOWi0ur95BbLxNQ==} engines: {node: '>=6.9.0'} - '@biomejs/biome@1.9.4': - resolution: {integrity: sha512-1rkd7G70+o9KkTn5KLmDYXihGoTaIGO9PIIN2ZB7UJxFrWw04CZHPYiMRjYsaDvVV7hP1dYNRLxSANLaBFGpog==} + '@biomejs/biome@2.2.0': + resolution: {integrity: sha512-3On3RSYLsX+n9KnoSgfoYlckYBoU6VRM22cw1gB4Y0OuUVSYd/O/2saOJMrA4HFfA1Ff0eacOvMN1yAAvHtzIw==} engines: {node: '>=14.21.3'} hasBin: true - '@biomejs/cli-darwin-arm64@1.9.4': - resolution: {integrity: sha512-bFBsPWrNvkdKrNCYeAp+xo2HecOGPAy9WyNyB/jKnnedgzl4W4Hb9ZMzYNbf8dMCGmUdSavlYHiR01QaYR58cw==} + '@biomejs/cli-darwin-arm64@2.2.0': + resolution: {integrity: sha512-zKbwUUh+9uFmWfS8IFxmVD6XwqFcENjZvEyfOxHs1epjdH3wyyMQG80FGDsmauPwS2r5kXdEM0v/+dTIA9FXAg==} engines: {node: '>=14.21.3'} cpu: [arm64] os: [darwin] - '@biomejs/cli-darwin-x64@1.9.4': - resolution: {integrity: sha512-ngYBh/+bEedqkSevPVhLP4QfVPCpb+4BBe2p7Xs32dBgs7rh9nY2AIYUL6BgLw1JVXV8GlpKmb/hNiuIxfPfZg==} + '@biomejs/cli-darwin-x64@2.2.0': + resolution: {integrity: sha512-+OmT4dsX2eTfhD5crUOPw3RPhaR+SKVspvGVmSdZ9y9O/AgL8pla6T4hOn1q+VAFBHuHhsdxDRJgFCSC7RaMOw==} engines: {node: '>=14.21.3'} cpu: [x64] os: [darwin] - '@biomejs/cli-linux-arm64-musl@1.9.4': - resolution: {integrity: sha512-v665Ct9WCRjGa8+kTr0CzApU0+XXtRgwmzIf1SeKSGAv+2scAlW6JR5PMFo6FzqqZ64Po79cKODKf3/AAmECqA==} + '@biomejs/cli-linux-arm64-musl@2.2.0': + resolution: {integrity: sha512-egKpOa+4FL9YO+SMUMLUvf543cprjevNc3CAgDNFLcjknuNMcZ0GLJYa3EGTCR2xIkIUJDVneBV3O9OcIlCEZQ==} engines: {node: '>=14.21.3'} cpu: [arm64] os: [linux] - '@biomejs/cli-linux-arm64@1.9.4': - resolution: {integrity: sha512-fJIW0+LYujdjUgJJuwesP4EjIBl/N/TcOX3IvIHJQNsAqvV2CHIogsmA94BPG6jZATS4Hi+xv4SkBBQSt1N4/g==} + '@biomejs/cli-linux-arm64@2.2.0': + resolution: {integrity: sha512-6eoRdF2yW5FnW9Lpeivh7Mayhq0KDdaDMYOJnH9aT02KuSIX5V1HmWJCQQPwIQbhDh68Zrcpl8inRlTEan0SXw==} engines: {node: '>=14.21.3'} cpu: [arm64] os: [linux] - '@biomejs/cli-linux-x64-musl@1.9.4': - resolution: {integrity: sha512-gEhi/jSBhZ2m6wjV530Yy8+fNqG8PAinM3oV7CyO+6c3CEh16Eizm21uHVsyVBEB6RIM8JHIl6AGYCv6Q6Q9Tg==} + '@biomejs/cli-linux-x64-musl@2.2.0': + resolution: {integrity: sha512-I5J85yWwUWpgJyC1CcytNSGusu2p9HjDnOPAFG4Y515hwRD0jpR9sT9/T1cKHtuCvEQ/sBvx+6zhz9l9wEJGAg==} engines: {node: '>=14.21.3'} cpu: [x64] os: [linux] - '@biomejs/cli-linux-x64@1.9.4': - resolution: {integrity: sha512-lRCJv/Vi3Vlwmbd6K+oQ0KhLHMAysN8lXoCI7XeHlxaajk06u7G+UsFSO01NAs5iYuWKmVZjmiOzJ0OJmGsMwg==} + '@biomejs/cli-linux-x64@2.2.0': + resolution: {integrity: sha512-5UmQx/OZAfJfi25zAnAGHUMuOd+LOsliIt119x2soA2gLggQYrVPA+2kMUxR6Mw5M1deUF/AWWP2qpxgH7Nyfw==} engines: {node: '>=14.21.3'} cpu: [x64] os: [linux] - '@biomejs/cli-win32-arm64@1.9.4': - resolution: {integrity: sha512-tlbhLk+WXZmgwoIKwHIHEBZUwxml7bRJgk0X2sPyNR3S93cdRq6XulAZRQJ17FYGGzWne0fgrXBKpl7l4M87Hg==} + '@biomejs/cli-win32-arm64@2.2.0': + resolution: {integrity: sha512-n9a1/f2CwIDmNMNkFs+JI0ZjFnMO0jdOyGNtihgUNFnlmd84yIYY2KMTBmMV58ZlVHjgmY5Y6E1hVTnSRieggA==} engines: {node: '>=14.21.3'} cpu: [arm64] os: [win32] - '@biomejs/cli-win32-x64@1.9.4': - resolution: {integrity: sha512-8Y5wMhVIPaWe6jw2H+KlEm4wP/f7EW3810ZLmDlrEEy5KvBsb9ECEfu/kMWD484ijfQ8+nIi0giMgu9g1UAuuA==} + '@biomejs/cli-win32-x64@2.2.0': + resolution: {integrity: sha512-Nawu5nHjP/zPKTIryh2AavzTc/KEg4um/MxWdXW0A6P/RZOyIpa7+QSjeXwAwX/utJGaCoXRPWtF3m5U/bB3Ww==} engines: {node: '>=14.21.3'} cpu: [x64] os: [win32] @@ -4545,39 +4545,39 @@ snapshots: '@babel/helper-string-parser': 7.27.1 '@babel/helper-validator-identifier': 7.27.1 - '@biomejs/biome@1.9.4': + '@biomejs/biome@2.2.0': optionalDependencies: - '@biomejs/cli-darwin-arm64': 1.9.4 - '@biomejs/cli-darwin-x64': 1.9.4 - '@biomejs/cli-linux-arm64': 1.9.4 - '@biomejs/cli-linux-arm64-musl': 1.9.4 - '@biomejs/cli-linux-x64': 1.9.4 - '@biomejs/cli-linux-x64-musl': 1.9.4 - '@biomejs/cli-win32-arm64': 1.9.4 - '@biomejs/cli-win32-x64': 1.9.4 + '@biomejs/cli-darwin-arm64': 2.2.0 + '@biomejs/cli-darwin-x64': 2.2.0 + '@biomejs/cli-linux-arm64': 2.2.0 + '@biomejs/cli-linux-arm64-musl': 2.2.0 + '@biomejs/cli-linux-x64': 2.2.0 + '@biomejs/cli-linux-x64-musl': 2.2.0 + '@biomejs/cli-win32-arm64': 2.2.0 + '@biomejs/cli-win32-x64': 2.2.0 - '@biomejs/cli-darwin-arm64@1.9.4': + '@biomejs/cli-darwin-arm64@2.2.0': optional: true - '@biomejs/cli-darwin-x64@1.9.4': + '@biomejs/cli-darwin-x64@2.2.0': optional: true - '@biomejs/cli-linux-arm64-musl@1.9.4': + '@biomejs/cli-linux-arm64-musl@2.2.0': optional: true - '@biomejs/cli-linux-arm64@1.9.4': + '@biomejs/cli-linux-arm64@2.2.0': optional: true - '@biomejs/cli-linux-x64-musl@1.9.4': + '@biomejs/cli-linux-x64-musl@2.2.0': optional: true - '@biomejs/cli-linux-x64@1.9.4': + '@biomejs/cli-linux-x64@2.2.0': optional: true - '@biomejs/cli-win32-arm64@1.9.4': + '@biomejs/cli-win32-arm64@2.2.0': optional: true - '@biomejs/cli-win32-x64@1.9.4': + '@biomejs/cli-win32-x64@2.2.0': optional: true '@clack/core@0.3.5': From 797ed56c0796ea4aba982ea88726d0eec471b3ab Mon Sep 17 00:00:00 2001 From: Aarnav Tale Date: Tue, 19 Aug 2025 17:53:34 -0400 Subject: [PATCH 77/97] chore: remove github pages workflow --- .github/workflows/docs.yml | 47 -------------------------------------- 1 file changed, 47 deletions(-) delete mode 100644 .github/workflows/docs.yml diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml deleted file mode 100644 index 299ac8c..0000000 --- a/.github/workflows/docs.yml +++ /dev/null @@ -1,47 +0,0 @@ -name: Deploy VitePress site to Pages -on: - push: - branches: ["main", "next", "next-nix-generate-docs"] - 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 - with: - fetch-depth: 1 - - uses: pnpm/action-setup@v3 - - name: Setup Node - uses: actions/setup-node@v4 - with: - node-version: 22 - cache: pnpm - - name: Setup Pages - uses: actions/configure-pages@v4 - - name: Install dependencies - run: pnpm install - - name: Build with VitePress - run: pnpm run docs:build - - name: Upload artifact - uses: actions/upload-pages-artifact@v3 - with: - path: docs/.vitepress/dist - deploy: - environment: - name: github-pages - url: ${{ steps.deployment.outputs.page_url }} - needs: build - runs-on: ubuntu-latest - name: Deploy - steps: - - name: Deploy to GitHub Pages - id: deployment - uses: actions/deploy-pages@v4 From bcd87453bd4657ed8e5edeaef902cd628cc9e606 Mon Sep 17 00:00:00 2001 From: Aarnav Tale Date: Wed, 20 Aug 2025 11:25:11 -0400 Subject: [PATCH 78/97] chore: update go mods --- go.mod | 28 +++++++++++------------- go.sum | 56 +++++++++++++++++++++--------------------------- nix/agent.nix | 2 +- nix/package.nix | 2 +- nix/ssh-wasm.nix | 2 +- 5 files changed, 39 insertions(+), 51 deletions(-) diff --git a/go.mod b/go.mod index b06e64f..a773cf4 100644 --- a/go.mod +++ b/go.mod @@ -1,14 +1,12 @@ module github.com/tale/headplane -go 1.24.0 - -toolchain go1.24.4 +go 1.24.4 require ( github.com/joho/godotenv v1.5.1 go4.org/mem v0.0.0-20240501181205-ae6ca9944745 - golang.org/x/crypto v0.38.0 - tailscale.com v1.84.1 + golang.org/x/crypto v0.41.0 + tailscale.com v1.86.4 ) require ( @@ -43,8 +41,6 @@ require ( github.com/google/go-cmp v0.6.0 // indirect github.com/google/nftables v0.2.1-0.20240414091927-5e242ec57806 // indirect github.com/google/uuid v1.6.0 // indirect - github.com/gorilla/csrf v1.7.3 // indirect - github.com/gorilla/securecookie v1.1.2 // indirect github.com/hdevalence/ed25519consensus v0.2.0 // indirect github.com/illarion/gonotify/v3 v3.0.2 // indirect github.com/jmespath/go-jmespath v0.4.0 // indirect @@ -65,19 +61,19 @@ require ( github.com/tailscale/netlink v1.1.1-0.20240822203006-4d49adab4de7 // indirect github.com/tailscale/peercred v0.0.0-20250107143737-35a0c7bd7edc // indirect github.com/tailscale/web-client-prebuilt v0.0.0-20250124233751-d4cd19a26976 // indirect - github.com/tailscale/wireguard-go v0.0.0-20250304000100-91a0587fb251 // indirect + github.com/tailscale/wireguard-go v0.0.0-20250716170648-1d0488a3d7da // indirect github.com/vishvananda/netns v0.0.4 // indirect github.com/x448/float16 v0.8.4 // indirect go4.org/netipx v0.0.0-20231129151722-fdeea329fbba // indirect golang.org/x/exp v0.0.0-20250210185358-939b2ce775ac // indirect - golang.org/x/mod v0.23.0 // indirect - golang.org/x/net v0.36.0 // indirect - golang.org/x/sync v0.14.0 // indirect - golang.org/x/sys v0.33.0 // indirect - golang.org/x/term v0.32.0 // indirect - golang.org/x/text v0.25.0 // indirect - golang.org/x/time v0.10.0 // indirect - golang.org/x/tools v0.30.0 // indirect + golang.org/x/mod v0.26.0 // indirect + golang.org/x/net v0.42.0 // indirect + golang.org/x/sync v0.16.0 // indirect + golang.org/x/sys v0.35.0 // indirect + golang.org/x/term v0.34.0 // indirect + golang.org/x/text v0.28.0 // indirect + golang.org/x/time v0.11.0 // indirect + golang.org/x/tools v0.35.0 // indirect golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 // indirect golang.zx2c4.com/wireguard/windows v0.5.3 // indirect gvisor.dev/gvisor v0.0.0-20250205023644-9414b50a5633 // indirect diff --git a/go.sum b/go.sum index 1d5fc1f..b8140ce 100644 --- a/go.sum +++ b/go.sum @@ -59,8 +59,6 @@ github.com/digitalocean/go-smbios v0.0.0-20180907143718-390a4f403a8e h1:vUmf0yez github.com/digitalocean/go-smbios v0.0.0-20180907143718-390a4f403a8e/go.mod h1:YTIHhz/QFSYnu/EhlF2SpU2Uk+32abacUYA5ZPljz1A= github.com/djherbis/times v1.6.0 h1:w2ctJ92J8fBvWPxugmXIv7Nz7Q3iDMKNx9v5ocVH20c= github.com/djherbis/times v1.6.0/go.mod h1:gOHeRAz2h+VJNZ5Gmc/o7iD9k4wW7NMVqieYCY99oc0= -github.com/dsnet/try v0.0.3 h1:ptR59SsrcFUYbT/FhAbKTV6iLkeD6O18qfIWRml2fqI= -github.com/dsnet/try v0.0.3/go.mod h1:WBM8tRpUmnXXhY1U6/S8dt6UWdHTQ7y8A5YSkRCkq40= github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= github.com/fxamacker/cbor/v2 v2.8.0 h1:fFtUGXUzXPHTIUdne5+zzMPTfffl3RD5qYnkY40vtxU= @@ -85,16 +83,10 @@ github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-tpm v0.9.4 h1:awZRf9FwOeTunQmHoDYSHJps3ie6f1UlhS1fOdPEt1I= github.com/google/go-tpm v0.9.4/go.mod h1:h9jEsEECg7gtLis0upRBQU+GhYVH6jMjrFxI8u6bVUY= -github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= -github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/nftables v0.2.1-0.20240414091927-5e242ec57806 h1:wG8RYIyctLhdFk6Vl1yPGtSRtwGpVkWyZww1OCil2MI= github.com/google/nftables v0.2.1-0.20240414091927-5e242ec57806/go.mod h1:Beg6V6zZ3oEn0JuiUQ4wqwuyqqzasOltcoXPtgLbFp4= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/gorilla/csrf v1.7.3 h1:BHWt6FTLZAb2HtWT5KDBf6qgpZzvtbp9QWDRKZMXJC0= -github.com/gorilla/csrf v1.7.3/go.mod h1:F1Fj3KG23WYHE6gozCmBAezKookxbIvUJT+121wTuLk= -github.com/gorilla/securecookie v1.1.2 h1:YCIWL56dvtr73r6715mJs5ZvhtnY73hBvEF8kXD8ePA= -github.com/gorilla/securecookie v1.1.2/go.mod h1:NfCASbcHqRSY+3a8tlWJwsQap2VX5pwzwo4h3eOamfo= github.com/hdevalence/ed25519consensus v0.2.0 h1:37ICyZqdyj0lAZ8P4D1d1id3HqbbG1N3iBb1Tb4rdcU= github.com/hdevalence/ed25519consensus v0.2.0/go.mod h1:w3BHWjwJbFU29IRHL1Iqkw3sus+7FctEyM4RqDxYNzo= github.com/illarion/gonotify/v3 v3.0.2 h1:O7S6vcopHexutmpObkeWsnzMJt/r1hONIEogeVNmJMk= @@ -173,8 +165,8 @@ github.com/tailscale/web-client-prebuilt v0.0.0-20250124233751-d4cd19a26976 h1:U github.com/tailscale/web-client-prebuilt v0.0.0-20250124233751-d4cd19a26976/go.mod h1:agQPE6y6ldqCOui2gkIh7ZMztTkIQKH049tv8siLuNQ= github.com/tailscale/wf v0.0.0-20240214030419-6fbb0a674ee6 h1:l10Gi6w9jxvinoiq15g8OToDdASBni4CyJOdHY1Hr8M= github.com/tailscale/wf v0.0.0-20240214030419-6fbb0a674ee6/go.mod h1:ZXRML051h7o4OcI0d3AaILDIad/Xw0IkXaHM17dic1Y= -github.com/tailscale/wireguard-go v0.0.0-20250304000100-91a0587fb251 h1:h/41LFTrwMxB9Xvvug0kRdQCU5TlV1+pAMQw0ZtDE3U= -github.com/tailscale/wireguard-go v0.0.0-20250304000100-91a0587fb251/go.mod h1:BOm5fXUBFM+m9woLNBoxI9TaBXXhGNP50LX/TGIvGb4= +github.com/tailscale/wireguard-go v0.0.0-20250716170648-1d0488a3d7da h1:jVRUZPRs9sqyKlYHHzHjAqKN+6e/Vog6NpHYeNPJqOw= +github.com/tailscale/wireguard-go v0.0.0-20250716170648-1d0488a3d7da/go.mod h1:BOm5fXUBFM+m9woLNBoxI9TaBXXhGNP50LX/TGIvGb4= github.com/tailscale/xnet v0.0.0-20240729143630-8497ac4dab2e h1:zOGKqN5D5hHhiYUp091JqK7DPCqSARyUfduhGUY8Bek= github.com/tailscale/xnet v0.0.0-20240729143630-8497ac4dab2e/go.mod h1:orPd6JZXXRyuDusYilywte7k094d7dycXXU5YnWsrwg= github.com/tc-hib/winres v0.2.1 h1:YDE0FiP0VmtRaDn7+aaChp1KiF4owBiJa5l964l5ujA= @@ -192,36 +184,36 @@ go4.org/mem v0.0.0-20240501181205-ae6ca9944745 h1:Tl++JLUCe4sxGu8cTpDzRLd3tN7US4 go4.org/mem v0.0.0-20240501181205-ae6ca9944745/go.mod h1:reUoABIJ9ikfM5sgtSF3Wushcza7+WeD01VB9Lirh3g= go4.org/netipx v0.0.0-20231129151722-fdeea329fbba h1:0b9z3AuHCjxk0x/opv64kcgZLBseWJUpBw5I82+2U4M= go4.org/netipx v0.0.0-20231129151722-fdeea329fbba/go.mod h1:PLyyIXexvUFg3Owu6p/WfdlivPbZJsZdgWZlrGope/Y= -golang.org/x/crypto v0.38.0 h1:jt+WWG8IZlBnVbomuhg2Mdq0+BBQaHbtqHEFEigjUV8= -golang.org/x/crypto v0.38.0/go.mod h1:MvrbAqul58NNYPKnOra203SB9vpuZW0e+RRZV+Ggqjw= +golang.org/x/crypto v0.41.0 h1:WKYxWedPGCTVVl5+WHSSrOBT0O8lx32+zxmHxijgXp4= +golang.org/x/crypto v0.41.0/go.mod h1:pO5AFd7FA68rFak7rOAGVuygIISepHftHnr8dr6+sUc= golang.org/x/exp v0.0.0-20250210185358-939b2ce775ac h1:l5+whBCLH3iH2ZNHYLbAe58bo7yrN4mVcnkHDYz5vvs= golang.org/x/exp v0.0.0-20250210185358-939b2ce775ac/go.mod h1:hH+7mtFmImwwcMvScyxUhjuVHR3HGaDPMn9rMSUUbxo= golang.org/x/exp/typeparams v0.0.0-20240314144324-c7f7c6466f7f h1:phY1HzDcf18Aq9A8KkmRtY9WvOFIxN8wgfvy6Zm1DV8= golang.org/x/exp/typeparams v0.0.0-20240314144324-c7f7c6466f7f/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk= -golang.org/x/image v0.24.0 h1:AN7zRgVsbvmTfNyqIbbOraYL8mSwcKncEj8ofjgzcMQ= -golang.org/x/image v0.24.0/go.mod h1:4b/ITuLfqYq1hqZcjofwctIhi7sZh2WaCjvsBNjjya8= -golang.org/x/mod v0.23.0 h1:Zb7khfcRGKk+kqfxFaP5tZqCnDZMjC5VtUBs87Hr6QM= -golang.org/x/mod v0.23.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= -golang.org/x/net v0.36.0 h1:vWF2fRbw4qslQsQzgFqZff+BItCvGFQqKzKIzx1rmoA= -golang.org/x/net v0.36.0/go.mod h1:bFmbeoIPfrw4sMHNhb4J9f6+tPziuGjq7Jk/38fxi1I= +golang.org/x/image v0.27.0 h1:C8gA4oWU/tKkdCfYT6T2u4faJu3MeNS5O8UPWlPF61w= +golang.org/x/image v0.27.0/go.mod h1:xbdrClrAUway1MUTEZDq9mz/UpRwYAkFFNUslZtcB+g= +golang.org/x/mod v0.26.0 h1:EGMPT//Ezu+ylkCijjPc+f4Aih7sZvaAr+O3EHBxvZg= +golang.org/x/mod v0.26.0/go.mod h1:/j6NAhSk8iQ723BGAUyoAcn7SlD7s15Dp9Nd/SfeaFQ= +golang.org/x/net v0.42.0 h1:jzkYrhi3YQWD6MLBJcsklgQsoAcw89EcZbJw8Z614hs= +golang.org/x/net v0.42.0/go.mod h1:FF1RA5d3u7nAYA4z2TkclSCKh68eSXtiFwcWQpPXdt8= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.14.0 h1:woo0S4Yywslg6hp4eUFjTVOyKt0RookbpAHG4c1HmhQ= -golang.org/x/sync v0.14.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= +golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw= +golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= golang.org/x/sys v0.0.0-20200217220822-9197077df867/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200728102440-3e129f6d46b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20220817070843-5a390386f1f2/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw= -golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= -golang.org/x/term v0.32.0 h1:DR4lr0TjUs3epypdhTOkMmuF5CDFJ/8pOnbzMZPQ7bg= -golang.org/x/term v0.32.0/go.mod h1:uZG1FhGx848Sqfsq4/DlJr3xGGsYMu/L5GW4abiaEPQ= -golang.org/x/text v0.25.0 h1:qVyWApTSYLk/drJRO5mDlNYskwQznZmkpV2c8q9zls4= -golang.org/x/text v0.25.0/go.mod h1:WEdwpYrmk1qmdHvhkSTNPm3app7v4rsT8F2UD6+VHIA= -golang.org/x/time v0.10.0 h1:3usCWA8tQn0L8+hFJQNgzpWbd89begxN66o1Ojdn5L4= -golang.org/x/time v0.10.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= -golang.org/x/tools v0.30.0 h1:BgcpHewrV5AUp2G9MebG4XPFI1E2W41zU1SaqVA9vJY= -golang.org/x/tools v0.30.0/go.mod h1:c347cR/OJfw5TI+GfX7RUPNMdDRRbjvYTS0jPyvsVtY= +golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI= +golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/term v0.34.0 h1:O/2T7POpk0ZZ7MAzMeWFSg6S5IpWd/RXDlM9hgM3DR4= +golang.org/x/term v0.34.0/go.mod h1:5jC53AEywhIVebHgPVeg0mj8OD3VO9OzclacVrqpaAw= +golang.org/x/text v0.28.0 h1:rhazDwis8INMIwQ4tpjLDzUhx6RlXqZNPEM0huQojng= +golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU= +golang.org/x/time v0.11.0 h1:/bpjEDfN9tkoN/ryeYHnv5hcMlc8ncjMcM4XBk5NWV0= +golang.org/x/time v0.11.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg= +golang.org/x/tools v0.35.0 h1:mBffYraMEf7aa0sB+NuKnuCy8qI/9Bughn8dC2Gu5r0= +golang.org/x/tools v0.35.0/go.mod h1:NKdj5HkL/73byiZSJjqJgKn3ep7KjFkBOkR/Hps3VPw= golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 h1:B82qJJgjvYKsXS9jeunTOisW56dUokqW/FOteYJJ/yg= golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2/go.mod h1:deeaetjYA+DHMHg+sMSMI58GrEteJUUzzw7en6TJQcI= golang.zx2c4.com/wireguard/windows v0.5.3 h1:On6j2Rpn3OEMXqBq00QEDC7bWSZrPIHKIus8eIuExIE= @@ -240,5 +232,5 @@ howett.net/plist v1.0.0 h1:7CrbWYbPPO/PyNy38b2EB/+gYbjCe2DXBxgtOOZbSQM= howett.net/plist v1.0.0/go.mod h1:lqaXoTrLY4hg8tnEzNru53gicrbv7rrk+2xJA/7hw9g= software.sslmate.com/src/go-pkcs12 v0.4.0 h1:H2g08FrTvSFKUj+D309j1DPfk5APnIdAQAB8aEykJ5k= software.sslmate.com/src/go-pkcs12 v0.4.0/go.mod h1:Qiz0EyvDRJjjxGyUQa2cCNZn/wMyzrRJ/qcDXOQazLI= -tailscale.com v1.84.1 h1:xtuiYeAIUR+dRztPzzqUsjj+Fv/06vz28zoFaP1k/Os= -tailscale.com v1.84.1/go.mod h1:6/S63NMAhmncYT/1zIPDJkvCuZwMw+JnUuOfSPNazpo= +tailscale.com v1.86.4 h1:KHAmLyzVn50t2P5877wohBU0UPVeIMHC9XDzRw4Ycz4= +tailscale.com v1.86.4/go.mod h1:Lm8dnzU2i/Emw15r6sl3FRNp/liSQ/nYw6ZSQvIdZ1M= diff --git a/nix/agent.nix b/nix/agent.nix index bde9927..40cd0c7 100644 --- a/nix/agent.nix +++ b/nix/agent.nix @@ -3,7 +3,7 @@ buildGoModule { pname = "hp_agent"; version = (builtins.fromJSON (builtins.readFile ../package.json)).version; src = ../.; - vendorHash = "sha256-3hZzDORAH+D4FW6SkOv3Enddd+q36ZALryvCPD9E5Ac="; + vendorHash = "sha256-AyJx1os4auTmHvz+KLY+RGyysa3LWi8r2+QLxofPUH4="; ldflags = ["-s" "-w"]; env.CGO_ENABLED = 0; } diff --git a/nix/package.nix b/nix/package.nix index 6e4df32..fab5964 100644 --- a/nix/package.nix +++ b/nix/package.nix @@ -29,7 +29,7 @@ in pnpmDeps = pnpm_10.fetchDeps { inherit pname version src; fetcherVersion = 2; - hash = "sha256-0r228fDAMcEmVDE4jrMBRzZyQPp3aE4SyxGp7Oap9dI="; + hash = "sha256-drsQWU1/cDcbJ75CerXQSYFNNAOgaX6rFn6wRom8fiU="; }; buildPhase = '' diff --git a/nix/ssh-wasm.nix b/nix/ssh-wasm.nix index 7f47495..0ef5b32 100644 --- a/nix/ssh-wasm.nix +++ b/nix/ssh-wasm.nix @@ -14,7 +14,7 @@ in version = (builtins.fromJSON (builtins.readFile ../package.json)).version; src = ../.; subPackages = ["cmd/hp_ssh"]; - vendorHash = "sha256-3hZzDORAH+D4FW6SkOv3Enddd+q36ZALryvCPD9E5Ac="; + vendorHash = "sha256-AyJx1os4auTmHvz+KLY+RGyysa3LWi8r2+QLxofPUH4="; env.CGO_ENABLED = 0; nativeBuildInputs = [go]; From 8fc657f86a801ef45d676c1551bc6910e2000280 Mon Sep 17 00:00:00 2001 From: Aarnav Tale Date: Wed, 20 Aug 2025 13:58:19 -0400 Subject: [PATCH 79/97] feat: handle logging from the agent --- app/server/hp-agent.ts | 26 +++++++++++-- cmd/hp_agent/hp_agent.go | 43 ++++++++++++++++----- go.mod | 1 - go.sum | 2 - internal/config.go | 45 ---------------------- internal/hpagent/handler.go | 55 --------------------------- internal/preflight.go | 48 ------------------------ internal/util/logger.go | 74 +++---------------------------------- nix/agent.nix | 2 +- nix/ssh-wasm.nix | 2 +- 10 files changed, 63 insertions(+), 235 deletions(-) delete mode 100644 internal/config.go delete mode 100644 internal/hpagent/handler.go delete mode 100644 internal/preflight.go diff --git a/app/server/hp-agent.ts b/app/server/hp-agent.ts index c8050d8..fe9b059 100644 --- a/app/server/hp-agent.ts +++ b/app/server/hp-agent.ts @@ -1,8 +1,8 @@ import { ChildProcessWithoutNullStreams, spawn } from 'node:child_process'; import EventEmitter from 'node:events'; -import { constants, access, mkdir, open } from 'node:fs/promises'; +import { access, constants, mkdir, open } from 'node:fs/promises'; import { getegid, geteuid } from 'node:process'; -import { Interface, createInterface } from 'node:readline'; +import { createInterface, Interface } from 'node:readline'; import { inArray } from 'drizzle-orm'; import { LibSQLDatabase } from 'drizzle-orm/libsql/driver-core'; import { HostInfo } from '~/types'; @@ -240,7 +240,6 @@ class HeadplaneAgent extends EventEmitter { gid: getegid?.() ?? undefined, env: { HOME: process.env.HOME, - HEADPLANE_EMBEDDED: 'true', HEADPLANE_AGENT_WORK_DIR: this.options.work_dir, HEADPLANE_AGENT_DEBUG: log.debugEnabled ? 'true' : 'false', HEADPLANE_AGENT_HOSTNAME: this.options.host_name, @@ -396,5 +395,26 @@ class HeadplaneAgent extends EventEmitter { this.emit('error', new Error(error)); return; } + + if (line.startsWith('LOG')) { + const logSnippet = line.slice(4).trim(); + const [level, ...messageParts] = logSnippet.split(' '); + const message = messageParts.join(' '); + switch (level) { + case 'INFO': + log.info('agent', message); + break; + case 'WARN': + log.warn('agent', message); + break; + case 'ERROR': + log.error('agent', message); + break; + default: + log.debug('agent', message); + } + + return; + } } } diff --git a/cmd/hp_agent/hp_agent.go b/cmd/hp_agent/hp_agent.go index c1400be..65c6506 100644 --- a/cmd/hp_agent/hp_agent.go +++ b/cmd/hp_agent/hp_agent.go @@ -1,11 +1,13 @@ package main import ( + "bufio" + "context" "fmt" + "os" + "strings" - _ "github.com/joho/godotenv/autoload" "github.com/tale/headplane/internal/config" - "github.com/tale/headplane/internal/hpagent" "github.com/tale/headplane/internal/tsnet" "github.com/tale/headplane/internal/util" ) @@ -24,15 +26,36 @@ func main() { log.SetDebug(cfg.Debug) agent := tsnet.NewAgent(cfg) - - agent.Connect() defer agent.Shutdown() - log.Msg(&Register{ - Type: "register", - ID: agent.ID, - }) + scanner := bufio.NewScanner(os.Stdin) + for scanner.Scan() { + line := scanner.Bytes() + directive := strings.TrimSpace(string(line)) + log.Debug("Received directive: %s", directive) - fmt.Println("READY") - hpagent.FollowMaster(agent) + switch directive { + case "START": + agent.Connect() + fmt.Printf("READY %s\n", agent.ID) + + case "SHUTDOWN": + agent.Shutdown() + os.Exit(0) + + case "PING": + fmt.Printf("PONG %s\n", agent.ID) + + case "REFRESH": + err := agent.DispatchHostInfo(context.Background()) + if err != nil { + fmt.Printf("ERROR %s\n", err) + } + } + } + + if err := scanner.Err(); err != nil { + fmt.Printf("ERROR %s\n", err) + os.Exit(1) + } } diff --git a/go.mod b/go.mod index a773cf4..314bbca 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,6 @@ module github.com/tale/headplane go 1.24.4 require ( - github.com/joho/godotenv v1.5.1 go4.org/mem v0.0.0-20240501181205-ae6ca9944745 golang.org/x/crypto v0.41.0 tailscale.com v1.86.4 diff --git a/go.sum b/go.sum index b8140ce..e2368ed 100644 --- a/go.sum +++ b/go.sum @@ -99,8 +99,6 @@ github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9Y github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8= github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= -github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= -github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= github.com/jsimonetti/rtnetlink v1.4.0 h1:Z1BF0fRgcETPEa0Kt0MRk3yV5+kF1FWTni6KUFKrq2I= github.com/jsimonetti/rtnetlink v1.4.0/go.mod h1:5W1jDvWdnthFJ7fxYX1GMK07BUpI4oskfOqvPteYS6E= github.com/klauspost/compress v1.17.11 h1:In6xLpyWOi1+C7tXUUWv2ot1QvBjxevKAaI6IXrJmUc= diff --git a/internal/config.go b/internal/config.go deleted file mode 100644 index d33c014..0000000 --- a/internal/config.go +++ /dev/null @@ -1,45 +0,0 @@ -package config - -import "os" - -// Config represents the configuration for the agent. -type Config struct { - Debug bool - Hostname string - TSControlURL string - TSAuthKey string - WorkDir string -} - -const ( - DebugEnv = "HEADPLANE_AGENT_DEBUG" - HostnameEnv = "HEADPLANE_AGENT_HOSTNAME" - TSControlURLEnv = "HEADPLANE_AGENT_TS_SERVER" - TSAuthKeyEnv = "HEADPLANE_AGENT_TS_AUTHKEY" - WorkDirEnv = "HEADPLANE_AGENT_WORK_DIR" -) - -// Load reads the agent configuration from environment variables. -func Load() (*Config, error) { - c := &Config{ - Debug: false, - Hostname: os.Getenv(HostnameEnv), - TSControlURL: os.Getenv(TSControlURLEnv), - TSAuthKey: os.Getenv(TSAuthKeyEnv), - WorkDir: os.Getenv(WorkDirEnv), - } - - if os.Getenv(DebugEnv) == "true" { - c.Debug = true - } - - if err := validateRequired(c); err != nil { - return nil, err - } - - if err := validateTSReady(c); err != nil { - return nil, err - } - - return c, nil -} diff --git a/internal/hpagent/handler.go b/internal/hpagent/handler.go deleted file mode 100644 index c6d5327..0000000 --- a/internal/hpagent/handler.go +++ /dev/null @@ -1,55 +0,0 @@ -package hpagent - -import ( - "bufio" - "context" - "fmt" - - "os" - - "github.com/tale/headplane/internal/tsnet" - "github.com/tale/headplane/internal/util" -) - -// Starts listening for messages from stdin -func FollowMaster(agent *tsnet.TSAgent) { - log := util.GetLogger() - scanner := bufio.NewScanner(os.Stdin) - log.Info("Listening for messages from Headplane master on stdin") - - for scanner.Scan() { - line := scanner.Bytes() - directive := string(line) - - log.Debug("Received directive from master: %s", directive) - switch directive { - case "SHUTDOWN": - log.Debug("Received SHUTDOWN directive from master, shutting down agent") - agent.Shutdown() - return - - case "START": - log.Debug("Received START directive from master, starting agent") - // TODO: Start the agent here instead of in main - fmt.Println("READY " + agent.ID) - continue - - case "PING": - log.Debug("Received PING directive from master, responding with PONG") - fmt.Println("PONG " + agent.ID) - continue - - case "REFRESH": - log.Debug("Received REFRESH directive from master, refreshing status for all nodes") - err := agent.DispatchHostInfo(context.Background()) - if err != nil { - log.Error("Error refreshing host info: %s", err) - fmt.Println("ERR " + err.Error()) - } - } - } - - if err := scanner.Err(); err != nil { - log.Fatal("Error reading from stdin: %s", err) - } -} diff --git a/internal/preflight.go b/internal/preflight.go deleted file mode 100644 index 9634d8c..0000000 --- a/internal/preflight.go +++ /dev/null @@ -1,48 +0,0 @@ -package config - -import ( - "fmt" - "net/http" - "strings" -) - -// Checks to make sure all required environment variables are set -func validateRequired(config *Config) error { - if config.Hostname == "" { - return fmt.Errorf("%s is required", HostnameEnv) - } - - if config.TSControlURL == "" { - return fmt.Errorf("%s is required", TSControlURLEnv) - } - - if config.TSAuthKey == "" { - return fmt.Errorf("%s is required", TSAuthKeyEnv) - } - - if config.WorkDir == "" { - return fmt.Errorf("%s is required", WorkDirEnv) - } - - return nil -} - -// Pings the Tailscale control server to make sure it's up and running -func validateTSReady(config *Config) error { - testURL := config.TSControlURL - if strings.HasSuffix(testURL, "/") { - testURL = testURL[:len(testURL)-1] - } - - testURL = fmt.Sprintf("%s/health", testURL) - resp, err := http.Get(testURL) - if err != nil { - return fmt.Errorf("Failed to connect to TS control server: %s", err) - } - - if resp.StatusCode != 200 { - return fmt.Errorf("Failed to connect to TS control server: %s", resp.Status) - } - - return nil -} diff --git a/internal/util/logger.go b/internal/util/logger.go index 9754633..7e45240 100644 --- a/internal/util/logger.go +++ b/internal/util/logger.go @@ -4,19 +4,16 @@ import ( "encoding/json" "fmt" "os" - "strings" "sync" - "time" ) type LogLevel string const ( - LevelInfo LogLevel = "info" - LevelDebug LogLevel = "debug" - LevelError LogLevel = "error" - LevelFatal LogLevel = "fatal" - LevelMsg LogLevel = "msg" + LevelInfo LogLevel = "INFO" + LevelDebug LogLevel = "DEBUG" + LevelError LogLevel = "ERROR" + LevelFatal LogLevel = "FATAL" ) type LogMessage struct { @@ -61,19 +58,8 @@ func (l *Logger) SetDebug(enabled bool) { func (l *Logger) log(level LogLevel, format string, v ...any) { msg := fmt.Sprintf(format, v...) - timestamp := time.Now().Format(time.RFC3339) - // Manually construct compact JSON line for performance - line := `{"Level":"` + string(level) + - `","Time":"` + timestamp + - `","Message":"` + escapeString(msg) + `"}` + "\n" - - if level == LevelError || level == LevelFatal { - os.Stderr.WriteString(line) - } - - // Always write to stdout but also write to stderr for errors - os.Stdout.WriteString(line) + fmt.Printf("LOG %s %s\n", level, msg) if level == LevelFatal { os.Exit(1) } @@ -88,53 +74,3 @@ func (l *Logger) Debug(format string, v ...any) { func (l *Logger) Info(format string, v ...any) { l.log(LevelInfo, format, v...) } func (l *Logger) Error(format string, v ...any) { l.log(LevelError, format, v...) } func (l *Logger) Fatal(format string, v ...any) { l.log(LevelFatal, format, v...) } - -func (l *Logger) Msg(obj any) { - entry := l.pool.Get().(*LogMessage) - defer l.pool.Put(entry) - - entry.Level = LevelMsg - entry.Time = time.Now().Format(time.RFC3339) - entry.Message = obj - - // Because the encoder is tied to STDOUT we get a message - _ = l.encoder.Encode(entry) - - // Reset the entry for reuse - entry.Level = "" - entry.Time = "" - entry.Message = nil -} - -func escapeString(s string) string { - var b strings.Builder - b.Grow(len(s) + 16) // pre-grow to reduce reallocs - - for i := 0; i < len(s); i++ { - c := s[i] - switch c { - case '"': - b.WriteString(`\"`) - case '\\': - b.WriteString(`\\`) - case '\b': - b.WriteString(`\b`) - case '\f': - b.WriteString(`\f`) - case '\n': - b.WriteString(`\n`) - case '\r': - b.WriteString(`\r`) - case '\t': - b.WriteString(`\t`) - default: - if c < 0x20 { - // Control characters like 0x01, 0x07 (bell), etc. - fmt.Fprintf(&b, `\u%04x`, c) - } else { - b.WriteByte(c) - } - } - } - return b.String() -} diff --git a/nix/agent.nix b/nix/agent.nix index 40cd0c7..02063e0 100644 --- a/nix/agent.nix +++ b/nix/agent.nix @@ -3,7 +3,7 @@ buildGoModule { pname = "hp_agent"; version = (builtins.fromJSON (builtins.readFile ../package.json)).version; src = ../.; - vendorHash = "sha256-AyJx1os4auTmHvz+KLY+RGyysa3LWi8r2+QLxofPUH4="; + vendorHash = "sha256-cPE8cnfTdzi6hAmSXujKmfd5ezivc3sQ6DKOZubCpYI="; ldflags = ["-s" "-w"]; env.CGO_ENABLED = 0; } diff --git a/nix/ssh-wasm.nix b/nix/ssh-wasm.nix index 0ef5b32..d51e12c 100644 --- a/nix/ssh-wasm.nix +++ b/nix/ssh-wasm.nix @@ -14,7 +14,7 @@ in version = (builtins.fromJSON (builtins.readFile ../package.json)).version; src = ../.; subPackages = ["cmd/hp_ssh"]; - vendorHash = "sha256-AyJx1os4auTmHvz+KLY+RGyysa3LWi8r2+QLxofPUH4="; + vendorHash = "sha256-cPE8cnfTdzi6hAmSXujKmfd5ezivc3sQ6DKOZubCpYI="; env.CGO_ENABLED = 0; nativeBuildInputs = [go]; From 356abab90a65bd8f7a5d6f92451829c1e8c407f4 Mon Sep 17 00:00:00 2001 From: Aarnav Tale Date: Thu, 21 Aug 2025 11:16:27 -0400 Subject: [PATCH 80/97] feat: do insane type validation for the config --- app/routes/machines/machine.tsx | 15 ++++++- app/server/config/loader.ts | 70 ++++++++++++++++++--------------- 2 files changed, 52 insertions(+), 33 deletions(-) diff --git a/app/routes/machines/machine.tsx b/app/routes/machines/machine.tsx index 660e9f4..2a8dc7d 100644 --- a/app/routes/machines/machine.tsx +++ b/app/routes/machines/machine.tsx @@ -108,7 +108,10 @@ export default function Page() {
- {node.user.name || node.user.displayName || node.user.email || node.user.id} + {node.user.name || + node.user.displayName || + node.user.email || + node.user.id}
@@ -253,7 +256,15 @@ export default function Page() { variant="flat" >
- + { +export function interpolateEnvVars(str: string): string { + return str.replace(/\$\{([^}]+)\}/g, (_, varName) => { const value = env[varName]; if (value === undefined) { throw new ConfigError(`Environment variable "${varName}" not found`); @@ -52,9 +52,9 @@ export async function loadConfig({ loadEnv, path }: EnvOverrides) { if (!loadEnv) { log.debug('config', 'Environment variable overrides are disabled'); log.debug('config', 'This also disables the loading of a .env file'); - config = await loadSecretsFromFiles(config); + const moddedConfig = await loadSecretsFromFiles(config); log.debug('config', 'Loaded file-based secrets'); - return config; + return moddedConfig; } log.info('config', 'Loading a .env file (if available)'); @@ -67,10 +67,10 @@ export async function loadConfig({ loadEnv, path }: EnvOverrides) { ); } - config = await loadSecretsFromFiles(config); + const moddedConfig = await loadSecretsFromFiles(config); log.debug('config', 'Loaded file-based secrets'); - return config; + return moddedConfig; } /** @@ -78,13 +78,40 @@ export async function loadConfig({ loadEnv, path }: EnvOverrides) { * reads that file and assigns its contents to the corresponding key * without the suffix, then removes the "_path" property. */ -const SECRET_PATH_KEYS = new Set([ +const SECRET_PATH_KEYS = [ 'pre_authkey_path', 'client_secret_path', 'headscale_api_key_path', 'cookie_secret_path', -]); -async function loadSecretsFromFiles(obj: T): Promise { +] as const; + +// For fast set hashing lookups, but we still need the array for typings +const SECRET_PATH_KEY_SET = new Set(SECRET_PATH_KEYS); + +type SecretPathKey = (typeof SECRET_PATH_KEYS)[number]; +type StripPath = S extends `${infer T}_path` ? T : never; +type KeysToPromote = Extract; +type MappedKeys = StripPath>; + +type NonNullablized = Omit | MappedKeys> & { + [K in MappedKeys]-?: string; +}; + +type NestedNonNullablized = T extends readonly (infer U)[] + ? readonly NestedNonNullablized[] + : T extends (infer U)[] + ? NestedNonNullablized[] + : T extends object + ? { + [K in keyof NonNullablized]: NestedNonNullablized< + NonNullablized[K] + >; + } + : T; + +async function loadSecretsFromFiles( + obj: T, +): Promise> { // Work with a Record so we can mutate/delete properties const record = obj as Record; @@ -97,7 +124,7 @@ async function loadSecretsFromFiles(obj: T): Promise { continue; } - if (SECRET_PATH_KEYS.has(key) && typeof val === 'string') { + if (SECRET_PATH_KEY_SET.has(key) && typeof val === 'string') { try { const path = interpolateEnvVars(val); const content = await readFile(path, 'utf8'); @@ -114,26 +141,7 @@ async function loadSecretsFromFiles(obj: T): Promise { } // Cast back to the original T so callers keep their precise type - return record as T; -} - -export async function hp_loadConfig() { - // // OIDC Related Checks - // if (config.oidc) { - // if (!config.oidc.client_secret && !config.oidc.client_secret_path) { - // log.error('CFGX', 'OIDC configuration is missing a secret, disabling'); - // log.error( - // 'CFGX', - // 'Please specify either `oidc.client_secret` or `oidc.client_secret_path`', - // ); - // } - // if (config.oidc?.strict_validation) { - // const result = await testOidc(config.oidc); - // if (!result) { - // log.error('CFGX', 'OIDC configuration failed validation, disabling'); - // } - // } - // } + return record as NestedNonNullablized; } async function validateConfigPath(path: string) { From ff3bdc1898aed2944b0a90d93c2c5a7396433a27 Mon Sep 17 00:00:00 2001 From: Aarnav Tale Date: Thu, 21 Aug 2025 11:33:45 -0400 Subject: [PATCH 81/97] feat: remove octicons icon pack --- app/components/Code.tsx | 2 +- app/components/Error.tsx | 17 ++++---- app/routes/dns/dialogs/add-ns.tsx | 18 ++++----- .../machines/components/machine-row.tsx | 35 ++++++++--------- app/routes/machines/overview.tsx | 4 +- app/routes/settings/components/agent.tsx | 33 ---------------- .../settings/components/agent/manage.tsx | 39 ------------------- app/routes/settings/overview.tsx | 10 ++--- 8 files changed, 44 insertions(+), 114 deletions(-) delete mode 100644 app/routes/settings/components/agent.tsx delete mode 100644 app/routes/settings/components/agent/manage.tsx diff --git a/app/components/Code.tsx b/app/components/Code.tsx index f973554..17e8676 100644 --- a/app/components/Code.tsx +++ b/app/components/Code.tsx @@ -21,7 +21,6 @@ export default function Code({ isCopyable, children, className }: CodeProps) { {children} {isCopyable && (
@@ -194,30 +193,30 @@ export function mapTagsToComponents(node: PopulatedNode, uiTags: string[]) { switch (tag) { case 'exit-approved': case 'exit-waiting': - return ; + return ; case 'subnet-approved': case 'subnet-waiting': - return ; + return ; case 'expired': case 'no-expiry': return ( ); case 'tailscale-ssh': - return ; + return ; case 'headplane-agent': - return ; + return ; default: - return; + return null; } }); } diff --git a/app/routes/machines/overview.tsx b/app/routes/machines/overview.tsx index b03a45d..d692a68 100644 --- a/app/routes/machines/overview.tsx +++ b/app/routes/machines/overview.tsx @@ -1,4 +1,4 @@ -import { InfoIcon } from '@primer/octicons-react'; +import { Info } from 'lucide-react'; import type { ActionFunctionArgs, LoaderFunctionArgs } from 'react-router'; import { useLoaderData } from 'react-router'; import Code from '~/components/Code'; @@ -110,7 +110,7 @@ export default function Page() {

Addresses

{data.magic ? ( - + Since MagicDNS is enabled, you can access devices based on their name and also at{' '} diff --git a/app/routes/settings/components/agent.tsx b/app/routes/settings/components/agent.tsx deleted file mode 100644 index 70fc7df..0000000 --- a/app/routes/settings/components/agent.tsx +++ /dev/null @@ -1,33 +0,0 @@ -import { ArrowRightIcon } from '@primer/octicons-react'; -import { Link as RemixLink } from 'react-router'; -import Button from '~/components/Button'; -import Link from '~/components/Link'; -import cn from '~/utils/cn'; - -export default function AgentSection() { - return ( - <> -
-

Local Agent

-

- Headplane provides a local agent that can be installed on a server to - provide additional features including viewing device information and - SSH access via the web interface (soon). To learn more about the agent - visit the{' '} - - Headplane documentation - -

-
- -
- Manage Agent - -
-
- - ); -} diff --git a/app/routes/settings/components/agent/manage.tsx b/app/routes/settings/components/agent/manage.tsx deleted file mode 100644 index 3414f04..0000000 --- a/app/routes/settings/components/agent/manage.tsx +++ /dev/null @@ -1,39 +0,0 @@ -import Card from '~/components/Card'; -import StatusCircle from '~/components/StatusCircle'; -import type { HostInfo } from '~/types'; -import * as hinfo from '~/utils/host-info'; - -export type Props = { - reachable: boolean; - hostInfo: HostInfo; -}; - -export default function AgentManagement({ reachable, hostInfo }: Props) { - console.log('hostInfo:', hostInfo); - return ( -
-

Local Agent Configuration

-

- A local agent has already been configured for this Headplane instance. - You can manage the agent settings here. -

- -
- -
-

- {hostInfo.Hostname ?? 'Unknown'} -

-

- {hinfo.getTSVersion(hostInfo)} - - {hinfo.getOSInfo(hostInfo)} - -

-
-
- {JSON.stringify(hostInfo)} -
-
- ); -} diff --git a/app/routes/settings/overview.tsx b/app/routes/settings/overview.tsx index 3580627..5c9f135 100644 --- a/app/routes/settings/overview.tsx +++ b/app/routes/settings/overview.tsx @@ -1,4 +1,4 @@ -import { ArrowRightIcon } from '@primer/octicons-react'; +import { ArrowRight } from 'lucide-react'; import { LoaderFunctionArgs, Link as RemixLink, @@ -34,8 +34,8 @@ export default function Page() { add devices to your Tailnet. To learn more about using pre-authentication keys, visit the{' '} Tailscale documentation @@ -44,7 +44,7 @@ export default function Page() {
Manage Auth Keys - +
{config && oidc ? ( @@ -60,8 +60,8 @@ export default function Page() { groups and Headplane will also respect these settings when authenticating.{' '} Learn More @@ -70,7 +70,7 @@ export default function Page() {
Manage Restrictions - +
From 6c5656334d7a7a0fdeaf37259a56f1f6d939eef3 Mon Sep 17 00:00:00 2001 From: Aarnav Tale Date: Thu, 21 Aug 2025 11:39:51 -0400 Subject: [PATCH 82/97] chore: update and prune deps --- nix/package.nix | 2 +- package.json | 56 +- pnpm-lock.yaml | 2701 ++++++++++++++++++++--------------------------- 3 files changed, 1199 insertions(+), 1560 deletions(-) diff --git a/nix/package.nix b/nix/package.nix index 73bfa2e..657384e 100644 --- a/nix/package.nix +++ b/nix/package.nix @@ -28,7 +28,7 @@ in pnpmDeps = pnpm_10.fetchDeps { inherit (finalAttrs) pname version src; - hash = "sha256-xjjkqbgjYaAGYAmlTFE+Lq3Hp6myZKaW3br0YTDNhQA="; + hash = "sha256-Xi4IZEFFHx2Wp7r1qleGj8BHZOKLWsSfFT4UKbF+9Y8="; fetcherVersion = 1; }; diff --git a/package.json b/package.json index 731f05c..db2b8d9 100644 --- a/package.json +++ b/package.json @@ -20,20 +20,19 @@ "@dnd-kit/modifiers": "^7.0.0", "@dnd-kit/sortable": "^8.0.0", "@dnd-kit/utilities": "^3.2.2", - "@faker-js/faker": "^9.8.0", - "@fontsource-variable/inter": "^5.2.5", + "@faker-js/faker": "9.9.0", + "@fontsource-variable/inter": "^5.2.6", "@kubernetes/client-node": "^1.3.0", - "@libsql/client": "^0.15.9", - "@primer/octicons-react": "^19.15.2", - "@react-aria/toast": "3.0.3", + "@libsql/client": "0.15.12", + "@react-aria/toast": "3.0.6", "@react-router/node": "^7.8.1", - "@react-stately/toast": "3.1.0", + "@react-stately/toast": "3.1.2", "@shopify/lang-jsonc": "^1.0.1", "@types/react": "^19.1.10", "@types/react-dom": "^19.1.7", - "@uiw/codemirror-theme-github": "^4.23.12", - "@uiw/codemirror-theme-xcode": "^4.23.12", - "@uiw/react-codemirror": "^4.23.12", + "@uiw/codemirror-theme-github": "4.25.1", + "@uiw/codemirror-theme-xcode": "4.25.1", + "@uiw/react-codemirror": "4.25.1", "@xterm/addon-clipboard": "^0.1.0", "@xterm/addon-fit": "^0.10.0", "@xterm/addon-unicode11": "^0.8.0", @@ -42,47 +41,46 @@ "@xterm/xterm": "^5.5.0", "arktype": "^2.1.20", "clsx": "^2.1.1", - "dotenv": "^16.5.0", - "drizzle-orm": "^0.44.2", - "isbot": "^5.1.28", - "jose": "6.0.12", - "lucide-react": "^0.511.0", + "dotenv": "17.2.1", + "drizzle-orm": "0.44.4", + "isbot": "5.1.30", + "jose": "6.0.13", + "lucide-react": "^0.540.0", "mime": "^4.0.7", - "openid-client": "^6.5.0", + "openid-client": "6.6.4", "react": "19.1.1", - "react-aria": "^3.40.0", - "react-codemirror-merge": "^4.23.12", + "react-aria": "3.42.0", + "react-codemirror-merge": "4.25.1", "react-dom": "19.1.1", "react-error-boundary": "^6.0.0", "react-icons": "^5.5.0", "react-router": "^7.8.1", "react-router-hono-server": "2.21.0", - "react-stately": "^3.38.0", + "react-stately": "3.40.0", "remix-utils": "^8.8.0", - "tailwind-merge": "^3.3.0", + "tailwind-merge": "3.3.1", "ulidx": "2.4.1", - "undici": "^7.10.0", + "undici": "7.14.0", "usehooks-ts": "^3.1.1", - "yaml": "^2.8.0" + "yaml": "2.8.1" }, "devDependencies": { "@biomejs/biome": "^2.2.0", "@react-router/dev": "^7.8.1", "@tailwindcss/vite": "^4.1.12", "@types/node": "^24.3.0", - "@types/websocket": "^1.0.10", - "@typescript/native-preview": "7.0.0-dev.20250818.1", - "drizzle-kit": "^0.31.1", + "@typescript/native-preview": "7.0.0-dev.20250821.1", + "drizzle-kit": "^0.31.4", "js-yaml": "^4.1.0", - "lefthook": "^1.11.13", - "postcss": "^8.5.4", + "lefthook": "^1.12.3", + "postcss": "^8.5.6", "react-router-dom": "^7.8.1", - "react-scan": "^0.3.4", - "tailwindcss": "^4.1.8", + "react-scan": "^0.4.3", + "tailwindcss": "^4.1.12", "tailwindcss-animate": "^1.0.7", "tailwindcss-react-aria-components": "^2.0.0", "typescript": "^5.9.2", - "vite": "npm:rolldown-vite@latest", + "vite": "npm:rolldown-vite@7.1.4", "vite-tsconfig-paths": "^5.1.4", "vitepress": "next", "vitest": "^3.1.3" diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index c0188b7..f003445 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -26,29 +26,26 @@ importers: specifier: ^3.2.2 version: 3.2.2(react@19.1.1) '@faker-js/faker': - specifier: ^9.8.0 - version: 9.8.0 + specifier: 9.9.0 + version: 9.9.0 '@fontsource-variable/inter': - specifier: ^5.2.5 - version: 5.2.5 + specifier: ^5.2.6 + version: 5.2.6 '@kubernetes/client-node': specifier: ^1.3.0 version: 1.3.0(bufferutil@4.0.9)(utf-8-validate@5.0.10) '@libsql/client': - specifier: ^0.15.9 - version: 0.15.9(bufferutil@4.0.9)(utf-8-validate@5.0.10) - '@primer/octicons-react': - specifier: ^19.15.2 - version: 19.15.2(react@19.1.1) + specifier: 0.15.12 + version: 0.15.12(bufferutil@4.0.9)(utf-8-validate@5.0.10) '@react-aria/toast': - specifier: 3.0.3 - version: 3.0.3(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + specifier: 3.0.6 + version: 3.0.6(react-dom@19.1.1(react@19.1.1))(react@19.1.1) '@react-router/node': specifier: ^7.8.1 version: 7.8.1(react-router@7.8.1(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(typescript@5.9.2) '@react-stately/toast': - specifier: 3.1.0 - version: 3.1.0(react@19.1.1) + specifier: 3.1.2 + version: 3.1.2(react@19.1.1) '@shopify/lang-jsonc': specifier: ^1.0.1 version: 1.0.1 @@ -59,14 +56,14 @@ importers: specifier: ^19.1.7 version: 19.1.7(@types/react@19.1.10) '@uiw/codemirror-theme-github': - specifier: ^4.23.12 - version: 4.23.12(@codemirror/language@6.11.0)(@codemirror/state@6.5.2)(@codemirror/view@6.36.8) + specifier: 4.25.1 + version: 4.25.1(@codemirror/language@6.11.0)(@codemirror/state@6.5.2)(@codemirror/view@6.36.8) '@uiw/codemirror-theme-xcode': - specifier: ^4.23.12 - version: 4.23.12(@codemirror/language@6.11.0)(@codemirror/state@6.5.2)(@codemirror/view@6.36.8) + specifier: 4.25.1 + version: 4.25.1(@codemirror/language@6.11.0)(@codemirror/state@6.5.2)(@codemirror/view@6.36.8) '@uiw/react-codemirror': - specifier: ^4.23.12 - version: 4.23.12(@babel/runtime@7.27.3)(@codemirror/autocomplete@6.18.2(@codemirror/language@6.11.0)(@codemirror/state@6.5.2)(@codemirror/view@6.36.8)(@lezer/common@1.2.3))(@codemirror/language@6.11.0)(@codemirror/lint@6.8.2)(@codemirror/search@6.5.7)(@codemirror/state@6.5.2)(@codemirror/theme-one-dark@6.1.2)(@codemirror/view@6.36.8)(codemirror@6.0.1(@lezer/common@1.2.3))(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + specifier: 4.25.1 + version: 4.25.1(@babel/runtime@7.27.3)(@codemirror/autocomplete@6.18.2(@codemirror/language@6.11.0)(@codemirror/state@6.5.2)(@codemirror/view@6.36.8)(@lezer/common@1.2.3))(@codemirror/language@6.11.0)(@codemirror/lint@6.8.2)(@codemirror/search@6.5.7)(@codemirror/state@6.5.2)(@codemirror/theme-one-dark@6.1.2)(@codemirror/view@6.36.8)(codemirror@6.0.1(@lezer/common@1.2.3))(react-dom@19.1.1(react@19.1.1))(react@19.1.1) '@xterm/addon-clipboard': specifier: ^0.1.0 version: 0.1.0(@xterm/xterm@5.5.0) @@ -92,35 +89,35 @@ importers: specifier: ^2.1.1 version: 2.1.1 dotenv: - specifier: ^16.5.0 - version: 16.5.0 + specifier: 17.2.1 + version: 17.2.1 drizzle-orm: - specifier: ^0.44.2 - version: 0.44.2(@libsql/client@0.15.9(bufferutil@4.0.9)(utf-8-validate@5.0.10))(@types/better-sqlite3@7.6.13)(better-sqlite3@11.10.0) + specifier: 0.44.4 + version: 0.44.4(@libsql/client@0.15.12(bufferutil@4.0.9)(utf-8-validate@5.0.10))(@types/better-sqlite3@7.6.13)(better-sqlite3@11.10.0) isbot: - specifier: ^5.1.28 - version: 5.1.28 + specifier: 5.1.30 + version: 5.1.30 jose: - specifier: 6.0.12 - version: 6.0.12 + specifier: 6.0.13 + version: 6.0.13 lucide-react: - specifier: ^0.511.0 - version: 0.511.0(react@19.1.1) + specifier: ^0.540.0 + version: 0.540.0(react@19.1.1) mime: specifier: ^4.0.7 version: 4.0.7 openid-client: - specifier: ^6.5.0 - version: 6.5.0 + specifier: 6.6.4 + version: 6.6.4 react: specifier: 19.1.1 version: 19.1.1 react-aria: - specifier: ^3.40.0 - version: 3.40.0(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + specifier: 3.42.0 + version: 3.42.0(react-dom@19.1.1(react@19.1.1))(react@19.1.1) react-codemirror-merge: - specifier: ^4.23.12 - version: 4.23.12(@babel/runtime@7.27.3)(@codemirror/autocomplete@6.18.2(@codemirror/language@6.11.0)(@codemirror/state@6.5.2)(@codemirror/view@6.36.8)(@lezer/common@1.2.3))(@codemirror/language@6.11.0)(@codemirror/lint@6.8.2)(@codemirror/search@6.5.7)(@codemirror/state@6.5.2)(@codemirror/theme-one-dark@6.1.2)(@codemirror/view@6.36.8)(codemirror@6.0.1(@lezer/common@1.2.3))(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + specifier: 4.25.1 + version: 4.25.1(@babel/runtime@7.27.3)(@codemirror/autocomplete@6.18.2(@codemirror/language@6.11.0)(@codemirror/state@6.5.2)(@codemirror/view@6.36.8)(@lezer/common@1.2.3))(@codemirror/language@6.11.0)(@codemirror/lint@6.8.2)(@codemirror/search@6.5.7)(@codemirror/state@6.5.2)(@codemirror/theme-one-dark@6.1.2)(@codemirror/view@6.36.8)(codemirror@6.0.1(@lezer/common@1.2.3))(react-dom@19.1.1(react@19.1.1))(react@19.1.1) react-dom: specifier: 19.1.1 version: 19.1.1(react@19.1.1) @@ -135,89 +132,86 @@ importers: version: 7.8.1(react-dom@19.1.1(react@19.1.1))(react@19.1.1) react-router-hono-server: specifier: 2.21.0 - version: 2.21.0(patch_hash=b68723a36649e2c3bd9e9edcfff3a8bc0fdb48e1cf6d64eba37c04f7bfbb1417)(@hono/node-server@1.19.0(hono@4.7.10))(@react-router/dev@7.8.1(@types/node@24.3.0)(jiti@2.5.1)(lightningcss@1.30.1)(react-dom@19.1.1(react@19.1.1))(react-router@7.8.1(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(react@19.1.1)(rolldown-vite@7.1.3(@types/node@24.3.0)(esbuild@0.25.5)(jiti@2.5.1)(terser@5.39.0)(tsx@4.19.4)(yaml@2.8.0))(terser@5.39.0)(tsx@4.19.4)(typescript@5.9.2)(yaml@2.8.0))(@types/react@19.1.10)(bufferutil@4.0.9)(react-router@7.8.1(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(rolldown-vite@7.1.3(@types/node@24.3.0)(esbuild@0.25.5)(jiti@2.5.1)(terser@5.39.0)(tsx@4.19.4)(yaml@2.8.0))(utf-8-validate@5.0.10) + version: 2.21.0(patch_hash=b68723a36649e2c3bd9e9edcfff3a8bc0fdb48e1cf6d64eba37c04f7bfbb1417)(@hono/node-server@1.19.0(hono@4.7.10))(@react-router/dev@7.8.1(@types/node@24.3.0)(jiti@2.5.1)(lightningcss@1.30.1)(react-dom@19.1.1(react@19.1.1))(react-router@7.8.1(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(react@19.1.1)(rolldown-vite@7.1.4(@types/node@24.3.0)(esbuild@0.25.9)(jiti@2.5.1)(terser@5.39.0)(tsx@4.20.4)(yaml@2.8.1))(terser@5.39.0)(tsx@4.20.4)(typescript@5.9.2)(yaml@2.8.1))(@types/react@19.1.10)(bufferutil@4.0.9)(react-router@7.8.1(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(rolldown-vite@7.1.4(@types/node@24.3.0)(esbuild@0.25.9)(jiti@2.5.1)(terser@5.39.0)(tsx@4.20.4)(yaml@2.8.1))(utf-8-validate@5.0.10) react-stately: - specifier: ^3.38.0 - version: 3.38.0(react@19.1.1) + specifier: 3.40.0 + version: 3.40.0(react@19.1.1) remix-utils: specifier: ^8.8.0 - version: 8.8.0(react-router@7.8.1(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(react@19.1.1)(zod@3.25.33) + version: 8.8.0(react-router@7.8.1(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(react@19.1.1) tailwind-merge: - specifier: ^3.3.0 - version: 3.3.0 + specifier: 3.3.1 + version: 3.3.1 ulidx: specifier: 2.4.1 version: 2.4.1 undici: - specifier: ^7.10.0 - version: 7.10.0 + specifier: 7.14.0 + version: 7.14.0 usehooks-ts: specifier: ^3.1.1 version: 3.1.1(react@19.1.1) yaml: - specifier: ^2.8.0 - version: 2.8.0 + specifier: 2.8.1 + version: 2.8.1 devDependencies: '@biomejs/biome': specifier: ^2.2.0 version: 2.2.0 '@react-router/dev': specifier: ^7.8.1 - version: 7.8.1(@types/node@24.3.0)(jiti@2.5.1)(lightningcss@1.30.1)(react-dom@19.1.1(react@19.1.1))(react-router@7.8.1(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(react@19.1.1)(rolldown-vite@7.1.3(@types/node@24.3.0)(esbuild@0.25.5)(jiti@2.5.1)(terser@5.39.0)(tsx@4.19.4)(yaml@2.8.0))(terser@5.39.0)(tsx@4.19.4)(typescript@5.9.2)(yaml@2.8.0) + version: 7.8.1(@types/node@24.3.0)(jiti@2.5.1)(lightningcss@1.30.1)(react-dom@19.1.1(react@19.1.1))(react-router@7.8.1(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(react@19.1.1)(rolldown-vite@7.1.4(@types/node@24.3.0)(esbuild@0.25.9)(jiti@2.5.1)(terser@5.39.0)(tsx@4.20.4)(yaml@2.8.1))(terser@5.39.0)(tsx@4.20.4)(typescript@5.9.2)(yaml@2.8.1) '@tailwindcss/vite': specifier: ^4.1.12 - version: 4.1.12(rolldown-vite@7.1.3(@types/node@24.3.0)(esbuild@0.25.5)(jiti@2.5.1)(terser@5.39.0)(tsx@4.19.4)(yaml@2.8.0)) + version: 4.1.12(rolldown-vite@7.1.4(@types/node@24.3.0)(esbuild@0.25.9)(jiti@2.5.1)(terser@5.39.0)(tsx@4.20.4)(yaml@2.8.1)) '@types/node': specifier: ^24.3.0 version: 24.3.0 - '@types/websocket': - specifier: ^1.0.10 - version: 1.0.10 '@typescript/native-preview': - specifier: 7.0.0-dev.20250818.1 - version: 7.0.0-dev.20250818.1 + specifier: 7.0.0-dev.20250821.1 + version: 7.0.0-dev.20250821.1 drizzle-kit: - specifier: ^0.31.1 - version: 0.31.1 + specifier: ^0.31.4 + version: 0.31.4 js-yaml: specifier: ^4.1.0 version: 4.1.0 lefthook: - specifier: ^1.11.13 - version: 1.11.13 + specifier: ^1.12.3 + version: 1.12.3 postcss: - specifier: ^8.5.4 - version: 8.5.4 + specifier: ^8.5.6 + version: 8.5.6 react-router-dom: specifier: ^7.8.1 version: 7.8.1(react-dom@19.1.1(react@19.1.1))(react@19.1.1) react-scan: - specifier: ^0.3.4 - version: 0.3.4(@types/react@19.1.10)(react-dom@19.1.1(react@19.1.1))(react-router-dom@7.8.1(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(react-router@7.8.1(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(react@19.1.1)(rollup@4.46.3) + specifier: ^0.4.3 + version: 0.4.3(@types/react@19.1.10)(react-dom@19.1.1(react@19.1.1))(react-router-dom@7.8.1(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(react-router@7.8.1(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(react@19.1.1)(rollup@4.46.3) tailwindcss: - specifier: ^4.1.8 - version: 4.1.8 + specifier: ^4.1.12 + version: 4.1.12 tailwindcss-animate: specifier: ^1.0.7 - version: 1.0.7(tailwindcss@4.1.8) + version: 1.0.7(tailwindcss@4.1.12) tailwindcss-react-aria-components: specifier: ^2.0.0 - version: 2.0.0(tailwindcss@4.1.8) + version: 2.0.0(tailwindcss@4.1.12) typescript: specifier: ^5.9.2 version: 5.9.2 vite: - specifier: npm:rolldown-vite@latest - version: rolldown-vite@7.1.3(@types/node@24.3.0)(esbuild@0.25.5)(jiti@2.5.1)(terser@5.39.0)(tsx@4.19.4)(yaml@2.8.0) + specifier: npm:rolldown-vite@7.1.4 + version: rolldown-vite@7.1.4(@types/node@24.3.0)(esbuild@0.25.9)(jiti@2.5.1)(terser@5.39.0)(tsx@4.20.4)(yaml@2.8.1) vite-tsconfig-paths: specifier: ^5.1.4 - version: 5.1.4(rolldown-vite@7.1.3(@types/node@24.3.0)(esbuild@0.25.5)(jiti@2.5.1)(terser@5.39.0)(tsx@4.19.4)(yaml@2.8.0))(typescript@5.9.2) + version: 5.1.4(rolldown-vite@7.1.4(@types/node@24.3.0)(esbuild@0.25.9)(jiti@2.5.1)(terser@5.39.0)(tsx@4.20.4)(yaml@2.8.1))(typescript@5.9.2) vitepress: specifier: next - version: 2.0.0-alpha.11(@types/node@24.3.0)(jiti@2.5.1)(lightningcss@1.30.1)(postcss@8.5.4)(terser@5.39.0)(tsx@4.19.4)(typescript@5.9.2)(yaml@2.8.0) + version: 2.0.0-alpha.11(@types/node@24.3.0)(jiti@2.5.1)(lightningcss@1.30.1)(postcss@8.5.6)(terser@5.39.0)(tsx@4.20.4)(typescript@5.9.2)(yaml@2.8.1) vitest: specifier: ^3.1.3 - version: 3.2.4(@types/node@24.3.0)(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.39.0)(tsx@4.19.4)(yaml@2.8.0) + version: 3.2.4(@types/node@24.3.0)(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.39.0)(tsx@4.20.4)(yaml@2.8.1) packages: @@ -239,10 +233,6 @@ packages: resolution: {integrity: sha512-V42wFfx1ymFte+ecf6iXghnnP8kWTO+ZLXIyZq+1LAXHHvTZdVxicn4yiVYdYMGaCO3tmqub11AorKkv+iodqw==} engines: {node: '>=6.9.0'} - '@babel/core@7.27.3': - resolution: {integrity: sha512-hyrN8ivxfvJ4i0fIJuV4EOlV0WDMz5Ui4StRTgVaAvWeiRCilXgwVvxJKtFQ3TKtHgJscB2YiXKGNJuVwhQMtA==} - engines: {node: '>=6.9.0'} - '@babel/core@7.28.3': resolution: {integrity: sha512-yDBHV9kQNcr2/sUr9jghVyz9C3Y5G2zUM2H2lo+9mKv4sFgbA8s8Z9t8D1jiTkGoO/NoIfKMyKWr4s6CN23ZwQ==} engines: {node: '>=6.9.0'} @@ -281,12 +271,6 @@ packages: resolution: {integrity: sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==} engines: {node: '>=6.9.0'} - '@babel/helper-module-transforms@7.27.3': - resolution: {integrity: sha512-dSOvYwvyLsWBeIRyOeHXp5vPj5l1I011r52FM1+r1jCERv+aFXYk4whgQccYEGYxK2H3ZAIA8nuPkQ0HaUo3qg==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0 - '@babel/helper-module-transforms@7.28.3': resolution: {integrity: sha512-gytXUbs8k2sXS9PnQptz5o0QnpLL51SwASIORY6XaBKF88nsOT0Zw9szLqlSGQDP/4TljBAD5y98p2U1fqkdsw==} engines: {node: '>=6.9.0'} @@ -323,10 +307,6 @@ packages: resolution: {integrity: sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==} engines: {node: '>=6.9.0'} - '@babel/helpers@7.27.3': - resolution: {integrity: sha512-h/eKy9agOya1IGuLaZ9tEUgz+uIRXcbtOhRtUyyMf8JFmn1iT13vnl/IGVWSkdOCG/pC57U4S1jnAabAavTMwg==} - engines: {node: '>=6.9.0'} - '@babel/helpers@7.28.3': resolution: {integrity: sha512-PTNtvUQihsAsDHMOP5pfobP8C6CM4JWXmP8DrEIt46c3r2bf87Ua1zoqevsMo9g+tWDwgWrFP5EIxuBx5RudAw==} engines: {node: '>=6.9.0'} @@ -548,12 +528,6 @@ packages: resolution: {integrity: sha512-FxEMIkJKnodyA1OaCUoEvbYRkoZlLZ4d/eXFu9Fh8CbBBgP5EmZxrfTRyN0qpXZ4vOvqnE5YdRdcrmUUXuU+dA==} deprecated: 'Merged into tsx: https://tsx.is' - '@esbuild/aix-ppc64@0.25.5': - resolution: {integrity: sha512-9o3TMmpmftaCMepOdA5k/yDw8SfInyzWWTjYTFCX3kPSDJMROQTb8jg+h9Cnwnmm1vOzvxN7gIfB5V2ewpjtGA==} - engines: {node: '>=18'} - cpu: [ppc64] - os: [aix] - '@esbuild/aix-ppc64@0.25.9': resolution: {integrity: sha512-OaGtL73Jck6pBKjNIe24BnFE6agGl+6KxDtTfHhy1HmhthfKouEcOhqpSL64K4/0WCtbKFLOdzD/44cJ4k9opA==} engines: {node: '>=18'} @@ -566,12 +540,6 @@ packages: cpu: [arm64] os: [android] - '@esbuild/android-arm64@0.25.5': - resolution: {integrity: sha512-VGzGhj4lJO+TVGV1v8ntCZWJktV7SGCs3Pn1GRWI1SBFtRALoomm8k5E9Pmwg3HOAal2VDc2F9+PM/rEY6oIDg==} - engines: {node: '>=18'} - cpu: [arm64] - os: [android] - '@esbuild/android-arm64@0.25.9': resolution: {integrity: sha512-IDrddSmpSv51ftWslJMvl3Q2ZT98fUSL2/rlUXuVqRXHCs5EUF1/f+jbjF5+NG9UffUDMCiTyh8iec7u8RlTLg==} engines: {node: '>=18'} @@ -584,12 +552,6 @@ packages: cpu: [arm] os: [android] - '@esbuild/android-arm@0.25.5': - resolution: {integrity: sha512-AdJKSPeEHgi7/ZhuIPtcQKr5RQdo6OO2IL87JkianiMYMPbCtot9fxPbrMiBADOWWm3T2si9stAiVsGbTQFkbA==} - engines: {node: '>=18'} - cpu: [arm] - os: [android] - '@esbuild/android-arm@0.25.9': resolution: {integrity: sha512-5WNI1DaMtxQ7t7B6xa572XMXpHAaI/9Hnhk8lcxF4zVN4xstUgTlvuGDorBguKEnZO70qwEcLpfifMLoxiPqHQ==} engines: {node: '>=18'} @@ -602,12 +564,6 @@ packages: cpu: [x64] os: [android] - '@esbuild/android-x64@0.25.5': - resolution: {integrity: sha512-D2GyJT1kjvO//drbRT3Hib9XPwQeWd9vZoBJn+bu/lVsOZ13cqNdDeqIF/xQ5/VmWvMduP6AmXvylO/PIc2isw==} - engines: {node: '>=18'} - cpu: [x64] - os: [android] - '@esbuild/android-x64@0.25.9': resolution: {integrity: sha512-I853iMZ1hWZdNllhVZKm34f4wErd4lMyeV7BLzEExGEIZYsOzqDWDf+y082izYUE8gtJnYHdeDpN/6tUdwvfiw==} engines: {node: '>=18'} @@ -620,12 +576,6 @@ packages: cpu: [arm64] os: [darwin] - '@esbuild/darwin-arm64@0.25.5': - resolution: {integrity: sha512-GtaBgammVvdF7aPIgH2jxMDdivezgFu6iKpmT+48+F8Hhg5J/sfnDieg0aeG/jfSvkYQU2/pceFPDKlqZzwnfQ==} - engines: {node: '>=18'} - cpu: [arm64] - os: [darwin] - '@esbuild/darwin-arm64@0.25.9': resolution: {integrity: sha512-XIpIDMAjOELi/9PB30vEbVMs3GV1v2zkkPnuyRRURbhqjyzIINwj+nbQATh4H9GxUgH1kFsEyQMxwiLFKUS6Rg==} engines: {node: '>=18'} @@ -638,12 +588,6 @@ packages: cpu: [x64] os: [darwin] - '@esbuild/darwin-x64@0.25.5': - resolution: {integrity: sha512-1iT4FVL0dJ76/q1wd7XDsXrSW+oLoquptvh4CLR4kITDtqi2e/xwXwdCVH8hVHU43wgJdsq7Gxuzcs6Iq/7bxQ==} - engines: {node: '>=18'} - cpu: [x64] - os: [darwin] - '@esbuild/darwin-x64@0.25.9': resolution: {integrity: sha512-jhHfBzjYTA1IQu8VyrjCX4ApJDnH+ez+IYVEoJHeqJm9VhG9Dh2BYaJritkYK3vMaXrf7Ogr/0MQ8/MeIefsPQ==} engines: {node: '>=18'} @@ -656,12 +600,6 @@ packages: cpu: [arm64] os: [freebsd] - '@esbuild/freebsd-arm64@0.25.5': - resolution: {integrity: sha512-nk4tGP3JThz4La38Uy/gzyXtpkPW8zSAmoUhK9xKKXdBCzKODMc2adkB2+8om9BDYugz+uGV7sLmpTYzvmz6Sw==} - engines: {node: '>=18'} - cpu: [arm64] - os: [freebsd] - '@esbuild/freebsd-arm64@0.25.9': resolution: {integrity: sha512-z93DmbnY6fX9+KdD4Ue/H6sYs+bhFQJNCPZsi4XWJoYblUqT06MQUdBCpcSfuiN72AbqeBFu5LVQTjfXDE2A6Q==} engines: {node: '>=18'} @@ -674,12 +612,6 @@ packages: cpu: [x64] os: [freebsd] - '@esbuild/freebsd-x64@0.25.5': - resolution: {integrity: sha512-PrikaNjiXdR2laW6OIjlbeuCPrPaAl0IwPIaRv+SMV8CiM8i2LqVUHFC1+8eORgWyY7yhQY+2U2fA55mBzReaw==} - engines: {node: '>=18'} - cpu: [x64] - os: [freebsd] - '@esbuild/freebsd-x64@0.25.9': resolution: {integrity: sha512-mrKX6H/vOyo5v71YfXWJxLVxgy1kyt1MQaD8wZJgJfG4gq4DpQGpgTB74e5yBeQdyMTbgxp0YtNj7NuHN0PoZg==} engines: {node: '>=18'} @@ -692,12 +624,6 @@ packages: cpu: [arm64] os: [linux] - '@esbuild/linux-arm64@0.25.5': - resolution: {integrity: sha512-Z9kfb1v6ZlGbWj8EJk9T6czVEjjq2ntSYLY2cw6pAZl4oKtfgQuS4HOq41M/BcoLPzrUbNd+R4BXFyH//nHxVg==} - engines: {node: '>=18'} - cpu: [arm64] - os: [linux] - '@esbuild/linux-arm64@0.25.9': resolution: {integrity: sha512-BlB7bIcLT3G26urh5Dmse7fiLmLXnRlopw4s8DalgZ8ef79Jj4aUcYbk90g8iCa2467HX8SAIidbL7gsqXHdRw==} engines: {node: '>=18'} @@ -710,12 +636,6 @@ packages: cpu: [arm] os: [linux] - '@esbuild/linux-arm@0.25.5': - resolution: {integrity: sha512-cPzojwW2okgh7ZlRpcBEtsX7WBuqbLrNXqLU89GxWbNt6uIg78ET82qifUy3W6OVww6ZWobWub5oqZOVtwolfw==} - engines: {node: '>=18'} - cpu: [arm] - os: [linux] - '@esbuild/linux-arm@0.25.9': resolution: {integrity: sha512-HBU2Xv78SMgaydBmdor38lg8YDnFKSARg1Q6AT0/y2ezUAKiZvc211RDFHlEZRFNRVhcMamiToo7bDx3VEOYQw==} engines: {node: '>=18'} @@ -728,12 +648,6 @@ packages: cpu: [ia32] os: [linux] - '@esbuild/linux-ia32@0.25.5': - resolution: {integrity: sha512-sQ7l00M8bSv36GLV95BVAdhJ2QsIbCuCjh/uYrWiMQSUuV+LpXwIqhgJDcvMTj+VsQmqAHL2yYaasENvJ7CDKA==} - engines: {node: '>=18'} - cpu: [ia32] - os: [linux] - '@esbuild/linux-ia32@0.25.9': resolution: {integrity: sha512-e7S3MOJPZGp2QW6AK6+Ly81rC7oOSerQ+P8L0ta4FhVi+/j/v2yZzx5CqqDaWjtPFfYz21Vi1S0auHrap3Ma3A==} engines: {node: '>=18'} @@ -746,12 +660,6 @@ packages: cpu: [loong64] os: [linux] - '@esbuild/linux-loong64@0.25.5': - resolution: {integrity: sha512-0ur7ae16hDUC4OL5iEnDb0tZHDxYmuQyhKhsPBV8f99f6Z9KQM02g33f93rNH5A30agMS46u2HP6qTdEt6Q1kg==} - engines: {node: '>=18'} - cpu: [loong64] - os: [linux] - '@esbuild/linux-loong64@0.25.9': resolution: {integrity: sha512-Sbe10Bnn0oUAB2AalYztvGcK+o6YFFA/9829PhOCUS9vkJElXGdphz0A3DbMdP8gmKkqPmPcMJmJOrI3VYB1JQ==} engines: {node: '>=18'} @@ -764,12 +672,6 @@ packages: cpu: [mips64el] os: [linux] - '@esbuild/linux-mips64el@0.25.5': - resolution: {integrity: sha512-kB/66P1OsHO5zLz0i6X0RxlQ+3cu0mkxS3TKFvkb5lin6uwZ/ttOkP3Z8lfR9mJOBk14ZwZ9182SIIWFGNmqmg==} - engines: {node: '>=18'} - cpu: [mips64el] - os: [linux] - '@esbuild/linux-mips64el@0.25.9': resolution: {integrity: sha512-YcM5br0mVyZw2jcQeLIkhWtKPeVfAerES5PvOzaDxVtIyZ2NUBZKNLjC5z3/fUlDgT6w89VsxP2qzNipOaaDyA==} engines: {node: '>=18'} @@ -782,12 +684,6 @@ packages: cpu: [ppc64] os: [linux] - '@esbuild/linux-ppc64@0.25.5': - resolution: {integrity: sha512-UZCmJ7r9X2fe2D6jBmkLBMQetXPXIsZjQJCjgwpVDz+YMcS6oFR27alkgGv3Oqkv07bxdvw7fyB71/olceJhkQ==} - engines: {node: '>=18'} - cpu: [ppc64] - os: [linux] - '@esbuild/linux-ppc64@0.25.9': resolution: {integrity: sha512-++0HQvasdo20JytyDpFvQtNrEsAgNG2CY1CLMwGXfFTKGBGQT3bOeLSYE2l1fYdvML5KUuwn9Z8L1EWe2tzs1w==} engines: {node: '>=18'} @@ -800,12 +696,6 @@ packages: cpu: [riscv64] os: [linux] - '@esbuild/linux-riscv64@0.25.5': - resolution: {integrity: sha512-kTxwu4mLyeOlsVIFPfQo+fQJAV9mh24xL+y+Bm6ej067sYANjyEw1dNHmvoqxJUCMnkBdKpvOn0Ahql6+4VyeA==} - engines: {node: '>=18'} - cpu: [riscv64] - os: [linux] - '@esbuild/linux-riscv64@0.25.9': resolution: {integrity: sha512-uNIBa279Y3fkjV+2cUjx36xkx7eSjb8IvnL01eXUKXez/CBHNRw5ekCGMPM0BcmqBxBcdgUWuUXmVWwm4CH9kg==} engines: {node: '>=18'} @@ -818,12 +708,6 @@ packages: cpu: [s390x] os: [linux] - '@esbuild/linux-s390x@0.25.5': - resolution: {integrity: sha512-K2dSKTKfmdh78uJ3NcWFiqyRrimfdinS5ErLSn3vluHNeHVnBAFWC8a4X5N+7FgVE1EjXS1QDZbpqZBjfrqMTQ==} - engines: {node: '>=18'} - cpu: [s390x] - os: [linux] - '@esbuild/linux-s390x@0.25.9': resolution: {integrity: sha512-Mfiphvp3MjC/lctb+7D287Xw1DGzqJPb/J2aHHcHxflUo+8tmN/6d4k6I2yFR7BVo5/g7x2Monq4+Yew0EHRIA==} engines: {node: '>=18'} @@ -836,24 +720,12 @@ packages: cpu: [x64] os: [linux] - '@esbuild/linux-x64@0.25.5': - resolution: {integrity: sha512-uhj8N2obKTE6pSZ+aMUbqq+1nXxNjZIIjCjGLfsWvVpy7gKCOL6rsY1MhRh9zLtUtAI7vpgLMK6DxjO8Qm9lJw==} - engines: {node: '>=18'} - cpu: [x64] - os: [linux] - '@esbuild/linux-x64@0.25.9': resolution: {integrity: sha512-iSwByxzRe48YVkmpbgoxVzn76BXjlYFXC7NvLYq+b+kDjyyk30J0JY47DIn8z1MO3K0oSl9fZoRmZPQI4Hklzg==} engines: {node: '>=18'} cpu: [x64] os: [linux] - '@esbuild/netbsd-arm64@0.25.5': - resolution: {integrity: sha512-pwHtMP9viAy1oHPvgxtOv+OkduK5ugofNTVDilIzBLpoWAM16r7b/mxBvfpuQDpRQFMfuVr5aLcn4yveGvBZvw==} - engines: {node: '>=18'} - cpu: [arm64] - os: [netbsd] - '@esbuild/netbsd-arm64@0.25.9': resolution: {integrity: sha512-9jNJl6FqaUG+COdQMjSCGW4QiMHH88xWbvZ+kRVblZsWrkXlABuGdFJ1E9L7HK+T0Yqd4akKNa/lO0+jDxQD4Q==} engines: {node: '>=18'} @@ -866,24 +738,12 @@ packages: cpu: [x64] os: [netbsd] - '@esbuild/netbsd-x64@0.25.5': - resolution: {integrity: sha512-WOb5fKrvVTRMfWFNCroYWWklbnXH0Q5rZppjq0vQIdlsQKuw6mdSihwSo4RV/YdQ5UCKKvBy7/0ZZYLBZKIbwQ==} - engines: {node: '>=18'} - cpu: [x64] - os: [netbsd] - '@esbuild/netbsd-x64@0.25.9': resolution: {integrity: sha512-RLLdkflmqRG8KanPGOU7Rpg829ZHu8nFy5Pqdi9U01VYtG9Y0zOG6Vr2z4/S+/3zIyOxiK6cCeYNWOFR9QP87g==} engines: {node: '>=18'} cpu: [x64] os: [netbsd] - '@esbuild/openbsd-arm64@0.25.5': - resolution: {integrity: sha512-7A208+uQKgTxHd0G0uqZO8UjK2R0DDb4fDmERtARjSHWxqMTye4Erz4zZafx7Di9Cv+lNHYuncAkiGFySoD+Mw==} - engines: {node: '>=18'} - cpu: [arm64] - os: [openbsd] - '@esbuild/openbsd-arm64@0.25.9': resolution: {integrity: sha512-YaFBlPGeDasft5IIM+CQAhJAqS3St3nJzDEgsgFixcfZeyGPCd6eJBWzke5piZuZ7CtL656eOSYKk4Ls2C0FRQ==} engines: {node: '>=18'} @@ -896,12 +756,6 @@ packages: cpu: [x64] os: [openbsd] - '@esbuild/openbsd-x64@0.25.5': - resolution: {integrity: sha512-G4hE405ErTWraiZ8UiSoesH8DaCsMm0Cay4fsFWOOUcz8b8rC6uCvnagr+gnioEjWn0wC+o1/TAHt+It+MpIMg==} - engines: {node: '>=18'} - cpu: [x64] - os: [openbsd] - '@esbuild/openbsd-x64@0.25.9': resolution: {integrity: sha512-1MkgTCuvMGWuqVtAvkpkXFmtL8XhWy+j4jaSO2wxfJtilVCi0ZE37b8uOdMItIHz4I6z1bWWtEX4CJwcKYLcuA==} engines: {node: '>=18'} @@ -920,12 +774,6 @@ packages: cpu: [x64] os: [sunos] - '@esbuild/sunos-x64@0.25.5': - resolution: {integrity: sha512-l+azKShMy7FxzY0Rj4RCt5VD/q8mG/e+mDivgspo+yL8zW7qEwctQ6YqKX34DTEleFAvCIUviCFX1SDZRSyMQA==} - engines: {node: '>=18'} - cpu: [x64] - os: [sunos] - '@esbuild/sunos-x64@0.25.9': resolution: {integrity: sha512-WjH4s6hzo00nNezhp3wFIAfmGZ8U7KtrJNlFMRKxiI9mxEK1scOMAaa9i4crUtu+tBr+0IN6JCuAcSBJZfnphw==} engines: {node: '>=18'} @@ -938,12 +786,6 @@ packages: cpu: [arm64] os: [win32] - '@esbuild/win32-arm64@0.25.5': - resolution: {integrity: sha512-O2S7SNZzdcFG7eFKgvwUEZ2VG9D/sn/eIiz8XRZ1Q/DO5a3s76Xv0mdBzVM5j5R639lXQmPmSo0iRpHqUUrsxw==} - engines: {node: '>=18'} - cpu: [arm64] - os: [win32] - '@esbuild/win32-arm64@0.25.9': resolution: {integrity: sha512-mGFrVJHmZiRqmP8xFOc6b84/7xa5y5YvR1x8djzXpJBSv/UsNK6aqec+6JDjConTgvvQefdGhFDAs2DLAds6gQ==} engines: {node: '>=18'} @@ -956,12 +798,6 @@ packages: cpu: [ia32] os: [win32] - '@esbuild/win32-ia32@0.25.5': - resolution: {integrity: sha512-onOJ02pqs9h1iMJ1PQphR+VZv8qBMQ77Klcsqv9CNW2w6yLqoURLcgERAIurY6QE63bbLuqgP9ATqajFLK5AMQ==} - engines: {node: '>=18'} - cpu: [ia32] - os: [win32] - '@esbuild/win32-ia32@0.25.9': resolution: {integrity: sha512-b33gLVU2k11nVx1OhX3C8QQP6UHQK4ZtN56oFWvVXvz2VkDoe6fbG8TOgHFxEvqeqohmRnIHe5A1+HADk4OQww==} engines: {node: '>=18'} @@ -974,24 +810,18 @@ packages: cpu: [x64] os: [win32] - '@esbuild/win32-x64@0.25.5': - resolution: {integrity: sha512-TXv6YnJ8ZMVdX+SXWVBo/0p8LTcrUYngpWjvm91TMjjBQii7Oz11Lw5lbDV5Y0TzuhSJHwiH4hEtC1I42mMS0g==} - engines: {node: '>=18'} - cpu: [x64] - os: [win32] - '@esbuild/win32-x64@0.25.9': resolution: {integrity: sha512-PPOl1mi6lpLNQxnGoyAfschAodRFYXJ+9fs6WHXz7CSWKbOqiMZsubC+BQsVKuul+3vKLuwTHsS2c2y9EoKwxQ==} engines: {node: '>=18'} cpu: [x64] os: [win32] - '@faker-js/faker@9.8.0': - resolution: {integrity: sha512-U9wpuSrJC93jZBxx/Qq2wPjCuYISBueyVUGK7qqdmj7r/nxaxwW8AQDCLeRO7wZnjj94sh3p246cAYjUKuqgfg==} + '@faker-js/faker@9.9.0': + resolution: {integrity: sha512-OEl393iCOoo/z8bMezRlJu+GlRGlsKbUAN7jKB6LhnKoqKve5DXRpalbItIIcwnCjs1k/FOPjFzcA6Qn+H+YbA==} engines: {node: '>=18.0.0', npm: '>=9.0.0'} - '@fontsource-variable/inter@5.2.5': - resolution: {integrity: sha512-TrWffUAFOnT8zroE9YmGybagoOgM/HjRqMQ8k9R0vVgXlnUh/vnpbGPAS/Caz1KIlOPnPGh6fvJbb7DHbFCncA==} + '@fontsource-variable/inter@5.2.6': + resolution: {integrity: sha512-jks/bficUPQ9nn7GvXvHtlQIPudW7Wx8CrlZoY8bhxgeobNxlQan8DclUJuYF2loYRrGpfrhCIZZspXYysiVGg==} '@formatjs/ecma402-abstract@2.3.4': resolution: {integrity: sha512-qrycXDeaORzIqNhBOx0btnhpD1c+/qFIHAN9znofuMJX6QBwtbrmlpWfD4oiUUD2vJUOIYFA/gYtg2KAMGG7sA==} @@ -1040,17 +870,17 @@ packages: '@iconify/types@2.0.0': resolution: {integrity: sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg==} - '@internationalized/date@3.8.1': - resolution: {integrity: sha512-PgVE6B6eIZtzf9Gu5HvJxRK3ufUFz9DhspELuhW/N0GuMGMTLvPQNRkHP2hTuP9lblOk+f+1xi96sPiPXANXAA==} + '@internationalized/date@3.8.2': + resolution: {integrity: sha512-/wENk7CbvLbkUvX1tu0mwq49CVkkWpkXubGel6birjRPyo6uQ4nQpnq5xZu823zRCwwn82zgHrvgF1vZyvmVgA==} - '@internationalized/message@3.1.7': - resolution: {integrity: sha512-gLQlhEW4iO7DEFPf/U7IrIdA3UyLGS0opeqouaFwlMObLUzwexRjbygONHDVbC9G9oFLXsLyGKYkJwqXw/QADg==} + '@internationalized/message@3.1.8': + resolution: {integrity: sha512-Rwk3j/TlYZhn3HQ6PyXUV0XP9Uv42jqZGNegt0BXlxjE6G3+LwHjbQZAGHhCnCPdaA6Tvd3ma/7QzLlLkJxAWA==} - '@internationalized/number@3.6.2': - resolution: {integrity: sha512-E5QTOlMg9wo5OrKdHD6edo1JJlIoOsylh0+mbf0evi1tHJwMZfJSaBpGtnJV9N7w3jeiioox9EG/EWRWPh82vg==} + '@internationalized/number@3.6.4': + resolution: {integrity: sha512-P+/h+RDaiX8EGt3shB9AYM1+QgkvHmJ5rKi4/59k4sg9g58k9rqsRW0WxRO7jCoHyvVbFRRFKmVTdFYdehrxHg==} - '@internationalized/string@3.2.6': - resolution: {integrity: sha512-LR2lnM4urJta5/wYJVV7m8qk5DrMZmLRTuFhbQO5b9/sKLHgty6unQy1Li4+Su2DWydmB4aZdS5uxBRXIq2aAw==} + '@internationalized/string@3.2.7': + resolution: {integrity: sha512-D4OHBjrinH+PFZPvfCXvG28n2LSykWcJ7GIioQL+ok0LON15SdfoUssoHzzOUmVZLbRoREsQXVzA6r8JKsbP6A==} '@isaacs/cliui@8.0.2': resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==} @@ -1120,19 +950,19 @@ packages: '@lezer/lr@1.4.2': resolution: {integrity: sha512-pu0K1jCIdnQ12aWNaAVU5bzi7Bd1w54J3ECgANPmYLtQKP0HBj2cE/5coBD66MT10xbtIuUr7tg0Shbsvk0mDA==} - '@libsql/client@0.15.9': - resolution: {integrity: sha512-VT3do0a0vwYVaNcp/y05ikkKS3OrFR5UeEf5SUuYZVgKVl1Nc1k9ajoYSsOid8AD/vlhLDB5yFQaV4HmT/OB9w==} + '@libsql/client@0.15.12': + resolution: {integrity: sha512-JIqB0XsNrqYqBQZuhcgZdTcQoNOoQ5AMF+1yxc7vcZrLtm42QJwRazmTuBfyDwtWASEmVgjxeaLF4NT1iyVX8g==} - '@libsql/core@0.15.9': - resolution: {integrity: sha512-4OVdeAmuaCUq5hYT8NNn0nxlO9AcA/eTjXfUZ+QK8MT3Dz7Z76m73x7KxjU6I64WyXX98dauVH2b9XM+d84npw==} + '@libsql/core@0.15.12': + resolution: {integrity: sha512-S3tF6885ZizVjfym7f8SevL2VId/+DzxiKmP5zFbrhA8oMLh2XH8bYXChmhab7o9qUSHx+XjK4jCFpUwR5g+Ig==} - '@libsql/darwin-arm64@0.5.13': - resolution: {integrity: sha512-ASz/EAMLDLx3oq9PVvZ4zBXXHbz2TxtxUwX2xpTRFR4V4uSHAN07+jpLu3aK5HUBLuv58z7+GjaL5w/cyjR28Q==} + '@libsql/darwin-arm64@0.5.17': + resolution: {integrity: sha512-WTYG2skZsUnZmfZ2v7WFj7s3/5s2PfrYBZOWBKOnxHA8g4XCDc/4bFDaqob9Q2e88+GC7cWeJ8VNkVBFpD2Xxg==} cpu: [arm64] os: [darwin] - '@libsql/darwin-x64@0.5.13': - resolution: {integrity: sha512-kzglniv1difkq8opusSXM7u9H0WoEPeKxw0ixIfcGfvlCVMJ+t9UNtXmyNHW68ljdllje6a4C6c94iPmIYafYA==} + '@libsql/darwin-x64@0.5.17': + resolution: {integrity: sha512-ab0RlTR4KYrxgjNrZhAhY/10GibKoq6G0W4oi0kdm+eYiAv/Ip8GDMpSaZdAcoKA4T+iKR/ehczKHnMEB8MFxA==} cpu: [x64] os: [darwin] @@ -1146,38 +976,38 @@ packages: '@libsql/isomorphic-ws@0.1.5': resolution: {integrity: sha512-DtLWIH29onUYR00i0GlQ3UdcTRC6EP4u9w/h9LxpUZJWRMARk6dQwZ6Jkd+QdwVpuAOrdxt18v0K2uIYR3fwFg==} - '@libsql/linux-arm-gnueabihf@0.5.13': - resolution: {integrity: sha512-UEW+VZN2r0mFkfztKOS7cqfS8IemuekbjUXbXCwULHtusww2QNCXvM5KU9eJCNE419SZCb0qaEWYytcfka8qeA==} + '@libsql/linux-arm-gnueabihf@0.5.17': + resolution: {integrity: sha512-PcASh4k47RqC+kMWAbLUKf1y6Do0q8vnUGi0yhKY4ghJcimMExViBimjbjYRSa+WIb/zh3QxNoXOhQAXx3tiuw==} cpu: [arm] os: [linux] - '@libsql/linux-arm-musleabihf@0.5.13': - resolution: {integrity: sha512-NMDgLqryYBv4Sr3WoO/m++XDjR5KLlw9r/JK4Ym6A1XBv2bxQQNhH0Lxx3bjLW8qqhBD4+0xfms4d2cOlexPyA==} + '@libsql/linux-arm-musleabihf@0.5.17': + resolution: {integrity: sha512-vxOkSLG9Wspit+SNle84nuIzMtr2G2qaxFzW7BhsZBjlZ8+kErf9RXcT2YJQdJYxmBYRbsOrc91gg0jLEQVCqg==} cpu: [arm] os: [linux] - '@libsql/linux-arm64-gnu@0.5.13': - resolution: {integrity: sha512-/wCxVdrwl1ee6D6LEjwl+w4SxuLm5UL9Kb1LD5n0bBGs0q+49ChdPPh7tp175iRgkcrTgl23emymvt1yj3KxVQ==} + '@libsql/linux-arm64-gnu@0.5.17': + resolution: {integrity: sha512-L8jnaN01TxjBJlDuDTX2W2BKzBkAOhcnKfCOf3xzvvygblxnDOK0whkYwIXeTfwtd/rr4jN/d6dZD/bcHiDxEQ==} cpu: [arm64] os: [linux] - '@libsql/linux-arm64-musl@0.5.13': - resolution: {integrity: sha512-xnVAbZIanUgX57XqeI5sNaDnVilp0Di5syCLSEo+bRyBobe/1IAeehNZpyVbCy91U2N6rH1C/mZU7jicVI9x+A==} + '@libsql/linux-arm64-musl@0.5.17': + resolution: {integrity: sha512-HfFD7TzQtmmTwyQsuiHhWZdMRtdNpKJ1p4tbMMTMRECk+971NFHrj69D64cc2ClVTAmn7fA9XibKPil7WN/Q7w==} cpu: [arm64] os: [linux] - '@libsql/linux-x64-gnu@0.5.13': - resolution: {integrity: sha512-/mfMRxcQAI9f8t7tU3QZyh25lXgXKzgin9B9TOSnchD73PWtsVhlyfA6qOCfjQl5kr4sHscdXD5Yb3KIoUgrpQ==} + '@libsql/linux-x64-gnu@0.5.17': + resolution: {integrity: sha512-5l3XxWqUPVFrtX0xnZaXwqsXs0BFbP4w6ahRFTPSdXU50YBfUOajFznJRB6bJTMsCvraDSD0IkHhjSNfrE1CuQ==} cpu: [x64] os: [linux] - '@libsql/linux-x64-musl@0.5.13': - resolution: {integrity: sha512-rdefPTpQCVwUjIQYbDLMv3qpd5MdrT0IeD0UZPGqhT9AWU8nJSQoj2lfyIDAWEz7PPOVCY4jHuEn7FS2sw9kRA==} + '@libsql/linux-x64-musl@0.5.17': + resolution: {integrity: sha512-FvSpWlwc+dIeYIFYlsSv+UdQ/NiZWr+SstwVji+QZ//8NnvzwWQU9cgP+Vpps6Qiq4jyYQm9chJhTYOVT9Y3BA==} cpu: [x64] os: [linux] - '@libsql/win32-x64-msvc@0.5.13': - resolution: {integrity: sha512-aNcmDrD1Ws+dNZIv9ECbxBQumqB9MlSVEykwfXJpqv/593nABb8Ttg5nAGUPtnADyaGDTrGvPPP81d/KsKho4Q==} + '@libsql/win32-x64-msvc@0.5.17': + resolution: {integrity: sha512-f5bGH8+3A5sn6Lrqg8FsQ09a1pYXPnKGXGTFiAYlfQXVst1tUTxDTugnuWcJYKXyzDe/T7ccxyIZXeSmPOhq8A==} cpu: [x64] os: [win32] @@ -1225,289 +1055,283 @@ packages: resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} engines: {node: '>=14'} - '@preact/signals-core@1.8.0': - resolution: {integrity: sha512-OBvUsRZqNmjzCZXWLxkZfhcgT+Fk8DDcT/8vD6a1xhDemodyy87UJRJfASMuSD8FaAIeGgGm85ydXhm7lr4fyA==} + '@preact/signals-core@1.12.0': + resolution: {integrity: sha512-etWpENXm469RHMWIZGblgWrapbIGcRcbccEGGaLkFez3PjlI3XkBrUtSiNFsIfV/DN16PxMOxbWAZUIaLFyJDg==} '@preact/signals@1.3.2': resolution: {integrity: sha512-naxcJgUJ6BTOROJ7C3QML7KvwKwCXQJYTc5L/b0eEsdYgPB6SxwoQ1vDGcS0Q7GVjAenVq/tXrybVdFShHYZWg==} peerDependencies: preact: 10.x - '@primer/octicons-react@19.15.2': - resolution: {integrity: sha512-bVpBKLm5wOdVlwvjCU/uD4JeeVv5iQWpCO0kgkUYX7yzGgV0mf427kdzqKacYMY5rgcbpmiCKAqFe4j6hXz0ow==} - engines: {node: '>=8'} - peerDependencies: - react: '>=16.3' - - '@react-aria/breadcrumbs@3.5.24': - resolution: {integrity: sha512-CRheGyyM8afPJvDHLXn/mmGG/WAr/z2LReK3DlPdxVKcsOn7g3NIRxAcAIAJQlDLdOiu1SXHiZe6uu2jPhHrxA==} + '@react-aria/breadcrumbs@3.5.27': + resolution: {integrity: sha512-fuXD9nvBaBVZO0Z6EntBlxQD621/2Ldcxz76jFjc4V/jNOq/6BIVQRtpnAYYrSTiW3ZV2IoAyxRWNxQU22hOow==} peerDependencies: react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - '@react-aria/button@3.13.1': - resolution: {integrity: sha512-E49qcbBRgofXYfWbli50bepWVNtQBq7qewL9XsX7nHkwPPUe1IRwJOnWZqYMgwwhUBOXfnsR6/TssiXqZsrJdw==} + '@react-aria/button@3.14.0': + resolution: {integrity: sha512-we6z+2GpZO8lGD6EPmYH2S87kLCpU14D2E3tD2vES+SS2sZM2qcm2dUGpeo4+gZqBToLWKEBAGCSlkWEtgS19A==} peerDependencies: react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - '@react-aria/calendar@3.8.1': - resolution: {integrity: sha512-S931yi8jJ6CgUQJk+h/PEl+V0n1dUYr9n6nKXmZeU3940to4DauqwvmD9sg67hFHJ0QGroHT/s29yIfa5MfQcg==} + '@react-aria/calendar@3.9.0': + resolution: {integrity: sha512-YxHLqL/LZrgwYGKzlQ96Fgt6gC+Q1L8k56sD51jJAtiD+YtT/pKJfK1zjZ3rtHtPTDYzosJ8vFgOmZNpnKQpXQ==} peerDependencies: react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - '@react-aria/checkbox@3.15.5': - resolution: {integrity: sha512-b9c76DBSYTdacSogbsvjkdZomTo5yhBNMmR5ufO544HQ718Ry8q8JmVbtmF/+dkZN7KGnBQCltzGLzXH0Vc0Zg==} + '@react-aria/checkbox@3.16.0': + resolution: {integrity: sha512-XPaMz1/iVBG6EbJOPYlNtvr+q4f0axJeoIvyzWW3ciIdDSX/3jYuFg/sv/b3OQQl389cbQ/WUBQyWre/uXWVEg==} peerDependencies: react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - '@react-aria/color@3.0.7': - resolution: {integrity: sha512-3DcYxEWBrcuHSBq0OqCs6GySuy6eOue8/ngC31j/8aMXR+O4mGpXi0wo3rSQGFmGq/4Ri986cI2iGwZOkzpMHg==} + '@react-aria/color@3.1.0': + resolution: {integrity: sha512-95qcCmz5Ss6o1Z4Z7X3pEEQxoUA83qGNQkpjOvobcHbNWKfhvOAsUzdBleOx2NpyBzY16OAnhWR7PJZwR4AqiA==} peerDependencies: react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - '@react-aria/combobox@3.12.3': - resolution: {integrity: sha512-nCLFSQjOR3r3tB1AURtZKSZhi2euBMw0QxsIjnMVF73BQOfwfHMrIFctNULbL070gEnXofzeBd3ykJQpnsGH+Q==} + '@react-aria/combobox@3.13.0': + resolution: {integrity: sha512-eBa8aWcL3Ar/BvgSaqYDmNQP70LPZ7us2myM31QQt2YDRptqGHd44wzXCts9SaDVIeMVy+AEY2NkuxrVE6yNrw==} peerDependencies: react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - '@react-aria/datepicker@3.14.3': - resolution: {integrity: sha512-gDc+bM0EaY3BuIW8IJu/ARJV78bRpOaHp+B08EW4N2qJvc7Bs+EmGLnxMrB6Ny+YxNxsYdQRA/FqiytVYOEk8w==} + '@react-aria/datepicker@3.15.0': + resolution: {integrity: sha512-AONeLj7sMKz4JmzCu4bhsqwcNFXCSWoaBhi4wOJO9+WYmxudn5mSI9ez8NMCVn+s5kcYpyvzrrAFf/DvQ4UDgw==} peerDependencies: react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - '@react-aria/dialog@3.5.25': - resolution: {integrity: sha512-hVP/TvjUnPgckg4qibc/TDH54O+BzW95hxApxBw1INyViRm95PxdCQDqBdQ/ZW7Gv6J2aUBCGihX7kINPf70ow==} + '@react-aria/dialog@3.5.28': + resolution: {integrity: sha512-S9dgdFBQc9LbhyBiHwGPSATwtvsIl6h+UnxDJ4oKBSse+wxdAyshbZv2tyO5RFbe3k73SAgU7yKocfg7YyRM0A==} peerDependencies: react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - '@react-aria/disclosure@3.0.5': - resolution: {integrity: sha512-YrazXoIzVq48soJpVMb2Iq/CB+lglwfKLsml5UfpE0MGlJJ/jWtIZtodqQ8ree1YguMNTvtESazTlMo7ZLsasQ==} + '@react-aria/disclosure@3.0.7': + resolution: {integrity: sha512-g17smH+5v7B6JijzN20rIRUmE2N8owYK/4blR6tIyS+oLIHr+Crkt1ErNoUWynibj2/4gDd9KGrKyzwB4vxK9g==} peerDependencies: react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - '@react-aria/dnd@3.9.3': - resolution: {integrity: sha512-Sjb+UQxG58/paOZXsVKiqLautV4FyILr3tLxMG4Q04QOUzatqlz91APt7RsVMdizk6bVB7Lg74AEypHbXVzhDQ==} + '@react-aria/dnd@3.11.0': + resolution: {integrity: sha512-jr47o7Fy55eYjSKWqRyuWKPnynpgC4cE9YXnYg5xa+1woRefIF2IyteOxgSHeX16+6ef2UDSsvC61T3gS6NWxQ==} peerDependencies: react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - '@react-aria/focus@3.20.3': - resolution: {integrity: sha512-rR5uZUMSY4xLHmpK/I8bP1V6vUNHFo33gTvrvNUsAKKqvMfa7R2nu5A6v97dr5g6tVH6xzpdkPsOJCWh90H2cw==} + '@react-aria/focus@3.21.0': + resolution: {integrity: sha512-7NEGtTPsBy52EZ/ToVKCu0HSelE3kq9qeis+2eEq90XSuJOMaDHUQrA7RC2Y89tlEwQB31bud/kKRi9Qme1dkA==} peerDependencies: react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - '@react-aria/form@3.0.16': - resolution: {integrity: sha512-N1bDsJfmnyDesayK0Ii6UPH6JWiF6Wz8WSveQ2y5004XHoIWn5LpWmOqnRedvyw4Yedw33schlvrY7ENEwMdpg==} + '@react-aria/form@3.1.0': + resolution: {integrity: sha512-aDAOZafrn0V8e09mDAtCvc+JnpnkFM9X8cbI5+fdXsXAA+JxO+3uRRfnJHBlIL0iLc4C4OVWxBxWToV95pg1KA==} peerDependencies: react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - '@react-aria/grid@3.14.0': - resolution: {integrity: sha512-/tJB7xnSruORJ8tlFHja4SfL8/EW5v4cBLiyD5z48m7IdG33jXR8Cv4Pi5uQqs8zKdnpqZ1wDG3GQxNDwZavpg==} + '@react-aria/grid@3.14.3': + resolution: {integrity: sha512-O4Ius5tJqKcMGfQT6IXD4MnEOeq6f/59nKmfCLTXMREFac/oxafqanUx3zrEVYbaqLOjEmONcd8S61ptQM6aPg==} peerDependencies: react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - '@react-aria/gridlist@3.13.0': - resolution: {integrity: sha512-RHURMo063qbbA8WXCJxGL+5xmSx6yW7Z/V2jycrVcZFOYqj2EgU953aVjpaT/FSyH8/AEioU9oE64YmiEfWUUA==} + '@react-aria/gridlist@3.13.3': + resolution: {integrity: sha512-U2x/1MpdrAgK/vay2s2nVSko4WysajlMS+L8c18HE/ig2to+C8tCPWH2UuK4jTQWrK5x/PxTH+/yvtytljnIuQ==} peerDependencies: react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - '@react-aria/i18n@3.12.9': - resolution: {integrity: sha512-Fim0FLfY05kcpIILdOtqcw58c3sksvmVY8kICSwKCuSek4wYfwJdU28p/sRptw4adJhqN8Cbssvkf/J8zL2GgA==} + '@react-aria/i18n@3.12.11': + resolution: {integrity: sha512-1mxUinHbGJ6nJ/uSl62dl48vdZfWTBZePNF/wWQy98gR0qNFXLeusd7CsEmJT1971CR5i/WNYUo1ezNlIJnu6A==} peerDependencies: react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - '@react-aria/interactions@3.25.1': - resolution: {integrity: sha512-ntLrlgqkmZupbbjekz3fE/n3eQH2vhncx8gUp0+N+GttKWevx7jos11JUBjnJwb1RSOPgRUFcrluOqBp0VgcfQ==} + '@react-aria/interactions@3.25.4': + resolution: {integrity: sha512-HBQMxgUPHrW8V63u9uGgBymkMfj6vdWbB0GgUJY49K9mBKMsypcHeWkWM6+bF7kxRO728/IK8bWDV6whDbqjHg==} peerDependencies: react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - '@react-aria/label@3.7.18': - resolution: {integrity: sha512-Ht9D+xkI2Aysn+JNiHE+UZT4FUOGPF7Lfrmp7xdJCA/tEqqF3xW/pAh+UCNOnnWmH8jTYnUg3bCp4G6GQUxKCQ==} + '@react-aria/label@3.7.20': + resolution: {integrity: sha512-Hw7OsC2GBnjptyW1lC1+SNoSIZA0eIh02QnNDr1XX2S+BPfn958NxoI7sJIstO/TUpQVNqdjEN/NI6+cyuJE6g==} peerDependencies: react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - '@react-aria/landmark@3.0.3': - resolution: {integrity: sha512-mcmHijInDZZY3W9r0SeRuXsHW8Km9rBWKB3eoBz+PVuyJYMuabhQ2mUB5xTbqbnV++Srr7j/59g+Lbw5gAN4lw==} + '@react-aria/landmark@3.0.5': + resolution: {integrity: sha512-klUgRGQyTv5qWFQ0EMMLBOLa87qSTGjWoiMvytL9EgJCACkn/OzNMPbqVSkMADvadDyWCMWFYWvfweLxl3T5yw==} peerDependencies: react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - '@react-aria/link@3.8.1': - resolution: {integrity: sha512-ujq7+XIP7OXHu7m2NObvHsl41B/oIBAYI0D+hsxEQo3+x6Q/OUxp9EX2sX4d7TBWvchFmhr6jJdER0QMmeSO/A==} + '@react-aria/link@3.8.4': + resolution: {integrity: sha512-7cPRGIo7x6ZZv1dhp2xGjqLR1snazSQgl7tThrBDL5E8f6Yr7SVpxOOK5/EBmfpFkhkmmXEO/Fgo/GPJdc6Vmw==} peerDependencies: react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - '@react-aria/listbox@3.14.4': - resolution: {integrity: sha512-bW3D7KcnQIF77F3zDRMIGQ6e5e1wHTNUtbKJLE423u1Dhc7K2x0pksir0gLGwElhiBW544lY1jv3kFLOeKa6ng==} + '@react-aria/listbox@3.14.7': + resolution: {integrity: sha512-U5a+AIDblaeQTIA1MDFUaYIKoPwPNAuY7SwkuA5Z7ClDOeQJkiyExmAoKcUXwUkrLULQcbOPKr401q38IL3T7Q==} peerDependencies: react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - '@react-aria/live-announcer@3.4.2': - resolution: {integrity: sha512-6+yNF9ZrZ4YJ60Oxy2gKI4/xy6WUv1iePDCFJkgpNVuOEYi8W8czff8ctXu/RPB25OJx5v2sCw9VirRogTo2zA==} + '@react-aria/live-announcer@3.4.4': + resolution: {integrity: sha512-PTTBIjNRnrdJOIRTDGNifY2d//kA7GUAwRFJNOEwSNG4FW+Bq9awqLiflw0JkpyB0VNIwou6lqKPHZVLsGWOXA==} - '@react-aria/menu@3.18.3': - resolution: {integrity: sha512-D0C4CM/QaxhCo2pLWNP+nfgnAeaSZWOdPMo9pnH/toRsoeTbnD6xO1hLhYsOx5ge+hrzjQvthjUrsjPB1AM/BQ==} + '@react-aria/menu@3.19.0': + resolution: {integrity: sha512-VLUGbZedKJvK2OFWEpa86GPIaj9QcWox/R9JXmNk6nyrAz/V46OBQENdliV26PEdBZgzrVxGvmkjaH7ZsN/32Q==} peerDependencies: react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - '@react-aria/meter@3.4.23': - resolution: {integrity: sha512-FgmB/+cTE/sz+wTpTSmj9hFXw4nzfMUJGvXIePnF6f5Gx6J/U7aLEvNk7sXCp76apOu8k7ccma1nCsEvj74x7w==} + '@react-aria/meter@3.4.25': + resolution: {integrity: sha512-6IqOnwuEt8z6UDy8Ru3ZZRZIUiELD0N3Wi/udMfR8gz4oznutvnRCMpRXkVVaVLYQfRglybu2/Lxfe+rq8WiRg==} peerDependencies: react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - '@react-aria/numberfield@3.11.14': - resolution: {integrity: sha512-UvhPlRwVmbNEBBqfgL41P10H1jL4C7P2hWqsVw72tZQJl5k5ujeOzRWk8mkmg+D4FCZvv4iSPJhmyEP8HkgsWg==} + '@react-aria/numberfield@3.12.0': + resolution: {integrity: sha512-JkgkjYsZ9lN5m3//X3buOKVrA/QJEeeXJ+5T5r6AmF29YdIhD1Plf5AEOWoRpZWQ25chH7FI/Orsf4h3/SLOpg==} peerDependencies: react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - '@react-aria/overlays@3.27.1': - resolution: {integrity: sha512-wepzwNLkgem6kVlLm6yk7zNIMAt0KPy8vAWlxdfpXWD/hBI30ULl71gL/BxRa5EYG1GMvlOwNti3whzy9lm3eQ==} + '@react-aria/overlays@3.28.0': + resolution: {integrity: sha512-qaHahAXTmxXULgg2/UfWEIwfgdKsn27XYryXAWWDu2CAZTcbI+5mGwYrQZSDWraM6v5PUUepzOVvm7hjTqiMFw==} peerDependencies: react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - '@react-aria/progress@3.4.23': - resolution: {integrity: sha512-uSQBVY64k+CCey82U67KyWnjAfuuHF0fG6y76kIB8GHI8tGfd1NkXo4ioaxiY0SS+BYGqwqJYYMUzQMpOBTN1A==} + '@react-aria/progress@3.4.25': + resolution: {integrity: sha512-KD9Gow+Ip6ZCBdsarR+Hby3c4d99I6L95Ruf7tbCh4ut9i9Dbr+x99OwhpAbT0g549cOyeIqxutPkT+JuzrRuA==} peerDependencies: react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - '@react-aria/radio@3.11.3': - resolution: {integrity: sha512-o10G8RUuHnAGZYzkc5PQw7mj4LMZqmGkoihDeHF2NDa9h44Ce5oeCPwRvCKYbumZDOyDY15ZIZhTUzjHt2w6fA==} + '@react-aria/radio@3.12.0': + resolution: {integrity: sha512-//0zZUuHtbm6uZR9+sNRNzVcQpjJKjZj57bDD0lMNj3NZp/Tkw+zXIFy6j1adv3JMe6iYkzEgaB7YRDD1Fe/ZA==} peerDependencies: react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - '@react-aria/searchfield@3.8.4': - resolution: {integrity: sha512-WnAvU9ct8+Asb8FFhGw6bggBmRaPe9qZPgYacenmRItwN+7UVTwEBVB9umO2bN3PLGm3CKgop10znd6ATiAbJA==} + '@react-aria/searchfield@3.8.7': + resolution: {integrity: sha512-15jfALRyz5EAA5tvIELVfUlqTFdk8oG442OiS3Xq/jJij8uKRzwUdnL57EVTFYyg+VMLp/t5wX+obXYcRG+kdQ==} peerDependencies: react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - '@react-aria/select@3.15.5': - resolution: {integrity: sha512-2v8QmcPsZzlOjc/zsLbMcKeMKZoa+FZboxfjq4koUXtuaLhgopENChkfPLaXEGxqsejANs4dAoqiOiwwrGAaLQ==} + '@react-aria/select@3.16.0': + resolution: {integrity: sha512-UkiLSxMOKWW24qnhZdOObkFLpauvmu0T6wuPXbdQgwlis/UeLzDamPAWc6loRFJgHCpJftaaaWVQG3ks4NX7ew==} peerDependencies: react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - '@react-aria/selection@3.24.1': - resolution: {integrity: sha512-nHUksgjg92iHgseH9L+krk9rX19xGJLTDeobKBX7eoAXQMqQjefu+oDwT0VYdI/qqNURNELE/KPZIVLC4PB81w==} + '@react-aria/selection@3.25.0': + resolution: {integrity: sha512-Q3U0Ya0PTP/TR0a2g+7YEbFVLphiWthmEkHyvOx9HsKSjE8w9wXY3C14DZWKskB/BBrXKJuOWxBDa0xhC83S+A==} peerDependencies: react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - '@react-aria/separator@3.4.9': - resolution: {integrity: sha512-5ZKVQ/5I2+fw8WyVCQLGjQKsMKlTIieLPf8NvdC24a+pmiUluyUuqfPYdI8s6lcnjG0gbOzZB+jKvDRQbIvMPQ==} + '@react-aria/separator@3.4.11': + resolution: {integrity: sha512-WwYEb7Wga4YQvlEwbzlVcVkfByullcORKtIe30pmh1YkTRRVJhbRPaE/mwcSMufbfjSYdtDavxmF+WY7Tdb9/A==} peerDependencies: react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - '@react-aria/slider@3.7.19': - resolution: {integrity: sha512-GONrMMz9zsx0ySbUTebWdqRjAuu6EEW+lLf3qUzcqkIYR8QZVTS8RLPt7FmGHKCTDIaBs8D2yv9puIfKAo1QAA==} + '@react-aria/slider@3.8.0': + resolution: {integrity: sha512-D7Sa7q21cV3gBid7frjoYw6924qYqNdJn2oai1BEemHSuwQatRlm1o2j+fnPTy9sYZfNOqXYnv5YjEn0o1T+Gw==} peerDependencies: react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - '@react-aria/spinbutton@3.6.15': - resolution: {integrity: sha512-dVKaRgrSU2utxCd4kqAA8BPrC1PVI1eiJ8gvlVbg25LbwK4dg1WPXQUK+80TbrJc9mOEooPiJvzw59IoQLMNRg==} + '@react-aria/spinbutton@3.6.17': + resolution: {integrity: sha512-gdGc3kkqpvFUd9XsrhPwQHMrG2TY0LVuGGgjvaZwF/ONm9FMz393ogCM0P484HsjU50hClO+yiRRgNjdwDIzPQ==} peerDependencies: react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - '@react-aria/ssr@3.9.8': - resolution: {integrity: sha512-lQDE/c9uTfBSDOjaZUJS8xP2jCKVk4zjQeIlCH90xaLhHDgbpCdns3xvFpJJujfj3nI4Ll9K7A+ONUBDCASOuw==} + '@react-aria/ssr@3.9.10': + resolution: {integrity: sha512-hvTm77Pf+pMBhuBm760Li0BVIO38jv1IBws1xFm1NoL26PU+fe+FMW5+VZWyANR6nYL65joaJKZqOdTQMkO9IQ==} engines: {node: '>= 12'} peerDependencies: react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - '@react-aria/switch@3.7.3': - resolution: {integrity: sha512-tFdJmcHaLgW23cS2R713vcJdVbsjDTRk8OLdG/sMziPBY3C00/exuSIb57xTS7KrE0hBYfnLJQTcmDNqdM8+9Q==} + '@react-aria/switch@3.7.6': + resolution: {integrity: sha512-C+Od8hZNZCf3thgtZnZKzHl5b/63Q9xf+Pw6ugLA1qaKazwp46x1EwUVVqVhfAeVhmag++eHs8Lol5ZwQEinjQ==} peerDependencies: react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - '@react-aria/table@3.17.3': - resolution: {integrity: sha512-hs3akyNMeeAPIfa+YKMxJyupSjywW5OGzJtOw/Z0j6pV8KXSeMEXNYkSuJY+m5Q1mdunoiiogs0kE3B0r2izQA==} + '@react-aria/table@3.17.6': + resolution: {integrity: sha512-PSEaeKOIazVEaykeTLudPbDLytJgOPLZJalS/xXY0/KL+Gi0Olchmz4tvS0WBe87ChmlVi6GQqU+stk23aZVWg==} peerDependencies: react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - '@react-aria/tabs@3.10.3': - resolution: {integrity: sha512-TYfwaRrI0mQMefmoHeTKXdczpb53qpPr+3nnveGl+BocG94wmjIqK6kncboVbPdykgQCIAMd2d9GFpK01+zXrA==} + '@react-aria/tabs@3.10.6': + resolution: {integrity: sha512-L8MaE7+bu6ByDOUxNPpMMYxdHULhKUfBoXdsSsXqb1z3QxdFW2zovfag0dvpyVWB6ALghX2T0PlTUNqaKA5tGw==} peerDependencies: react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - '@react-aria/tag@3.6.0': - resolution: {integrity: sha512-OkLyFYTFVUYB339eugw2r6vIcrWq47O15x4sKNkDUo6YBx9ci9tdoib4DlzwuiiKVr/vmw1WMow6VK4zOtuLng==} + '@react-aria/tag@3.7.0': + resolution: {integrity: sha512-nU0Sl7u82RBn8XLNyrjkXhtw+xbJD9fyjesmDu7zeOq78e4eunKW7OZ/9+t+Lyu5wW+B7vKvetIgkdXKPQm3MA==} peerDependencies: react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - '@react-aria/textfield@3.17.3': - resolution: {integrity: sha512-p/Z0fyE0CnzIrnCf42gxeSCNYon7//XkcbPwUS4U9dz2VLk2GnEn9NZXPYgTp+08ebQEn0pB1QIchX79yFEguw==} + '@react-aria/textfield@3.18.0': + resolution: {integrity: sha512-kCwbyDHi2tRaD/OjagA3m3q2mMZUPeXY7hRqhDxpl2MwyIdd+/PQOJLM8tZr5+m2zvBx+ffOcjZMGTMwMtoV5w==} peerDependencies: react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - '@react-aria/toast@3.0.3': - resolution: {integrity: sha512-7HWTKIVwS1JFC8//BQbRtGFaAdq4SljvI3yI5amLr90CyVM0sugTtcSX9a8BPnp1j9ao+6bmOi/wrV48mze1PA==} + '@react-aria/toast@3.0.6': + resolution: {integrity: sha512-PoCLWoZzdHIMYY0zIU3WYsHAHPS52sN1gzGRJ+cr5zogU8wwg8lwFZCvs/yql0IhQLsO930zcCXWeL/NsCMrlA==} peerDependencies: react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - '@react-aria/toggle@3.11.3': - resolution: {integrity: sha512-S6ShToNR6TukRJh8qDdyl9b2Bcsx43eurUB5USANn4ycPov8+bIxQnxiknjssZx7jD8vX4jruuNh7BjFbNsGFw==} + '@react-aria/toggle@3.12.0': + resolution: {integrity: sha512-JfcrF8xUEa2CbbUXp+WQiTBVwSM/dm21v5kueQlksvLfXG6DGE8/zjM6tJFErrFypAasc1JXyrI4dspLOWCfRA==} peerDependencies: react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - '@react-aria/toolbar@3.0.0-beta.16': - resolution: {integrity: sha512-TnNvtxADalMzs9Et51hWPpGyiHr1dt++UYR7pIo1H7vO+HwXl6uH4HxbFDS5CyV69j2cQlcGrkj13LoWFkBECw==} + '@react-aria/toolbar@3.0.0-beta.19': + resolution: {integrity: sha512-G4sgtOUTUUJHznXlpKcY64SxD2gKOqIQXZXjWTVcY/Q5hAjl8gbTt5XIED22GmeIgd/tVl6+lddGj6ESze4vSg==} peerDependencies: react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - '@react-aria/tooltip@3.8.3': - resolution: {integrity: sha512-8JHRqffH5vUw7og6mlCRzb4h95/R5RpOxGFfEGw7aami14XMo6tZg7wMgwDUAEiVqNerRWYaw+tk7nCUQXo1Sg==} + '@react-aria/tooltip@3.8.6': + resolution: {integrity: sha512-lW/PegiswGLlCP0CM4FH2kbIrEe4Li2SoklzIRh4nXZtiLIexswoE5/5af7PMtoMAl31or6fHZleVLzZD4VcfA==} peerDependencies: react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - '@react-aria/tree@3.0.3': - resolution: {integrity: sha512-kdA0CCUD8luCrXZFo0rX1c0LI8jovYMuWsPiI5OpmiEKGA5HaVFFW/H9t/XSYdVc/JO08zbeZ/WacTusKeOT3Q==} + '@react-aria/tree@3.1.2': + resolution: {integrity: sha512-duyAoxSIzgIEP1UvCivx8uY7GZxo8nhfSsHW77GO+UMgwBjWkrvHnYQXBYbLq1GLqLxuDN+U7SFe8Az7+HcbOg==} peerDependencies: react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - '@react-aria/utils@3.29.0': - resolution: {integrity: sha512-jSOrZimCuT1iKNVlhjIxDkAhgF7HSp3pqyT6qjg/ZoA0wfqCi/okmrMPiWSAKBnkgX93N8GYTLT3CIEO6WZe9Q==} + '@react-aria/utils@3.30.0': + resolution: {integrity: sha512-ydA6y5G1+gbem3Va2nczj/0G0W7/jUVo/cbN10WA5IizzWIwMP5qhFr7macgbKfHMkZ+YZC3oXnt2NNre5odKw==} peerDependencies: react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - '@react-aria/visually-hidden@3.8.23': - resolution: {integrity: sha512-D37GHtAcxCck8BtCiGTNDniGqtldJuN0cRlW1PJ684zM4CdmkSPqKbt5IUKUfqheS9Vt7HxYsj1VREDW+0kaGA==} + '@react-aria/visually-hidden@3.8.26': + resolution: {integrity: sha512-Lz36lTVaQbv5Kn74sPv0l9SnLQ5XHKCoq2zilP14Eb4QixDIqR7Ovj43m+6wi9pynf29jtOb/8D/9jrTjbmmgw==} peerDependencies: react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 @@ -1540,271 +1364,271 @@ packages: typescript: optional: true - '@react-stately/calendar@3.8.1': - resolution: {integrity: sha512-pTPRmPRD/0JeKhCRvXhVIH/yBimtIHnZGUxH12dcTl3MLxjXQDTn6/LWK0s4rzJcjsC+EzGUCVBBXgESb7PUlw==} + '@react-stately/calendar@3.8.3': + resolution: {integrity: sha512-HTWD6ZKQcXDlvj6glEEG0oi2Tpkaw19y5rK526s04zJs894wFqM9PK0WHthEYqjCeQJ5B/OkyG19XX4lENxnZw==} peerDependencies: react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - '@react-stately/checkbox@3.6.14': - resolution: {integrity: sha512-eGl0GP/F/nUrA33gDCYikyXK+Yer7sFOx8T4EU2AF4E8n1VQIRiVNaxDg7Ar6L3CMKor01urppFHFJsBUnSgyw==} + '@react-stately/checkbox@3.7.0': + resolution: {integrity: sha512-opViVhNvxFVHjXhM4nA/E03uvbLazsIKloXX9JtyBCZAQRUag17dpmkekfIkHvP4o7z7AWFoibD8JBFV1IrMcQ==} peerDependencies: react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - '@react-stately/collections@3.12.4': - resolution: {integrity: sha512-H+47fRkwYX2/BdSA+NLTzbR+8QclZXyBgC7tHH3dzljyxNimhrMDnbmk520nvGCebNf3nuxtFHq9iVTLpazSVA==} + '@react-stately/collections@3.12.6': + resolution: {integrity: sha512-S158RKWGZSodbJXKZDdcnrLzFxzFmyRWDNakQd1nBGhSrW2JV8lDn9ku5Og7TrjoEpkz//B2oId648YT792ilw==} peerDependencies: react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - '@react-stately/color@3.8.5': - resolution: {integrity: sha512-yi1MQAbYuAYKu0AtMO+mWQWlWk6OzGMa9j4PGtQN2PI5Uv1NylWOvdquxbUJ4GUAuSYNopYG8Ci9MZMwtito8w==} + '@react-stately/color@3.9.0': + resolution: {integrity: sha512-9eG0gDxVIu+A+DTdfwyYuU4pR788pVdq1Snpk8el787OsOb5WiuT4C4VWJb5Qbrq2PiFhhZmxuJXpzz4B1gW3A==} peerDependencies: react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - '@react-stately/combobox@3.10.5': - resolution: {integrity: sha512-27SkClMqbMAKuVnmXhYzYisbLfzV7MO/DEiqWO4/3l+PZ+whL7Wi/Ek7Wqlfluid/y4pN4EkHCKNt4HJ2mhORQ==} + '@react-stately/combobox@3.11.0': + resolution: {integrity: sha512-W9COXdSOC+uqCZrRHJI0K7emlPb/Tx4A89JHWBcFmiAk+hs1Cnlyjw3aaqEiT8A8/HxDNMO9QcfisWC1iNyE9A==} peerDependencies: react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - '@react-stately/data@3.13.0': - resolution: {integrity: sha512-7LYPxVbWB6tvmLYKO19H5G5YtXV6eKCSXisOUiL9fVnOcGOPDK5z310sj9TP5vaX7zVPtwy0lDBUrZuRfhvQIQ==} + '@react-stately/data@3.13.2': + resolution: {integrity: sha512-xdCqR8dJ3cnvO8EdCeuQ335dOuBqEV4z/3LnpxmR11gyn8dWwtY5O794g5+AS0KqCgd9W0v7iBrRywq5UT2pCA==} peerDependencies: react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - '@react-stately/datepicker@3.14.1': - resolution: {integrity: sha512-ad3IOrRppy/F8FZpznGacsaWWHdzUGZ4vpymD+y6TYeQ+RQvS9PLA5Z1TanH9iqLZgkf6bvVggJFg/hhDh2hmg==} + '@react-stately/datepicker@3.15.0': + resolution: {integrity: sha512-OuBx+h802CoANy6KNR6XuZCndiyRf9vpB32CYZX86nqWy21GSTeT73G41ze5cAH88A/6zmtpYK24nTlk8bdfWA==} peerDependencies: react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - '@react-stately/disclosure@3.0.4': - resolution: {integrity: sha512-RE4hYnDYgsd5bi01z/hZHShRGKxW++xCA6PCufxtipc1sxZGUF4Sb1tTSIxOjh1dq5iDVdrAQAS6en0weaGgLA==} + '@react-stately/disclosure@3.0.6': + resolution: {integrity: sha512-tR2IzcS7JbgAXy9U0gxQQGRHKIqgC7nj3xsY5U9QGCE1BKzwf/84iDE63AXpLRje31yuYzwXsJs6UrE9wSjb3g==} peerDependencies: react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - '@react-stately/dnd@3.5.4': - resolution: {integrity: sha512-YkvkehpsSeGZPH7S7EYyLchSxZPhzShdf9Zjh6UAsM7mAcxjRsChMqsf6zuM+l0jgMo40Ka1mvwDYegz92Qkyg==} + '@react-stately/dnd@3.6.1': + resolution: {integrity: sha512-cbBLptL+tpXFQ0oU0v6GBtSvzP0doohyhCIr8pOzk6aYutFI0c5JZw8LGoKN/GLfXkm7iPyrfCKeKqDlDTHCzQ==} peerDependencies: react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - '@react-stately/flags@3.1.1': - resolution: {integrity: sha512-XPR5gi5LfrPdhxZzdIlJDz/B5cBf63l4q6/AzNqVWFKgd0QqY5LvWJftXkklaIUpKSJkIKQb8dphuZXDtkWNqg==} + '@react-stately/flags@3.1.2': + resolution: {integrity: sha512-2HjFcZx1MyQXoPqcBGALwWWmgFVUk2TuKVIQxCbRq7fPyWXIl6VHcakCLurdtYC2Iks7zizvz0Idv48MQ38DWg==} - '@react-stately/form@3.1.4': - resolution: {integrity: sha512-A6GOaZ9oEIo5/XOE+JT9Z8OBt0osIOfes4EcIxGS1C9ght/Smg0gNcIJ2/Wle8qmro4RoJcza2yJ+EglVOuE0w==} + '@react-stately/form@3.2.0': + resolution: {integrity: sha512-PfefxvT7/BIhAGpD4oQpdcxnL8cfN0ZTQxQq+Wmb9z3YzK1oM8GFxb8eGdDRG71JeF8WUNMAQVZFhgl00Z/YKg==} peerDependencies: react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - '@react-stately/grid@3.11.2': - resolution: {integrity: sha512-P0vfK5B1NW8glYD6QMrR2X/7UMXx2J8v48QIQV6KgLZjFbyXhzRb+MY0BoIy4tUfJL0yQU2GKbKKVSUIQxbv0g==} + '@react-stately/grid@3.11.4': + resolution: {integrity: sha512-oaXFSk2eM0PJ0GVniGA0ZlTpAA0AL0O4MQ7V3cHqZAQbwSO0n2pT31GM0bSVnYP/qTF5lQHo3ECmRQCz0fVyMw==} peerDependencies: react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - '@react-stately/list@3.12.2': - resolution: {integrity: sha512-XPGvdPidOV4hnpmaUNc4C/1jX7ZhBwmAI9p6bEXDA3du3XrWess6MWcaQvPxXbrZ6ZX8/OyOC2wp7ixJoJRGyA==} + '@react-stately/list@3.12.4': + resolution: {integrity: sha512-r7vMM//tpmagyNlRzl2NFPPtx+az5R9pM6q7aI4aBf6/zpZt2eX2UW5gaDTGlkQng7r6OGyAgJD52jmGcCJk7Q==} peerDependencies: react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - '@react-stately/menu@3.9.4': - resolution: {integrity: sha512-sqYcSBuTEtCebZuByUou2aZzwlnrrOlrvmGwFNJy49N3LXXXPENCcCERuWa8TE9eBevIVTQorBZlID6rFG+wdQ==} + '@react-stately/menu@3.9.6': + resolution: {integrity: sha512-2rVtgeVAiyr7qL8BhmCK/4el49rna/5kADRH5NfPdpXw8ZzaiiHq2RtX443Txj7pUU82CJWQn+CRobq7k6ZTEw==} peerDependencies: react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - '@react-stately/numberfield@3.9.12': - resolution: {integrity: sha512-E56RuRRdu/lzd8e5aEifP4n8CL/as0sZqIQFSyMv/ZUIIGeksqy+zykzo01skaHKY8u2NixrVHPVDtvPcRuooA==} + '@react-stately/numberfield@3.10.0': + resolution: {integrity: sha512-6C8ML4/e2tcn01BRNfFLxetVaWwz0n0pVROnVpo8p761c6lmTqohqEMNcXCVNw9H0wsa1hug2a1S5PcN2OXgag==} peerDependencies: react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - '@react-stately/overlays@3.6.16': - resolution: {integrity: sha512-+Ve/TBlUNg3otVC4ZfCq1a8q8FwC7xNebWkVOCGviTqiYodPCGqBwR9Z1xonuFLF/HuQYqALHHTOZtxceU+nVQ==} + '@react-stately/overlays@3.6.18': + resolution: {integrity: sha512-g8n2FtDCxIg2wQ09R7lrM2niuxMPCdP17bxsPV9hyYnN6m42aAKGOhzWrFOK+3phQKgk/E1JQZEvKw1cyyGo1A==} peerDependencies: react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - '@react-stately/radio@3.10.13': - resolution: {integrity: sha512-q7UKcVYY7rqpxKfYRzvKVEqFhxElDFX2c+xliZQtjXuSexhxRb2xjEh+bDkhzbXzrJkrBT6VmE/rSYPurC3xTw==} + '@react-stately/radio@3.11.0': + resolution: {integrity: sha512-hsCmKb9e/ygmzBADFYIGpEQ43LrxjWnlKESgxphvlv0Klla4d6XLAYSFOTX1kcjSztpvVWrdl4cIfmKVF1pz2g==} peerDependencies: react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - '@react-stately/searchfield@3.5.12': - resolution: {integrity: sha512-RC3QTEPVNUbgtuqzpwPUfbV9UkUC1j4XkHoynWDbMt0bE0tPe2Picnl0/r/kq6MO527idV6Ur4zuOF4x9a97LQ==} + '@react-stately/searchfield@3.5.14': + resolution: {integrity: sha512-OAycTULyF/UWy7Odyzw5lZV2yWH+Cy7fWsZxDUedeUs4Aiwbb6D4ph9pGb0RvhD4S3+B490a2ijGgfsaDeorMA==} peerDependencies: react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - '@react-stately/select@3.6.13': - resolution: {integrity: sha512-saZo67CreQZPdmqvz9+P6N4kjohpwdVncH98qBi0Q2FvxGAMnpJQgx97rtfDvnSziST5Yx1JnMI4kSSndbtFwg==} + '@react-stately/select@3.7.0': + resolution: {integrity: sha512-OWLOCKBEj8/XI+vzBSSHQAJu0Hf9Xl/flMhYh47f2b45bO++DRLcVsi8nycPNisudvK6xMQ8a/h4FwjePrCXfg==} peerDependencies: react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - '@react-stately/selection@3.20.2': - resolution: {integrity: sha512-Fw6nnG+VKMsncsY4SNxGYOhnHojVFzFv+Uhy6P39QBp6AXtSaRKMg2VR4MPxQ7XgOjHh5ZuSvCY1RwocweqjwQ==} + '@react-stately/selection@3.20.4': + resolution: {integrity: sha512-Hxmc6NtECStYo+Z2uBRhQ80KPhbSF7xXv9eb4qN8dhyuSnsD6c0wc6oAJsv18dldcFz8VrD48aP/uff9mj0hxQ==} peerDependencies: react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - '@react-stately/slider@3.6.4': - resolution: {integrity: sha512-6SdG0VJZLMRIBnPjqkbIsdyQcW9zJ5Br716cl/7kLT9owiIwMJiAdjdYHab5+8ShWzU2D8Ae+LdQk8ZxIiIjkg==} + '@react-stately/slider@3.7.0': + resolution: {integrity: sha512-quxqkyyxrxLELYEkPrIrucpVPdYDK8yyliv/vvNuHrjuLRIvx6UmssxqESp2EpZfwPYtEB29QXbAKT9+KuXoCQ==} peerDependencies: react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - '@react-stately/table@3.14.2': - resolution: {integrity: sha512-SqE5A/Ve5H2ApnAblMGBMGRzY7cgdQmNPzXB8tGVc38NsC/STmMkq9m54gAl8dBVNbLzzd6HJBe9lqz5keYIhQ==} + '@react-stately/table@3.14.4': + resolution: {integrity: sha512-uhwk8z3DemozD+yHBjSa4WyxKczpDkxhJhW7ZVOY+1jNuTYxc9/JxzPsHICrlDVV8EPWwwyMUz8eO/8rKN7DbA==} peerDependencies: react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - '@react-stately/tabs@3.8.2': - resolution: {integrity: sha512-lNpby7zUVdAeqo3mjGdPBxppEskOLyqR82LWBtP8Xg4olnjA5RmDFOuoJkIFttDX689zamjN3OE+Ra6WWgJczg==} + '@react-stately/tabs@3.8.4': + resolution: {integrity: sha512-2Tr4yXkcNDLyyxrZr+c4FnAW/wkSim3UhDUWoOgTCy3mwlQzdh9r5qJrOZRghn1QvF7p8Ahp7O7qxwd2ZGJrvQ==} peerDependencies: react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - '@react-stately/toast@3.1.0': - resolution: {integrity: sha512-9W2+evz+EARrjkR1QPLlOL5lcNpVo6PjMAIygRSaCPJ6ftQAZ6B+7xTFGPFabWh83gwXQDUgoSwC3/vosvxZaQ==} + '@react-stately/toast@3.1.2': + resolution: {integrity: sha512-HiInm7bck32khFBHZThTQaAF6e6/qm57F4mYRWdTq8IVeGDzpkbUYibnLxRhk0UZ5ybc6me+nqqPkG/lVmM42Q==} peerDependencies: react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - '@react-stately/toggle@3.8.4': - resolution: {integrity: sha512-JbKoXhkJ5P5nCrNXChMos3yNqkIeGXPDEMS/dfkHlsjQYxJfylRm4j/nWoDXxxkUmfkvXcNEMofMn9iO1+H0DQ==} + '@react-stately/toggle@3.9.0': + resolution: {integrity: sha512-1URd97R5nbFF9Hc1nQBhvln55EnOkLNz6pjtXU7TCnV4tYVbe+tc++hgr5XRt6KAfmuXxVDujlzRc6QjfCn0cQ==} peerDependencies: react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - '@react-stately/tooltip@3.5.4': - resolution: {integrity: sha512-HxNTqn9nMBuGbEVeeuZyhrzNbyW7sgwk+8o0mN/BrMrk7E/UBhyL2SUxXnAUQftpTjX+29hmx1sPhIprIDzR3Q==} + '@react-stately/tooltip@3.5.6': + resolution: {integrity: sha512-BnOtE7726t1sCKPGbwzzEtEx40tjpbJvw5yqpoVnAV0OLfrXtLVYfd7tWRHmZOYmhELaUnY+gm3ZFYtwvnjs+A==} peerDependencies: react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - '@react-stately/tree@3.8.10': - resolution: {integrity: sha512-sMqBRKAAZMiXJwlzAFpkXqUaGlNBfKnL8usAiKdoeGcLLJt2Ni9gPoPOLBJSPqLOAFCgLWtr5IYjdhel9aXRzQ==} + '@react-stately/tree@3.9.1': + resolution: {integrity: sha512-dyoPIvPK/cs03Tg/MQSODi2kKYW1zaiOG9KC2P0c8b44mywU2ojBKzhSJky3dBkJ4VVGy7L+voBh50ELMjEa8Q==} peerDependencies: react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - '@react-stately/utils@3.10.6': - resolution: {integrity: sha512-O76ip4InfTTzAJrg8OaZxKU4vvjMDOpfA/PGNOytiXwBbkct2ZeZwaimJ8Bt9W1bj5VsZ81/o/tW4BacbdDOMA==} + '@react-stately/utils@3.10.8': + resolution: {integrity: sha512-SN3/h7SzRsusVQjQ4v10LaVsDc81jyyR0DD5HnsQitm/I5WDpaSr2nRHtyloPFU48jlql1XX/S04T2DLQM7Y3g==} peerDependencies: react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - '@react-types/breadcrumbs@3.7.13': - resolution: {integrity: sha512-x94KEZaLIeHt9lqAkuaOopX5+rqCTMSHsciThUsBHK7QT64zrw6x2G1WKQ4zB4h52RGF5b+3sFXeR4bgX2sVLQ==} + '@react-types/breadcrumbs@3.7.15': + resolution: {integrity: sha512-0RsymrsOAsx443XRDJ1krK+Lusr4t0qqExmzFe7/XYXOn/RbGKjzSdezsoWfTy8Hjks0YbfQPVKnNxg9LKv4XA==} peerDependencies: react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - '@react-types/button@3.12.1': - resolution: {integrity: sha512-z87stl4llWTi4C5qhUK1PKcEsG59uF/ZQpkRhMzX0KfgXobJY6yiIrry2xrpnlTPIVST6K1+kARhhSDOZ8zhLw==} + '@react-types/button@3.13.0': + resolution: {integrity: sha512-hwvcNnBjDeNvWheWfBhmkJSzC48ub5rZq0DnpemB3XKOvv5WcF9p6rrQZsQ3egNGkh0Z+bKfr2QfotgOkccHSw==} peerDependencies: react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - '@react-types/calendar@3.7.1': - resolution: {integrity: sha512-a/wGT9vZewPNL72Xni8T/gv4IS2w6iRtryqMF425OL+kaCQrxJYlkDxb74bQs9+k9ZYabrxJgz9vFcFnY7S9gw==} + '@react-types/calendar@3.7.3': + resolution: {integrity: sha512-gofPgVpSawJ0iGO01SbVH46u3gdykHlGT5BfGU1cRnsOR2tJX38dekO/rnuGsMQYF0+kU6U9YVae+XoOFJNnWg==} peerDependencies: react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - '@react-types/checkbox@3.9.4': - resolution: {integrity: sha512-fU3Q1Nw+zbXKm68ba8V7cQzpiX0rIiAUKrBTl2BK97QiTlGBDvMCf4TfEuaNoGbJq+gx+X3n/3yr6c3IAb0ZIg==} + '@react-types/checkbox@3.10.0': + resolution: {integrity: sha512-DJ84ilBDvZddE/Sul97Otee4M6psrPRaJm2a1Bc7M3Y5UKo6d6RGXdcDarRRpbnS7BeAbVanKiMS2ygI9QHh9g==} peerDependencies: react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - '@react-types/color@3.0.5': - resolution: {integrity: sha512-72uZ0B3EcaC2DGOpnhwHSVxcvQ3UDNSVR2gVx7PgUCGlEjhnn9i0UErIP8ZzV2RsAvjK6MrGs7ZCwZtl+LxCcg==} + '@react-types/color@3.1.0': + resolution: {integrity: sha512-mqx76zdq/GyI7hdx+NTdTrCG6qmf1Uk3w/zWKF80OAesLqqs9XavQQZlRPu1Cg/fHiAHIBOLYTnLf8w+T2IMsw==} peerDependencies: react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - '@react-types/combobox@3.13.5': - resolution: {integrity: sha512-wqHBF0YDkrp4Ylyxpd3xhnDECe5eao27bsu+4AvjlVKtaxaoppNq2MwSzkuSSS/GEUXT6K9DDjrGFcp07ad5gA==} + '@react-types/combobox@3.13.7': + resolution: {integrity: sha512-R7MQ4Qm4fryo6FCg3Vo/l9wxkYVG05trsLbxzMvvxCMkpcoHUPhy8Ll33eXA3YP74Rs/IaM9d0d/amSUZ4M9wg==} peerDependencies: react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - '@react-types/datepicker@3.12.1': - resolution: {integrity: sha512-+wv57fVd6Y/+KnHNEmVzfrQtWs85Ga1Xb63AIkBk+E294aMqFYqRg0dQds6V/qrP758TWnXUrhKza1zMbjHalw==} + '@react-types/datepicker@3.13.0': + resolution: {integrity: sha512-AG/iGcdQ5SVSjw8Ta7bCdGNkMda+e+Z7lOHxDawL44SII8LtZroBDlaCpb178Tvo17bBfJ6TvWXlvSpBY8GPRg==} peerDependencies: react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - '@react-types/dialog@3.5.18': - resolution: {integrity: sha512-g18CzT5xmiX/numpS6MrOGEGln8Xp9rr+zO70Dg+jM4GBOjXZp3BeclYQr9uisxGaj2uFLnORv9gNMMKxLNF6A==} + '@react-types/dialog@3.5.20': + resolution: {integrity: sha512-ebn8jW/xW/nmRATaWIPHVBIpIFWSaqjrAxa58f5TXer5FtCD9pUuzAQDmy/o22ucB0yvn6Kl+fjb3SMbMdALZQ==} peerDependencies: react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - '@react-types/grid@3.3.2': - resolution: {integrity: sha512-NwfydUbPc1zVi/Rp7+oRN2+vE1xMokc2J+nr0VcHwFGt1bR1psakHu45Pk/t763BDvPr/A3xIHc1rk3eWEhxJw==} + '@react-types/grid@3.3.4': + resolution: {integrity: sha512-8XNn7Czhl+D1b2zRwdO8c3oBJmKgevT/viKJB4qBVFOhK0l/p3HYDZUMdeclvUfSt4wx4ASpI7MD3v1vmN54oA==} peerDependencies: react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - '@react-types/link@3.6.1': - resolution: {integrity: sha512-IZDSc10AuVKe7V8Te+3q8d220oANE4N43iljQe3yHg7GZOfH/51bv8FPUukreLs1t2fgtGeNAzG71Ep+j/jXIw==} + '@react-types/link@3.6.3': + resolution: {integrity: sha512-XIYEl9ZPa5mLy8uGQabdhPaFVmnvxNSYF59t0vs/IV0yxeoPvrjKjRAbXS+WP9zYMXIkHYNYYucriCkqKhotJA==} peerDependencies: react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - '@react-types/listbox@3.7.0': - resolution: {integrity: sha512-26Lp0Gou502VJLDSrIpMg7LQuVHznxzyuSY/zzyNX9eopukXvHn682u90fwDqgmZz7dzxUOWtuwDea+bp/UjtA==} + '@react-types/listbox@3.7.2': + resolution: {integrity: sha512-MRpBhApR1jJNASoVWsEvH5vf89TJw+l9Lt1ssawop0K2iYF5PmkthRdqcpYcTkFu5+f5QvFchVsNJ3TKD4cf2A==} peerDependencies: react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - '@react-types/menu@3.10.1': - resolution: {integrity: sha512-wkyWzIqaCbUYiD7YXr8YvdimB1bxQHqgj6uE4MKzryCbVqb4L8fRUM0V6AHkQS1TxBYNkNn1h4g7XNd5Vmyf3Q==} + '@react-types/menu@3.10.3': + resolution: {integrity: sha512-Vd3t7fEbIOiq7kBAHaihfYf+/3Fuh0yK2KNjJ70BPtlAhMRMDVG3m0PheSTm3FFfj+uAdQdfc2YKPnMBbWjDuQ==} peerDependencies: react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - '@react-types/meter@3.4.9': - resolution: {integrity: sha512-Jhd873zc/Bx/86NB9nasMUWc013VnURVtMYbbkuRWiFr/ZoEvZzO1uoSIXf+Sob4xpiVhT/ltvJZTK4t4B9lTg==} + '@react-types/meter@3.4.11': + resolution: {integrity: sha512-c4jnDWFxDp09fNpCDrq6l2RxOxcolmf/frvdtVA/d4SGvfEOoqeUakpVDuOqDD0bU58tQPG3fqT2zH8vpWiJew==} peerDependencies: react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - '@react-types/numberfield@3.8.11': - resolution: {integrity: sha512-D66Bop7M3JKzBV2vsECsVYfPrx8eRIx4/K2KLo/XjwMA7C34+Ou07f/bnD1TQQ/wr6XwiFxZTi6JsKDwnST+9Q==} + '@react-types/numberfield@3.8.13': + resolution: {integrity: sha512-zRSqInmxOTQJZt2fjAhuQK3Wa1vCOlKsRzUVvxTrE8gtQxlgFxirmobuUnjTEhwkFyb0bq8GvVfQV1E95Si2yw==} peerDependencies: react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - '@react-types/overlays@3.8.15': - resolution: {integrity: sha512-ppDfezvVYOJDHLZmTSmIXajxAo30l2a1jjy4G65uBYy8J8kTZU7mcfQql5Pii1TwybcNMsayf2WtPItiWmJnOA==} + '@react-types/overlays@3.9.0': + resolution: {integrity: sha512-T2DqMcDN5p8vb4vu2igoLrAtuewaNImLS8jsK7th7OjwQZfIWJn5Y45jSxHtXJUddEg1LkUjXYPSXCMerMcULw==} peerDependencies: react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - '@react-types/progress@3.5.12': - resolution: {integrity: sha512-wvhFz6vdlfKBtnzKvD/89N+0PF3yPQ+IVFRQvZ2TBrP7nF+ZA2pNLcZVcEYbKjHzmvEZRGu//ePC9hRJD9K30w==} + '@react-types/progress@3.5.14': + resolution: {integrity: sha512-GeGrjOeHR/p5qQ1gGlN68jb+lL47kuddxMgdR1iEnAlYGY4OtJoEN/EM5W2ZxJRKPcJmzdcY/p/J0PXa8URbSg==} peerDependencies: react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - '@react-types/radio@3.8.9': - resolution: {integrity: sha512-l4uzlxmGGuR8IkWrMYdKj1sc3Pgo/LdfEGuIgK+d8kjPu0AZcnSgp5Oz035bCosZUabY6dEWxQHIoAH2zN7YZA==} + '@react-types/radio@3.9.0': + resolution: {integrity: sha512-phndlgqMF6/9bOOhO3le00eozNfDU1E7OHWV2cWWhGSMRFuRdf7/d+NjVtavCX75+GJ50MxvXk+KB0fjTuvKyg==} peerDependencies: react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - '@react-types/searchfield@3.6.2': - resolution: {integrity: sha512-XQRQyJLNC9uLyCq+97eiqeQuM6+dCMrHu6aH6KSVt1Xh6HMmdx/TdSf6JrMkN+1xSxcW3lDE2iSf3jXDT87gag==} + '@react-types/searchfield@3.6.4': + resolution: {integrity: sha512-gRVWnRHf7pqU0lBVlkU6XsLxvaWTPnn0EomddIBCVh0msVIyvEea8CXJppu7EpvRh+grNpiMEYeijQ+u8hixlQ==} peerDependencies: react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - '@react-types/select@3.9.12': - resolution: {integrity: sha512-qo+9JS1kfMxuibmSmMp0faGKbeVftYnSk1f7Rh5PKi4tzMe3C0A9IAr27hUOfWeJMBOdetaoTpYmoXW6+CgW3g==} + '@react-types/select@3.10.0': + resolution: {integrity: sha512-+xJwYWJoJTCGsaiPAqb6QB79ub1WKIHSmOS9lh/fPUXfUszVs05jhajaN9KjrKmnXds5uh4u6l1JH5J1l2K5pw==} peerDependencies: react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - '@react-types/shared@3.29.1': - resolution: {integrity: sha512-KtM+cDf2CXoUX439rfEhbnEdAgFZX20UP2A35ypNIawR7/PFFPjQDWyA2EnClCcW/dLWJDEPX2U8+EJff8xqmQ==} + '@react-types/shared@3.31.0': + resolution: {integrity: sha512-ua5U6V66gDcbLZe4P2QeyNgPp4YWD1ymGA6j3n+s8CGExtrCPe64v+g4mvpT8Bnb985R96e4zFT61+m0YCwqMg==} peerDependencies: react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - '@react-types/slider@3.7.11': - resolution: {integrity: sha512-uNhNLhVrt/2teXBOJSoZXyXg308A72qe1HOmlGdJcnh8iXA35y5ZHzeK1P6ZOJ37Aeh7bYGm3/UdURmFgSlW7w==} + '@react-types/slider@3.8.0': + resolution: {integrity: sha512-eN6Fd3YCPseGfvfOJDtn9Lh9CrAb8tF3cTAprEcpnGrsxmdW9JQpcuciYuLM871X5D2fYg4WaYMpZaiYssjxBQ==} peerDependencies: react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - '@react-types/switch@3.5.11': - resolution: {integrity: sha512-PJbZHwlE98OSuLzI6b1ei6Qa+FaiwlCRH3tOTdx/wPSdqmD3mRWEn7E9ftM6FC8hnxl/LrGLszQMT62yEQp5vQ==} + '@react-types/switch@3.5.13': + resolution: {integrity: sha512-C2EhKBu7g7xhKboPPxhyKtROEti80Ck7TBnKclXt0D4LiwbzpR3qGfuzB+7YFItnhiauP7Uxe+bAfM5ojjtm9w==} peerDependencies: react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - '@react-types/table@3.13.0': - resolution: {integrity: sha512-kn+OsEWJfUSSb4N4J0yl+tqx5grDpcaWcu2J8hA62hQCr/Leuj946ScYaKA9a/p0MAaOAaeCWx/Zcss6F8gJIQ==} + '@react-types/table@3.13.2': + resolution: {integrity: sha512-3/BpFIWHXTcGgQEfip87gMNCWPtPNsc3gFkW4qtsevQ+V0577KyNyvQgvFrqMZKnvz3NWFKyshBb7PTevsus4Q==} peerDependencies: react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - '@react-types/tabs@3.3.15': - resolution: {integrity: sha512-VLgh9YLQdS4FQSk0sGTNHEVN2jeC0fZvOqEFHaEDgDyDgVOukxYuHjqVIx2IavYu1yNBrGO2b6P4M6dF+hcgwQ==} + '@react-types/tabs@3.3.17': + resolution: {integrity: sha512-cLcdxWNJe0Kf/pKuPQbEF9Fl+axiP4gB/WVjmAdhCgQ5LCJw2dGcy1LI1SXrlS3PVclbnujD1DJ8z1lIW4Tmww==} peerDependencies: react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - '@react-types/textfield@3.12.2': - resolution: {integrity: sha512-dMm0cGLG5bkJYvt6lqXIty5HXTZjuIpa9I8jAIYua//J8tESAOE9BA285Zl43kx7cZGtgrHKHVFjITDLNUrNhA==} + '@react-types/textfield@3.12.4': + resolution: {integrity: sha512-cOgzI1dT8X1JMNQ9u2UKoV2L28ROkbFEtzY9At0MqTZYYSxYp3Q7i+XRqIBehu8jOMuCtN9ed9EgwVSfkicyLQ==} peerDependencies: react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - '@react-types/tooltip@3.4.17': - resolution: {integrity: sha512-yjySKA1uzJAbio+xGv03DUoWIajteqtsXMd4Y3AJEdBFqSYhXbyrgAxw0oJDgRAgRxY4Rx5Hrhvbt/z7Di94QQ==} + '@react-types/tooltip@3.4.19': + resolution: {integrity: sha512-OR/pwZReWbCIxuHJYB1L4fTwliA+mzVvUJMWwXIRy6Eh5d07spS3FZEKFvOgjMxA1nyv5PLf8eyr5RuuP1GGAA==} peerDependencies: react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 @@ -1884,8 +1708,8 @@ packages: '@rolldown/pluginutils@1.0.0-beta.33': resolution: {integrity: sha512-she25NCG6NoEPC/SEB4pHs5STcnfI4VBFOzjeI63maSPrWME5J2XC8ogrBgp8NaE/xzj28/kbpSaebiMvFRj+w==} - '@rollup/pluginutils@5.1.4': - resolution: {integrity: sha512-USm05zrsFxYLPdWWq+K3STlWiT/3ELn3RcV5hJMghpeAIhxfsUIg6mt12CBJBInWMV4VneoV7SfGv8xIwo2qNQ==} + '@rollup/pluginutils@5.2.0': + resolution: {integrity: sha512-qWJ2ZTbmumwiLFomfzTyt5Kng4hwPi9rwCYN4SHb6eaRU1KNO4ccxINHr/VhH4GgPlt1XfSTLX2LBTme8ne4Zw==} engines: {node: '>=14.0.0'} peerDependencies: rollup: ^1.20.0||^2.0.0||^3.0.0||^4.0.0 @@ -2183,61 +2007,58 @@ packages: '@types/web-bluetooth@0.0.21': resolution: {integrity: sha512-oIQLCGWtcFZy2JW77j9k8nHzAOpqMHLQejDA48XXMWH6tjCQHz5RCFz1bzsmROyL6PUm+LLnUiI4BCn221inxA==} - '@types/websocket@1.0.10': - resolution: {integrity: sha512-svjGZvPB7EzuYS94cI7a+qhwgGU1y89wUgjT6E2wVUfmAGIvRfT7obBvRtnhXCSsoMdlG4gBFGE7MfkIXZLoww==} - '@types/ws@8.18.1': resolution: {integrity: sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==} - '@typescript/native-preview-darwin-arm64@7.0.0-dev.20250818.1': - resolution: {integrity: sha512-LVYgDjaOHMxy2OKG9WKPgYKcUFB/VxbhXHwMjPGPZMpx96YwgeVnhVY4hTHE/rIfUm1tdeUXSSDxUMUv6KMJZw==} + '@typescript/native-preview-darwin-arm64@7.0.0-dev.20250821.1': + resolution: {integrity: sha512-b3L13MVLvigBTf8jc6fDQnbWi4RC5XoXgaakftm+gQtFNtriU5Iqfl3ROBjiwPgUMmuXfjnSpau1zKwDY8M5zw==} engines: {node: '>=20.6.0'} cpu: [arm64] os: [darwin] - '@typescript/native-preview-darwin-x64@7.0.0-dev.20250818.1': - resolution: {integrity: sha512-z9FMbVVnppjsES3kmzIekozyJLp0TwYwTfvP+f5BX/2W5KZ+06GKZf5rI7ZaEoWscFmD/QCnfCe/k8LZlNhmXg==} + '@typescript/native-preview-darwin-x64@7.0.0-dev.20250821.1': + resolution: {integrity: sha512-EIFcmzaFhM176D6jHD1aJ1FGos9sTokWeFvVfJVDSk27box/xMNtfwM7OXg17OIWoF2IDC6zpyOV4mOs+9ADCg==} engines: {node: '>=20.6.0'} cpu: [x64] os: [darwin] - '@typescript/native-preview-linux-arm64@7.0.0-dev.20250818.1': - resolution: {integrity: sha512-80gfjzmvyJRPJAl7RSXona76xyrkh8343ebUcFzia/yFL9x1dij3i+jAA7LI0hrjcp5+27KtSTfB99mxdmuelA==} + '@typescript/native-preview-linux-arm64@7.0.0-dev.20250821.1': + resolution: {integrity: sha512-V1Ok+gnzLrrMIjUdIOf8uiNEy7t9OJ7/x3G8/0F/WRfVyT0p1Tcv70g1nEStiMk+60wjyyP76F81GDn9wXg0aQ==} engines: {node: '>=20.6.0'} cpu: [arm64] os: [linux] - '@typescript/native-preview-linux-arm@7.0.0-dev.20250818.1': - resolution: {integrity: sha512-XVYgb2d2jtf/IRbK7b0PsIWCBDzylc27UXnZj3pGzSHdkBSral+sb3X3bZivWnDMy4jSKdSYq1MS/4YASug41w==} + '@typescript/native-preview-linux-arm@7.0.0-dev.20250821.1': + resolution: {integrity: sha512-oFv7qu8uLmJtHcebQYbP6s7tG3kq+hstXHkOU76d2Ke1F1kJDaQmOz4MU1nY/AVtOs6dqQPPJ0jfpnrgsq8fYg==} engines: {node: '>=20.6.0'} cpu: [arm] os: [linux] - '@typescript/native-preview-linux-x64@7.0.0-dev.20250818.1': - resolution: {integrity: sha512-mo7v4rkuwg239i8Rmyvjgv2XrWCIjMGn+L0fX765Yxbw9wSPlvT8IkmJOTGoxIGByjeId6AKTgYHuJZb3uUwxQ==} + '@typescript/native-preview-linux-x64@7.0.0-dev.20250821.1': + resolution: {integrity: sha512-l5UPAxtM10B0nECUHpmSwcxxDZJhGB44uaFaMpKrDVC3Cl4/4M//HgRcYA83+VSDA+Nz1naanXBNTWFgTY6ygw==} engines: {node: '>=20.6.0'} cpu: [x64] os: [linux] - '@typescript/native-preview-win32-arm64@7.0.0-dev.20250818.1': - resolution: {integrity: sha512-yeHHlhotR8q6OhBKLX6MSy6bNenE6pxuAUeXI5Ym5Mrm/ZxVcEd8iFusA3QWwa4Fx0a+WKNNOS+LUpGNAlVogQ==} + '@typescript/native-preview-win32-arm64@7.0.0-dev.20250821.1': + resolution: {integrity: sha512-79ICW5hDFZRjNDXpvKdnCYuNaDo/SlGrL2x2cwqm7CXor/bh1QJ9v993AQHP2ZRc13GxHT+SIIUi4FXVhy47ug==} engines: {node: '>=20.6.0'} cpu: [arm64] os: [win32] - '@typescript/native-preview-win32-x64@7.0.0-dev.20250818.1': - resolution: {integrity: sha512-E3XzKN0bLSjfMy7/gklqotdDN8Ih5czWiq1Zll8KfFEHSfmUJ1x3/SP76qF+zGk2n2b851b81lWj7VplHkk99A==} + '@typescript/native-preview-win32-x64@7.0.0-dev.20250821.1': + resolution: {integrity: sha512-n9DOZc8e3azx5FedX5U4llAsUF250Z0TOfNxnB/E9njWCkoh8mguKFMM+RQpoppV2jl7nhfpzlNJ1Vgp1OA45Q==} engines: {node: '>=20.6.0'} cpu: [x64] os: [win32] - '@typescript/native-preview@7.0.0-dev.20250818.1': - resolution: {integrity: sha512-fJ2mjyx0Sk0ylOzBTjA3ubsNDCLshNcGFmS4Ouv1wx4Sf/G/o2YpyGw+ImsG+pXumpVC3nuAlLjvqoM+VpSsyQ==} + '@typescript/native-preview@7.0.0-dev.20250821.1': + resolution: {integrity: sha512-o6rAnWoRc+IDZ+sxblSn2ZEbdhl5ggK4IvYcNrYAF1phiZKC3PS2WT12O0EJ9MT9Xtu3+32gxqJZ4YORaEIGKQ==} engines: {node: '>=20.6.0'} hasBin: true - '@uiw/codemirror-extensions-basic-setup@4.23.12': - resolution: {integrity: sha512-l9vuiXOTFDBetYrRLDmz3jDxQHDsrVAZ2Y6dVfmrqi2AsulsDu+y7csW0JsvaMqo79rYkaIZg8yeqmDgMb7VyQ==} + '@uiw/codemirror-extensions-basic-setup@4.25.1': + resolution: {integrity: sha512-zxgA2QkvP3ZDKxTBc9UltNFTrSeFezGXcZtZj6qcsBxiMzowoEMP5mVwXcKjpzldpZVRuY+JCC+RsekEgid4vg==} peerDependencies: '@codemirror/autocomplete': '>=6.0.0' '@codemirror/commands': '>=6.0.0' @@ -2247,29 +2068,29 @@ packages: '@codemirror/state': '>=6.0.0' '@codemirror/view': '>=6.0.0' - '@uiw/codemirror-theme-github@4.23.12': - resolution: {integrity: sha512-yxgycQxA1fNVdrjIZ7H7pq+9Q+BeKLmD5oq5oOlw7kVJrnToOMBylv5oIWplVd2s2LFo47lIhWrVC9Ay3b6Baw==} + '@uiw/codemirror-theme-github@4.25.1': + resolution: {integrity: sha512-l70gUrYAxHlpMWmG8A/I4PN5h5zzPxV+c9pRtsw+VtdmdgVWMh3JaSsuFZlf6h311s+/WB2FMZKdFw5SwrFWaA==} - '@uiw/codemirror-theme-xcode@4.23.12': - resolution: {integrity: sha512-9KnTjhXFqwGphQl18CGRzRc6yB8SZkyv4L7u+j1QOq/nFmk3i44HKuHiuKY+J+XfFq3Cmxg629o3CK4ecMsvUQ==} + '@uiw/codemirror-theme-xcode@4.25.1': + resolution: {integrity: sha512-Pgf6jNz6T8+h7e94xJN0VpyJuJjbQSH/3xWEmEwP22d+Fkupsf+Bjz2oyfDjKpHIdMhIzSlcPF1qrFL7H5m5Lw==} - '@uiw/codemirror-themes@4.23.12': - resolution: {integrity: sha512-8etEByfS9yttFZW0rcWhdZc7/JXJKRWlU5lHmJCI3GydZNGCzydNA+HtK9nWKpJUndVc58Q2sqSC5OIcwq8y6A==} + '@uiw/codemirror-themes@4.25.1': + resolution: {integrity: sha512-6o8tQ8bdq14RuVFpZ7l9u8KnuPq824uG3U1VV933Uhv8mfaxaoaOQSjv6T2bQUPhjH6ZlEu5+tAMkOfIL21eIQ==} peerDependencies: '@codemirror/language': '>=6.0.0' '@codemirror/state': '>=6.0.0' '@codemirror/view': '>=6.0.0' - '@uiw/react-codemirror@4.23.12': - resolution: {integrity: sha512-yseqWdzoAAGAW7i/NiU8YrfSLVOEBjQvSx1KpDTFVV/nn0AlAZoDVTIPEBgdXrPlVUQoCrwgpEaj3uZCklk9QA==} + '@uiw/react-codemirror@4.25.1': + resolution: {integrity: sha512-eESBKHndoYkaEGlKCwRO4KrnTw1HkWBxVpEeqntoWTpoFEUYxdLWUYmkPBVk4/u8YzVy9g91nFfIRpqe5LjApg==} peerDependencies: '@babel/runtime': '>=7.11.0' '@codemirror/state': '>=6.0.0' '@codemirror/theme-one-dark': '>=6.0.0' '@codemirror/view': '>=6.0.0' codemirror: '>=6.0.0' - react: '>=16.8.0' - react-dom: '>=16.8.0' + react: '>=17.0.0' + react-dom: '>=17.0.0' '@ungap/structured-clone@1.3.0': resolution: {integrity: sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==} @@ -2438,11 +2259,6 @@ packages: '@xterm/xterm@5.5.0': resolution: {integrity: sha512-hqJHYaQb5OptNunnyAnkHyM8aCjZ1MEIDTQu1iIbbTD/xops91NB5yq1ZK/dC2JDbVWtF23zUtl9JE2NqwT87A==} - acorn@8.14.1: - resolution: {integrity: sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg==} - engines: {node: '>=0.4.0'} - hasBin: true - acorn@8.15.0: resolution: {integrity: sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==} engines: {node: '>=0.4.0'} @@ -2536,8 +2352,8 @@ packages: bindings@1.5.0: resolution: {integrity: sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==} - bippy@0.3.14: - resolution: {integrity: sha512-lg8i4YjVBcAsF8mKVaWjbgG/yo1fY9SUbGgilsnkOnLyU37Jj0XDLWTBeuZjFrrd6LNf1od8/tO/soIi2KUTrw==} + bippy@0.3.18: + resolution: {integrity: sha512-ndTOYOJh/yf988NVRS8W0dhhqSXS88kWVbmKbePtbUP1eaRCN07cjjRVZwIFSdL1HtQ4mhtxJYmqCBT8NyAl/g==} peerDependencies: react: '>=17.0.1' @@ -2713,16 +2529,16 @@ packages: devlop@1.1.0: resolution: {integrity: sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==} - dotenv@16.5.0: - resolution: {integrity: sha512-m/C+AwOAr9/W1UOIZUo232ejMNnJAJtYQjUbHoNTBNTJSvqzzDh7vnrei3o3r3m9blf6ZoDkvcw0VmozNRFJxg==} + dotenv@17.2.1: + resolution: {integrity: sha512-kQhDYKZecqnM0fCnzI5eIv5L4cAe/iRI+HqMbO/hbRdTAeXDG+M9FjipUxNfbARuEg4iHIbhnhs78BCHNbSxEQ==} engines: {node: '>=12'} - drizzle-kit@0.31.1: - resolution: {integrity: sha512-PUjYKWtzOzPtdtQlTHQG3qfv4Y0XT8+Eas6UbxCmxTj7qgMf+39dDujf1BP1I+qqZtw9uzwTh8jYtkMuCq+B0Q==} + drizzle-kit@0.31.4: + resolution: {integrity: sha512-tCPWVZWZqWVx2XUsVpJRnH9Mx0ClVOf5YUHerZ5so1OKSlqww4zy1R5ksEdGRcO3tM3zj0PYN6V48TbQCL1RfA==} hasBin: true - drizzle-orm@0.44.2: - resolution: {integrity: sha512-zGAqBzWWkVSFjZpwPOrmCrgO++1kZ5H/rZ4qTGeGOe18iXGVJWf3WPfHOVwFIbmi8kHjfJstC6rJomzGx8g/dQ==} + drizzle-orm@0.44.4: + resolution: {integrity: sha512-ZyzKFpTC/Ut3fIqc2c0dPZ6nhchQXriTsqTNs4ayRgl6sZcFlMs9QZKPSHXK4bdOf41GHGWf+FrpcDDYwW+W6Q==} peerDependencies: '@aws-sdk/client-rds-data': '>=3' '@cloudflare/workers-types': '>=4' @@ -2875,11 +2691,6 @@ packages: engines: {node: '>=12'} hasBin: true - esbuild@0.25.5: - resolution: {integrity: sha512-P8OtKZRv/5J5hhz0cUAdu/cLuPIKXpQl1R9pZtvmHWQvrAUVd0UNIPT4IB4W3rNOqVO0rlqHmCIbSwxh/c9yUQ==} - engines: {node: '>=18'} - hasBin: true - esbuild@0.25.9: resolution: {integrity: sha512-CRbODhYyQx3qp7ZEwzxOk4JBqmD/seJrzPa/cGjY1VtIn5E09Oi9/dB4JwctnfZ8Q8iT7rioVv5k/FNT/uf54g==} engines: {node: '>=18'} @@ -3068,8 +2879,8 @@ packages: resolution: {integrity: sha512-ZhMwEosbFJkA0YhFnNDgTM4ZxDRsS6HqTo7qsZM08fehyRYIYa0yHu5R6mgo1n/8MgaPBXiPimPD77baVFYg+A==} engines: {node: '>=12.13'} - isbot@5.1.28: - resolution: {integrity: sha512-qrOp4g3xj8YNse4biorv6O5ZShwsJM0trsoda4y7j/Su7ZtTTfVXFzbKkpgcSoDrHS8FcTuUwcU04YimZlZOxw==} + isbot@5.1.30: + resolution: {integrity: sha512-3wVJEonAns1OETX83uWsk5IAne2S5zfDcntD2hbtU23LelSqNXzXs9zKjMPOLMzroCgIjCfjYAEHrd2D6FOkiA==} engines: {node: '>=18'} isexe@2.0.0: @@ -3087,8 +2898,8 @@ packages: resolution: {integrity: sha512-twQoecYPiVA5K/h6SxtORw/Bs3ar+mLUtoPSc7iMXzQzK8d7eJ/R09wmTwAjiamETn1cXYPGfNnu7DMoHgu12w==} hasBin: true - jose@6.0.12: - resolution: {integrity: sha512-T8xypXs8CpmiIi78k0E+Lk7T2zlK4zDyg+o1CZ4AkOHgDg98ogdP2BeZ61lTFKFyoEwJ9RgAgN+SdM3iPgNonQ==} + jose@6.0.13: + resolution: {integrity: sha512-Yms4GpbmdANamS51kKK6w4hRlKx8KTxbWyAAKT/MhUMtqbIqh5mb2HjhTNUbk7TFL8/MBB5zWSDohL7ed4k/UA==} js-base64@3.7.7: resolution: {integrity: sha512-7rCnleh0z2CkXhH67J8K1Ytz0b2Y+yxTPL+/KOJoa20hfnVQ/3/T6W/KflYI4bRHRagNeXeU2bkNGI3v1oS/lw==} @@ -3136,62 +2947,62 @@ packages: layerr@3.0.0: resolution: {integrity: sha512-tv754Ki2dXpPVApOrjTyRo4/QegVb9eVFq4mjqp4+NM5NaX7syQvN5BBNfV/ZpAHCEHV24XdUVrBAoka4jt3pA==} - lefthook-darwin-arm64@1.11.13: - resolution: {integrity: sha512-gHwHofXupCtzNLN+8esdWfFTnAEkmBxE/WKA0EwxPPJXdZYa1GUsiG5ipq/CdG/0j8ekYyM9Hzyrrk5BqJ42xw==} + lefthook-darwin-arm64@1.12.3: + resolution: {integrity: sha512-j1lwaosWRy3vhz8oQgCS1M6EUFN95aIYeNuqkczsBoAA6BDNAmVP1ctYEIYUK4bYaIgENbqbA9prYMAhyzh6Og==} cpu: [arm64] os: [darwin] - lefthook-darwin-x64@1.11.13: - resolution: {integrity: sha512-zYxkWNUirmTidhskY9J9AwxvdMi3YKH+TqZ3AQ1EOqkOwPBWJQW5PbnzsXDrd3YnrtZScYm/tE/moXJpEPPIpQ==} + lefthook-darwin-x64@1.12.3: + resolution: {integrity: sha512-x6aWFfLQX4m5zQ4X9zh5+hHOE5XTvNjz2zB9DI+xbIBLs2RRg0xJNT3OfgSrBU1QtEBneJ5dRQP5nl47td9GDQ==} cpu: [x64] os: [darwin] - lefthook-freebsd-arm64@1.11.13: - resolution: {integrity: sha512-gJzWnllcMcivusmPorEkXPpEciKotlBHn7QxWwYaSjss/U3YdZu+NTjDO30b3qeiVlyq4RAZ4BPKJODXxHHwUA==} + lefthook-freebsd-arm64@1.12.3: + resolution: {integrity: sha512-41OmulLqVZ0EOHmmHouJrpL59SwDD7FLoso4RsQVIBPaf8fHacdLo07Ye28VWQ5XolZQvnWcr1YXKo4JhqQMyw==} cpu: [arm64] os: [freebsd] - lefthook-freebsd-x64@1.11.13: - resolution: {integrity: sha512-689XdchgtDvZQWFFx1szUvm/mqrq/v6laki0odq5FAfcSgUeLu3w+z6UicBS5l55eFJuQTDNKARFqrKJ04e+Vw==} + lefthook-freebsd-x64@1.12.3: + resolution: {integrity: sha512-741/JRCJIS++hgYEH2uefN4FsH872V7gy2zDhcfQofiZnWP7+qhl4Wmwi8IpjIu4X7hLOC4cT18LOVU5L8KV9Q==} cpu: [x64] os: [freebsd] - lefthook-linux-arm64@1.11.13: - resolution: {integrity: sha512-ujCLbaZg5S/Ao8KZAcNSb+Y3gl898ZEM0YKyiZmZo22dFFpm/5gcV46pF3xaqIw5IpH+3YYDTKDU+qTetmARyQ==} + lefthook-linux-arm64@1.12.3: + resolution: {integrity: sha512-BXIy1aDFZmFgmebJliNrEqZfX1lSOD4b/USvANv1UirFrNgTq5SRssd1CKfflT2PwKX6LsJTD4WabLLWZOxp9A==} cpu: [arm64] os: [linux] - lefthook-linux-x64@1.11.13: - resolution: {integrity: sha512-O5WdodeBtFOXQlvPcckqp4W/yqVM9DbVQBkvOxwSJlmsxO4sGYK1TqdxH9ihLB85B2kPPssZj9ze36/oizzhVQ==} + lefthook-linux-x64@1.12.3: + resolution: {integrity: sha512-FRdwdj5jsQAP2eVrtkVUqMqYNCbQ2Ix84izy29/BvLlu/hVypAGbDfUkgFnsmAd6ZsCBeYCEtPuqyg3E3SO0Rg==} cpu: [x64] os: [linux] - lefthook-openbsd-arm64@1.11.13: - resolution: {integrity: sha512-SyBpciUfvY/lUDbZu7L6MtL/SVG2+yMTckBgb4PdJQhJlisY0IsyOYdlTw2icPPrY7JnwdsFv8UW0EJOB76W4g==} + lefthook-openbsd-arm64@1.12.3: + resolution: {integrity: sha512-tch5wXY4GOjKAYohH7OFoxNdVHuUSYt2Pulo2VTkMYEG8IrvJnRO5MkvgHtKDHzU5mfABQYv5+ccJykDx5hQWA==} cpu: [arm64] os: [openbsd] - lefthook-openbsd-x64@1.11.13: - resolution: {integrity: sha512-6+/0j6O2dzo9cjTWUKfL2J6hRR7Krna/ssqnW8cWh8QHZKO9WJn34epto9qgjeHwSysou8byI7Mwv5zOGthLCQ==} + lefthook-openbsd-x64@1.12.3: + resolution: {integrity: sha512-IHbHg/rUFXrAN7LnjcQEtutCHBaD49CZge96Hpk0GZ2eEG5GTCNRnUyEf+Kf3+RTqHFgwtADdpeDa/ZaGZTM4g==} cpu: [x64] os: [openbsd] - lefthook-windows-arm64@1.11.13: - resolution: {integrity: sha512-w5TwZ8bsZ17uOMtYGc5oEb4tCHjNTSeSXRy6H9Yic8E7IsPZtZLkaZGnIIwgXFuhhrcCdc6FuTvKt2tyV7EW2g==} + lefthook-windows-arm64@1.12.3: + resolution: {integrity: sha512-wghcE5TSpb+mbtemUV6uAo9hEK09kxRzhf2nPdeDX+fw42cL2TGZsbaCnDyzaY144C+L2/wEWrLIHJMnZYkuqA==} cpu: [arm64] os: [win32] - lefthook-windows-x64@1.11.13: - resolution: {integrity: sha512-7lvwnIs8CNOXKU4y3i1Pbqna+QegIORkSD2VCuHBNpIJ8H84NpjoG3tKU91IM/aI1a2eUvCk+dw+1rfMRz7Ytg==} + lefthook-windows-x64@1.12.3: + resolution: {integrity: sha512-7Co/L8e2x2hGC1L33jDJ4ZlTkO3PJm25GOGpLfN1kqwhGB/uzMLeTI/PBczjlIN8isUv26ouNd9rVR7Bibrwyg==} cpu: [x64] os: [win32] - lefthook@1.11.13: - resolution: {integrity: sha512-SDTk3D4nW1XRpR9u9fdYQ/qj1xeZVIwZmIFdJUnyq+w9ZLdCCvIrOmtD8SFiJowSevISjQDC+f9nqyFXUxc0SQ==} + lefthook@1.12.3: + resolution: {integrity: sha512-huMg+mGp6wHPjkaLdchuOvxVRMzvz6OVdhivatiH2Qn47O5Zm46jwzbVPYIanX6N/8ZTjGLBxv8tZ0KYmKt/Jg==} hasBin: true - libsql@0.5.13: - resolution: {integrity: sha512-5Bwoa/CqzgkTwySgqHA5TsaUDRrdLIbdM4egdPcaAnqO3aC+qAgS6BwdzuZwARA5digXwiskogZ8H7Yy4XfdOg==} + libsql@0.5.17: + resolution: {integrity: sha512-RRlj5XQI9+Wq+/5UY8EnugSWfRmHEw4hn3DKlPrkUgZONsge1PwTtHcpStP6MSNi8ohcbsRgEHJaymA33a8cBw==} os: [darwin, linux, win32] lightningcss-darwin-arm64@1.30.1: @@ -3278,8 +3089,8 @@ packages: resolution: {integrity: sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==} engines: {node: '>=12'} - lucide-react@0.511.0: - resolution: {integrity: sha512-VK5a2ydJ7xm8GvBeKLS9mu1pVK6ucef9780JVUjw6bAjJL/QXnd4Y0p7SPeOUMC27YhzNCZvm5d/QX0Tp3rc0w==} + lucide-react@0.540.0: + resolution: {integrity: sha512-armkCAqQvO62EIX4Hq7hqX/q11WSZu0Jd23cnnqx0/49yIxGXyL/zyZfBxNN9YDx0ensPTb4L+DjTh3yQXUxtQ==} peerDependencies: react: ^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0 @@ -3421,8 +3232,8 @@ packages: resolution: {integrity: sha512-1dKY+86/AIiq1tkKVD3l0WI+Gd3vkknVGAggsFeBkTvbhMQ1OND/LKkYv4JtXPKUJ8bOTCyLiqEg2P6QNdK+Gg==} engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} - oauth4webapi@3.5.1: - resolution: {integrity: sha512-txg/jZQwcbaF7PMJgY7aoxc9QuCxHVFMiEkDIJ60DwDz3PbtXPQnrzo+3X4IRYGChIwWLabRBRpf1k9hO9+xrQ==} + oauth4webapi@3.7.0: + resolution: {integrity: sha512-Q52wTPUWPsVLVVmTViXPQFMW2h2xv2jnDGxypjpelCFKaOjLsm7AxYuOk1oQgFm95VNDbuggasu9htXrz6XwKw==} once@1.4.0: resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} @@ -3433,8 +3244,8 @@ packages: oniguruma-to-es@4.3.3: resolution: {integrity: sha512-rPiZhzC3wXwE59YQMRDodUwwT9FZ9nNBwQQfsd1wfdtlKEyCdRV0avrTcSZ5xlIvGRVPd/cx6ZN45ECmS39xvg==} - openid-client@6.5.0: - resolution: {integrity: sha512-fAfYaTnOYE2kQCqEJGX9KDObW2aw7IQy4jWpU/+3D3WoCFLbix5Hg6qIPQ6Js9r7f8jDUmsnnguRNCSw4wU/IQ==} + openid-client@6.6.4: + resolution: {integrity: sha512-PLWVhRksRnNH05sqeuCX/PR+1J70NyZcAcPske+FeF732KKONd3v0p5Utx1ro1iLfCglH8B3/+dA1vqIHDoIiA==} package-json-from-dist@1.0.1: resolution: {integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==} @@ -3474,26 +3285,22 @@ packages: resolution: {integrity: sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==} engines: {node: '>=12'} - playwright-core@1.52.0: - resolution: {integrity: sha512-l2osTgLXSMeuLZOML9qYODUQoPPnUsKsb5/P6LJ2e6uPKXUdPK5WYhN4z03G+YNbWmGDY4YENauNu4ZKczreHg==} + playwright-core@1.55.0: + resolution: {integrity: sha512-GvZs4vU3U5ro2nZpeiwyb0zuFaqb9sUiAJuyrWpcGouD8y9/HLgGbNRjIph7zU9D3hnPaisMl9zG9CgFi/biIg==} engines: {node: '>=18'} hasBin: true - playwright@1.52.0: - resolution: {integrity: sha512-JAwMNMBlxJ2oD1kce4KPtMkDeKGHQstdpFPcPH3maElAXon/QZeTvtsfXmTMRyO9TslfoYOXkSsvao2nE1ilTw==} + playwright@1.55.0: + resolution: {integrity: sha512-sdCWStblvV1YU909Xqx0DhOjPZE4/5lJsIS84IfN9dAZfcl/CIZ5O8l3o0j7hPMjDvqoTF8ZUcc+i/GL5erstA==} engines: {node: '>=18'} hasBin: true - postcss@8.5.4: - resolution: {integrity: sha512-QSa9EBe+uwlGTFmHsPKokv3B/oEMQZxfqW0QqNCyhpa6mB1afzulwn8hihglqAb2pOw+BJgNlmXQ8la2VeHB7w==} - engines: {node: ^10 || ^12 || >=14} - postcss@8.5.6: resolution: {integrity: sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==} engines: {node: ^10 || ^12 || >=14} - preact@10.26.7: - resolution: {integrity: sha512-43xS+QYc1X1IPbw03faSgY6I6OYWcLrJRv3hU0+qMOfh/XCHcP0MX2CVjNARYR2cC/guu975sta4OcjlczxD7g==} + preact@10.27.1: + resolution: {integrity: sha512-V79raXEWch/rbqoNc7nT9E4ep7lu+mI3+sBmfRD4i1M73R3WLYcCtdI0ibxGVf4eQL8ZIz2nFacqEC+rmnOORQ==} prebuild-install@7.1.3: resolution: {integrity: sha512-8Mf2cbV7x1cXPUILADGI3wuhfqWvtiLA1iclTDbFRZkgRQS0NqsPZphna9V+HyTEadheuPmjaJMsbzKQFOzLug==} @@ -3537,14 +3344,14 @@ packages: resolution: {integrity: sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==} hasBin: true - react-aria@3.40.0: - resolution: {integrity: sha512-pxZusRI1jCBIvJkORJnhAXey/5U/VJa1whCeP6ETzRKepJiXLRPjJerHHJw+3Q6kAJXADL9qds5xdq4nvmyLRA==} + react-aria@3.42.0: + resolution: {integrity: sha512-lZF1tVmcO6mTWBHpmo4r58lBxIkt/DeF1gu5vrLv2lF4H213VGdSIG8ogQgMc2NaLHK720wafYVM2m5pRUIKdg==} peerDependencies: react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - react-codemirror-merge@4.23.12: - resolution: {integrity: sha512-BRacz67tcZ5KQnNodJ9ltMd1WmZyQdREOnxcRfVWUr8USSuIcgn1fVYr+6xaEWFS2tTRJJzjUbemZURrlefIXw==} + react-codemirror-merge@4.25.1: + resolution: {integrity: sha512-zjWLz/I1/OkBcAXGxE2vX5LiTOBKQXxmFFGpnUYV7+ZuZr4/UKogpWsY+AI3+ZdDkbn47w+CL8hjRwN7nS7Cqw==} peerDependencies: '@babel/runtime': '>=7.11.0' '@codemirror/state': '>=6.0.0' @@ -3611,8 +3418,8 @@ packages: react-dom: optional: true - react-scan@0.3.4: - resolution: {integrity: sha512-jUkgs+sfK1B7T1jvZ0rQmKZtvUUE7cHB6qDTfCfOTmTJYH2aAFB1fGmE4DXBbQ1kUF+AdjyNT0Lc73LL/dQIbg==} + react-scan@0.4.3: + resolution: {integrity: sha512-jhAQuQ1nja6HUYrSpbmNFHqZPsRCXk8Yqu0lHoRIw9eb8N96uTfXCpVyQhTTnJ/nWqnwuvxbpKVG/oWZT8+iTQ==} hasBin: true peerDependencies: '@remix-run/react': '>=1.0.0' @@ -3631,8 +3438,8 @@ packages: react-router-dom: optional: true - react-stately@3.38.0: - resolution: {integrity: sha512-zS06DsDhH44z7bsOkMHJ0gnjuLO3UWZ33l7JOgFscrv1qa33IG9fn707sI7GAJdLgDiWXJbeFvXdix2jR1fU1w==} + react-stately@3.40.0: + resolution: {integrity: sha512-Icg2q1pxTskx2dph3cFUu9RUQcInq25WZfUcKroX1Kl4jWxBobnfMvuxvJHHkysJh77IsnLmhF3+8If5oCoMFQ==} peerDependencies: react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 @@ -3709,8 +3516,8 @@ packages: rfdc@1.4.1: resolution: {integrity: sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==} - rolldown-vite@7.1.3: - resolution: {integrity: sha512-J22JlCJQbIRxR6KGwpf76Uq3R4wk6avQHCijXGPsXsbVpPt+Uavg5M5dvzP7XdlAKL8uZE5Xof9j4Y9yu+cxxg==} + rolldown-vite@7.1.4: + resolution: {integrity: sha512-VE0cXhJfTypUhm71w4pR62dMyqw8JKHWMdbUBSDVqZTGGpZz5Zkw+cT47rvBR/SQ9E9F2GtlW02rWIY2T9HdLg==} engines: {node: ^20.19.0 || >=22.12.0} hasBin: true peerDependencies: @@ -3900,8 +3707,8 @@ packages: tabbable@6.2.0: resolution: {integrity: sha512-Cat63mxsVJlzYvN51JmVXIgNoUokrIaT2zLclCXjRd8boZ0004U4KCs/sToJ75C6sdlByWxpYnb5Boif1VSFew==} - tailwind-merge@3.3.0: - resolution: {integrity: sha512-fyW/pEfcQSiigd5SNn0nApUOxx0zB/dm6UDU/rEwc2c3sX2smWUNbapHv+QRqLGVp9GWX3THIa7MUGPo+YkDzQ==} + tailwind-merge@3.3.1: + resolution: {integrity: sha512-gBXpgUm/3rp1lMZZrM/w7D8GKqshif0zAymAhbCyIt8KMe+0v9DQ7cdYLR4FHH/cKpdTXb+A/tKKU3eolfsI+g==} tailwindcss-animate@1.0.7: resolution: {integrity: sha512-bl6mpH3T7I3UFxuvDEXLxy/VuFxBk5bbzplh7tXI68mwMokNYd1t9qPBHlnyTwfa4JGC4zP516I1hYYtQ/vspA==} @@ -3916,9 +3723,6 @@ packages: tailwindcss@4.1.12: resolution: {integrity: sha512-DzFtxOi+7NsFf7DBtI3BJsynR+0Yp6etH+nRPTbpWnS2pZBaSksv/JGctNwSWzbFjp0vxSqknaUylseZqMDGrA==} - tailwindcss@4.1.8: - resolution: {integrity: sha512-kjeW8gjdxasbmFKpVGrGd5T4i40mV5J2Rasw48QARfYeQ8YS9x02ON9SFWax3Qf616rt4Cp3nVNIj6Hd1mP3og==} - tapable@2.2.2: resolution: {integrity: sha512-Re10+NauLTMCudc7T5WLFLAwDhQ0JWdrMK+9B2M8zR5hRExKmsRDCBA7/aV/pNJFltmBFO5BAMlQFi/vq3nKOg==} engines: {node: '>=6'} @@ -3992,8 +3796,8 @@ packages: tslib@2.8.1: resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} - tsx@4.19.4: - resolution: {integrity: sha512-gK5GVzDkJK1SI1zwHf32Mqxf2tSJkNx+eYcNly5+nHvWqXUJYUkWBQtKauoESz3ymezAI++ZwT855x5p5eop+Q==} + tsx@4.20.4: + resolution: {integrity: sha512-yyxBKfORQ7LuRt/BQKBXrpcq59ZvSW0XxwfjAt3w2/8PmdxaFzijtMhTawprSHhpzeM5BgU2hXHG3lklIERZXg==} engines: {node: '>=18.0.0'} hasBin: true @@ -4022,8 +3826,8 @@ packages: undici-types@7.10.0: resolution: {integrity: sha512-t5Fy/nfn+14LuOc2KNYg75vZqClpAiqscVvMygNnlsHBFpSXdJaYtXMcdNLpl/Qvc3P2cB3s6lOV51nqsFq4ag==} - undici@7.10.0: - resolution: {integrity: sha512-u5otvFBOBZvmdjWLVW+5DAc9Nkq8f24g0O9oY7qw2JVIF1VocIFoyz9JFkuVOS2j41AufeO0xnlweJ2RLT8nGw==} + undici@7.14.0: + resolution: {integrity: sha512-Vqs8HTzjpQXZeXdpsfChQTlafcMQaaIwnGwLam1wudSSjlJeQ3bw1j+TLPePgrCnCpUXx7Ba5Pdpf5OBih62NQ==} engines: {node: '>=20.18.1'} unist-util-is@6.0.0: @@ -4263,17 +4067,14 @@ packages: resolution: {integrity: sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==} engines: {node: '>=18'} - yaml@2.8.0: - resolution: {integrity: sha512-4lLa/EcQCB0cJkyts+FpIRx5G/llPxfP6VQU5KByHEhLxY3IJCH0f0Hy1MHI8sClTvsIb8qwRJ6R/ZdlDJ/leQ==} + yaml@2.8.1: + resolution: {integrity: sha512-lcYcMxX2PO9XMGvAJkJ3OsNMw+/7FKes7/hgerGUYWIoWu5j/+YQqcZr5JnPZWzOsEBgMbSbiSTn/dv/69Mkpw==} engines: {node: '>= 14.6'} hasBin: true zimmerframe@1.1.2: resolution: {integrity: sha512-rAbqEGa8ovJy4pyBxZM70hg4pE6gDgaQ0Sl9M3enG3I0d6H4XSAM3GeNGLKnsBpuijUow064sf7ww1nutC5/3w==} - zod@3.25.33: - resolution: {integrity: sha512-RnBGYCwJFrLi/hUmeqbYjSIrS/strWjN5PHWgUfyVq96nKycSa4gp4+p6hQGwvcabZs0DWRGzyLhEeQWl+5NhA==} - zwitch@2.0.4: resolution: {integrity: sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==} @@ -4298,26 +4099,6 @@ snapshots: '@babel/compat-data@7.27.3': {} - '@babel/core@7.27.3': - dependencies: - '@ampproject/remapping': 2.3.0 - '@babel/code-frame': 7.27.1 - '@babel/generator': 7.27.3 - '@babel/helper-compilation-targets': 7.27.2 - '@babel/helper-module-transforms': 7.27.3(@babel/core@7.27.3) - '@babel/helpers': 7.27.3 - '@babel/parser': 7.27.3 - '@babel/template': 7.27.2 - '@babel/traverse': 7.27.3 - '@babel/types': 7.27.3 - convert-source-map: 2.0.0 - debug: 4.4.1 - gensync: 1.0.0-beta.2 - json5: 2.2.3 - semver: 6.3.1 - transitivePeerDependencies: - - supports-color - '@babel/core@7.28.3': dependencies: '@ampproject/remapping': 2.3.0 @@ -4395,15 +4176,6 @@ snapshots: transitivePeerDependencies: - supports-color - '@babel/helper-module-transforms@7.27.3(@babel/core@7.27.3)': - dependencies: - '@babel/core': 7.27.3 - '@babel/helper-module-imports': 7.27.1 - '@babel/helper-validator-identifier': 7.27.1 - '@babel/traverse': 7.27.3 - transitivePeerDependencies: - - supports-color - '@babel/helper-module-transforms@7.28.3(@babel/core@7.28.3)': dependencies: '@babel/core': 7.28.3 @@ -4441,11 +4213,6 @@ snapshots: '@babel/helper-validator-option@7.27.1': {} - '@babel/helpers@7.27.3': - dependencies: - '@babel/template': 7.27.2 - '@babel/types': 7.27.3 - '@babel/helpers@7.28.3': dependencies: '@babel/template': 7.27.2 @@ -4717,183 +4484,120 @@ snapshots: '@esbuild-kit/core-utils': 3.3.2 get-tsconfig: 4.10.1 - '@esbuild/aix-ppc64@0.25.5': - optional: true - '@esbuild/aix-ppc64@0.25.9': optional: true '@esbuild/android-arm64@0.18.20': optional: true - '@esbuild/android-arm64@0.25.5': - optional: true - '@esbuild/android-arm64@0.25.9': optional: true '@esbuild/android-arm@0.18.20': optional: true - '@esbuild/android-arm@0.25.5': - optional: true - '@esbuild/android-arm@0.25.9': optional: true '@esbuild/android-x64@0.18.20': optional: true - '@esbuild/android-x64@0.25.5': - optional: true - '@esbuild/android-x64@0.25.9': optional: true '@esbuild/darwin-arm64@0.18.20': optional: true - '@esbuild/darwin-arm64@0.25.5': - optional: true - '@esbuild/darwin-arm64@0.25.9': optional: true '@esbuild/darwin-x64@0.18.20': optional: true - '@esbuild/darwin-x64@0.25.5': - optional: true - '@esbuild/darwin-x64@0.25.9': optional: true '@esbuild/freebsd-arm64@0.18.20': optional: true - '@esbuild/freebsd-arm64@0.25.5': - optional: true - '@esbuild/freebsd-arm64@0.25.9': optional: true '@esbuild/freebsd-x64@0.18.20': optional: true - '@esbuild/freebsd-x64@0.25.5': - optional: true - '@esbuild/freebsd-x64@0.25.9': optional: true '@esbuild/linux-arm64@0.18.20': optional: true - '@esbuild/linux-arm64@0.25.5': - optional: true - '@esbuild/linux-arm64@0.25.9': optional: true '@esbuild/linux-arm@0.18.20': optional: true - '@esbuild/linux-arm@0.25.5': - optional: true - '@esbuild/linux-arm@0.25.9': optional: true '@esbuild/linux-ia32@0.18.20': optional: true - '@esbuild/linux-ia32@0.25.5': - optional: true - '@esbuild/linux-ia32@0.25.9': optional: true '@esbuild/linux-loong64@0.18.20': optional: true - '@esbuild/linux-loong64@0.25.5': - optional: true - '@esbuild/linux-loong64@0.25.9': optional: true '@esbuild/linux-mips64el@0.18.20': optional: true - '@esbuild/linux-mips64el@0.25.5': - optional: true - '@esbuild/linux-mips64el@0.25.9': optional: true '@esbuild/linux-ppc64@0.18.20': optional: true - '@esbuild/linux-ppc64@0.25.5': - optional: true - '@esbuild/linux-ppc64@0.25.9': optional: true '@esbuild/linux-riscv64@0.18.20': optional: true - '@esbuild/linux-riscv64@0.25.5': - optional: true - '@esbuild/linux-riscv64@0.25.9': optional: true '@esbuild/linux-s390x@0.18.20': optional: true - '@esbuild/linux-s390x@0.25.5': - optional: true - '@esbuild/linux-s390x@0.25.9': optional: true '@esbuild/linux-x64@0.18.20': optional: true - '@esbuild/linux-x64@0.25.5': - optional: true - '@esbuild/linux-x64@0.25.9': optional: true - '@esbuild/netbsd-arm64@0.25.5': - optional: true - '@esbuild/netbsd-arm64@0.25.9': optional: true '@esbuild/netbsd-x64@0.18.20': optional: true - '@esbuild/netbsd-x64@0.25.5': - optional: true - '@esbuild/netbsd-x64@0.25.9': optional: true - '@esbuild/openbsd-arm64@0.25.5': - optional: true - '@esbuild/openbsd-arm64@0.25.9': optional: true '@esbuild/openbsd-x64@0.18.20': optional: true - '@esbuild/openbsd-x64@0.25.5': - optional: true - '@esbuild/openbsd-x64@0.25.9': optional: true @@ -4903,42 +4607,30 @@ snapshots: '@esbuild/sunos-x64@0.18.20': optional: true - '@esbuild/sunos-x64@0.25.5': - optional: true - '@esbuild/sunos-x64@0.25.9': optional: true '@esbuild/win32-arm64@0.18.20': optional: true - '@esbuild/win32-arm64@0.25.5': - optional: true - '@esbuild/win32-arm64@0.25.9': optional: true '@esbuild/win32-ia32@0.18.20': optional: true - '@esbuild/win32-ia32@0.25.5': - optional: true - '@esbuild/win32-ia32@0.25.9': optional: true '@esbuild/win32-x64@0.18.20': optional: true - '@esbuild/win32-x64@0.25.5': - optional: true - '@esbuild/win32-x64@0.25.9': optional: true - '@faker-js/faker@9.8.0': {} + '@faker-js/faker@9.9.0': {} - '@fontsource-variable/inter@5.2.5': {} + '@fontsource-variable/inter@5.2.6': {} '@formatjs/ecma402-abstract@2.3.4': dependencies: @@ -4991,20 +4683,20 @@ snapshots: '@iconify/types@2.0.0': {} - '@internationalized/date@3.8.1': + '@internationalized/date@3.8.2': dependencies: '@swc/helpers': 0.5.17 - '@internationalized/message@3.1.7': + '@internationalized/message@3.1.8': dependencies: '@swc/helpers': 0.5.17 intl-messageformat: 10.7.16 - '@internationalized/number@3.6.2': + '@internationalized/number@3.6.4': dependencies: '@swc/helpers': 0.5.17 - '@internationalized/string@3.2.6': + '@internationalized/string@3.2.7': dependencies: '@swc/helpers': 0.5.17 @@ -5083,7 +4775,7 @@ snapshots: js-yaml: 4.1.0 jsonpath-plus: 10.3.0 node-fetch: 2.7.0 - openid-client: 6.5.0 + openid-client: 6.6.4 rfc4648: 1.5.4 socks-proxy-agent: 8.0.5 stream-buffers: 3.0.3 @@ -5106,25 +4798,25 @@ snapshots: dependencies: '@lezer/common': 1.2.3 - '@libsql/client@0.15.9(bufferutil@4.0.9)(utf-8-validate@5.0.10)': + '@libsql/client@0.15.12(bufferutil@4.0.9)(utf-8-validate@5.0.10)': dependencies: - '@libsql/core': 0.15.9 + '@libsql/core': 0.15.12 '@libsql/hrana-client': 0.7.0(bufferutil@4.0.9)(utf-8-validate@5.0.10) js-base64: 3.7.7 - libsql: 0.5.13 + libsql: 0.5.17 promise-limit: 2.7.0 transitivePeerDependencies: - bufferutil - utf-8-validate - '@libsql/core@0.15.9': + '@libsql/core@0.15.12': dependencies: js-base64: 3.7.7 - '@libsql/darwin-arm64@0.5.13': + '@libsql/darwin-arm64@0.5.17': optional: true - '@libsql/darwin-x64@0.5.13': + '@libsql/darwin-x64@0.5.17': optional: true '@libsql/hrana-client@0.7.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)': @@ -5147,25 +4839,25 @@ snapshots: - bufferutil - utf-8-validate - '@libsql/linux-arm-gnueabihf@0.5.13': + '@libsql/linux-arm-gnueabihf@0.5.17': optional: true - '@libsql/linux-arm-musleabihf@0.5.13': + '@libsql/linux-arm-musleabihf@0.5.17': optional: true - '@libsql/linux-arm64-gnu@0.5.13': + '@libsql/linux-arm64-gnu@0.5.17': optional: true - '@libsql/linux-arm64-musl@0.5.13': + '@libsql/linux-arm64-musl@0.5.17': optional: true - '@libsql/linux-x64-gnu@0.5.13': + '@libsql/linux-x64-gnu@0.5.17': optional: true - '@libsql/linux-x64-musl@0.5.13': + '@libsql/linux-x64-musl@0.5.17': optional: true - '@libsql/win32-x64-msvc@0.5.13': + '@libsql/win32-x64-msvc@0.5.17': optional: true '@marijn/find-cluster-break@1.0.2': {} @@ -5224,609 +4916,605 @@ snapshots: '@pkgjs/parseargs@0.11.0': optional: true - '@preact/signals-core@1.8.0': {} + '@preact/signals-core@1.12.0': {} - '@preact/signals@1.3.2(preact@10.26.7)': + '@preact/signals@1.3.2(preact@10.27.1)': dependencies: - '@preact/signals-core': 1.8.0 - preact: 10.26.7 + '@preact/signals-core': 1.12.0 + preact: 10.27.1 - '@primer/octicons-react@19.15.2(react@19.1.1)': + '@react-aria/breadcrumbs@3.5.27(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': dependencies: - react: 19.1.1 - - '@react-aria/breadcrumbs@3.5.24(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': - dependencies: - '@react-aria/i18n': 3.12.9(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@react-aria/link': 3.8.1(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@react-aria/utils': 3.29.0(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@react-types/breadcrumbs': 3.7.13(react@19.1.1) - '@react-types/shared': 3.29.1(react@19.1.1) + '@react-aria/i18n': 3.12.11(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-aria/link': 3.8.4(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-aria/utils': 3.30.0(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-types/breadcrumbs': 3.7.15(react@19.1.1) + '@react-types/shared': 3.31.0(react@19.1.1) '@swc/helpers': 0.5.17 react: 19.1.1 react-dom: 19.1.1(react@19.1.1) - '@react-aria/button@3.13.1(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': + '@react-aria/button@3.14.0(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': dependencies: - '@react-aria/interactions': 3.25.1(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@react-aria/toolbar': 3.0.0-beta.16(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@react-aria/utils': 3.29.0(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@react-stately/toggle': 3.8.4(react@19.1.1) - '@react-types/button': 3.12.1(react@19.1.1) - '@react-types/shared': 3.29.1(react@19.1.1) + '@react-aria/interactions': 3.25.4(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-aria/toolbar': 3.0.0-beta.19(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-aria/utils': 3.30.0(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-stately/toggle': 3.9.0(react@19.1.1) + '@react-types/button': 3.13.0(react@19.1.1) + '@react-types/shared': 3.31.0(react@19.1.1) '@swc/helpers': 0.5.17 react: 19.1.1 react-dom: 19.1.1(react@19.1.1) - '@react-aria/calendar@3.8.1(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': + '@react-aria/calendar@3.9.0(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': dependencies: - '@internationalized/date': 3.8.1 - '@react-aria/i18n': 3.12.9(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@react-aria/interactions': 3.25.1(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@react-aria/live-announcer': 3.4.2 - '@react-aria/utils': 3.29.0(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@react-stately/calendar': 3.8.1(react@19.1.1) - '@react-types/button': 3.12.1(react@19.1.1) - '@react-types/calendar': 3.7.1(react@19.1.1) - '@react-types/shared': 3.29.1(react@19.1.1) + '@internationalized/date': 3.8.2 + '@react-aria/i18n': 3.12.11(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-aria/interactions': 3.25.4(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-aria/live-announcer': 3.4.4 + '@react-aria/utils': 3.30.0(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-stately/calendar': 3.8.3(react@19.1.1) + '@react-types/button': 3.13.0(react@19.1.1) + '@react-types/calendar': 3.7.3(react@19.1.1) + '@react-types/shared': 3.31.0(react@19.1.1) '@swc/helpers': 0.5.17 react: 19.1.1 react-dom: 19.1.1(react@19.1.1) - '@react-aria/checkbox@3.15.5(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': + '@react-aria/checkbox@3.16.0(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': dependencies: - '@react-aria/form': 3.0.16(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@react-aria/interactions': 3.25.1(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@react-aria/label': 3.7.18(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@react-aria/toggle': 3.11.3(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@react-aria/utils': 3.29.0(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@react-stately/checkbox': 3.6.14(react@19.1.1) - '@react-stately/form': 3.1.4(react@19.1.1) - '@react-stately/toggle': 3.8.4(react@19.1.1) - '@react-types/checkbox': 3.9.4(react@19.1.1) - '@react-types/shared': 3.29.1(react@19.1.1) + '@react-aria/form': 3.1.0(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-aria/interactions': 3.25.4(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-aria/label': 3.7.20(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-aria/toggle': 3.12.0(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-aria/utils': 3.30.0(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-stately/checkbox': 3.7.0(react@19.1.1) + '@react-stately/form': 3.2.0(react@19.1.1) + '@react-stately/toggle': 3.9.0(react@19.1.1) + '@react-types/checkbox': 3.10.0(react@19.1.1) + '@react-types/shared': 3.31.0(react@19.1.1) '@swc/helpers': 0.5.17 react: 19.1.1 react-dom: 19.1.1(react@19.1.1) - '@react-aria/color@3.0.7(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': + '@react-aria/color@3.1.0(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': dependencies: - '@react-aria/i18n': 3.12.9(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@react-aria/interactions': 3.25.1(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@react-aria/numberfield': 3.11.14(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@react-aria/slider': 3.7.19(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@react-aria/spinbutton': 3.6.15(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@react-aria/textfield': 3.17.3(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@react-aria/utils': 3.29.0(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@react-aria/visually-hidden': 3.8.23(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@react-stately/color': 3.8.5(react@19.1.1) - '@react-stately/form': 3.1.4(react@19.1.1) - '@react-types/color': 3.0.5(react@19.1.1) - '@react-types/shared': 3.29.1(react@19.1.1) + '@react-aria/i18n': 3.12.11(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-aria/interactions': 3.25.4(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-aria/numberfield': 3.12.0(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-aria/slider': 3.8.0(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-aria/spinbutton': 3.6.17(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-aria/textfield': 3.18.0(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-aria/utils': 3.30.0(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-aria/visually-hidden': 3.8.26(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-stately/color': 3.9.0(react@19.1.1) + '@react-stately/form': 3.2.0(react@19.1.1) + '@react-types/color': 3.1.0(react@19.1.1) + '@react-types/shared': 3.31.0(react@19.1.1) '@swc/helpers': 0.5.17 react: 19.1.1 react-dom: 19.1.1(react@19.1.1) - '@react-aria/combobox@3.12.3(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': + '@react-aria/combobox@3.13.0(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': dependencies: - '@react-aria/focus': 3.20.3(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@react-aria/i18n': 3.12.9(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@react-aria/listbox': 3.14.4(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@react-aria/live-announcer': 3.4.2 - '@react-aria/menu': 3.18.3(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@react-aria/overlays': 3.27.1(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@react-aria/selection': 3.24.1(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@react-aria/textfield': 3.17.3(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@react-aria/utils': 3.29.0(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@react-stately/collections': 3.12.4(react@19.1.1) - '@react-stately/combobox': 3.10.5(react@19.1.1) - '@react-stately/form': 3.1.4(react@19.1.1) - '@react-types/button': 3.12.1(react@19.1.1) - '@react-types/combobox': 3.13.5(react@19.1.1) - '@react-types/shared': 3.29.1(react@19.1.1) + '@react-aria/focus': 3.21.0(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-aria/i18n': 3.12.11(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-aria/listbox': 3.14.7(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-aria/live-announcer': 3.4.4 + '@react-aria/menu': 3.19.0(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-aria/overlays': 3.28.0(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-aria/selection': 3.25.0(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-aria/textfield': 3.18.0(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-aria/utils': 3.30.0(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-stately/collections': 3.12.6(react@19.1.1) + '@react-stately/combobox': 3.11.0(react@19.1.1) + '@react-stately/form': 3.2.0(react@19.1.1) + '@react-types/button': 3.13.0(react@19.1.1) + '@react-types/combobox': 3.13.7(react@19.1.1) + '@react-types/shared': 3.31.0(react@19.1.1) '@swc/helpers': 0.5.17 react: 19.1.1 react-dom: 19.1.1(react@19.1.1) - '@react-aria/datepicker@3.14.3(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': + '@react-aria/datepicker@3.15.0(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': dependencies: - '@internationalized/date': 3.8.1 - '@internationalized/number': 3.6.2 - '@internationalized/string': 3.2.6 - '@react-aria/focus': 3.20.3(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@react-aria/form': 3.0.16(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@react-aria/i18n': 3.12.9(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@react-aria/interactions': 3.25.1(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@react-aria/label': 3.7.18(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@react-aria/spinbutton': 3.6.15(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@react-aria/utils': 3.29.0(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@react-stately/datepicker': 3.14.1(react@19.1.1) - '@react-stately/form': 3.1.4(react@19.1.1) - '@react-types/button': 3.12.1(react@19.1.1) - '@react-types/calendar': 3.7.1(react@19.1.1) - '@react-types/datepicker': 3.12.1(react@19.1.1) - '@react-types/dialog': 3.5.18(react@19.1.1) - '@react-types/shared': 3.29.1(react@19.1.1) + '@internationalized/date': 3.8.2 + '@internationalized/number': 3.6.4 + '@internationalized/string': 3.2.7 + '@react-aria/focus': 3.21.0(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-aria/form': 3.1.0(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-aria/i18n': 3.12.11(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-aria/interactions': 3.25.4(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-aria/label': 3.7.20(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-aria/spinbutton': 3.6.17(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-aria/utils': 3.30.0(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-stately/datepicker': 3.15.0(react@19.1.1) + '@react-stately/form': 3.2.0(react@19.1.1) + '@react-types/button': 3.13.0(react@19.1.1) + '@react-types/calendar': 3.7.3(react@19.1.1) + '@react-types/datepicker': 3.13.0(react@19.1.1) + '@react-types/dialog': 3.5.20(react@19.1.1) + '@react-types/shared': 3.31.0(react@19.1.1) '@swc/helpers': 0.5.17 react: 19.1.1 react-dom: 19.1.1(react@19.1.1) - '@react-aria/dialog@3.5.25(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': + '@react-aria/dialog@3.5.28(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': dependencies: - '@react-aria/interactions': 3.25.1(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@react-aria/overlays': 3.27.1(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@react-aria/utils': 3.29.0(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@react-types/dialog': 3.5.18(react@19.1.1) - '@react-types/shared': 3.29.1(react@19.1.1) + '@react-aria/interactions': 3.25.4(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-aria/overlays': 3.28.0(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-aria/utils': 3.30.0(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-types/dialog': 3.5.20(react@19.1.1) + '@react-types/shared': 3.31.0(react@19.1.1) '@swc/helpers': 0.5.17 react: 19.1.1 react-dom: 19.1.1(react@19.1.1) - '@react-aria/disclosure@3.0.5(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': + '@react-aria/disclosure@3.0.7(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': dependencies: - '@react-aria/ssr': 3.9.8(react@19.1.1) - '@react-aria/utils': 3.29.0(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@react-stately/disclosure': 3.0.4(react@19.1.1) - '@react-types/button': 3.12.1(react@19.1.1) + '@react-aria/ssr': 3.9.10(react@19.1.1) + '@react-aria/utils': 3.30.0(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-stately/disclosure': 3.0.6(react@19.1.1) + '@react-types/button': 3.13.0(react@19.1.1) '@swc/helpers': 0.5.17 react: 19.1.1 react-dom: 19.1.1(react@19.1.1) - '@react-aria/dnd@3.9.3(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': + '@react-aria/dnd@3.11.0(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': dependencies: - '@internationalized/string': 3.2.6 - '@react-aria/i18n': 3.12.9(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@react-aria/interactions': 3.25.1(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@react-aria/live-announcer': 3.4.2 - '@react-aria/overlays': 3.27.1(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@react-aria/utils': 3.29.0(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@react-stately/dnd': 3.5.4(react@19.1.1) - '@react-types/button': 3.12.1(react@19.1.1) - '@react-types/shared': 3.29.1(react@19.1.1) + '@internationalized/string': 3.2.7 + '@react-aria/i18n': 3.12.11(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-aria/interactions': 3.25.4(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-aria/live-announcer': 3.4.4 + '@react-aria/overlays': 3.28.0(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-aria/utils': 3.30.0(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-stately/collections': 3.12.6(react@19.1.1) + '@react-stately/dnd': 3.6.1(react@19.1.1) + '@react-types/button': 3.13.0(react@19.1.1) + '@react-types/shared': 3.31.0(react@19.1.1) '@swc/helpers': 0.5.17 react: 19.1.1 react-dom: 19.1.1(react@19.1.1) - '@react-aria/focus@3.20.3(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': + '@react-aria/focus@3.21.0(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': dependencies: - '@react-aria/interactions': 3.25.1(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@react-aria/utils': 3.29.0(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@react-types/shared': 3.29.1(react@19.1.1) + '@react-aria/interactions': 3.25.4(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-aria/utils': 3.30.0(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-types/shared': 3.31.0(react@19.1.1) '@swc/helpers': 0.5.17 clsx: 2.1.1 react: 19.1.1 react-dom: 19.1.1(react@19.1.1) - '@react-aria/form@3.0.16(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': + '@react-aria/form@3.1.0(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': dependencies: - '@react-aria/interactions': 3.25.1(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@react-aria/utils': 3.29.0(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@react-stately/form': 3.1.4(react@19.1.1) - '@react-types/shared': 3.29.1(react@19.1.1) + '@react-aria/interactions': 3.25.4(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-aria/utils': 3.30.0(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-stately/form': 3.2.0(react@19.1.1) + '@react-types/shared': 3.31.0(react@19.1.1) '@swc/helpers': 0.5.17 react: 19.1.1 react-dom: 19.1.1(react@19.1.1) - '@react-aria/grid@3.14.0(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': + '@react-aria/grid@3.14.3(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': dependencies: - '@react-aria/focus': 3.20.3(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@react-aria/i18n': 3.12.9(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@react-aria/interactions': 3.25.1(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@react-aria/live-announcer': 3.4.2 - '@react-aria/selection': 3.24.1(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@react-aria/utils': 3.29.0(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@react-stately/collections': 3.12.4(react@19.1.1) - '@react-stately/grid': 3.11.2(react@19.1.1) - '@react-stately/selection': 3.20.2(react@19.1.1) - '@react-types/checkbox': 3.9.4(react@19.1.1) - '@react-types/grid': 3.3.2(react@19.1.1) - '@react-types/shared': 3.29.1(react@19.1.1) + '@react-aria/focus': 3.21.0(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-aria/i18n': 3.12.11(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-aria/interactions': 3.25.4(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-aria/live-announcer': 3.4.4 + '@react-aria/selection': 3.25.0(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-aria/utils': 3.30.0(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-stately/collections': 3.12.6(react@19.1.1) + '@react-stately/grid': 3.11.4(react@19.1.1) + '@react-stately/selection': 3.20.4(react@19.1.1) + '@react-types/checkbox': 3.10.0(react@19.1.1) + '@react-types/grid': 3.3.4(react@19.1.1) + '@react-types/shared': 3.31.0(react@19.1.1) '@swc/helpers': 0.5.17 react: 19.1.1 react-dom: 19.1.1(react@19.1.1) - '@react-aria/gridlist@3.13.0(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': + '@react-aria/gridlist@3.13.3(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': dependencies: - '@react-aria/focus': 3.20.3(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@react-aria/grid': 3.14.0(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@react-aria/i18n': 3.12.9(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@react-aria/interactions': 3.25.1(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@react-aria/selection': 3.24.1(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@react-aria/utils': 3.29.0(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@react-stately/collections': 3.12.4(react@19.1.1) - '@react-stately/list': 3.12.2(react@19.1.1) - '@react-stately/tree': 3.8.10(react@19.1.1) - '@react-types/shared': 3.29.1(react@19.1.1) + '@react-aria/focus': 3.21.0(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-aria/grid': 3.14.3(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-aria/i18n': 3.12.11(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-aria/interactions': 3.25.4(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-aria/selection': 3.25.0(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-aria/utils': 3.30.0(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-stately/list': 3.12.4(react@19.1.1) + '@react-stately/tree': 3.9.1(react@19.1.1) + '@react-types/shared': 3.31.0(react@19.1.1) '@swc/helpers': 0.5.17 react: 19.1.1 react-dom: 19.1.1(react@19.1.1) - '@react-aria/i18n@3.12.9(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': + '@react-aria/i18n@3.12.11(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': dependencies: - '@internationalized/date': 3.8.1 - '@internationalized/message': 3.1.7 - '@internationalized/number': 3.6.2 - '@internationalized/string': 3.2.6 - '@react-aria/ssr': 3.9.8(react@19.1.1) - '@react-aria/utils': 3.29.0(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@react-types/shared': 3.29.1(react@19.1.1) + '@internationalized/date': 3.8.2 + '@internationalized/message': 3.1.8 + '@internationalized/number': 3.6.4 + '@internationalized/string': 3.2.7 + '@react-aria/ssr': 3.9.10(react@19.1.1) + '@react-aria/utils': 3.30.0(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-types/shared': 3.31.0(react@19.1.1) '@swc/helpers': 0.5.17 react: 19.1.1 react-dom: 19.1.1(react@19.1.1) - '@react-aria/interactions@3.25.1(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': + '@react-aria/interactions@3.25.4(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': dependencies: - '@react-aria/ssr': 3.9.8(react@19.1.1) - '@react-aria/utils': 3.29.0(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@react-stately/flags': 3.1.1 - '@react-types/shared': 3.29.1(react@19.1.1) + '@react-aria/ssr': 3.9.10(react@19.1.1) + '@react-aria/utils': 3.30.0(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-stately/flags': 3.1.2 + '@react-types/shared': 3.31.0(react@19.1.1) '@swc/helpers': 0.5.17 react: 19.1.1 react-dom: 19.1.1(react@19.1.1) - '@react-aria/label@3.7.18(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': + '@react-aria/label@3.7.20(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': dependencies: - '@react-aria/utils': 3.29.0(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@react-types/shared': 3.29.1(react@19.1.1) + '@react-aria/utils': 3.30.0(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-types/shared': 3.31.0(react@19.1.1) '@swc/helpers': 0.5.17 react: 19.1.1 react-dom: 19.1.1(react@19.1.1) - '@react-aria/landmark@3.0.3(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': + '@react-aria/landmark@3.0.5(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': dependencies: - '@react-aria/utils': 3.29.0(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@react-types/shared': 3.29.1(react@19.1.1) + '@react-aria/utils': 3.30.0(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-types/shared': 3.31.0(react@19.1.1) '@swc/helpers': 0.5.17 react: 19.1.1 react-dom: 19.1.1(react@19.1.1) use-sync-external-store: 1.5.0(react@19.1.1) - '@react-aria/link@3.8.1(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': + '@react-aria/link@3.8.4(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': dependencies: - '@react-aria/interactions': 3.25.1(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@react-aria/utils': 3.29.0(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@react-types/link': 3.6.1(react@19.1.1) - '@react-types/shared': 3.29.1(react@19.1.1) + '@react-aria/interactions': 3.25.4(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-aria/utils': 3.30.0(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-types/link': 3.6.3(react@19.1.1) + '@react-types/shared': 3.31.0(react@19.1.1) '@swc/helpers': 0.5.17 react: 19.1.1 react-dom: 19.1.1(react@19.1.1) - '@react-aria/listbox@3.14.4(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': + '@react-aria/listbox@3.14.7(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': dependencies: - '@react-aria/interactions': 3.25.1(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@react-aria/label': 3.7.18(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@react-aria/selection': 3.24.1(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@react-aria/utils': 3.29.0(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@react-stately/collections': 3.12.4(react@19.1.1) - '@react-stately/list': 3.12.2(react@19.1.1) - '@react-types/listbox': 3.7.0(react@19.1.1) - '@react-types/shared': 3.29.1(react@19.1.1) + '@react-aria/interactions': 3.25.4(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-aria/label': 3.7.20(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-aria/selection': 3.25.0(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-aria/utils': 3.30.0(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-stately/collections': 3.12.6(react@19.1.1) + '@react-stately/list': 3.12.4(react@19.1.1) + '@react-types/listbox': 3.7.2(react@19.1.1) + '@react-types/shared': 3.31.0(react@19.1.1) '@swc/helpers': 0.5.17 react: 19.1.1 react-dom: 19.1.1(react@19.1.1) - '@react-aria/live-announcer@3.4.2': + '@react-aria/live-announcer@3.4.4': dependencies: '@swc/helpers': 0.5.17 - '@react-aria/menu@3.18.3(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': + '@react-aria/menu@3.19.0(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': dependencies: - '@react-aria/focus': 3.20.3(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@react-aria/i18n': 3.12.9(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@react-aria/interactions': 3.25.1(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@react-aria/overlays': 3.27.1(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@react-aria/selection': 3.24.1(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@react-aria/utils': 3.29.0(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@react-stately/collections': 3.12.4(react@19.1.1) - '@react-stately/menu': 3.9.4(react@19.1.1) - '@react-stately/selection': 3.20.2(react@19.1.1) - '@react-stately/tree': 3.8.10(react@19.1.1) - '@react-types/button': 3.12.1(react@19.1.1) - '@react-types/menu': 3.10.1(react@19.1.1) - '@react-types/shared': 3.29.1(react@19.1.1) + '@react-aria/focus': 3.21.0(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-aria/i18n': 3.12.11(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-aria/interactions': 3.25.4(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-aria/overlays': 3.28.0(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-aria/selection': 3.25.0(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-aria/utils': 3.30.0(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-stately/collections': 3.12.6(react@19.1.1) + '@react-stately/menu': 3.9.6(react@19.1.1) + '@react-stately/selection': 3.20.4(react@19.1.1) + '@react-stately/tree': 3.9.1(react@19.1.1) + '@react-types/button': 3.13.0(react@19.1.1) + '@react-types/menu': 3.10.3(react@19.1.1) + '@react-types/shared': 3.31.0(react@19.1.1) '@swc/helpers': 0.5.17 react: 19.1.1 react-dom: 19.1.1(react@19.1.1) - '@react-aria/meter@3.4.23(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': + '@react-aria/meter@3.4.25(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': dependencies: - '@react-aria/progress': 3.4.23(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@react-types/meter': 3.4.9(react@19.1.1) - '@react-types/shared': 3.29.1(react@19.1.1) + '@react-aria/progress': 3.4.25(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-types/meter': 3.4.11(react@19.1.1) + '@react-types/shared': 3.31.0(react@19.1.1) '@swc/helpers': 0.5.17 react: 19.1.1 react-dom: 19.1.1(react@19.1.1) - '@react-aria/numberfield@3.11.14(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': + '@react-aria/numberfield@3.12.0(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': dependencies: - '@react-aria/i18n': 3.12.9(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@react-aria/interactions': 3.25.1(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@react-aria/spinbutton': 3.6.15(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@react-aria/textfield': 3.17.3(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@react-aria/utils': 3.29.0(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@react-stately/form': 3.1.4(react@19.1.1) - '@react-stately/numberfield': 3.9.12(react@19.1.1) - '@react-types/button': 3.12.1(react@19.1.1) - '@react-types/numberfield': 3.8.11(react@19.1.1) - '@react-types/shared': 3.29.1(react@19.1.1) + '@react-aria/i18n': 3.12.11(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-aria/interactions': 3.25.4(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-aria/spinbutton': 3.6.17(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-aria/textfield': 3.18.0(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-aria/utils': 3.30.0(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-stately/form': 3.2.0(react@19.1.1) + '@react-stately/numberfield': 3.10.0(react@19.1.1) + '@react-types/button': 3.13.0(react@19.1.1) + '@react-types/numberfield': 3.8.13(react@19.1.1) + '@react-types/shared': 3.31.0(react@19.1.1) '@swc/helpers': 0.5.17 react: 19.1.1 react-dom: 19.1.1(react@19.1.1) - '@react-aria/overlays@3.27.1(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': + '@react-aria/overlays@3.28.0(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': dependencies: - '@react-aria/focus': 3.20.3(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@react-aria/i18n': 3.12.9(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@react-aria/interactions': 3.25.1(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@react-aria/ssr': 3.9.8(react@19.1.1) - '@react-aria/utils': 3.29.0(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@react-aria/visually-hidden': 3.8.23(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@react-stately/overlays': 3.6.16(react@19.1.1) - '@react-types/button': 3.12.1(react@19.1.1) - '@react-types/overlays': 3.8.15(react@19.1.1) - '@react-types/shared': 3.29.1(react@19.1.1) + '@react-aria/focus': 3.21.0(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-aria/i18n': 3.12.11(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-aria/interactions': 3.25.4(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-aria/ssr': 3.9.10(react@19.1.1) + '@react-aria/utils': 3.30.0(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-aria/visually-hidden': 3.8.26(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-stately/overlays': 3.6.18(react@19.1.1) + '@react-types/button': 3.13.0(react@19.1.1) + '@react-types/overlays': 3.9.0(react@19.1.1) + '@react-types/shared': 3.31.0(react@19.1.1) '@swc/helpers': 0.5.17 react: 19.1.1 react-dom: 19.1.1(react@19.1.1) - '@react-aria/progress@3.4.23(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': + '@react-aria/progress@3.4.25(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': dependencies: - '@react-aria/i18n': 3.12.9(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@react-aria/label': 3.7.18(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@react-aria/utils': 3.29.0(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@react-types/progress': 3.5.12(react@19.1.1) - '@react-types/shared': 3.29.1(react@19.1.1) + '@react-aria/i18n': 3.12.11(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-aria/label': 3.7.20(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-aria/utils': 3.30.0(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-types/progress': 3.5.14(react@19.1.1) + '@react-types/shared': 3.31.0(react@19.1.1) '@swc/helpers': 0.5.17 react: 19.1.1 react-dom: 19.1.1(react@19.1.1) - '@react-aria/radio@3.11.3(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': + '@react-aria/radio@3.12.0(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': dependencies: - '@react-aria/focus': 3.20.3(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@react-aria/form': 3.0.16(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@react-aria/i18n': 3.12.9(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@react-aria/interactions': 3.25.1(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@react-aria/label': 3.7.18(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@react-aria/utils': 3.29.0(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@react-stately/radio': 3.10.13(react@19.1.1) - '@react-types/radio': 3.8.9(react@19.1.1) - '@react-types/shared': 3.29.1(react@19.1.1) + '@react-aria/focus': 3.21.0(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-aria/form': 3.1.0(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-aria/i18n': 3.12.11(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-aria/interactions': 3.25.4(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-aria/label': 3.7.20(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-aria/utils': 3.30.0(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-stately/radio': 3.11.0(react@19.1.1) + '@react-types/radio': 3.9.0(react@19.1.1) + '@react-types/shared': 3.31.0(react@19.1.1) '@swc/helpers': 0.5.17 react: 19.1.1 react-dom: 19.1.1(react@19.1.1) - '@react-aria/searchfield@3.8.4(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': + '@react-aria/searchfield@3.8.7(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': dependencies: - '@react-aria/i18n': 3.12.9(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@react-aria/textfield': 3.17.3(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@react-aria/utils': 3.29.0(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@react-stately/searchfield': 3.5.12(react@19.1.1) - '@react-types/button': 3.12.1(react@19.1.1) - '@react-types/searchfield': 3.6.2(react@19.1.1) - '@react-types/shared': 3.29.1(react@19.1.1) + '@react-aria/i18n': 3.12.11(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-aria/textfield': 3.18.0(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-aria/utils': 3.30.0(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-stately/searchfield': 3.5.14(react@19.1.1) + '@react-types/button': 3.13.0(react@19.1.1) + '@react-types/searchfield': 3.6.4(react@19.1.1) + '@react-types/shared': 3.31.0(react@19.1.1) '@swc/helpers': 0.5.17 react: 19.1.1 react-dom: 19.1.1(react@19.1.1) - '@react-aria/select@3.15.5(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': + '@react-aria/select@3.16.0(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': dependencies: - '@react-aria/form': 3.0.16(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@react-aria/i18n': 3.12.9(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@react-aria/interactions': 3.25.1(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@react-aria/label': 3.7.18(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@react-aria/listbox': 3.14.4(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@react-aria/menu': 3.18.3(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@react-aria/selection': 3.24.1(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@react-aria/utils': 3.29.0(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@react-aria/visually-hidden': 3.8.23(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@react-stately/select': 3.6.13(react@19.1.1) - '@react-types/button': 3.12.1(react@19.1.1) - '@react-types/select': 3.9.12(react@19.1.1) - '@react-types/shared': 3.29.1(react@19.1.1) + '@react-aria/form': 3.1.0(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-aria/i18n': 3.12.11(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-aria/interactions': 3.25.4(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-aria/label': 3.7.20(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-aria/listbox': 3.14.7(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-aria/menu': 3.19.0(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-aria/selection': 3.25.0(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-aria/utils': 3.30.0(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-aria/visually-hidden': 3.8.26(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-stately/select': 3.7.0(react@19.1.1) + '@react-types/button': 3.13.0(react@19.1.1) + '@react-types/select': 3.10.0(react@19.1.1) + '@react-types/shared': 3.31.0(react@19.1.1) '@swc/helpers': 0.5.17 react: 19.1.1 react-dom: 19.1.1(react@19.1.1) - '@react-aria/selection@3.24.1(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': + '@react-aria/selection@3.25.0(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': dependencies: - '@react-aria/focus': 3.20.3(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@react-aria/i18n': 3.12.9(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@react-aria/interactions': 3.25.1(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@react-aria/utils': 3.29.0(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@react-stately/selection': 3.20.2(react@19.1.1) - '@react-types/shared': 3.29.1(react@19.1.1) + '@react-aria/focus': 3.21.0(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-aria/i18n': 3.12.11(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-aria/interactions': 3.25.4(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-aria/utils': 3.30.0(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-stately/selection': 3.20.4(react@19.1.1) + '@react-types/shared': 3.31.0(react@19.1.1) '@swc/helpers': 0.5.17 react: 19.1.1 react-dom: 19.1.1(react@19.1.1) - '@react-aria/separator@3.4.9(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': + '@react-aria/separator@3.4.11(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': dependencies: - '@react-aria/utils': 3.29.0(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@react-types/shared': 3.29.1(react@19.1.1) + '@react-aria/utils': 3.30.0(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-types/shared': 3.31.0(react@19.1.1) '@swc/helpers': 0.5.17 react: 19.1.1 react-dom: 19.1.1(react@19.1.1) - '@react-aria/slider@3.7.19(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': + '@react-aria/slider@3.8.0(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': dependencies: - '@react-aria/i18n': 3.12.9(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@react-aria/interactions': 3.25.1(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@react-aria/label': 3.7.18(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@react-aria/utils': 3.29.0(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@react-stately/slider': 3.6.4(react@19.1.1) - '@react-types/shared': 3.29.1(react@19.1.1) - '@react-types/slider': 3.7.11(react@19.1.1) + '@react-aria/i18n': 3.12.11(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-aria/interactions': 3.25.4(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-aria/label': 3.7.20(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-aria/utils': 3.30.0(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-stately/slider': 3.7.0(react@19.1.1) + '@react-types/shared': 3.31.0(react@19.1.1) + '@react-types/slider': 3.8.0(react@19.1.1) '@swc/helpers': 0.5.17 react: 19.1.1 react-dom: 19.1.1(react@19.1.1) - '@react-aria/spinbutton@3.6.15(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': + '@react-aria/spinbutton@3.6.17(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': dependencies: - '@react-aria/i18n': 3.12.9(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@react-aria/live-announcer': 3.4.2 - '@react-aria/utils': 3.29.0(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@react-types/button': 3.12.1(react@19.1.1) - '@react-types/shared': 3.29.1(react@19.1.1) + '@react-aria/i18n': 3.12.11(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-aria/live-announcer': 3.4.4 + '@react-aria/utils': 3.30.0(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-types/button': 3.13.0(react@19.1.1) + '@react-types/shared': 3.31.0(react@19.1.1) '@swc/helpers': 0.5.17 react: 19.1.1 react-dom: 19.1.1(react@19.1.1) - '@react-aria/ssr@3.9.8(react@19.1.1)': + '@react-aria/ssr@3.9.10(react@19.1.1)': dependencies: '@swc/helpers': 0.5.17 react: 19.1.1 - '@react-aria/switch@3.7.3(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': + '@react-aria/switch@3.7.6(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': dependencies: - '@react-aria/toggle': 3.11.3(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@react-stately/toggle': 3.8.4(react@19.1.1) - '@react-types/shared': 3.29.1(react@19.1.1) - '@react-types/switch': 3.5.11(react@19.1.1) + '@react-aria/toggle': 3.12.0(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-stately/toggle': 3.9.0(react@19.1.1) + '@react-types/shared': 3.31.0(react@19.1.1) + '@react-types/switch': 3.5.13(react@19.1.1) '@swc/helpers': 0.5.17 react: 19.1.1 react-dom: 19.1.1(react@19.1.1) - '@react-aria/table@3.17.3(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': + '@react-aria/table@3.17.6(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': dependencies: - '@react-aria/focus': 3.20.3(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@react-aria/grid': 3.14.0(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@react-aria/i18n': 3.12.9(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@react-aria/interactions': 3.25.1(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@react-aria/live-announcer': 3.4.2 - '@react-aria/utils': 3.29.0(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@react-aria/visually-hidden': 3.8.23(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@react-stately/collections': 3.12.4(react@19.1.1) - '@react-stately/flags': 3.1.1 - '@react-stately/table': 3.14.2(react@19.1.1) - '@react-types/checkbox': 3.9.4(react@19.1.1) - '@react-types/grid': 3.3.2(react@19.1.1) - '@react-types/shared': 3.29.1(react@19.1.1) - '@react-types/table': 3.13.0(react@19.1.1) + '@react-aria/focus': 3.21.0(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-aria/grid': 3.14.3(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-aria/i18n': 3.12.11(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-aria/interactions': 3.25.4(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-aria/live-announcer': 3.4.4 + '@react-aria/utils': 3.30.0(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-aria/visually-hidden': 3.8.26(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-stately/collections': 3.12.6(react@19.1.1) + '@react-stately/flags': 3.1.2 + '@react-stately/table': 3.14.4(react@19.1.1) + '@react-types/checkbox': 3.10.0(react@19.1.1) + '@react-types/grid': 3.3.4(react@19.1.1) + '@react-types/shared': 3.31.0(react@19.1.1) + '@react-types/table': 3.13.2(react@19.1.1) '@swc/helpers': 0.5.17 react: 19.1.1 react-dom: 19.1.1(react@19.1.1) - '@react-aria/tabs@3.10.3(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': + '@react-aria/tabs@3.10.6(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': dependencies: - '@react-aria/focus': 3.20.3(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@react-aria/i18n': 3.12.9(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@react-aria/selection': 3.24.1(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@react-aria/utils': 3.29.0(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@react-stately/tabs': 3.8.2(react@19.1.1) - '@react-types/shared': 3.29.1(react@19.1.1) - '@react-types/tabs': 3.3.15(react@19.1.1) + '@react-aria/focus': 3.21.0(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-aria/i18n': 3.12.11(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-aria/selection': 3.25.0(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-aria/utils': 3.30.0(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-stately/tabs': 3.8.4(react@19.1.1) + '@react-types/shared': 3.31.0(react@19.1.1) + '@react-types/tabs': 3.3.17(react@19.1.1) '@swc/helpers': 0.5.17 react: 19.1.1 react-dom: 19.1.1(react@19.1.1) - '@react-aria/tag@3.6.0(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': + '@react-aria/tag@3.7.0(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': dependencies: - '@react-aria/gridlist': 3.13.0(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@react-aria/i18n': 3.12.9(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@react-aria/interactions': 3.25.1(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@react-aria/label': 3.7.18(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@react-aria/selection': 3.24.1(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@react-aria/utils': 3.29.0(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@react-stately/list': 3.12.2(react@19.1.1) - '@react-types/button': 3.12.1(react@19.1.1) - '@react-types/shared': 3.29.1(react@19.1.1) + '@react-aria/gridlist': 3.13.3(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-aria/i18n': 3.12.11(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-aria/interactions': 3.25.4(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-aria/label': 3.7.20(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-aria/selection': 3.25.0(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-aria/utils': 3.30.0(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-stately/list': 3.12.4(react@19.1.1) + '@react-types/button': 3.13.0(react@19.1.1) + '@react-types/shared': 3.31.0(react@19.1.1) '@swc/helpers': 0.5.17 react: 19.1.1 react-dom: 19.1.1(react@19.1.1) - '@react-aria/textfield@3.17.3(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': + '@react-aria/textfield@3.18.0(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': dependencies: - '@react-aria/form': 3.0.16(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@react-aria/interactions': 3.25.1(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@react-aria/label': 3.7.18(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@react-aria/utils': 3.29.0(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@react-stately/form': 3.1.4(react@19.1.1) - '@react-stately/utils': 3.10.6(react@19.1.1) - '@react-types/shared': 3.29.1(react@19.1.1) - '@react-types/textfield': 3.12.2(react@19.1.1) + '@react-aria/form': 3.1.0(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-aria/interactions': 3.25.4(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-aria/label': 3.7.20(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-aria/utils': 3.30.0(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-stately/form': 3.2.0(react@19.1.1) + '@react-stately/utils': 3.10.8(react@19.1.1) + '@react-types/shared': 3.31.0(react@19.1.1) + '@react-types/textfield': 3.12.4(react@19.1.1) '@swc/helpers': 0.5.17 react: 19.1.1 react-dom: 19.1.1(react@19.1.1) - '@react-aria/toast@3.0.3(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': + '@react-aria/toast@3.0.6(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': dependencies: - '@react-aria/i18n': 3.12.9(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@react-aria/interactions': 3.25.1(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@react-aria/landmark': 3.0.3(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@react-aria/utils': 3.29.0(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@react-stately/toast': 3.1.0(react@19.1.1) - '@react-types/button': 3.12.1(react@19.1.1) - '@react-types/shared': 3.29.1(react@19.1.1) + '@react-aria/i18n': 3.12.11(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-aria/interactions': 3.25.4(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-aria/landmark': 3.0.5(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-aria/utils': 3.30.0(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-stately/toast': 3.1.2(react@19.1.1) + '@react-types/button': 3.13.0(react@19.1.1) + '@react-types/shared': 3.31.0(react@19.1.1) '@swc/helpers': 0.5.17 react: 19.1.1 react-dom: 19.1.1(react@19.1.1) - '@react-aria/toggle@3.11.3(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': + '@react-aria/toggle@3.12.0(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': dependencies: - '@react-aria/interactions': 3.25.1(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@react-aria/utils': 3.29.0(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@react-stately/toggle': 3.8.4(react@19.1.1) - '@react-types/checkbox': 3.9.4(react@19.1.1) - '@react-types/shared': 3.29.1(react@19.1.1) + '@react-aria/interactions': 3.25.4(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-aria/utils': 3.30.0(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-stately/toggle': 3.9.0(react@19.1.1) + '@react-types/checkbox': 3.10.0(react@19.1.1) + '@react-types/shared': 3.31.0(react@19.1.1) '@swc/helpers': 0.5.17 react: 19.1.1 react-dom: 19.1.1(react@19.1.1) - '@react-aria/toolbar@3.0.0-beta.16(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': + '@react-aria/toolbar@3.0.0-beta.19(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': dependencies: - '@react-aria/focus': 3.20.3(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@react-aria/i18n': 3.12.9(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@react-aria/utils': 3.29.0(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@react-types/shared': 3.29.1(react@19.1.1) + '@react-aria/focus': 3.21.0(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-aria/i18n': 3.12.11(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-aria/utils': 3.30.0(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-types/shared': 3.31.0(react@19.1.1) '@swc/helpers': 0.5.17 react: 19.1.1 react-dom: 19.1.1(react@19.1.1) - '@react-aria/tooltip@3.8.3(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': + '@react-aria/tooltip@3.8.6(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': dependencies: - '@react-aria/interactions': 3.25.1(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@react-aria/utils': 3.29.0(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@react-stately/tooltip': 3.5.4(react@19.1.1) - '@react-types/shared': 3.29.1(react@19.1.1) - '@react-types/tooltip': 3.4.17(react@19.1.1) + '@react-aria/interactions': 3.25.4(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-aria/utils': 3.30.0(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-stately/tooltip': 3.5.6(react@19.1.1) + '@react-types/shared': 3.31.0(react@19.1.1) + '@react-types/tooltip': 3.4.19(react@19.1.1) '@swc/helpers': 0.5.17 react: 19.1.1 react-dom: 19.1.1(react@19.1.1) - '@react-aria/tree@3.0.3(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': + '@react-aria/tree@3.1.2(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': dependencies: - '@react-aria/gridlist': 3.13.0(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@react-aria/i18n': 3.12.9(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@react-aria/selection': 3.24.1(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@react-aria/utils': 3.29.0(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@react-stately/tree': 3.8.10(react@19.1.1) - '@react-types/button': 3.12.1(react@19.1.1) - '@react-types/shared': 3.29.1(react@19.1.1) + '@react-aria/gridlist': 3.13.3(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-aria/i18n': 3.12.11(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-aria/selection': 3.25.0(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-aria/utils': 3.30.0(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-stately/tree': 3.9.1(react@19.1.1) + '@react-types/button': 3.13.0(react@19.1.1) + '@react-types/shared': 3.31.0(react@19.1.1) '@swc/helpers': 0.5.17 react: 19.1.1 react-dom: 19.1.1(react@19.1.1) - '@react-aria/utils@3.29.0(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': + '@react-aria/utils@3.30.0(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': dependencies: - '@react-aria/ssr': 3.9.8(react@19.1.1) - '@react-stately/flags': 3.1.1 - '@react-stately/utils': 3.10.6(react@19.1.1) - '@react-types/shared': 3.29.1(react@19.1.1) + '@react-aria/ssr': 3.9.10(react@19.1.1) + '@react-stately/flags': 3.1.2 + '@react-stately/utils': 3.10.8(react@19.1.1) + '@react-types/shared': 3.31.0(react@19.1.1) '@swc/helpers': 0.5.17 clsx: 2.1.1 react: 19.1.1 react-dom: 19.1.1(react@19.1.1) - '@react-aria/visually-hidden@3.8.23(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': + '@react-aria/visually-hidden@3.8.26(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': dependencies: - '@react-aria/interactions': 3.25.1(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@react-aria/utils': 3.29.0(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@react-types/shared': 3.29.1(react@19.1.1) + '@react-aria/interactions': 3.25.4(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-aria/utils': 3.30.0(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-types/shared': 3.31.0(react@19.1.1) '@swc/helpers': 0.5.17 react: 19.1.1 react-dom: 19.1.1(react@19.1.1) - '@react-router/dev@7.8.1(@types/node@24.3.0)(jiti@2.5.1)(lightningcss@1.30.1)(react-dom@19.1.1(react@19.1.1))(react-router@7.8.1(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(react@19.1.1)(rolldown-vite@7.1.3(@types/node@24.3.0)(esbuild@0.25.5)(jiti@2.5.1)(terser@5.39.0)(tsx@4.19.4)(yaml@2.8.0))(terser@5.39.0)(tsx@4.19.4)(typescript@5.9.2)(yaml@2.8.0)': + '@react-router/dev@7.8.1(@types/node@24.3.0)(jiti@2.5.1)(lightningcss@1.30.1)(react-dom@19.1.1(react@19.1.1))(react-router@7.8.1(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(react@19.1.1)(rolldown-vite@7.1.4(@types/node@24.3.0)(esbuild@0.25.9)(jiti@2.5.1)(terser@5.39.0)(tsx@4.20.4)(yaml@2.8.1))(terser@5.39.0)(tsx@4.20.4)(typescript@5.9.2)(yaml@2.8.1)': dependencies: '@babel/core': 7.28.3 '@babel/generator': 7.28.3 @@ -5837,14 +5525,14 @@ snapshots: '@babel/types': 7.28.2 '@npmcli/package-json': 4.0.1 '@react-router/node': 7.8.1(react-router@7.8.1(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(typescript@5.9.2) - '@vitejs/plugin-rsc': 0.4.11(react-dom@19.1.1(react@19.1.1))(react@19.1.1)(rolldown-vite@7.1.3(@types/node@24.3.0)(esbuild@0.25.5)(jiti@2.5.1)(terser@5.39.0)(tsx@4.19.4)(yaml@2.8.0)) + '@vitejs/plugin-rsc': 0.4.11(react-dom@19.1.1(react@19.1.1))(react@19.1.1)(rolldown-vite@7.1.4(@types/node@24.3.0)(esbuild@0.25.9)(jiti@2.5.1)(terser@5.39.0)(tsx@4.20.4)(yaml@2.8.1)) arg: 5.0.2 babel-dead-code-elimination: 1.0.10 chokidar: 4.0.3 dedent: 1.6.0 es-module-lexer: 1.7.0 exit-hook: 2.2.1 - isbot: 5.1.28 + isbot: 5.1.30 jsesc: 3.0.2 lodash: 4.17.21 pathe: 1.1.2 @@ -5856,8 +5544,8 @@ snapshots: set-cookie-parser: 2.7.1 tinyglobby: 0.2.14 valibot: 0.41.0(typescript@5.9.2) - vite: rolldown-vite@7.1.3(@types/node@24.3.0)(esbuild@0.25.5)(jiti@2.5.1)(terser@5.39.0)(tsx@4.19.4)(yaml@2.8.0) - vite-node: 3.2.4(@types/node@24.3.0)(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.39.0)(tsx@4.19.4)(yaml@2.8.0) + vite: rolldown-vite@7.1.4(@types/node@24.3.0)(esbuild@0.25.9)(jiti@2.5.1)(terser@5.39.0)(tsx@4.20.4)(yaml@2.8.1) + vite-node: 3.2.4(@types/node@24.3.0)(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.39.0)(tsx@4.20.4)(yaml@2.8.1) optionalDependencies: typescript: 5.9.2 transitivePeerDependencies: @@ -5885,376 +5573,376 @@ snapshots: optionalDependencies: typescript: 5.9.2 - '@react-stately/calendar@3.8.1(react@19.1.1)': + '@react-stately/calendar@3.8.3(react@19.1.1)': dependencies: - '@internationalized/date': 3.8.1 - '@react-stately/utils': 3.10.6(react@19.1.1) - '@react-types/calendar': 3.7.1(react@19.1.1) - '@react-types/shared': 3.29.1(react@19.1.1) + '@internationalized/date': 3.8.2 + '@react-stately/utils': 3.10.8(react@19.1.1) + '@react-types/calendar': 3.7.3(react@19.1.1) + '@react-types/shared': 3.31.0(react@19.1.1) '@swc/helpers': 0.5.17 react: 19.1.1 - '@react-stately/checkbox@3.6.14(react@19.1.1)': + '@react-stately/checkbox@3.7.0(react@19.1.1)': dependencies: - '@react-stately/form': 3.1.4(react@19.1.1) - '@react-stately/utils': 3.10.6(react@19.1.1) - '@react-types/checkbox': 3.9.4(react@19.1.1) - '@react-types/shared': 3.29.1(react@19.1.1) + '@react-stately/form': 3.2.0(react@19.1.1) + '@react-stately/utils': 3.10.8(react@19.1.1) + '@react-types/checkbox': 3.10.0(react@19.1.1) + '@react-types/shared': 3.31.0(react@19.1.1) '@swc/helpers': 0.5.17 react: 19.1.1 - '@react-stately/collections@3.12.4(react@19.1.1)': + '@react-stately/collections@3.12.6(react@19.1.1)': dependencies: - '@react-types/shared': 3.29.1(react@19.1.1) + '@react-types/shared': 3.31.0(react@19.1.1) '@swc/helpers': 0.5.17 react: 19.1.1 - '@react-stately/color@3.8.5(react@19.1.1)': + '@react-stately/color@3.9.0(react@19.1.1)': dependencies: - '@internationalized/number': 3.6.2 - '@internationalized/string': 3.2.6 - '@react-stately/form': 3.1.4(react@19.1.1) - '@react-stately/numberfield': 3.9.12(react@19.1.1) - '@react-stately/slider': 3.6.4(react@19.1.1) - '@react-stately/utils': 3.10.6(react@19.1.1) - '@react-types/color': 3.0.5(react@19.1.1) - '@react-types/shared': 3.29.1(react@19.1.1) + '@internationalized/number': 3.6.4 + '@internationalized/string': 3.2.7 + '@react-stately/form': 3.2.0(react@19.1.1) + '@react-stately/numberfield': 3.10.0(react@19.1.1) + '@react-stately/slider': 3.7.0(react@19.1.1) + '@react-stately/utils': 3.10.8(react@19.1.1) + '@react-types/color': 3.1.0(react@19.1.1) + '@react-types/shared': 3.31.0(react@19.1.1) '@swc/helpers': 0.5.17 react: 19.1.1 - '@react-stately/combobox@3.10.5(react@19.1.1)': + '@react-stately/combobox@3.11.0(react@19.1.1)': dependencies: - '@react-stately/collections': 3.12.4(react@19.1.1) - '@react-stately/form': 3.1.4(react@19.1.1) - '@react-stately/list': 3.12.2(react@19.1.1) - '@react-stately/overlays': 3.6.16(react@19.1.1) - '@react-stately/select': 3.6.13(react@19.1.1) - '@react-stately/utils': 3.10.6(react@19.1.1) - '@react-types/combobox': 3.13.5(react@19.1.1) - '@react-types/shared': 3.29.1(react@19.1.1) + '@react-stately/collections': 3.12.6(react@19.1.1) + '@react-stately/form': 3.2.0(react@19.1.1) + '@react-stately/list': 3.12.4(react@19.1.1) + '@react-stately/overlays': 3.6.18(react@19.1.1) + '@react-stately/select': 3.7.0(react@19.1.1) + '@react-stately/utils': 3.10.8(react@19.1.1) + '@react-types/combobox': 3.13.7(react@19.1.1) + '@react-types/shared': 3.31.0(react@19.1.1) '@swc/helpers': 0.5.17 react: 19.1.1 - '@react-stately/data@3.13.0(react@19.1.1)': + '@react-stately/data@3.13.2(react@19.1.1)': dependencies: - '@react-types/shared': 3.29.1(react@19.1.1) + '@react-types/shared': 3.31.0(react@19.1.1) '@swc/helpers': 0.5.17 react: 19.1.1 - '@react-stately/datepicker@3.14.1(react@19.1.1)': + '@react-stately/datepicker@3.15.0(react@19.1.1)': dependencies: - '@internationalized/date': 3.8.1 - '@internationalized/string': 3.2.6 - '@react-stately/form': 3.1.4(react@19.1.1) - '@react-stately/overlays': 3.6.16(react@19.1.1) - '@react-stately/utils': 3.10.6(react@19.1.1) - '@react-types/datepicker': 3.12.1(react@19.1.1) - '@react-types/shared': 3.29.1(react@19.1.1) + '@internationalized/date': 3.8.2 + '@internationalized/string': 3.2.7 + '@react-stately/form': 3.2.0(react@19.1.1) + '@react-stately/overlays': 3.6.18(react@19.1.1) + '@react-stately/utils': 3.10.8(react@19.1.1) + '@react-types/datepicker': 3.13.0(react@19.1.1) + '@react-types/shared': 3.31.0(react@19.1.1) '@swc/helpers': 0.5.17 react: 19.1.1 - '@react-stately/disclosure@3.0.4(react@19.1.1)': + '@react-stately/disclosure@3.0.6(react@19.1.1)': dependencies: - '@react-stately/utils': 3.10.6(react@19.1.1) - '@react-types/shared': 3.29.1(react@19.1.1) + '@react-stately/utils': 3.10.8(react@19.1.1) + '@react-types/shared': 3.31.0(react@19.1.1) '@swc/helpers': 0.5.17 react: 19.1.1 - '@react-stately/dnd@3.5.4(react@19.1.1)': + '@react-stately/dnd@3.6.1(react@19.1.1)': dependencies: - '@react-stately/selection': 3.20.2(react@19.1.1) - '@react-types/shared': 3.29.1(react@19.1.1) + '@react-stately/selection': 3.20.4(react@19.1.1) + '@react-types/shared': 3.31.0(react@19.1.1) '@swc/helpers': 0.5.17 react: 19.1.1 - '@react-stately/flags@3.1.1': + '@react-stately/flags@3.1.2': dependencies: '@swc/helpers': 0.5.17 - '@react-stately/form@3.1.4(react@19.1.1)': + '@react-stately/form@3.2.0(react@19.1.1)': dependencies: - '@react-types/shared': 3.29.1(react@19.1.1) + '@react-types/shared': 3.31.0(react@19.1.1) '@swc/helpers': 0.5.17 react: 19.1.1 - '@react-stately/grid@3.11.2(react@19.1.1)': + '@react-stately/grid@3.11.4(react@19.1.1)': dependencies: - '@react-stately/collections': 3.12.4(react@19.1.1) - '@react-stately/selection': 3.20.2(react@19.1.1) - '@react-types/grid': 3.3.2(react@19.1.1) - '@react-types/shared': 3.29.1(react@19.1.1) + '@react-stately/collections': 3.12.6(react@19.1.1) + '@react-stately/selection': 3.20.4(react@19.1.1) + '@react-types/grid': 3.3.4(react@19.1.1) + '@react-types/shared': 3.31.0(react@19.1.1) '@swc/helpers': 0.5.17 react: 19.1.1 - '@react-stately/list@3.12.2(react@19.1.1)': + '@react-stately/list@3.12.4(react@19.1.1)': dependencies: - '@react-stately/collections': 3.12.4(react@19.1.1) - '@react-stately/selection': 3.20.2(react@19.1.1) - '@react-stately/utils': 3.10.6(react@19.1.1) - '@react-types/shared': 3.29.1(react@19.1.1) + '@react-stately/collections': 3.12.6(react@19.1.1) + '@react-stately/selection': 3.20.4(react@19.1.1) + '@react-stately/utils': 3.10.8(react@19.1.1) + '@react-types/shared': 3.31.0(react@19.1.1) '@swc/helpers': 0.5.17 react: 19.1.1 - '@react-stately/menu@3.9.4(react@19.1.1)': + '@react-stately/menu@3.9.6(react@19.1.1)': dependencies: - '@react-stately/overlays': 3.6.16(react@19.1.1) - '@react-types/menu': 3.10.1(react@19.1.1) - '@react-types/shared': 3.29.1(react@19.1.1) + '@react-stately/overlays': 3.6.18(react@19.1.1) + '@react-types/menu': 3.10.3(react@19.1.1) + '@react-types/shared': 3.31.0(react@19.1.1) '@swc/helpers': 0.5.17 react: 19.1.1 - '@react-stately/numberfield@3.9.12(react@19.1.1)': + '@react-stately/numberfield@3.10.0(react@19.1.1)': dependencies: - '@internationalized/number': 3.6.2 - '@react-stately/form': 3.1.4(react@19.1.1) - '@react-stately/utils': 3.10.6(react@19.1.1) - '@react-types/numberfield': 3.8.11(react@19.1.1) + '@internationalized/number': 3.6.4 + '@react-stately/form': 3.2.0(react@19.1.1) + '@react-stately/utils': 3.10.8(react@19.1.1) + '@react-types/numberfield': 3.8.13(react@19.1.1) '@swc/helpers': 0.5.17 react: 19.1.1 - '@react-stately/overlays@3.6.16(react@19.1.1)': + '@react-stately/overlays@3.6.18(react@19.1.1)': dependencies: - '@react-stately/utils': 3.10.6(react@19.1.1) - '@react-types/overlays': 3.8.15(react@19.1.1) + '@react-stately/utils': 3.10.8(react@19.1.1) + '@react-types/overlays': 3.9.0(react@19.1.1) '@swc/helpers': 0.5.17 react: 19.1.1 - '@react-stately/radio@3.10.13(react@19.1.1)': + '@react-stately/radio@3.11.0(react@19.1.1)': dependencies: - '@react-stately/form': 3.1.4(react@19.1.1) - '@react-stately/utils': 3.10.6(react@19.1.1) - '@react-types/radio': 3.8.9(react@19.1.1) - '@react-types/shared': 3.29.1(react@19.1.1) + '@react-stately/form': 3.2.0(react@19.1.1) + '@react-stately/utils': 3.10.8(react@19.1.1) + '@react-types/radio': 3.9.0(react@19.1.1) + '@react-types/shared': 3.31.0(react@19.1.1) '@swc/helpers': 0.5.17 react: 19.1.1 - '@react-stately/searchfield@3.5.12(react@19.1.1)': + '@react-stately/searchfield@3.5.14(react@19.1.1)': dependencies: - '@react-stately/utils': 3.10.6(react@19.1.1) - '@react-types/searchfield': 3.6.2(react@19.1.1) + '@react-stately/utils': 3.10.8(react@19.1.1) + '@react-types/searchfield': 3.6.4(react@19.1.1) '@swc/helpers': 0.5.17 react: 19.1.1 - '@react-stately/select@3.6.13(react@19.1.1)': + '@react-stately/select@3.7.0(react@19.1.1)': dependencies: - '@react-stately/form': 3.1.4(react@19.1.1) - '@react-stately/list': 3.12.2(react@19.1.1) - '@react-stately/overlays': 3.6.16(react@19.1.1) - '@react-types/select': 3.9.12(react@19.1.1) - '@react-types/shared': 3.29.1(react@19.1.1) + '@react-stately/form': 3.2.0(react@19.1.1) + '@react-stately/list': 3.12.4(react@19.1.1) + '@react-stately/overlays': 3.6.18(react@19.1.1) + '@react-types/select': 3.10.0(react@19.1.1) + '@react-types/shared': 3.31.0(react@19.1.1) '@swc/helpers': 0.5.17 react: 19.1.1 - '@react-stately/selection@3.20.2(react@19.1.1)': + '@react-stately/selection@3.20.4(react@19.1.1)': dependencies: - '@react-stately/collections': 3.12.4(react@19.1.1) - '@react-stately/utils': 3.10.6(react@19.1.1) - '@react-types/shared': 3.29.1(react@19.1.1) + '@react-stately/collections': 3.12.6(react@19.1.1) + '@react-stately/utils': 3.10.8(react@19.1.1) + '@react-types/shared': 3.31.0(react@19.1.1) '@swc/helpers': 0.5.17 react: 19.1.1 - '@react-stately/slider@3.6.4(react@19.1.1)': + '@react-stately/slider@3.7.0(react@19.1.1)': dependencies: - '@react-stately/utils': 3.10.6(react@19.1.1) - '@react-types/shared': 3.29.1(react@19.1.1) - '@react-types/slider': 3.7.11(react@19.1.1) + '@react-stately/utils': 3.10.8(react@19.1.1) + '@react-types/shared': 3.31.0(react@19.1.1) + '@react-types/slider': 3.8.0(react@19.1.1) '@swc/helpers': 0.5.17 react: 19.1.1 - '@react-stately/table@3.14.2(react@19.1.1)': + '@react-stately/table@3.14.4(react@19.1.1)': dependencies: - '@react-stately/collections': 3.12.4(react@19.1.1) - '@react-stately/flags': 3.1.1 - '@react-stately/grid': 3.11.2(react@19.1.1) - '@react-stately/selection': 3.20.2(react@19.1.1) - '@react-stately/utils': 3.10.6(react@19.1.1) - '@react-types/grid': 3.3.2(react@19.1.1) - '@react-types/shared': 3.29.1(react@19.1.1) - '@react-types/table': 3.13.0(react@19.1.1) + '@react-stately/collections': 3.12.6(react@19.1.1) + '@react-stately/flags': 3.1.2 + '@react-stately/grid': 3.11.4(react@19.1.1) + '@react-stately/selection': 3.20.4(react@19.1.1) + '@react-stately/utils': 3.10.8(react@19.1.1) + '@react-types/grid': 3.3.4(react@19.1.1) + '@react-types/shared': 3.31.0(react@19.1.1) + '@react-types/table': 3.13.2(react@19.1.1) '@swc/helpers': 0.5.17 react: 19.1.1 - '@react-stately/tabs@3.8.2(react@19.1.1)': + '@react-stately/tabs@3.8.4(react@19.1.1)': dependencies: - '@react-stately/list': 3.12.2(react@19.1.1) - '@react-types/shared': 3.29.1(react@19.1.1) - '@react-types/tabs': 3.3.15(react@19.1.1) + '@react-stately/list': 3.12.4(react@19.1.1) + '@react-types/shared': 3.31.0(react@19.1.1) + '@react-types/tabs': 3.3.17(react@19.1.1) '@swc/helpers': 0.5.17 react: 19.1.1 - '@react-stately/toast@3.1.0(react@19.1.1)': + '@react-stately/toast@3.1.2(react@19.1.1)': dependencies: '@swc/helpers': 0.5.17 react: 19.1.1 use-sync-external-store: 1.5.0(react@19.1.1) - '@react-stately/toggle@3.8.4(react@19.1.1)': + '@react-stately/toggle@3.9.0(react@19.1.1)': dependencies: - '@react-stately/utils': 3.10.6(react@19.1.1) - '@react-types/checkbox': 3.9.4(react@19.1.1) - '@react-types/shared': 3.29.1(react@19.1.1) + '@react-stately/utils': 3.10.8(react@19.1.1) + '@react-types/checkbox': 3.10.0(react@19.1.1) + '@react-types/shared': 3.31.0(react@19.1.1) '@swc/helpers': 0.5.17 react: 19.1.1 - '@react-stately/tooltip@3.5.4(react@19.1.1)': + '@react-stately/tooltip@3.5.6(react@19.1.1)': dependencies: - '@react-stately/overlays': 3.6.16(react@19.1.1) - '@react-types/tooltip': 3.4.17(react@19.1.1) + '@react-stately/overlays': 3.6.18(react@19.1.1) + '@react-types/tooltip': 3.4.19(react@19.1.1) '@swc/helpers': 0.5.17 react: 19.1.1 - '@react-stately/tree@3.8.10(react@19.1.1)': + '@react-stately/tree@3.9.1(react@19.1.1)': dependencies: - '@react-stately/collections': 3.12.4(react@19.1.1) - '@react-stately/selection': 3.20.2(react@19.1.1) - '@react-stately/utils': 3.10.6(react@19.1.1) - '@react-types/shared': 3.29.1(react@19.1.1) + '@react-stately/collections': 3.12.6(react@19.1.1) + '@react-stately/selection': 3.20.4(react@19.1.1) + '@react-stately/utils': 3.10.8(react@19.1.1) + '@react-types/shared': 3.31.0(react@19.1.1) '@swc/helpers': 0.5.17 react: 19.1.1 - '@react-stately/utils@3.10.6(react@19.1.1)': + '@react-stately/utils@3.10.8(react@19.1.1)': dependencies: '@swc/helpers': 0.5.17 react: 19.1.1 - '@react-types/breadcrumbs@3.7.13(react@19.1.1)': + '@react-types/breadcrumbs@3.7.15(react@19.1.1)': dependencies: - '@react-types/link': 3.6.1(react@19.1.1) - '@react-types/shared': 3.29.1(react@19.1.1) + '@react-types/link': 3.6.3(react@19.1.1) + '@react-types/shared': 3.31.0(react@19.1.1) react: 19.1.1 - '@react-types/button@3.12.1(react@19.1.1)': + '@react-types/button@3.13.0(react@19.1.1)': dependencies: - '@react-types/shared': 3.29.1(react@19.1.1) + '@react-types/shared': 3.31.0(react@19.1.1) react: 19.1.1 - '@react-types/calendar@3.7.1(react@19.1.1)': + '@react-types/calendar@3.7.3(react@19.1.1)': dependencies: - '@internationalized/date': 3.8.1 - '@react-types/shared': 3.29.1(react@19.1.1) + '@internationalized/date': 3.8.2 + '@react-types/shared': 3.31.0(react@19.1.1) react: 19.1.1 - '@react-types/checkbox@3.9.4(react@19.1.1)': + '@react-types/checkbox@3.10.0(react@19.1.1)': dependencies: - '@react-types/shared': 3.29.1(react@19.1.1) + '@react-types/shared': 3.31.0(react@19.1.1) react: 19.1.1 - '@react-types/color@3.0.5(react@19.1.1)': + '@react-types/color@3.1.0(react@19.1.1)': dependencies: - '@react-types/shared': 3.29.1(react@19.1.1) - '@react-types/slider': 3.7.11(react@19.1.1) + '@react-types/shared': 3.31.0(react@19.1.1) + '@react-types/slider': 3.8.0(react@19.1.1) react: 19.1.1 - '@react-types/combobox@3.13.5(react@19.1.1)': + '@react-types/combobox@3.13.7(react@19.1.1)': dependencies: - '@react-types/shared': 3.29.1(react@19.1.1) + '@react-types/shared': 3.31.0(react@19.1.1) react: 19.1.1 - '@react-types/datepicker@3.12.1(react@19.1.1)': + '@react-types/datepicker@3.13.0(react@19.1.1)': dependencies: - '@internationalized/date': 3.8.1 - '@react-types/calendar': 3.7.1(react@19.1.1) - '@react-types/overlays': 3.8.15(react@19.1.1) - '@react-types/shared': 3.29.1(react@19.1.1) + '@internationalized/date': 3.8.2 + '@react-types/calendar': 3.7.3(react@19.1.1) + '@react-types/overlays': 3.9.0(react@19.1.1) + '@react-types/shared': 3.31.0(react@19.1.1) react: 19.1.1 - '@react-types/dialog@3.5.18(react@19.1.1)': + '@react-types/dialog@3.5.20(react@19.1.1)': dependencies: - '@react-types/overlays': 3.8.15(react@19.1.1) - '@react-types/shared': 3.29.1(react@19.1.1) + '@react-types/overlays': 3.9.0(react@19.1.1) + '@react-types/shared': 3.31.0(react@19.1.1) react: 19.1.1 - '@react-types/grid@3.3.2(react@19.1.1)': + '@react-types/grid@3.3.4(react@19.1.1)': dependencies: - '@react-types/shared': 3.29.1(react@19.1.1) + '@react-types/shared': 3.31.0(react@19.1.1) react: 19.1.1 - '@react-types/link@3.6.1(react@19.1.1)': + '@react-types/link@3.6.3(react@19.1.1)': dependencies: - '@react-types/shared': 3.29.1(react@19.1.1) + '@react-types/shared': 3.31.0(react@19.1.1) react: 19.1.1 - '@react-types/listbox@3.7.0(react@19.1.1)': + '@react-types/listbox@3.7.2(react@19.1.1)': dependencies: - '@react-types/shared': 3.29.1(react@19.1.1) + '@react-types/shared': 3.31.0(react@19.1.1) react: 19.1.1 - '@react-types/menu@3.10.1(react@19.1.1)': + '@react-types/menu@3.10.3(react@19.1.1)': dependencies: - '@react-types/overlays': 3.8.15(react@19.1.1) - '@react-types/shared': 3.29.1(react@19.1.1) + '@react-types/overlays': 3.9.0(react@19.1.1) + '@react-types/shared': 3.31.0(react@19.1.1) react: 19.1.1 - '@react-types/meter@3.4.9(react@19.1.1)': + '@react-types/meter@3.4.11(react@19.1.1)': dependencies: - '@react-types/progress': 3.5.12(react@19.1.1) + '@react-types/progress': 3.5.14(react@19.1.1) react: 19.1.1 - '@react-types/numberfield@3.8.11(react@19.1.1)': + '@react-types/numberfield@3.8.13(react@19.1.1)': dependencies: - '@react-types/shared': 3.29.1(react@19.1.1) + '@react-types/shared': 3.31.0(react@19.1.1) react: 19.1.1 - '@react-types/overlays@3.8.15(react@19.1.1)': + '@react-types/overlays@3.9.0(react@19.1.1)': dependencies: - '@react-types/shared': 3.29.1(react@19.1.1) + '@react-types/shared': 3.31.0(react@19.1.1) react: 19.1.1 - '@react-types/progress@3.5.12(react@19.1.1)': + '@react-types/progress@3.5.14(react@19.1.1)': dependencies: - '@react-types/shared': 3.29.1(react@19.1.1) + '@react-types/shared': 3.31.0(react@19.1.1) react: 19.1.1 - '@react-types/radio@3.8.9(react@19.1.1)': + '@react-types/radio@3.9.0(react@19.1.1)': dependencies: - '@react-types/shared': 3.29.1(react@19.1.1) + '@react-types/shared': 3.31.0(react@19.1.1) react: 19.1.1 - '@react-types/searchfield@3.6.2(react@19.1.1)': + '@react-types/searchfield@3.6.4(react@19.1.1)': dependencies: - '@react-types/shared': 3.29.1(react@19.1.1) - '@react-types/textfield': 3.12.2(react@19.1.1) + '@react-types/shared': 3.31.0(react@19.1.1) + '@react-types/textfield': 3.12.4(react@19.1.1) react: 19.1.1 - '@react-types/select@3.9.12(react@19.1.1)': + '@react-types/select@3.10.0(react@19.1.1)': dependencies: - '@react-types/shared': 3.29.1(react@19.1.1) + '@react-types/shared': 3.31.0(react@19.1.1) react: 19.1.1 - '@react-types/shared@3.29.1(react@19.1.1)': + '@react-types/shared@3.31.0(react@19.1.1)': dependencies: react: 19.1.1 - '@react-types/slider@3.7.11(react@19.1.1)': + '@react-types/slider@3.8.0(react@19.1.1)': dependencies: - '@react-types/shared': 3.29.1(react@19.1.1) + '@react-types/shared': 3.31.0(react@19.1.1) react: 19.1.1 - '@react-types/switch@3.5.11(react@19.1.1)': + '@react-types/switch@3.5.13(react@19.1.1)': dependencies: - '@react-types/shared': 3.29.1(react@19.1.1) + '@react-types/shared': 3.31.0(react@19.1.1) react: 19.1.1 - '@react-types/table@3.13.0(react@19.1.1)': + '@react-types/table@3.13.2(react@19.1.1)': dependencies: - '@react-types/grid': 3.3.2(react@19.1.1) - '@react-types/shared': 3.29.1(react@19.1.1) + '@react-types/grid': 3.3.4(react@19.1.1) + '@react-types/shared': 3.31.0(react@19.1.1) react: 19.1.1 - '@react-types/tabs@3.3.15(react@19.1.1)': + '@react-types/tabs@3.3.17(react@19.1.1)': dependencies: - '@react-types/shared': 3.29.1(react@19.1.1) + '@react-types/shared': 3.31.0(react@19.1.1) react: 19.1.1 - '@react-types/textfield@3.12.2(react@19.1.1)': + '@react-types/textfield@3.12.4(react@19.1.1)': dependencies: - '@react-types/shared': 3.29.1(react@19.1.1) + '@react-types/shared': 3.31.0(react@19.1.1) react: 19.1.1 - '@react-types/tooltip@3.4.17(react@19.1.1)': + '@react-types/tooltip@3.4.19(react@19.1.1)': dependencies: - '@react-types/overlays': 3.8.15(react@19.1.1) - '@react-types/shared': 3.29.1(react@19.1.1) + '@react-types/overlays': 3.9.0(react@19.1.1) + '@react-types/shared': 3.31.0(react@19.1.1) react: 19.1.1 '@rolldown/binding-android-arm64@1.0.0-beta.33': @@ -6305,11 +5993,11 @@ snapshots: '@rolldown/pluginutils@1.0.0-beta.33': {} - '@rollup/pluginutils@5.1.4(rollup@4.46.3)': + '@rollup/pluginutils@5.2.0(rollup@4.46.3)': dependencies: - '@types/estree': 1.0.7 + '@types/estree': 1.0.8 estree-walker: 2.0.2 - picomatch: 4.0.2 + picomatch: 4.0.3 optionalDependencies: rollup: 4.46.3 @@ -6484,12 +6172,12 @@ snapshots: '@tailwindcss/oxide-win32-arm64-msvc': 4.1.12 '@tailwindcss/oxide-win32-x64-msvc': 4.1.12 - '@tailwindcss/vite@4.1.12(rolldown-vite@7.1.3(@types/node@24.3.0)(esbuild@0.25.5)(jiti@2.5.1)(terser@5.39.0)(tsx@4.19.4)(yaml@2.8.0))': + '@tailwindcss/vite@4.1.12(rolldown-vite@7.1.4(@types/node@24.3.0)(esbuild@0.25.9)(jiti@2.5.1)(terser@5.39.0)(tsx@4.20.4)(yaml@2.8.1))': dependencies: '@tailwindcss/node': 4.1.12 '@tailwindcss/oxide': 4.1.12 tailwindcss: 4.1.12 - vite: rolldown-vite@7.1.3(@types/node@24.3.0)(esbuild@0.25.5)(jiti@2.5.1)(terser@5.39.0)(tsx@4.19.4)(yaml@2.8.0) + vite: rolldown-vite@7.1.4(@types/node@24.3.0)(esbuild@0.25.9)(jiti@2.5.1)(terser@5.39.0)(tsx@4.20.4)(yaml@2.8.1) '@tybys/wasm-util@0.10.0': dependencies: @@ -6567,46 +6255,42 @@ snapshots: '@types/web-bluetooth@0.0.21': {} - '@types/websocket@1.0.10': - dependencies: - '@types/node': 24.3.0 - '@types/ws@8.18.1': dependencies: '@types/node': 24.3.0 - '@typescript/native-preview-darwin-arm64@7.0.0-dev.20250818.1': + '@typescript/native-preview-darwin-arm64@7.0.0-dev.20250821.1': optional: true - '@typescript/native-preview-darwin-x64@7.0.0-dev.20250818.1': + '@typescript/native-preview-darwin-x64@7.0.0-dev.20250821.1': optional: true - '@typescript/native-preview-linux-arm64@7.0.0-dev.20250818.1': + '@typescript/native-preview-linux-arm64@7.0.0-dev.20250821.1': optional: true - '@typescript/native-preview-linux-arm@7.0.0-dev.20250818.1': + '@typescript/native-preview-linux-arm@7.0.0-dev.20250821.1': optional: true - '@typescript/native-preview-linux-x64@7.0.0-dev.20250818.1': + '@typescript/native-preview-linux-x64@7.0.0-dev.20250821.1': optional: true - '@typescript/native-preview-win32-arm64@7.0.0-dev.20250818.1': + '@typescript/native-preview-win32-arm64@7.0.0-dev.20250821.1': optional: true - '@typescript/native-preview-win32-x64@7.0.0-dev.20250818.1': + '@typescript/native-preview-win32-x64@7.0.0-dev.20250821.1': optional: true - '@typescript/native-preview@7.0.0-dev.20250818.1': + '@typescript/native-preview@7.0.0-dev.20250821.1': optionalDependencies: - '@typescript/native-preview-darwin-arm64': 7.0.0-dev.20250818.1 - '@typescript/native-preview-darwin-x64': 7.0.0-dev.20250818.1 - '@typescript/native-preview-linux-arm': 7.0.0-dev.20250818.1 - '@typescript/native-preview-linux-arm64': 7.0.0-dev.20250818.1 - '@typescript/native-preview-linux-x64': 7.0.0-dev.20250818.1 - '@typescript/native-preview-win32-arm64': 7.0.0-dev.20250818.1 - '@typescript/native-preview-win32-x64': 7.0.0-dev.20250818.1 + '@typescript/native-preview-darwin-arm64': 7.0.0-dev.20250821.1 + '@typescript/native-preview-darwin-x64': 7.0.0-dev.20250821.1 + '@typescript/native-preview-linux-arm': 7.0.0-dev.20250821.1 + '@typescript/native-preview-linux-arm64': 7.0.0-dev.20250821.1 + '@typescript/native-preview-linux-x64': 7.0.0-dev.20250821.1 + '@typescript/native-preview-win32-arm64': 7.0.0-dev.20250821.1 + '@typescript/native-preview-win32-x64': 7.0.0-dev.20250821.1 - '@uiw/codemirror-extensions-basic-setup@4.23.12(@codemirror/autocomplete@6.18.2(@codemirror/language@6.11.0)(@codemirror/state@6.5.2)(@codemirror/view@6.36.8)(@lezer/common@1.2.3))(@codemirror/commands@6.8.1)(@codemirror/language@6.11.0)(@codemirror/lint@6.8.2)(@codemirror/search@6.5.7)(@codemirror/state@6.5.2)(@codemirror/view@6.36.8)': + '@uiw/codemirror-extensions-basic-setup@4.25.1(@codemirror/autocomplete@6.18.2(@codemirror/language@6.11.0)(@codemirror/state@6.5.2)(@codemirror/view@6.36.8)(@lezer/common@1.2.3))(@codemirror/commands@6.8.1)(@codemirror/language@6.11.0)(@codemirror/lint@6.8.2)(@codemirror/search@6.5.7)(@codemirror/state@6.5.2)(@codemirror/view@6.36.8)': dependencies: '@codemirror/autocomplete': 6.18.2(@codemirror/language@6.11.0)(@codemirror/state@6.5.2)(@codemirror/view@6.36.8)(@lezer/common@1.2.3) '@codemirror/commands': 6.8.1 @@ -6616,36 +6300,36 @@ snapshots: '@codemirror/state': 6.5.2 '@codemirror/view': 6.36.8 - '@uiw/codemirror-theme-github@4.23.12(@codemirror/language@6.11.0)(@codemirror/state@6.5.2)(@codemirror/view@6.36.8)': + '@uiw/codemirror-theme-github@4.25.1(@codemirror/language@6.11.0)(@codemirror/state@6.5.2)(@codemirror/view@6.36.8)': dependencies: - '@uiw/codemirror-themes': 4.23.12(@codemirror/language@6.11.0)(@codemirror/state@6.5.2)(@codemirror/view@6.36.8) + '@uiw/codemirror-themes': 4.25.1(@codemirror/language@6.11.0)(@codemirror/state@6.5.2)(@codemirror/view@6.36.8) transitivePeerDependencies: - '@codemirror/language' - '@codemirror/state' - '@codemirror/view' - '@uiw/codemirror-theme-xcode@4.23.12(@codemirror/language@6.11.0)(@codemirror/state@6.5.2)(@codemirror/view@6.36.8)': + '@uiw/codemirror-theme-xcode@4.25.1(@codemirror/language@6.11.0)(@codemirror/state@6.5.2)(@codemirror/view@6.36.8)': dependencies: - '@uiw/codemirror-themes': 4.23.12(@codemirror/language@6.11.0)(@codemirror/state@6.5.2)(@codemirror/view@6.36.8) + '@uiw/codemirror-themes': 4.25.1(@codemirror/language@6.11.0)(@codemirror/state@6.5.2)(@codemirror/view@6.36.8) transitivePeerDependencies: - '@codemirror/language' - '@codemirror/state' - '@codemirror/view' - '@uiw/codemirror-themes@4.23.12(@codemirror/language@6.11.0)(@codemirror/state@6.5.2)(@codemirror/view@6.36.8)': + '@uiw/codemirror-themes@4.25.1(@codemirror/language@6.11.0)(@codemirror/state@6.5.2)(@codemirror/view@6.36.8)': dependencies: '@codemirror/language': 6.11.0 '@codemirror/state': 6.5.2 '@codemirror/view': 6.36.8 - '@uiw/react-codemirror@4.23.12(@babel/runtime@7.27.3)(@codemirror/autocomplete@6.18.2(@codemirror/language@6.11.0)(@codemirror/state@6.5.2)(@codemirror/view@6.36.8)(@lezer/common@1.2.3))(@codemirror/language@6.11.0)(@codemirror/lint@6.8.2)(@codemirror/search@6.5.7)(@codemirror/state@6.5.2)(@codemirror/theme-one-dark@6.1.2)(@codemirror/view@6.36.8)(codemirror@6.0.1(@lezer/common@1.2.3))(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': + '@uiw/react-codemirror@4.25.1(@babel/runtime@7.27.3)(@codemirror/autocomplete@6.18.2(@codemirror/language@6.11.0)(@codemirror/state@6.5.2)(@codemirror/view@6.36.8)(@lezer/common@1.2.3))(@codemirror/language@6.11.0)(@codemirror/lint@6.8.2)(@codemirror/search@6.5.7)(@codemirror/state@6.5.2)(@codemirror/theme-one-dark@6.1.2)(@codemirror/view@6.36.8)(codemirror@6.0.1(@lezer/common@1.2.3))(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': dependencies: '@babel/runtime': 7.27.3 '@codemirror/commands': 6.8.1 '@codemirror/state': 6.5.2 '@codemirror/theme-one-dark': 6.1.2 '@codemirror/view': 6.36.8 - '@uiw/codemirror-extensions-basic-setup': 4.23.12(@codemirror/autocomplete@6.18.2(@codemirror/language@6.11.0)(@codemirror/state@6.5.2)(@codemirror/view@6.36.8)(@lezer/common@1.2.3))(@codemirror/commands@6.8.1)(@codemirror/language@6.11.0)(@codemirror/lint@6.8.2)(@codemirror/search@6.5.7)(@codemirror/state@6.5.2)(@codemirror/view@6.36.8) + '@uiw/codemirror-extensions-basic-setup': 4.25.1(@codemirror/autocomplete@6.18.2(@codemirror/language@6.11.0)(@codemirror/state@6.5.2)(@codemirror/view@6.36.8)(@lezer/common@1.2.3))(@codemirror/commands@6.8.1)(@codemirror/language@6.11.0)(@codemirror/lint@6.8.2)(@codemirror/search@6.5.7)(@codemirror/state@6.5.2)(@codemirror/view@6.36.8) codemirror: 6.0.1(@lezer/common@1.2.3) react: 19.1.1 react-dom: 19.1.1(react@19.1.1) @@ -6657,7 +6341,7 @@ snapshots: '@ungap/structured-clone@1.3.0': {} - '@vitejs/plugin-rsc@0.4.11(react-dom@19.1.1(react@19.1.1))(react@19.1.1)(rolldown-vite@7.1.3(@types/node@24.3.0)(esbuild@0.25.5)(jiti@2.5.1)(terser@5.39.0)(tsx@4.19.4)(yaml@2.8.0))': + '@vitejs/plugin-rsc@0.4.11(react-dom@19.1.1(react@19.1.1))(react@19.1.1)(rolldown-vite@7.1.4(@types/node@24.3.0)(esbuild@0.25.9)(jiti@2.5.1)(terser@5.39.0)(tsx@4.20.4)(yaml@2.8.1))': dependencies: '@mjackson/node-fetch-server': 0.7.0 es-module-lexer: 1.7.0 @@ -6667,13 +6351,13 @@ snapshots: react: 19.1.1 react-dom: 19.1.1(react@19.1.1) turbo-stream: 3.1.0 - vite: rolldown-vite@7.1.3(@types/node@24.3.0)(esbuild@0.25.5)(jiti@2.5.1)(terser@5.39.0)(tsx@4.19.4)(yaml@2.8.0) - vitefu: 1.1.1(rolldown-vite@7.1.3(@types/node@24.3.0)(esbuild@0.25.5)(jiti@2.5.1)(terser@5.39.0)(tsx@4.19.4)(yaml@2.8.0)) + vite: rolldown-vite@7.1.4(@types/node@24.3.0)(esbuild@0.25.9)(jiti@2.5.1)(terser@5.39.0)(tsx@4.20.4)(yaml@2.8.1) + vitefu: 1.1.1(rolldown-vite@7.1.4(@types/node@24.3.0)(esbuild@0.25.9)(jiti@2.5.1)(terser@5.39.0)(tsx@4.20.4)(yaml@2.8.1)) - '@vitejs/plugin-vue@6.0.1(vite@7.1.2(@types/node@24.3.0)(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.39.0)(tsx@4.19.4)(yaml@2.8.0))(vue@3.5.18(typescript@5.9.2))': + '@vitejs/plugin-vue@6.0.1(vite@7.1.2(@types/node@24.3.0)(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.39.0)(tsx@4.20.4)(yaml@2.8.1))(vue@3.5.18(typescript@5.9.2))': dependencies: '@rolldown/pluginutils': 1.0.0-beta.29 - vite: 7.1.2(@types/node@24.3.0)(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.39.0)(tsx@4.19.4)(yaml@2.8.0) + vite: 7.1.2(@types/node@24.3.0)(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.39.0)(tsx@4.20.4)(yaml@2.8.1) vue: 3.5.18(typescript@5.9.2) '@vitest/expect@3.2.4': @@ -6684,13 +6368,13 @@ snapshots: chai: 5.2.1 tinyrainbow: 2.0.0 - '@vitest/mocker@3.2.4(vite@7.1.2(@types/node@24.3.0)(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.39.0)(tsx@4.19.4)(yaml@2.8.0))': + '@vitest/mocker@3.2.4(vite@7.1.2(@types/node@24.3.0)(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.39.0)(tsx@4.20.4)(yaml@2.8.1))': dependencies: '@vitest/spy': 3.2.4 estree-walker: 3.0.3 magic-string: 0.30.17 optionalDependencies: - vite: 7.1.2(@types/node@24.3.0)(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.39.0)(tsx@4.19.4)(yaml@2.8.0) + vite: 7.1.2(@types/node@24.3.0)(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.39.0)(tsx@4.20.4)(yaml@2.8.1) '@vitest/pretty-format@3.2.4': dependencies: @@ -6834,9 +6518,6 @@ snapshots: '@xterm/xterm@5.5.0': {} - acorn@8.14.1: - optional: true - acorn@8.15.0: optional: true @@ -6919,7 +6600,7 @@ snapshots: file-uri-to-path: 1.0.0 optional: true - bippy@0.3.14(@types/react@19.1.10)(react@19.1.1): + bippy@0.3.18(@types/react@19.1.10)(react@19.1.1): dependencies: '@types/react-reconciler': 0.28.9(@types/react@19.1.10) react: 19.1.1 @@ -7076,20 +6757,20 @@ snapshots: dependencies: dequal: 2.0.3 - dotenv@16.5.0: {} + dotenv@17.2.1: {} - drizzle-kit@0.31.1: + drizzle-kit@0.31.4: dependencies: '@drizzle-team/brocli': 0.10.2 '@esbuild-kit/esm-loader': 2.6.5 - esbuild: 0.25.5 - esbuild-register: 3.6.0(esbuild@0.25.5) + esbuild: 0.25.9 + esbuild-register: 3.6.0(esbuild@0.25.9) transitivePeerDependencies: - supports-color - drizzle-orm@0.44.2(@libsql/client@0.15.9(bufferutil@4.0.9)(utf-8-validate@5.0.10))(@types/better-sqlite3@7.6.13)(better-sqlite3@11.10.0): + drizzle-orm@0.44.4(@libsql/client@0.15.12(bufferutil@4.0.9)(utf-8-validate@5.0.10))(@types/better-sqlite3@7.6.13)(better-sqlite3@11.10.0): optionalDependencies: - '@libsql/client': 0.15.9(bufferutil@4.0.9)(utf-8-validate@5.0.10) + '@libsql/client': 0.15.12(bufferutil@4.0.9)(utf-8-validate@5.0.10) '@types/better-sqlite3': 7.6.13 better-sqlite3: 11.10.0 @@ -7142,10 +6823,10 @@ snapshots: has-tostringtag: 1.0.2 hasown: 2.0.2 - esbuild-register@3.6.0(esbuild@0.25.5): + esbuild-register@3.6.0(esbuild@0.25.9): dependencies: debug: 4.4.1 - esbuild: 0.25.5 + esbuild: 0.25.9 transitivePeerDependencies: - supports-color @@ -7174,34 +6855,6 @@ snapshots: '@esbuild/win32-ia32': 0.18.20 '@esbuild/win32-x64': 0.18.20 - esbuild@0.25.5: - optionalDependencies: - '@esbuild/aix-ppc64': 0.25.5 - '@esbuild/android-arm': 0.25.5 - '@esbuild/android-arm64': 0.25.5 - '@esbuild/android-x64': 0.25.5 - '@esbuild/darwin-arm64': 0.25.5 - '@esbuild/darwin-x64': 0.25.5 - '@esbuild/freebsd-arm64': 0.25.5 - '@esbuild/freebsd-x64': 0.25.5 - '@esbuild/linux-arm': 0.25.5 - '@esbuild/linux-arm64': 0.25.5 - '@esbuild/linux-ia32': 0.25.5 - '@esbuild/linux-loong64': 0.25.5 - '@esbuild/linux-mips64el': 0.25.5 - '@esbuild/linux-ppc64': 0.25.5 - '@esbuild/linux-riscv64': 0.25.5 - '@esbuild/linux-s390x': 0.25.5 - '@esbuild/linux-x64': 0.25.5 - '@esbuild/netbsd-arm64': 0.25.5 - '@esbuild/netbsd-x64': 0.25.5 - '@esbuild/openbsd-arm64': 0.25.5 - '@esbuild/openbsd-x64': 0.25.5 - '@esbuild/sunos-x64': 0.25.5 - '@esbuild/win32-arm64': 0.25.5 - '@esbuild/win32-ia32': 0.25.5 - '@esbuild/win32-x64': 0.25.5 - esbuild@0.25.9: optionalDependencies: '@esbuild/aix-ppc64': 0.25.9 @@ -7412,7 +7065,7 @@ snapshots: is-what@4.1.16: {} - isbot@5.1.28: {} + isbot@5.1.30: {} isexe@2.0.0: {} @@ -7428,7 +7081,7 @@ snapshots: jiti@2.5.1: {} - jose@6.0.12: {} + jose@6.0.13: {} js-base64@3.7.7: {} @@ -7460,63 +7113,63 @@ snapshots: layerr@3.0.0: {} - lefthook-darwin-arm64@1.11.13: + lefthook-darwin-arm64@1.12.3: optional: true - lefthook-darwin-x64@1.11.13: + lefthook-darwin-x64@1.12.3: optional: true - lefthook-freebsd-arm64@1.11.13: + lefthook-freebsd-arm64@1.12.3: optional: true - lefthook-freebsd-x64@1.11.13: + lefthook-freebsd-x64@1.12.3: optional: true - lefthook-linux-arm64@1.11.13: + lefthook-linux-arm64@1.12.3: optional: true - lefthook-linux-x64@1.11.13: + lefthook-linux-x64@1.12.3: optional: true - lefthook-openbsd-arm64@1.11.13: + lefthook-openbsd-arm64@1.12.3: optional: true - lefthook-openbsd-x64@1.11.13: + lefthook-openbsd-x64@1.12.3: optional: true - lefthook-windows-arm64@1.11.13: + lefthook-windows-arm64@1.12.3: optional: true - lefthook-windows-x64@1.11.13: + lefthook-windows-x64@1.12.3: optional: true - lefthook@1.11.13: + lefthook@1.12.3: optionalDependencies: - lefthook-darwin-arm64: 1.11.13 - lefthook-darwin-x64: 1.11.13 - lefthook-freebsd-arm64: 1.11.13 - lefthook-freebsd-x64: 1.11.13 - lefthook-linux-arm64: 1.11.13 - lefthook-linux-x64: 1.11.13 - lefthook-openbsd-arm64: 1.11.13 - lefthook-openbsd-x64: 1.11.13 - lefthook-windows-arm64: 1.11.13 - lefthook-windows-x64: 1.11.13 + lefthook-darwin-arm64: 1.12.3 + lefthook-darwin-x64: 1.12.3 + lefthook-freebsd-arm64: 1.12.3 + lefthook-freebsd-x64: 1.12.3 + lefthook-linux-arm64: 1.12.3 + lefthook-linux-x64: 1.12.3 + lefthook-openbsd-arm64: 1.12.3 + lefthook-openbsd-x64: 1.12.3 + lefthook-windows-arm64: 1.12.3 + lefthook-windows-x64: 1.12.3 - libsql@0.5.13: + libsql@0.5.17: dependencies: '@neon-rs/load': 0.0.4 detect-libc: 2.0.2 optionalDependencies: - '@libsql/darwin-arm64': 0.5.13 - '@libsql/darwin-x64': 0.5.13 - '@libsql/linux-arm-gnueabihf': 0.5.13 - '@libsql/linux-arm-musleabihf': 0.5.13 - '@libsql/linux-arm64-gnu': 0.5.13 - '@libsql/linux-arm64-musl': 0.5.13 - '@libsql/linux-x64-gnu': 0.5.13 - '@libsql/linux-x64-musl': 0.5.13 - '@libsql/win32-x64-msvc': 0.5.13 + '@libsql/darwin-arm64': 0.5.17 + '@libsql/darwin-x64': 0.5.17 + '@libsql/linux-arm-gnueabihf': 0.5.17 + '@libsql/linux-arm-musleabihf': 0.5.17 + '@libsql/linux-arm64-gnu': 0.5.17 + '@libsql/linux-arm64-musl': 0.5.17 + '@libsql/linux-x64-gnu': 0.5.17 + '@libsql/linux-x64-musl': 0.5.17 + '@libsql/win32-x64-msvc': 0.5.17 lightningcss-darwin-arm64@1.30.1: optional: true @@ -7577,7 +7230,7 @@ snapshots: lru-cache@7.18.3: {} - lucide-react@0.511.0(react@19.1.1): + lucide-react@0.540.0(react@19.1.1): dependencies: react: 19.1.1 @@ -7709,7 +7362,7 @@ snapshots: npm-package-arg: 10.1.0 semver: 7.7.2 - oauth4webapi@3.5.1: {} + oauth4webapi@3.7.0: {} once@1.4.0: dependencies: @@ -7723,10 +7376,10 @@ snapshots: regex: 6.0.1 regex-recursion: 6.0.2 - openid-client@6.5.0: + openid-client@6.6.4: dependencies: - jose: 6.0.12 - oauth4webapi: 3.5.1 + jose: 6.0.13 + oauth4webapi: 3.7.0 package-json-from-dist@1.0.1: {} @@ -7757,27 +7410,21 @@ snapshots: picomatch@4.0.3: {} - playwright-core@1.52.0: {} + playwright-core@1.55.0: {} - playwright@1.52.0: + playwright@1.55.0: dependencies: - playwright-core: 1.52.0 + playwright-core: 1.55.0 optionalDependencies: fsevents: 2.3.2 - postcss@8.5.4: - dependencies: - nanoid: 3.3.11 - picocolors: 1.1.1 - source-map-js: 1.2.1 - postcss@8.5.6: dependencies: nanoid: 3.3.11 picocolors: 1.1.1 source-map-js: 1.2.1 - preact@10.26.7: {} + preact@10.27.1: {} prebuild-install@7.1.3: dependencies: @@ -7829,61 +7476,61 @@ snapshots: strip-json-comments: 2.0.1 optional: true - react-aria@3.40.0(react-dom@19.1.1(react@19.1.1))(react@19.1.1): + react-aria@3.42.0(react-dom@19.1.1(react@19.1.1))(react@19.1.1): dependencies: - '@internationalized/string': 3.2.6 - '@react-aria/breadcrumbs': 3.5.24(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@react-aria/button': 3.13.1(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@react-aria/calendar': 3.8.1(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@react-aria/checkbox': 3.15.5(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@react-aria/color': 3.0.7(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@react-aria/combobox': 3.12.3(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@react-aria/datepicker': 3.14.3(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@react-aria/dialog': 3.5.25(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@react-aria/disclosure': 3.0.5(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@react-aria/dnd': 3.9.3(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@react-aria/focus': 3.20.3(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@react-aria/gridlist': 3.13.0(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@react-aria/i18n': 3.12.9(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@react-aria/interactions': 3.25.1(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@react-aria/label': 3.7.18(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@react-aria/landmark': 3.0.3(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@react-aria/link': 3.8.1(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@react-aria/listbox': 3.14.4(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@react-aria/menu': 3.18.3(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@react-aria/meter': 3.4.23(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@react-aria/numberfield': 3.11.14(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@react-aria/overlays': 3.27.1(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@react-aria/progress': 3.4.23(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@react-aria/radio': 3.11.3(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@react-aria/searchfield': 3.8.4(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@react-aria/select': 3.15.5(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@react-aria/selection': 3.24.1(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@react-aria/separator': 3.4.9(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@react-aria/slider': 3.7.19(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@react-aria/ssr': 3.9.8(react@19.1.1) - '@react-aria/switch': 3.7.3(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@react-aria/table': 3.17.3(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@react-aria/tabs': 3.10.3(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@react-aria/tag': 3.6.0(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@react-aria/textfield': 3.17.3(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@react-aria/toast': 3.0.3(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@react-aria/tooltip': 3.8.3(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@react-aria/tree': 3.0.3(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@react-aria/utils': 3.29.0(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@react-aria/visually-hidden': 3.8.23(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@react-types/shared': 3.29.1(react@19.1.1) + '@internationalized/string': 3.2.7 + '@react-aria/breadcrumbs': 3.5.27(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-aria/button': 3.14.0(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-aria/calendar': 3.9.0(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-aria/checkbox': 3.16.0(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-aria/color': 3.1.0(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-aria/combobox': 3.13.0(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-aria/datepicker': 3.15.0(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-aria/dialog': 3.5.28(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-aria/disclosure': 3.0.7(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-aria/dnd': 3.11.0(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-aria/focus': 3.21.0(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-aria/gridlist': 3.13.3(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-aria/i18n': 3.12.11(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-aria/interactions': 3.25.4(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-aria/label': 3.7.20(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-aria/landmark': 3.0.5(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-aria/link': 3.8.4(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-aria/listbox': 3.14.7(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-aria/menu': 3.19.0(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-aria/meter': 3.4.25(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-aria/numberfield': 3.12.0(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-aria/overlays': 3.28.0(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-aria/progress': 3.4.25(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-aria/radio': 3.12.0(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-aria/searchfield': 3.8.7(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-aria/select': 3.16.0(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-aria/selection': 3.25.0(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-aria/separator': 3.4.11(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-aria/slider': 3.8.0(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-aria/ssr': 3.9.10(react@19.1.1) + '@react-aria/switch': 3.7.6(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-aria/table': 3.17.6(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-aria/tabs': 3.10.6(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-aria/tag': 3.7.0(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-aria/textfield': 3.18.0(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-aria/toast': 3.0.6(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-aria/tooltip': 3.8.6(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-aria/tree': 3.1.2(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-aria/utils': 3.30.0(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-aria/visually-hidden': 3.8.26(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@react-types/shared': 3.31.0(react@19.1.1) react: 19.1.1 react-dom: 19.1.1(react@19.1.1) - react-codemirror-merge@4.23.12(@babel/runtime@7.27.3)(@codemirror/autocomplete@6.18.2(@codemirror/language@6.11.0)(@codemirror/state@6.5.2)(@codemirror/view@6.36.8)(@lezer/common@1.2.3))(@codemirror/language@6.11.0)(@codemirror/lint@6.8.2)(@codemirror/search@6.5.7)(@codemirror/state@6.5.2)(@codemirror/theme-one-dark@6.1.2)(@codemirror/view@6.36.8)(codemirror@6.0.1(@lezer/common@1.2.3))(react-dom@19.1.1(react@19.1.1))(react@19.1.1): + react-codemirror-merge@4.25.1(@babel/runtime@7.27.3)(@codemirror/autocomplete@6.18.2(@codemirror/language@6.11.0)(@codemirror/state@6.5.2)(@codemirror/view@6.36.8)(@lezer/common@1.2.3))(@codemirror/language@6.11.0)(@codemirror/lint@6.8.2)(@codemirror/search@6.5.7)(@codemirror/state@6.5.2)(@codemirror/theme-one-dark@6.1.2)(@codemirror/view@6.36.8)(codemirror@6.0.1(@lezer/common@1.2.3))(react-dom@19.1.1(react@19.1.1))(react@19.1.1): dependencies: '@babel/runtime': 7.27.3 '@codemirror/merge': 6.10.1 '@codemirror/state': 6.5.2 '@codemirror/theme-one-dark': 6.1.2 '@codemirror/view': 6.36.8 - '@uiw/react-codemirror': 4.23.12(@babel/runtime@7.27.3)(@codemirror/autocomplete@6.18.2(@codemirror/language@6.11.0)(@codemirror/state@6.5.2)(@codemirror/view@6.36.8)(@lezer/common@1.2.3))(@codemirror/language@6.11.0)(@codemirror/lint@6.8.2)(@codemirror/search@6.5.7)(@codemirror/state@6.5.2)(@codemirror/theme-one-dark@6.1.2)(@codemirror/view@6.36.8)(codemirror@6.0.1(@lezer/common@1.2.3))(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@uiw/react-codemirror': 4.25.1(@babel/runtime@7.27.3)(@codemirror/autocomplete@6.18.2(@codemirror/language@6.11.0)(@codemirror/state@6.5.2)(@codemirror/view@6.36.8)(@lezer/common@1.2.3))(@codemirror/language@6.11.0)(@codemirror/lint@6.8.2)(@codemirror/search@6.5.7)(@codemirror/state@6.5.2)(@codemirror/theme-one-dark@6.1.2)(@codemirror/view@6.36.8)(codemirror@6.0.1(@lezer/common@1.2.3))(react-dom@19.1.1(react@19.1.1))(react@19.1.1) codemirror: 6.0.1(@lezer/common@1.2.3) react: 19.1.1 react-dom: 19.1.1(react@19.1.1) @@ -7915,17 +7562,17 @@ snapshots: react-dom: 19.1.1(react@19.1.1) react-router: 7.8.1(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - react-router-hono-server@2.21.0(patch_hash=b68723a36649e2c3bd9e9edcfff3a8bc0fdb48e1cf6d64eba37c04f7bfbb1417)(@hono/node-server@1.19.0(hono@4.7.10))(@react-router/dev@7.8.1(@types/node@24.3.0)(jiti@2.5.1)(lightningcss@1.30.1)(react-dom@19.1.1(react@19.1.1))(react-router@7.8.1(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(react@19.1.1)(rolldown-vite@7.1.3(@types/node@24.3.0)(esbuild@0.25.5)(jiti@2.5.1)(terser@5.39.0)(tsx@4.19.4)(yaml@2.8.0))(terser@5.39.0)(tsx@4.19.4)(typescript@5.9.2)(yaml@2.8.0))(@types/react@19.1.10)(bufferutil@4.0.9)(react-router@7.8.1(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(rolldown-vite@7.1.3(@types/node@24.3.0)(esbuild@0.25.5)(jiti@2.5.1)(terser@5.39.0)(tsx@4.19.4)(yaml@2.8.0))(utf-8-validate@5.0.10): + react-router-hono-server@2.21.0(patch_hash=b68723a36649e2c3bd9e9edcfff3a8bc0fdb48e1cf6d64eba37c04f7bfbb1417)(@hono/node-server@1.19.0(hono@4.7.10))(@react-router/dev@7.8.1(@types/node@24.3.0)(jiti@2.5.1)(lightningcss@1.30.1)(react-dom@19.1.1(react@19.1.1))(react-router@7.8.1(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(react@19.1.1)(rolldown-vite@7.1.4(@types/node@24.3.0)(esbuild@0.25.9)(jiti@2.5.1)(terser@5.39.0)(tsx@4.20.4)(yaml@2.8.1))(terser@5.39.0)(tsx@4.20.4)(typescript@5.9.2)(yaml@2.8.1))(@types/react@19.1.10)(bufferutil@4.0.9)(react-router@7.8.1(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(rolldown-vite@7.1.4(@types/node@24.3.0)(esbuild@0.25.9)(jiti@2.5.1)(terser@5.39.0)(tsx@4.20.4)(yaml@2.8.1))(utf-8-validate@5.0.10): dependencies: '@drizzle-team/brocli': 0.11.0 '@hono/node-server': 1.19.0(hono@4.7.10) '@hono/node-ws': 1.2.0(@hono/node-server@1.19.0(hono@4.7.10))(bufferutil@4.0.9)(hono@4.7.10)(utf-8-validate@5.0.10) '@hono/vite-dev-server': 0.20.1(hono@4.7.10) - '@react-router/dev': 7.8.1(@types/node@24.3.0)(jiti@2.5.1)(lightningcss@1.30.1)(react-dom@19.1.1(react@19.1.1))(react-router@7.8.1(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(react@19.1.1)(rolldown-vite@7.1.3(@types/node@24.3.0)(esbuild@0.25.5)(jiti@2.5.1)(terser@5.39.0)(tsx@4.19.4)(yaml@2.8.0))(terser@5.39.0)(tsx@4.19.4)(typescript@5.9.2)(yaml@2.8.0) + '@react-router/dev': 7.8.1(@types/node@24.3.0)(jiti@2.5.1)(lightningcss@1.30.1)(react-dom@19.1.1(react@19.1.1))(react-router@7.8.1(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(react@19.1.1)(rolldown-vite@7.1.4(@types/node@24.3.0)(esbuild@0.25.9)(jiti@2.5.1)(terser@5.39.0)(tsx@4.20.4)(yaml@2.8.1))(terser@5.39.0)(tsx@4.20.4)(typescript@5.9.2)(yaml@2.8.1) '@types/react': 19.1.10 hono: 4.7.10 react-router: 7.8.1(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - vite: rolldown-vite@7.1.3(@types/node@24.3.0)(esbuild@0.25.5)(jiti@2.5.1)(terser@5.39.0)(tsx@4.19.4)(yaml@2.8.0) + vite: rolldown-vite@7.1.4(@types/node@24.3.0)(esbuild@0.25.9)(jiti@2.5.1)(terser@5.39.0)(tsx@4.20.4)(yaml@2.8.1) transitivePeerDependencies: - bufferutil - utf-8-validate @@ -7938,27 +7585,27 @@ snapshots: optionalDependencies: react-dom: 19.1.1(react@19.1.1) - react-scan@0.3.4(@types/react@19.1.10)(react-dom@19.1.1(react@19.1.1))(react-router-dom@7.8.1(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(react-router@7.8.1(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(react@19.1.1)(rollup@4.46.3): + react-scan@0.4.3(@types/react@19.1.10)(react-dom@19.1.1(react@19.1.1))(react-router-dom@7.8.1(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(react-router@7.8.1(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(react@19.1.1)(rollup@4.46.3): dependencies: - '@babel/core': 7.27.3 - '@babel/generator': 7.27.3 - '@babel/types': 7.27.3 + '@babel/core': 7.28.3 + '@babel/generator': 7.28.3 + '@babel/types': 7.28.2 '@clack/core': 0.3.5 '@clack/prompts': 0.8.2 '@pivanov/utils': 0.0.2(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@preact/signals': 1.3.2(preact@10.26.7) - '@rollup/pluginutils': 5.1.4(rollup@4.46.3) + '@preact/signals': 1.3.2(preact@10.27.1) + '@rollup/pluginutils': 5.2.0(rollup@4.46.3) '@types/node': 20.19.11 - bippy: 0.3.14(@types/react@19.1.10)(react@19.1.1) - esbuild: 0.25.5 + bippy: 0.3.18(@types/react@19.1.10)(react@19.1.1) + esbuild: 0.25.9 estree-walker: 3.0.3 kleur: 4.1.5 mri: 1.2.0 - playwright: 1.52.0 - preact: 10.26.7 + playwright: 1.55.0 + preact: 10.27.1 react: 19.1.1 react-dom: 19.1.1(react@19.1.1) - tsx: 4.19.4 + tsx: 4.20.4 optionalDependencies: react-router: 7.8.1(react-dom@19.1.1(react@19.1.1))(react@19.1.1) react-router-dom: 7.8.1(react-dom@19.1.1(react@19.1.1))(react@19.1.1) @@ -7968,34 +7615,34 @@ snapshots: - rollup - supports-color - react-stately@3.38.0(react@19.1.1): + react-stately@3.40.0(react@19.1.1): dependencies: - '@react-stately/calendar': 3.8.1(react@19.1.1) - '@react-stately/checkbox': 3.6.14(react@19.1.1) - '@react-stately/collections': 3.12.4(react@19.1.1) - '@react-stately/color': 3.8.5(react@19.1.1) - '@react-stately/combobox': 3.10.5(react@19.1.1) - '@react-stately/data': 3.13.0(react@19.1.1) - '@react-stately/datepicker': 3.14.1(react@19.1.1) - '@react-stately/disclosure': 3.0.4(react@19.1.1) - '@react-stately/dnd': 3.5.4(react@19.1.1) - '@react-stately/form': 3.1.4(react@19.1.1) - '@react-stately/list': 3.12.2(react@19.1.1) - '@react-stately/menu': 3.9.4(react@19.1.1) - '@react-stately/numberfield': 3.9.12(react@19.1.1) - '@react-stately/overlays': 3.6.16(react@19.1.1) - '@react-stately/radio': 3.10.13(react@19.1.1) - '@react-stately/searchfield': 3.5.12(react@19.1.1) - '@react-stately/select': 3.6.13(react@19.1.1) - '@react-stately/selection': 3.20.2(react@19.1.1) - '@react-stately/slider': 3.6.4(react@19.1.1) - '@react-stately/table': 3.14.2(react@19.1.1) - '@react-stately/tabs': 3.8.2(react@19.1.1) - '@react-stately/toast': 3.1.0(react@19.1.1) - '@react-stately/toggle': 3.8.4(react@19.1.1) - '@react-stately/tooltip': 3.5.4(react@19.1.1) - '@react-stately/tree': 3.8.10(react@19.1.1) - '@react-types/shared': 3.29.1(react@19.1.1) + '@react-stately/calendar': 3.8.3(react@19.1.1) + '@react-stately/checkbox': 3.7.0(react@19.1.1) + '@react-stately/collections': 3.12.6(react@19.1.1) + '@react-stately/color': 3.9.0(react@19.1.1) + '@react-stately/combobox': 3.11.0(react@19.1.1) + '@react-stately/data': 3.13.2(react@19.1.1) + '@react-stately/datepicker': 3.15.0(react@19.1.1) + '@react-stately/disclosure': 3.0.6(react@19.1.1) + '@react-stately/dnd': 3.6.1(react@19.1.1) + '@react-stately/form': 3.2.0(react@19.1.1) + '@react-stately/list': 3.12.4(react@19.1.1) + '@react-stately/menu': 3.9.6(react@19.1.1) + '@react-stately/numberfield': 3.10.0(react@19.1.1) + '@react-stately/overlays': 3.6.18(react@19.1.1) + '@react-stately/radio': 3.11.0(react@19.1.1) + '@react-stately/searchfield': 3.5.14(react@19.1.1) + '@react-stately/select': 3.7.0(react@19.1.1) + '@react-stately/selection': 3.20.4(react@19.1.1) + '@react-stately/slider': 3.7.0(react@19.1.1) + '@react-stately/table': 3.14.4(react@19.1.1) + '@react-stately/tabs': 3.8.4(react@19.1.1) + '@react-stately/toast': 3.1.2(react@19.1.1) + '@react-stately/toggle': 3.9.0(react@19.1.1) + '@react-stately/tooltip': 3.5.6(react@19.1.1) + '@react-stately/tree': 3.9.1(react@19.1.1) + '@react-types/shared': 3.31.0(react@19.1.1) react: 19.1.1 react@19.1.1: {} @@ -8019,13 +7666,12 @@ snapshots: dependencies: regex-utilities: 2.3.0 - remix-utils@8.8.0(react-router@7.8.1(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(react@19.1.1)(zod@3.25.33): + remix-utils@8.8.0(react-router@7.8.1(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(react@19.1.1): dependencies: type-fest: 4.41.0 optionalDependencies: react: 19.1.1 react-router: 7.8.1(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - zod: 3.25.33 resolve-pkg-maps@1.0.0: {} @@ -8035,7 +7681,7 @@ snapshots: rfdc@1.4.1: {} - rolldown-vite@7.1.3(@types/node@24.3.0)(esbuild@0.25.5)(jiti@2.5.1)(terser@5.39.0)(tsx@4.19.4)(yaml@2.8.0): + rolldown-vite@7.1.4(@types/node@24.3.0)(esbuild@0.25.9)(jiti@2.5.1)(terser@5.39.0)(tsx@4.20.4)(yaml@2.8.1): dependencies: fdir: 6.5.0(picomatch@4.0.3) lightningcss: 1.30.1 @@ -8045,12 +7691,12 @@ snapshots: tinyglobby: 0.2.14 optionalDependencies: '@types/node': 24.3.0 - esbuild: 0.25.5 + esbuild: 0.25.9 fsevents: 2.3.3 jiti: 2.5.1 terser: 5.39.0 - tsx: 4.19.4 - yaml: 2.8.0 + tsx: 4.20.4 + yaml: 2.8.1 rolldown@1.0.0-beta.33: dependencies: @@ -8246,20 +7892,18 @@ snapshots: tabbable@6.2.0: {} - tailwind-merge@3.3.0: {} + tailwind-merge@3.3.1: {} - tailwindcss-animate@1.0.7(tailwindcss@4.1.8): + tailwindcss-animate@1.0.7(tailwindcss@4.1.12): dependencies: - tailwindcss: 4.1.8 + tailwindcss: 4.1.12 - tailwindcss-react-aria-components@2.0.0(tailwindcss@4.1.8): + tailwindcss-react-aria-components@2.0.0(tailwindcss@4.1.12): dependencies: - tailwindcss: 4.1.8 + tailwindcss: 4.1.12 tailwindcss@4.1.12: {} - tailwindcss@4.1.8: {} - tapable@2.2.2: {} tar-fs@2.1.3: @@ -8343,9 +7987,9 @@ snapshots: tslib@2.8.1: {} - tsx@4.19.4: + tsx@4.20.4: dependencies: - esbuild: 0.25.5 + esbuild: 0.25.9 get-tsconfig: 4.10.1 optionalDependencies: fsevents: 2.3.3 @@ -8369,7 +8013,7 @@ snapshots: undici-types@7.10.0: {} - undici@7.10.0: {} + undici@7.14.0: {} unist-util-is@6.0.0: dependencies: @@ -8396,7 +8040,7 @@ snapshots: unplugin@2.1.0: dependencies: - acorn: 8.14.1 + acorn: 8.15.0 webpack-virtual-modules: 0.6.2 optional: true @@ -8444,13 +8088,13 @@ snapshots: '@types/unist': 3.0.3 vfile-message: 4.0.3 - vite-node@3.2.4(@types/node@24.3.0)(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.39.0)(tsx@4.19.4)(yaml@2.8.0): + vite-node@3.2.4(@types/node@24.3.0)(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.39.0)(tsx@4.20.4)(yaml@2.8.1): dependencies: cac: 6.7.14 debug: 4.4.1 es-module-lexer: 1.7.0 pathe: 2.0.3 - vite: 7.1.2(@types/node@24.3.0)(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.39.0)(tsx@4.19.4)(yaml@2.8.0) + vite: 7.1.2(@types/node@24.3.0)(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.39.0)(tsx@4.20.4)(yaml@2.8.1) transitivePeerDependencies: - '@types/node' - jiti @@ -8465,18 +8109,18 @@ snapshots: - tsx - yaml - vite-tsconfig-paths@5.1.4(rolldown-vite@7.1.3(@types/node@24.3.0)(esbuild@0.25.5)(jiti@2.5.1)(terser@5.39.0)(tsx@4.19.4)(yaml@2.8.0))(typescript@5.9.2): + vite-tsconfig-paths@5.1.4(rolldown-vite@7.1.4(@types/node@24.3.0)(esbuild@0.25.9)(jiti@2.5.1)(terser@5.39.0)(tsx@4.20.4)(yaml@2.8.1))(typescript@5.9.2): dependencies: debug: 4.4.0 globrex: 0.1.2 tsconfck: 3.1.4(typescript@5.9.2) optionalDependencies: - vite: rolldown-vite@7.1.3(@types/node@24.3.0)(esbuild@0.25.5)(jiti@2.5.1)(terser@5.39.0)(tsx@4.19.4)(yaml@2.8.0) + vite: rolldown-vite@7.1.4(@types/node@24.3.0)(esbuild@0.25.9)(jiti@2.5.1)(terser@5.39.0)(tsx@4.20.4)(yaml@2.8.1) transitivePeerDependencies: - supports-color - typescript - vite@7.1.2(@types/node@24.3.0)(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.39.0)(tsx@4.19.4)(yaml@2.8.0): + vite@7.1.2(@types/node@24.3.0)(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.39.0)(tsx@4.20.4)(yaml@2.8.1): dependencies: esbuild: 0.25.9 fdir: 6.5.0(picomatch@4.0.3) @@ -8490,14 +8134,14 @@ snapshots: jiti: 2.5.1 lightningcss: 1.30.1 terser: 5.39.0 - tsx: 4.19.4 - yaml: 2.8.0 + tsx: 4.20.4 + yaml: 2.8.1 - vitefu@1.1.1(rolldown-vite@7.1.3(@types/node@24.3.0)(esbuild@0.25.5)(jiti@2.5.1)(terser@5.39.0)(tsx@4.19.4)(yaml@2.8.0)): + vitefu@1.1.1(rolldown-vite@7.1.4(@types/node@24.3.0)(esbuild@0.25.9)(jiti@2.5.1)(terser@5.39.0)(tsx@4.20.4)(yaml@2.8.1)): optionalDependencies: - vite: rolldown-vite@7.1.3(@types/node@24.3.0)(esbuild@0.25.5)(jiti@2.5.1)(terser@5.39.0)(tsx@4.19.4)(yaml@2.8.0) + vite: rolldown-vite@7.1.4(@types/node@24.3.0)(esbuild@0.25.9)(jiti@2.5.1)(terser@5.39.0)(tsx@4.20.4)(yaml@2.8.1) - vitepress@2.0.0-alpha.11(@types/node@24.3.0)(jiti@2.5.1)(lightningcss@1.30.1)(postcss@8.5.4)(terser@5.39.0)(tsx@4.19.4)(typescript@5.9.2)(yaml@2.8.0): + vitepress@2.0.0-alpha.11(@types/node@24.3.0)(jiti@2.5.1)(lightningcss@1.30.1)(postcss@8.5.6)(terser@5.39.0)(tsx@4.20.4)(typescript@5.9.2)(yaml@2.8.1): dependencies: '@docsearch/css': 4.0.0-beta.7 '@docsearch/js': 4.0.0-beta.7 @@ -8506,7 +8150,7 @@ snapshots: '@shikijs/transformers': 3.9.2 '@shikijs/types': 3.9.2 '@types/markdown-it': 14.1.2 - '@vitejs/plugin-vue': 6.0.1(vite@7.1.2(@types/node@24.3.0)(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.39.0)(tsx@4.19.4)(yaml@2.8.0))(vue@3.5.18(typescript@5.9.2)) + '@vitejs/plugin-vue': 6.0.1(vite@7.1.2(@types/node@24.3.0)(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.39.0)(tsx@4.20.4)(yaml@2.8.1))(vue@3.5.18(typescript@5.9.2)) '@vue/devtools-api': 8.0.0 '@vue/shared': 3.5.18 '@vueuse/core': 13.7.0(vue@3.5.18(typescript@5.9.2)) @@ -8515,10 +8159,10 @@ snapshots: mark.js: 8.11.1 minisearch: 7.1.2 shiki: 3.9.2 - vite: 7.1.2(@types/node@24.3.0)(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.39.0)(tsx@4.19.4)(yaml@2.8.0) + vite: 7.1.2(@types/node@24.3.0)(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.39.0)(tsx@4.20.4)(yaml@2.8.1) vue: 3.5.18(typescript@5.9.2) optionalDependencies: - postcss: 8.5.4 + postcss: 8.5.6 transitivePeerDependencies: - '@types/node' - async-validator @@ -8544,11 +8188,11 @@ snapshots: - universal-cookie - yaml - vitest@3.2.4(@types/node@24.3.0)(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.39.0)(tsx@4.19.4)(yaml@2.8.0): + vitest@3.2.4(@types/node@24.3.0)(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.39.0)(tsx@4.20.4)(yaml@2.8.1): dependencies: '@types/chai': 5.2.2 '@vitest/expect': 3.2.4 - '@vitest/mocker': 3.2.4(vite@7.1.2(@types/node@24.3.0)(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.39.0)(tsx@4.19.4)(yaml@2.8.0)) + '@vitest/mocker': 3.2.4(vite@7.1.2(@types/node@24.3.0)(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.39.0)(tsx@4.20.4)(yaml@2.8.1)) '@vitest/pretty-format': 3.2.4 '@vitest/runner': 3.2.4 '@vitest/snapshot': 3.2.4 @@ -8566,8 +8210,8 @@ snapshots: tinyglobby: 0.2.14 tinypool: 1.1.1 tinyrainbow: 2.0.0 - vite: 7.1.2(@types/node@24.3.0)(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.39.0)(tsx@4.19.4)(yaml@2.8.0) - vite-node: 3.2.4(@types/node@24.3.0)(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.39.0)(tsx@4.19.4)(yaml@2.8.0) + vite: 7.1.2(@types/node@24.3.0)(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.39.0)(tsx@4.20.4)(yaml@2.8.1) + vite-node: 3.2.4(@types/node@24.3.0)(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.39.0)(tsx@4.20.4)(yaml@2.8.1) why-is-node-running: 2.3.0 optionalDependencies: '@types/node': 24.3.0 @@ -8645,11 +8289,8 @@ snapshots: yallist@5.0.0: {} - yaml@2.8.0: {} + yaml@2.8.1: {} zimmerframe@1.1.2: {} - zod@3.25.33: - optional: true - zwitch@2.0.4: {} From cd4e8f83993a141503580a9dc084f8c1066c68de Mon Sep 17 00:00:00 2001 From: Aarnav Tale Date: Thu, 21 Aug 2025 11:49:17 -0400 Subject: [PATCH 83/97] chore: handle empty (already migrated) user oidc file --- app/server/web/sessions.ts | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/app/server/web/sessions.ts b/app/server/web/sessions.ts index 88b5724..47bcc64 100644 --- a/app/server/web/sessions.ts +++ b/app/server/web/sessions.ts @@ -246,6 +246,16 @@ async function migrateUserDatabase(path: string, db: LibSQLDatabase) { try { const data = await readFile(realPath, 'utf8'); + if (data.trim().length === 0) { + log.info('config', 'Old user database file is empty, nothing to migrate'); + log.info( + 'config', + 'You SHOULD remove oidc.user_storage_file from your config!', + ); + await rm(realPath, { force: true }); + return; + } + const users = JSON.parse(data.trim()) as { u?: string; c?: number; From 9183f805a6e2c0f7f7c0bdc873fe106fd5db29b3 Mon Sep 17 00:00:00 2001 From: Aarnav Tale Date: Thu, 21 Aug 2025 11:50:04 -0400 Subject: [PATCH 84/97] chore: type fixes --- app/components/Header.tsx | 40 +++++++++---------- .../settings/auth-keys/auth-key-row.tsx | 12 ++++-- 2 files changed, 28 insertions(+), 24 deletions(-) diff --git a/app/components/Header.tsx b/app/components/Header.tsx index e166c7f..97f108e 100644 --- a/app/components/Header.tsx +++ b/app/components/Header.tsx @@ -42,8 +42,6 @@ function TabLink({ name, to, icon }: TabLinkProps) { return (
cn( 'px-3 py-2 flex items-center rounded-md text-nowrap gap-x-2.5', @@ -54,6 +52,8 @@ function TabLink({ name, to, icon }: TabLinkProps) { isActive ? 'after:visible' : 'after:invisible', ) } + prefetch="intent" + to={to} > {icon} {name} @@ -64,13 +64,13 @@ function TabLink({ name, to, icon }: TabLinkProps) { function Link({ href, text }: LinkProps) { return ( {text} @@ -101,20 +101,21 @@ export default function Header(data: Props) { {data.user ? ( {data.user.picture ? ( {data.user.name ) : ( )} { if (key === 'logout') { submit( @@ -126,12 +127,11 @@ export default function Header(data: Props) { ); } }} - disabledKeys={['profile']} >
-

{data.user.name || data.user.displayName}

+

{data.user.name}

{data.user.email}

@@ -148,39 +148,39 @@ export default function Header(data: Props) {

- + { @@ -98,7 +97,7 @@ export default function MachineRow({ )} > {ip} - + ))} @@ -132,8 +131,8 @@ export default function MachineRow({ )} >

{node.online && !node.expired @@ -144,10 +143,10 @@ export default function MachineRow({