fix: upgrade to new tailscale go version to fix ssh

This commit is contained in:
Aarnav Tale
2025-09-27 12:10:15 -04:00
parent 4df15fd4b2
commit 1dfb2fb777
10 changed files with 66 additions and 82 deletions
+1 -1
View File
@@ -1,4 +1,4 @@
# REMEMBER TO UPDATE mise.toml TOO # REMEMBER TO UPDATE mise.toml TOO
pnpm 10.4.0 pnpm 10.4.0
node 22.16 node 22.16
go 1.24.4 go 1.25.1
+2 -2
View File
@@ -16,8 +16,8 @@ export default function Expire({ machine, isOpen, setIsOpen }: ExpireProps) {
This will disconnect the machine from your Tailnet. In order to This will disconnect the machine from your Tailnet. In order to
reconnect, you will need to re-authenticate from the device. reconnect, you will need to re-authenticate from the device.
</Dialog.Text> </Dialog.Text>
<input type="hidden" name="action_id" value="expire" /> <input name="action_id" type="hidden" value="expire" />
<input type="hidden" name="node_id" value={machine.id} /> <input name="node_id" type="hidden" value={machine.id} />
</Dialog.Panel> </Dialog.Panel>
</Dialog> </Dialog>
); );
+8 -7
View File
@@ -39,15 +39,16 @@ type IPNState =
| 'Running'; | 'Running';
interface XtermConfig { interface XtermConfig {
Rows: number; rows: number;
Cols: number; cols: number;
timeout?: number;
OnStdout: (data: string) => void; onStdout: (data: string) => void;
OnStderr: (data: string) => void; onStderr: (data: string) => void;
OnStdin: (func: (input: string) => void) => void; onStdin: (func: (input: string) => void) => void;
OnConnect: () => void; onConnect: () => void;
OnDisconnect: () => void; onDisconnect: () => void;
} }
interface SSHSession { interface SSHSession {
+11 -9
View File
@@ -81,24 +81,26 @@ export default function XTerm({ ipn, username, hostname }: XTermProps) {
let onUnload: ((e: Event) => void) | null = null; let onUnload: ((e: Event) => void) | null = null;
const session = ipn.OpenSSH(hostname, username, { const session = ipn.OpenSSH(hostname, username, {
Rows: terminal.rows, rows: terminal.rows,
Cols: terminal.cols, cols: terminal.cols,
OnStdout: (data) => terminal.write(data), onStdout: (data) => terminal.write(data),
OnStderr: (data) => { onStderr: (data) => {
terminal.write(data); terminal.write(data);
console.log('SSH stderr:', data); console.log('SSH stderr:', data);
toast(data); toast(data);
}, },
OnStdin: (func) => { onStdin: (func) => {
console.log('SSH session is ready to receive input');
inputRef.current = func; inputRef.current = func;
console.log('Stdin handler set', func);
}, },
OnConnect: () => { onConnect: () => {
console.log('SSH session connected'); console.log('SSH session connected');
setIsLoading(false); setIsLoading(false);
}, },
OnDisconnect: () => { onDisconnect: () => {
ro?.disconnect(); ro?.disconnect();
terminal.dispose(); terminal.dispose();
if (onUnload) { if (onUnload) {
@@ -156,8 +158,8 @@ export default function XTerm({ ipn, username, hostname }: XTermProps) {
</div> </div>
) : undefined} ) : undefined}
<div <div
ref={container}
className={cn('w-full h-full', isLoading ? 'opacity-0' : 'opacity-100')} className={cn('w-full h-full', isLoading ? 'opacity-0' : 'opacity-100')}
ref={container}
/> />
{term.current && isResizing ? ( {term.current && isResizing ? (
<div <div
@@ -172,7 +174,7 @@ export default function XTerm({ ipn, username, hostname }: XTermProps) {
{isFailed ? ( {isFailed ? (
<div <div
className={cn( className={cn(
'absolute left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2', 'absolute left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 text-center',
'px-4 py-2 bg-headplane-800 text-white rounded-full shadow z-50', 'px-4 py-2 bg-headplane-800 text-white rounded-full shadow z-50',
)} )}
> >
+4 -4
View File
@@ -1,11 +1,11 @@
module github.com/tale/headplane module github.com/tale/headplane
go 1.24.4 go 1.25.1
require ( require (
go4.org/mem v0.0.0-20240501181205-ae6ca9944745 go4.org/mem v0.0.0-20240501181205-ae6ca9944745
golang.org/x/crypto v0.41.0 golang.org/x/crypto v0.41.0
tailscale.com v1.86.4 tailscale.com v1.88.2
) )
require ( require (
@@ -32,7 +32,7 @@ require (
github.com/digitalocean/go-smbios v0.0.0-20180907143718-390a4f403a8e // indirect github.com/digitalocean/go-smbios v0.0.0-20180907143718-390a4f403a8e // indirect
github.com/fxamacker/cbor/v2 v2.8.0 // indirect github.com/fxamacker/cbor/v2 v2.8.0 // indirect
github.com/gaissmai/bart v0.18.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-json-experiment/json v0.0.0-20250813024750-ebf49471dced // indirect
github.com/go-ole/go-ole v1.3.0 // 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/godbus/dbus/v5 v5.1.1-0.20230522191255-76236955d466 // indirect
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
@@ -61,7 +61,7 @@ require (
github.com/tailscale/peercred v0.0.0-20250107143737-35a0c7bd7edc // 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/web-client-prebuilt v0.0.0-20250124233751-d4cd19a26976 // indirect
github.com/tailscale/wireguard-go v0.0.0-20250716170648-1d0488a3d7da // indirect github.com/tailscale/wireguard-go v0.0.0-20250716170648-1d0488a3d7da // indirect
github.com/vishvananda/netns v0.0.4 // indirect github.com/vishvananda/netns v0.0.5 // indirect
github.com/x448/float16 v0.8.4 // indirect github.com/x448/float16 v0.8.4 // indirect
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba // indirect go4.org/netipx v0.0.0-20231129151722-fdeea329fbba // indirect
golang.org/x/exp v0.0.0-20250210185358-939b2ce775ac // indirect golang.org/x/exp v0.0.0-20250210185358-939b2ce775ac // indirect
+8 -8
View File
@@ -67,8 +67,8 @@ 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/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 h1:Km/MVOFvclqxPM9dZBC4+QE564nU4gz4iZ0D9pMw28I=
github.com/github/fakeca v0.1.0/go.mod h1:+bormgoGMMuamOscx7N91aOuUST7wdaJ2rNjeohylyo= github.com/github/fakeca v0.1.0/go.mod h1:+bormgoGMMuamOscx7N91aOuUST7wdaJ2rNjeohylyo=
github.com/go-json-experiment/json v0.0.0-20250223041408-d3c622f1b874 h1:F8d1AJ6M9UQCavhwmO6ZsrYLfG8zVFWfEfMS2MXPkSY= github.com/go-json-experiment/json v0.0.0-20250813024750-ebf49471dced h1:Q311OHjMh/u5E2TITc++WlTP5We0xNseRMkHDyvhW7I=
github.com/go-json-experiment/json v0.0.0-20250223041408-d3c622f1b874/go.mod h1:TiCD2a1pcmjd7YnhGH0f/zKNcCD06B029pHhzV23c2M= github.com/go-json-experiment/json v0.0.0-20250813024750-ebf49471dced/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 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE=
github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78= 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 h1:cf60tHxREO3g1nroKr2osU3JWZsJzkfi7rEg+oAB0Lo=
@@ -174,8 +174,8 @@ github.com/u-root/u-root v0.14.0/go.mod h1:hAyZorapJe4qzbLWlAkmSVCJGbfoU9Pu4jpJ1
github.com/u-root/uio v0.0.0-20240224005618-d2acac8f3701 h1:pyC9PaHYZFgEKFdlp3G8RaCKgVpHZnecvArXvPXcFkM= 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/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.0-20200728191858-db3c7e526aae/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0=
github.com/vishvananda/netns v0.0.4 h1:Oeaw1EM2JMxD51g9uhtC0D7erkIjgmj8+JZc26m1YX8= github.com/vishvananda/netns v0.0.5 h1:DfiHV+j8bA32MFM7bfEunvT8IAqQ/NzSJHtcmW5zdEY=
github.com/vishvananda/netns v0.0.4/go.mod h1:SpkAiCQRtJ6TvvxPnOSyH3BMl6unz3xZlaprSwhNNJM= github.com/vishvananda/netns v0.0.5/go.mod h1:SpkAiCQRtJ6TvvxPnOSyH3BMl6unz3xZlaprSwhNNJM=
github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=
github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg=
go4.org/mem v0.0.0-20240501181205-ae6ca9944745 h1:Tl++JLUCe4sxGu8cTpDzRLd3tN7US4hOxG5YpKCzkek= go4.org/mem v0.0.0-20240501181205-ae6ca9944745 h1:Tl++JLUCe4sxGu8cTpDzRLd3tN7US4hOxG5YpKCzkek=
@@ -216,8 +216,8 @@ golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 h1:B82qJJgjvYKsXS9jeu
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2/go.mod h1:deeaetjYA+DHMHg+sMSMI58GrEteJUUzzw7en6TJQcI= 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 h1:On6j2Rpn3OEMXqBq00QEDC7bWSZrPIHKIus8eIuExIE=
golang.zx2c4.com/wireguard/windows v0.5.3/go.mod h1:9TEe8TJmtwyQebdFwAkEWOPr3prrtqm+REGFifP60hI= golang.zx2c4.com/wireguard/windows v0.5.3/go.mod h1:9TEe8TJmtwyQebdFwAkEWOPr3prrtqm+REGFifP60hI=
google.golang.org/protobuf v1.35.1 h1:m3LfL6/Ca+fqnjnlqQXNpFPABW1UD7mjh8KO2mKFytA= google.golang.org/protobuf v1.36.3 h1:82DV7MYdb8anAVi3qge1wSnMDrnKK7ebr+I0hHRN1BU=
google.golang.org/protobuf v1.35.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= google.golang.org/protobuf v1.36.3/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 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.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 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
@@ -230,5 +230,5 @@ howett.net/plist v1.0.0 h1:7CrbWYbPPO/PyNy38b2EB/+gYbjCe2DXBxgtOOZbSQM=
howett.net/plist v1.0.0/go.mod h1:lqaXoTrLY4hg8tnEzNru53gicrbv7rrk+2xJA/7hw9g= 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 h1:H2g08FrTvSFKUj+D309j1DPfk5APnIdAQAB8aEykJ5k=
software.sslmate.com/src/go-pkcs12 v0.4.0/go.mod h1:Qiz0EyvDRJjjxGyUQa2cCNZn/wMyzrRJ/qcDXOQazLI= software.sslmate.com/src/go-pkcs12 v0.4.0/go.mod h1:Qiz0EyvDRJjjxGyUQa2cCNZn/wMyzrRJ/qcDXOQazLI=
tailscale.com v1.86.4 h1:KHAmLyzVn50t2P5877wohBU0UPVeIMHC9XDzRw4Ycz4= tailscale.com v1.88.2 h1:S8S+gt/Vx4KDlVjNHk7spcyGihTcJflKMroSnwjp5kQ=
tailscale.com v1.86.4/go.mod h1:Lm8dnzU2i/Emw15r6sl3FRNp/liSQ/nYw6ZSQvIdZ1M= tailscale.com v1.88.2/go.mod h1:LHaTiwRgzebPDLgZ6RQQVzX+1SR5fbNl51fzm7UtMaw=
+10 -9
View File
@@ -14,7 +14,7 @@ import (
"tailscale.com/ipn/ipnlocal" "tailscale.com/ipn/ipnlocal"
"tailscale.com/ipn/ipnserver" "tailscale.com/ipn/ipnserver"
"tailscale.com/ipn/store/mem" "tailscale.com/ipn/store/mem"
"tailscale.com/net/netmon" // "tailscale.com/net/netmon"
"tailscale.com/net/netns" "tailscale.com/net/netns"
"tailscale.com/net/tsdial" "tailscale.com/net/tsdial"
"tailscale.com/safesocket" "tailscale.com/safesocket"
@@ -52,30 +52,31 @@ func NewTsWasmIpn(options *TsWasmNetOptions, callbacks *TsWasmNetCallbacks) (*Ts
// Base system (NewSystem() creates a bus automatically) // Base system (NewSystem() creates a bus automatically)
// We supply an in-memory store // We supply an in-memory store
sys := tsd.NewSystem() sys := tsd.NewSystem()
bus := sys.Bus.Get() // bus := sys.Bus.Get()
sys.Set(new(mem.Store)) sys.Set(new(mem.Store))
dialer := &tsdial.Dialer{Logf: logf} dialer := &tsdial.Dialer{Logf: logf}
netmon, err := netmon.New(bus, logf) // netmon, err := netmon.New(bus, logf)
if err != nil { // if err != nil {
return nil, err // return nil, err
} // }
// Userspace Wireguard engine // Userspace Wireguard engine
engine, err := wgengine.NewUserspaceEngine(logf, wgengine.Config{ engine, err := wgengine.NewUserspaceEngine(logf, wgengine.Config{
Dialer: dialer, Dialer: dialer,
NetMon: netmon, // NetMon: netmon,
SetSubsystem: sys.Set, SetSubsystem: sys.Set,
ControlKnobs: sys.ControlKnobs(), ControlKnobs: sys.ControlKnobs(),
HealthTracker: sys.HealthTracker(), HealthTracker: sys.HealthTracker(),
Metrics: sys.UserMetricsRegistry(), Metrics: sys.UserMetricsRegistry(),
EventBus: sys.Bus.Get(),
}) })
sys.Set(engine)
if err != nil { if err != nil {
return nil, err return nil, err
} }
sys.Set(engine)
tun := sys.Tun.Get() tun := sys.Tun.Get()
msock := sys.MagicSock.Get() msock := sys.MagicSock.Get()
dnsman := sys.DNSManager.Get() dnsman := sys.DNSManager.Get()
+19 -39
View File
@@ -59,12 +59,12 @@ func ParseSSHXtermConfig(obj js.Value) (*SSHXtermConfig, error) {
return nil, errors.New("SSHXtermConfig cannot be undefined or null") return nil, errors.New("SSHXtermConfig cannot be undefined or null")
} }
timeout := safeInt("Timeout", obj) timeout := safeInt("timeout", obj)
rows := safeInt("Rows", obj) rows := safeInt("rows", obj)
cols := safeInt("Cols", obj) cols := safeInt("cols", obj)
if rows <= 0 || cols <= 0 { if rows <= 0 || cols <= 0 {
return nil, errors.New("Rows and Cols must be positive integers") return nil, errors.New("`rows` and `cols` must be positive integers")
} }
if timeout <= 0 { if timeout <= 0 {
@@ -77,63 +77,43 @@ func ParseSSHXtermConfig(obj js.Value) (*SSHXtermConfig, error) {
Cols: cols, Cols: cols,
} }
onStdout := obj.Get("OnStdout") onStdout := obj.Get("onStdout")
if onStdout.IsUndefined() || onStdout.IsNull() { if onStdout.IsUndefined() || onStdout.IsNull() || (onStdout.Type() != js.TypeFunction) {
return nil, errors.New("OnStdout must be a function") return nil, errors.New("`onStdout` is required and must be a function")
}
if onStdout.Type() != js.TypeFunction {
return nil, errors.New("OnStdout must be a function")
} }
config.OnStdout = func(data string) { config.OnStdout = func(data string) {
onStdout.Invoke(data) onStdout.Invoke(data)
} }
onStderr := obj.Get("OnStderr") onStderr := obj.Get("onStderr")
if onStderr.IsUndefined() || onStderr.IsNull() { if onStderr.IsUndefined() || onStderr.IsNull() || (onStderr.Type() != js.TypeFunction) {
return nil, errors.New("OnStderr must be a function") return nil, errors.New("`onStderr` is required and must be a function")
}
if onStderr.Type() != js.TypeFunction {
return nil, errors.New("OnStderr must be a function")
} }
config.OnStderr = func(error string) { config.OnStderr = func(error string) {
onStderr.Invoke(error) onStderr.Invoke(error)
} }
onStdin := obj.Get("OnStdin") onStdin := obj.Get("onStdin")
if onStdin.IsUndefined() || onStdin.IsNull() { if onStdin.IsUndefined() || onStdin.IsNull() || (onStdin.Type() != js.TypeFunction) {
return nil, errors.New("OnStdin must be a function") return nil, errors.New("`onStdin` is required and must be a function")
}
if onStdin.Type() != js.TypeFunction {
return nil, errors.New("OnStdin must be a function")
} }
config.OnStdin = onStdin config.OnStdin = onStdin
onConnect := obj.Get("OnConnect") onConnect := obj.Get("onConnect")
if onConnect.IsUndefined() || onConnect.IsNull() { if onConnect.IsUndefined() || onConnect.IsNull() || (onConnect.Type() != js.TypeFunction) {
return nil, errors.New("OnConnect must be a function") return nil, errors.New("`onConnect` is required and must be a function")
}
if onConnect.Type() != js.TypeFunction {
return nil, errors.New("OnConnect must be a function")
} }
config.OnConnect = func() { config.OnConnect = func() {
onConnect.Invoke() onConnect.Invoke()
} }
onDisconnect := obj.Get("OnDisconnect") onDisconnect := obj.Get("onDisconnect")
if onDisconnect.IsUndefined() || onDisconnect.IsNull() { if onDisconnect.IsUndefined() || onDisconnect.IsNull() || (onDisconnect.Type() != js.TypeFunction) {
return nil, errors.New("OnDisconnect must be a function") return nil, errors.New("`onDisconnect` is required and must be a function")
}
if onDisconnect.Type() != js.TypeFunction {
return nil, errors.New("OnDisconnect must be a function")
} }
config.OnDisconnect = func() { config.OnDisconnect = func() {
+1 -1
View File
@@ -1,6 +1,6 @@
[tools] [tools]
# REMEMBER TO UPDATE .tool-versions TOO # REMEMBER TO UPDATE .tool-versions TOO
go = "1.24.4" go = "1.25.1"
pnpm = "10.4.0" pnpm = "10.4.0"
node = "22.16" node = "22.16"
+2 -2
View File
@@ -23,8 +23,8 @@ if (!version) {
const config = await readFile('config.example.yaml', 'utf-8'); const config = await readFile('config.example.yaml', 'utf-8');
const { server } = parse(config); const { server } = parse(config);
export default defineConfig(({ isSsrBuild }) => ({ export default defineConfig(({ mode, isSsrBuild }) => ({
base: `${prefix}/`, base: mode !== 'development' ? `${prefix}/` : undefined,
plugins: [ plugins: [
reactRouterHonoServer(), reactRouterHonoServer(),
reactRouter(), reactRouter(),