feat: improve windows support (#225)
release / build-web (push) Has been cancelled
release / build-bundles (aarch64-linux-gnu-gcc, arm64, linux, ubuntu-latest, linux-arm64) (push) Has been cancelled
release / build-bundles (amd64, darwin, macos-13, macos-x64) (push) Has been cancelled
release / build-bundles (amd64, linux, ubuntu-latest, linux-x64) (push) Has been cancelled
release / build-bundles (amd64, windows, windows-latest, windows-x64) (push) Has been cancelled
release / build-bundles (arm64, darwin, macos-14, macos-arm64) (push) Has been cancelled
release / build-web (push) Has been cancelled
release / build-bundles (aarch64-linux-gnu-gcc, arm64, linux, ubuntu-latest, linux-arm64) (push) Has been cancelled
release / build-bundles (amd64, darwin, macos-13, macos-x64) (push) Has been cancelled
release / build-bundles (amd64, linux, ubuntu-latest, linux-x64) (push) Has been cancelled
release / build-bundles (amd64, windows, windows-latest, windows-x64) (push) Has been cancelled
release / build-bundles (arm64, darwin, macos-14, macos-arm64) (push) Has been cancelled
feat: windows support
This commit is contained in:
@@ -38,13 +38,30 @@ jobs:
|
||||
include:
|
||||
- os: ubuntu-latest
|
||||
platform: linux-x64
|
||||
goos: linux
|
||||
goarch: amd64
|
||||
- os: ubuntu-latest
|
||||
platform: linux-arm64
|
||||
goos: linux
|
||||
goarch: arm64
|
||||
cc: aarch64-linux-gnu-gcc
|
||||
- os: macos-13
|
||||
platform: macos-x64
|
||||
goos: darwin
|
||||
goarch: amd64
|
||||
- os: macos-14
|
||||
platform: macos-arm64
|
||||
goos: darwin
|
||||
goarch: arm64
|
||||
- os: windows-latest
|
||||
platform: windows-x64
|
||||
goos: windows
|
||||
goarch: amd64
|
||||
runs-on: ${{ matrix.os }}
|
||||
env:
|
||||
CGO_ENABLED: 1
|
||||
GOOS: ${{ matrix.goos }}
|
||||
GOARCH: ${{ matrix.goarch }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-go@v6
|
||||
@@ -56,8 +73,16 @@ jobs:
|
||||
name: web-bundle
|
||||
path: dist/web
|
||||
|
||||
- name: Install cross-compilation tools (linux-arm64)
|
||||
if: matrix.platform == 'linux-arm64'
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y gcc-aarch64-linux-gnu
|
||||
|
||||
- name: Build backend binaries (unix)
|
||||
if: runner.os != 'Windows'
|
||||
env:
|
||||
CC: ${{ matrix.cc || '' }}
|
||||
run: |
|
||||
cd apps/backend
|
||||
mkdir -p bin
|
||||
@@ -68,6 +93,9 @@ jobs:
|
||||
if: runner.os == 'Windows'
|
||||
shell: pwsh
|
||||
run: |
|
||||
# Ensure MSYS2 MinGW gcc is available for CGO
|
||||
$env:Path = "C:\msys64\mingw64\bin;" + $env:Path
|
||||
gcc --version
|
||||
Set-Location apps/backend
|
||||
New-Item -ItemType Directory -Force -Path bin | Out-Null
|
||||
go build -o bin/kandev.exe ./cmd/kandev
|
||||
|
||||
@@ -10,6 +10,15 @@ APPS_DIR := apps
|
||||
PNPM := pnpm
|
||||
MAKE := make
|
||||
|
||||
# Cross-platform commands
|
||||
ifeq ($(OS),Windows_NT)
|
||||
RM = cmd /c del /s /q
|
||||
RMDIR = cmd /c rmdir /s /q
|
||||
else
|
||||
RM = rm -f
|
||||
RMDIR = rm -rf
|
||||
endif
|
||||
|
||||
# Colors for terminal output
|
||||
RESET := \033[0m
|
||||
BOLD := \033[1m
|
||||
@@ -251,11 +260,11 @@ clean-backend:
|
||||
.PHONY: clean-web
|
||||
clean-web:
|
||||
@printf "$(CYAN)Cleaning web artifacts...$(RESET)\n"
|
||||
@rm -rf $(WEB_DIR)/.next $(APPS_DIR)/node_modules
|
||||
@rm -rf $(APPS_DIR)/packages/*/node_modules
|
||||
@$(RMDIR) $(WEB_DIR)/.next $(APPS_DIR)/node_modules
|
||||
@$(RMDIR) $(APPS_DIR)/packages/*/node_modules
|
||||
|
||||
.PHONY: clean-db
|
||||
clean-db:
|
||||
@printf "$(CYAN)Removing local SQLite database...$(RESET)\n"
|
||||
@rm -f kandev.db kandev.db-wal kandev.db-shm \
|
||||
@$(RM) kandev.db kandev.db-wal kandev.db-shm \
|
||||
$(BACKEND_DIR)/kandev.db $(BACKEND_DIR)/kandev.db-wal $(BACKEND_DIR)/kandev.db-shm
|
||||
|
||||
+17
-6
@@ -7,10 +7,21 @@ GO := go
|
||||
GOFLAGS := -v
|
||||
LDFLAGS := -s -w
|
||||
|
||||
# Cross-platform commands
|
||||
ifeq ($(OS),Windows_NT)
|
||||
RM = cmd /c del /s /q
|
||||
RMDIR = cmd /c rmdir /s /q
|
||||
MKDIR = cmd /c mkdir
|
||||
else
|
||||
RM = rm -f
|
||||
RMDIR = rm -rf
|
||||
MKDIR = mkdir -p
|
||||
endif
|
||||
|
||||
# Build info
|
||||
VERSION ?= $(shell git describe --tags --always --dirty 2>/dev/null || echo "dev")
|
||||
COMMIT := $(shell git rev-parse --short HEAD 2>/dev/null || echo "unknown")
|
||||
BUILD_TIME := $(shell date -u '+%Y-%m-%dT%H:%M:%SZ')
|
||||
BUILD_TIME := $(shell date -u '+%Y-%m-%dT%H:%M:%SZ' 2>/dev/null || powershell -c "Get-Date -Format 'yyyy-MM-ddTHH:mm:ssZ'" 2>/dev/null || echo "unknown")
|
||||
|
||||
LDFLAGS += -X main.Version=$(VERSION) -X main.Commit=$(COMMIT) -X main.BuildTime=$(BUILD_TIME)
|
||||
|
||||
@@ -22,13 +33,13 @@ all: build
|
||||
## Build the mock-agent binary for testing
|
||||
build-mock-agent:
|
||||
@echo "Building mock-agent..."
|
||||
@mkdir -p $(BUILD_DIR)
|
||||
@$(MKDIR) $(BUILD_DIR)
|
||||
$(GO) build $(GOFLAGS) -ldflags "$(LDFLAGS)" -o $(BUILD_DIR)/mock-agent ./cmd/mock-agent
|
||||
|
||||
## Build the unified kandev binary
|
||||
build: build-agentctl build-mock-agent
|
||||
@echo "Building $(BINARY_NAME)..."
|
||||
@mkdir -p $(BUILD_DIR)
|
||||
@$(MKDIR) $(BUILD_DIR)
|
||||
$(GO) build $(GOFLAGS) -ldflags "$(LDFLAGS)" -o $(BUILD_DIR)/$(BINARY_NAME) ./cmd/kandev
|
||||
|
||||
## Build all binaries (unified + individual services + agentctl)
|
||||
@@ -39,13 +50,13 @@ build-all: build build-agentctl
|
||||
## Build the agentctl sidecar binary (includes embedded MCP server)
|
||||
build-agentctl:
|
||||
@echo "Building agentctl..."
|
||||
@mkdir -p $(BUILD_DIR)
|
||||
@$(MKDIR) $(BUILD_DIR)
|
||||
$(GO) build $(GOFLAGS) -ldflags "$(LDFLAGS)" -o $(BUILD_DIR)/agentctl ./cmd/agentctl
|
||||
|
||||
## Clean build artifacts
|
||||
clean:
|
||||
@echo "Cleaning..."
|
||||
@rm -rf $(BUILD_DIR)
|
||||
@$(RMDIR) $(BUILD_DIR)
|
||||
$(GO) clean
|
||||
|
||||
## Run the application
|
||||
@@ -97,7 +108,7 @@ deps:
|
||||
## Build for multiple platforms
|
||||
build-cross:
|
||||
@echo "Building for multiple platforms..."
|
||||
@mkdir -p $(BUILD_DIR)
|
||||
@$(MKDIR) $(BUILD_DIR)
|
||||
GOOS=linux GOARCH=amd64 $(GO) build -ldflags "$(LDFLAGS)" -o $(BUILD_DIR)/$(BINARY_NAME)-linux-amd64 ./cmd/kandev
|
||||
GOOS=darwin GOARCH=amd64 $(GO) build -ldflags "$(LDFLAGS)" -o $(BUILD_DIR)/$(BINARY_NAME)-darwin-amd64 ./cmd/kandev
|
||||
GOOS=darwin GOARCH=arm64 $(GO) build -ldflags "$(LDFLAGS)" -o $(BUILD_DIR)/$(BINARY_NAME)-darwin-arm64 ./cmd/kandev
|
||||
|
||||
@@ -5,6 +5,7 @@ go 1.24.0
|
||||
toolchain go1.24.11
|
||||
|
||||
require (
|
||||
github.com/UserExistsError/conpty v0.1.4
|
||||
github.com/coder/acp-go-sdk v0.6.3
|
||||
github.com/creack/pty v1.1.21
|
||||
github.com/docker/docker v28.5.2+incompatible
|
||||
|
||||
@@ -42,6 +42,8 @@ github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03
|
||||
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
|
||||
github.com/Microsoft/go-winio v0.4.21 h1:+6mVbXh4wPzUrl1COX9A+ZCvEpYsOBZ6/+kwDnvLyro=
|
||||
github.com/Microsoft/go-winio v0.4.21/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84=
|
||||
github.com/UserExistsError/conpty v0.1.4 h1:+3FhJhiqhyEJa+K5qaK3/w6w+sN3Nh9O9VbJyBS02to=
|
||||
github.com/UserExistsError/conpty v0.1.4/go.mod h1:PDglKIkX3O/2xVk0MV9a6bCWxRmPVfxqZoTG/5sSd9I=
|
||||
github.com/bahlo/generic-list-go v0.2.0 h1:5sz/EEAK+ls5wF+NeqDpk5+iNdMDXrh3z3nPnH1Wvgk=
|
||||
github.com/bahlo/generic-list-go v0.2.0/go.mod h1:2KvAjgMlE5NNynlg/5iLrrCCZ2+5xWbdbCW3pNTGyYg=
|
||||
github.com/buger/jsonparser v1.1.1 h1:2PnMjfWD7wBILjqQbt530v576A/cAbQvEW9gGIpYMUs=
|
||||
@@ -479,6 +481,7 @@ golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBc
|
||||
golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk=
|
||||
golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
|
||||
@@ -0,0 +1,75 @@
|
||||
package process
|
||||
|
||||
import "strings"
|
||||
|
||||
// escapeArg rewrites a command-line argument following the MSDN
|
||||
// CommandLineToArgvW parsing rules (same algorithm as Go's
|
||||
// syscall.EscapeArg on Windows):
|
||||
//
|
||||
// - every backslash (\) is doubled, but only if immediately
|
||||
// followed by a double quote (");
|
||||
// - every double quote (") is escaped with a backslash (\);
|
||||
// - finally, the argument is wrapped in double quotes only if
|
||||
// it contains spaces or tabs.
|
||||
// - an empty string becomes "" (two double-quote characters).
|
||||
func escapeArg(s string) string {
|
||||
if len(s) == 0 {
|
||||
return `""`
|
||||
}
|
||||
|
||||
needsBackslash := false
|
||||
hasSpace := false
|
||||
for i := 0; i < len(s); i++ {
|
||||
switch s[i] {
|
||||
case '"', '\\':
|
||||
needsBackslash = true
|
||||
case ' ', '\t':
|
||||
hasSpace = true
|
||||
}
|
||||
}
|
||||
|
||||
if !needsBackslash && !hasSpace {
|
||||
return s
|
||||
}
|
||||
if !needsBackslash {
|
||||
return `"` + s + `"`
|
||||
}
|
||||
|
||||
var b []byte
|
||||
if hasSpace {
|
||||
b = append(b, '"')
|
||||
}
|
||||
slashes := 0
|
||||
for i := 0; i < len(s); i++ {
|
||||
c := s[i]
|
||||
switch c {
|
||||
default:
|
||||
slashes = 0
|
||||
case '\\':
|
||||
slashes++
|
||||
case '"':
|
||||
for ; slashes > 0; slashes-- {
|
||||
b = append(b, '\\')
|
||||
}
|
||||
b = append(b, '\\')
|
||||
}
|
||||
b = append(b, c)
|
||||
}
|
||||
if hasSpace {
|
||||
for ; slashes > 0; slashes-- {
|
||||
b = append(b, '\\')
|
||||
}
|
||||
b = append(b, '"')
|
||||
}
|
||||
return string(b)
|
||||
}
|
||||
|
||||
// buildCmdLine joins arguments into a single command-line string
|
||||
// with proper quoting for Windows CreateProcess.
|
||||
func buildCmdLine(args []string) string {
|
||||
escaped := make([]string, len(args))
|
||||
for i, arg := range args {
|
||||
escaped[i] = escapeArg(arg)
|
||||
}
|
||||
return strings.Join(escaped, " ")
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
package process
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestEscapeArg(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
in string
|
||||
want string
|
||||
}{
|
||||
{"empty", "", `""`},
|
||||
{"simple", "hello", "hello"},
|
||||
{"with space", "hello world", `"hello world"`},
|
||||
{"with tab", "hello\tworld", `"hello` + "\t" + `world"`},
|
||||
{"with quote", `say "hi"`, `"say \"hi\""`},
|
||||
{"backslash no quote", `a\b`, `a\b`},
|
||||
{"backslash before quote", `a\"`, `a\\\"` },
|
||||
{"trailing backslash with space", `a b\`, `"a b\\"`},
|
||||
{"multiple trailing backslashes with space", `a b\\`, `"a b\\\\"`},
|
||||
{"only backslashes", `\\`, `\\`},
|
||||
{"backslash then quote", `\\"`, `\\\\\"` },
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got := escapeArg(tt.in)
|
||||
if got != tt.want {
|
||||
t.Errorf("escapeArg(%q) = %q, want %q", tt.in, got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuildCmdLine(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
args []string
|
||||
want string
|
||||
}{
|
||||
{"single", []string{"cmd.exe"}, "cmd.exe"},
|
||||
{"simple args", []string{"git", "status"}, "git status"},
|
||||
{"arg with space", []string{"C:\\Program Files\\app.exe", "-f"}, `"C:\Program Files\app.exe" -f`},
|
||||
{"arg with quote", []string{"echo", `"hello"`}, `echo \"hello\"`},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got := buildCmdLine(tt.args)
|
||||
if got != tt.want {
|
||||
t.Errorf("buildCmdLine(%v) = %q, want %q", tt.args, got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -11,16 +11,13 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"os/exec"
|
||||
"regexp"
|
||||
"sort"
|
||||
"strings"
|
||||
"sync"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/creack/pty"
|
||||
"github.com/google/uuid"
|
||||
"github.com/kandev/kandev/internal/agentctl/types"
|
||||
"github.com/kandev/kandev/internal/common/logger"
|
||||
@@ -70,7 +67,7 @@ type DirectOutputWriter interface {
|
||||
type interactiveProcess struct {
|
||||
info InteractiveProcessInfo
|
||||
cmd *exec.Cmd
|
||||
ptmx *os.File // PTY master file descriptor (using creack/pty)
|
||||
ptmx PtyHandle // PTY handle (Unix: creack/pty, Windows: ConPTY)
|
||||
buffer *ringBuffer
|
||||
|
||||
// Turn detection
|
||||
@@ -330,12 +327,9 @@ func (r *InteractiveRunner) startProcess(proc *interactiveProcess, cols, rows in
|
||||
// Note: Do NOT set Setpgid when using PTY - it conflicts with terminal control
|
||||
// The PTY session handles process group management
|
||||
|
||||
// Start process in PTY with exact dimensions from frontend using creack/pty
|
||||
// This is the battle-tested PTY library that properly handles resize/SIGWINCH
|
||||
ptmx, err := pty.StartWithSize(cmd, &pty.Winsize{
|
||||
Cols: uint16(cols),
|
||||
Rows: uint16(rows),
|
||||
})
|
||||
// Start process in PTY with exact dimensions from frontend
|
||||
// Unix: creack/pty, Windows: ConPTY
|
||||
ptmx, err := startPTYWithSize(cmd, cols, rows)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to start pty: %w", err)
|
||||
}
|
||||
@@ -473,7 +467,7 @@ func (r *InteractiveRunner) Stop(ctx context.Context, processID string) error {
|
||||
|
||||
// Terminate the process directly (PTY handles its own session management)
|
||||
if proc.cmd != nil && proc.cmd.Process != nil {
|
||||
_ = proc.cmd.Process.Signal(syscall.SIGTERM)
|
||||
_ = terminateProcess(proc.cmd.Process)
|
||||
|
||||
// Wait for the wait() goroutine to finish (it calls cmd.Wait).
|
||||
// If it doesn't exit in time, force-kill the process.
|
||||
@@ -515,14 +509,18 @@ func (r *InteractiveRunner) GetBySession(sessionID string) (*InteractiveProcessI
|
||||
}
|
||||
|
||||
// isProcessAlive checks if the underlying OS process is still running.
|
||||
// Uses a non-blocking check on waitDone which is closed when cmd.Wait returns.
|
||||
// Must be called with proc.mu held.
|
||||
func (r *InteractiveRunner) isProcessAlive(proc *interactiveProcess) bool {
|
||||
if proc.cmd != nil && proc.cmd.Process != nil {
|
||||
// Signal 0 checks if process exists without sending a signal
|
||||
err := proc.cmd.Process.Signal(syscall.Signal(0))
|
||||
return err == nil
|
||||
if proc.cmd == nil || proc.cmd.Process == nil {
|
||||
return false
|
||||
}
|
||||
select {
|
||||
case <-proc.waitDone:
|
||||
return false
|
||||
default:
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// IsProcessRunning checks if a process with the given ID exists and is running.
|
||||
@@ -605,13 +603,10 @@ func (r *InteractiveRunner) ResizeBySession(sessionID string, cols, rows uint16)
|
||||
statusTracker := proc.statusTracker
|
||||
proc.mu.Unlock()
|
||||
|
||||
// If process started, resize the PTY using creack/pty's Setsize
|
||||
// This properly sets the window size and sends SIGWINCH to the process
|
||||
// If process started, resize the PTY
|
||||
// On Unix this sends SIGWINCH; on Windows it updates the ConPTY dimensions
|
||||
if ptyInstance != nil {
|
||||
if err := pty.Setsize(ptyInstance, &pty.Winsize{
|
||||
Cols: cols,
|
||||
Rows: rows,
|
||||
}); err != nil {
|
||||
if err := ptyInstance.Resize(cols, rows); err != nil {
|
||||
return fmt.Errorf("failed to resize PTY: %w", err)
|
||||
}
|
||||
}
|
||||
@@ -835,25 +830,14 @@ func (r *InteractiveRunner) handleStateChange(proc *interactiveProcess, state Ag
|
||||
// 3. Adding a timeout here would leave the process unreachable and create leaks
|
||||
func (r *InteractiveRunner) wait(proc *interactiveProcess) {
|
||||
defer close(proc.waitDone)
|
||||
err := proc.cmd.Wait()
|
||||
exitCode := 0
|
||||
|
||||
proc.mu.Lock()
|
||||
ptyHandle := proc.ptmx
|
||||
proc.mu.Unlock()
|
||||
|
||||
exitCode, signalName, err := waitPtyProcess(proc.cmd, ptyHandle)
|
||||
status := types.ProcessStatusExited
|
||||
var signalName string
|
||||
if err != nil {
|
||||
if exitErr, ok := err.(*exec.ExitError); ok {
|
||||
if waitStatus, ok := exitErr.Sys().(syscall.WaitStatus); ok {
|
||||
if waitStatus.Signaled() {
|
||||
signalName = waitStatus.Signal().String()
|
||||
exitCode = 128 + int(waitStatus.Signal())
|
||||
} else {
|
||||
exitCode = waitStatus.ExitStatus()
|
||||
}
|
||||
} else {
|
||||
exitCode = 1
|
||||
}
|
||||
} else {
|
||||
exitCode = 1
|
||||
}
|
||||
status = types.ProcessStatusFailed
|
||||
}
|
||||
|
||||
@@ -1354,18 +1338,9 @@ func (r *InteractiveRunner) StartUserShell(ctx context.Context, sessionID, termi
|
||||
initialCommand = existingEntry.InitialCommand
|
||||
}
|
||||
|
||||
// Determine shell to use
|
||||
shell := preferredShell
|
||||
if shell == "" {
|
||||
shell = os.Getenv("SHELL")
|
||||
}
|
||||
if shell == "" {
|
||||
shell = "/bin/sh"
|
||||
}
|
||||
|
||||
req := InteractiveStartRequest{
|
||||
SessionID: sessionID,
|
||||
Command: []string{shell, "-l"}, // Login shell
|
||||
Command: defaultShellCommand(preferredShell),
|
||||
WorkingDir: workingDir,
|
||||
InitialCommand: initialCommand,
|
||||
DisableTurnDetection: true, // User shells must not trigger turn complete / MarkReady
|
||||
@@ -1403,7 +1378,7 @@ func (r *InteractiveRunner) StartUserShell(ctx context.Context, sessionID, termi
|
||||
zap.String("session_id", sessionID),
|
||||
zap.String("terminal_id", terminalID),
|
||||
zap.String("process_id", info.ID),
|
||||
zap.String("shell", shell),
|
||||
zap.String("shell", req.Command[0]),
|
||||
zap.String("working_dir", workingDir),
|
||||
zap.String("label", opts.Label),
|
||||
zap.String("initial_command", opts.InitialCommand))
|
||||
@@ -1544,10 +1519,7 @@ func (r *InteractiveRunner) ResizeUserShell(sessionID, terminalID string, cols,
|
||||
proc.mu.Unlock()
|
||||
|
||||
if ptyInstance != nil {
|
||||
if err := pty.Setsize(ptyInstance, &pty.Winsize{
|
||||
Cols: cols,
|
||||
Rows: rows,
|
||||
}); err != nil {
|
||||
if err := ptyInstance.Resize(cols, rows); err != nil {
|
||||
return fmt.Errorf("failed to resize PTY: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
//go:build !windows
|
||||
|
||||
package process
|
||||
|
||||
import (
|
||||
"os"
|
||||
"os/exec"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
// terminateProcess sends SIGTERM to the process for graceful shutdown.
|
||||
func terminateProcess(p *os.Process) error {
|
||||
return p.Signal(syscall.SIGTERM)
|
||||
}
|
||||
|
||||
// waitPtyProcess waits for the PTY process to exit and returns exit info.
|
||||
// On Unix, uses cmd.Wait() which inspects WaitStatus for signal information.
|
||||
func waitPtyProcess(cmd *exec.Cmd, _ PtyHandle) (exitCode int, signalName string, err error) {
|
||||
err = cmd.Wait()
|
||||
if err == nil {
|
||||
return 0, "", nil
|
||||
}
|
||||
exitErr, ok := err.(*exec.ExitError)
|
||||
if !ok {
|
||||
return 1, "", err
|
||||
}
|
||||
waitStatus, ok := exitErr.Sys().(syscall.WaitStatus)
|
||||
if !ok {
|
||||
return 1, "", err
|
||||
}
|
||||
if waitStatus.Signaled() {
|
||||
return 128 + int(waitStatus.Signal()), waitStatus.Signal().String(), err
|
||||
}
|
||||
return waitStatus.ExitStatus(), "", err
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
//go:build windows
|
||||
|
||||
package process
|
||||
|
||||
import (
|
||||
"os"
|
||||
"os/exec"
|
||||
)
|
||||
|
||||
// terminateProcess kills the process on Windows.
|
||||
// Windows does not support SIGTERM; process termination is immediate.
|
||||
func terminateProcess(p *os.Process) error {
|
||||
return p.Kill()
|
||||
}
|
||||
|
||||
// waitPtyProcess waits for the PTY process to exit and returns exit info.
|
||||
// On Windows, uses cmd.Process.Wait() since the process may have been started
|
||||
// via ConPTY rather than cmd.Start().
|
||||
func waitPtyProcess(cmd *exec.Cmd, _ PtyHandle) (exitCode int, signalName string, err error) {
|
||||
state, err := cmd.Process.Wait()
|
||||
if err != nil {
|
||||
return 1, "", err
|
||||
}
|
||||
code := state.ExitCode()
|
||||
if code != 0 {
|
||||
return code, "", &exec.ExitError{ProcessState: state}
|
||||
}
|
||||
return 0, "", nil
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
package process
|
||||
|
||||
import "io"
|
||||
|
||||
// PtyHandle abstracts PTY operations across Unix and Windows.
|
||||
// On Unix, this wraps creack/pty (*os.File).
|
||||
// On Windows, this wraps Windows ConPTY.
|
||||
type PtyHandle interface {
|
||||
io.ReadWriteCloser
|
||||
// Resize changes the PTY window size.
|
||||
Resize(cols, rows uint16) error
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
//go:build !windows
|
||||
|
||||
package process
|
||||
|
||||
import (
|
||||
"os"
|
||||
"os/exec"
|
||||
|
||||
"github.com/creack/pty"
|
||||
)
|
||||
|
||||
// unixPTY wraps a Unix PTY master file descriptor.
|
||||
type unixPTY struct {
|
||||
f *os.File
|
||||
}
|
||||
|
||||
func (p *unixPTY) Read(b []byte) (int, error) { return p.f.Read(b) }
|
||||
func (p *unixPTY) Write(b []byte) (int, error) { return p.f.Write(b) }
|
||||
func (p *unixPTY) Close() error { return p.f.Close() }
|
||||
|
||||
func (p *unixPTY) Resize(cols, rows uint16) error {
|
||||
return pty.Setsize(p.f, &pty.Winsize{Cols: cols, Rows: rows})
|
||||
}
|
||||
|
||||
// startPTYWithSize starts the command in a Unix PTY with the given dimensions.
|
||||
// The command is started via pty.StartWithSize which calls cmd.Start() internally.
|
||||
func startPTYWithSize(cmd *exec.Cmd, cols, rows int) (PtyHandle, error) {
|
||||
f, err := pty.StartWithSize(cmd, &pty.Winsize{
|
||||
Cols: uint16(cols),
|
||||
Rows: uint16(rows),
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &unixPTY{f: f}, nil
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
//go:build windows
|
||||
|
||||
package process
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
|
||||
"github.com/UserExistsError/conpty"
|
||||
)
|
||||
|
||||
// windowsPTY wraps a Windows ConPTY pseudo-console.
|
||||
type windowsPTY struct {
|
||||
cpty *conpty.ConPty
|
||||
}
|
||||
|
||||
func (p *windowsPTY) Read(b []byte) (int, error) { return p.cpty.Read(b) }
|
||||
func (p *windowsPTY) Write(b []byte) (int, error) { return p.cpty.Write(b) }
|
||||
func (p *windowsPTY) Close() error { return p.cpty.Close() }
|
||||
|
||||
func (p *windowsPTY) Resize(cols, rows uint16) error {
|
||||
return p.cpty.Resize(int(cols), int(rows))
|
||||
}
|
||||
|
||||
// startPTYWithSize starts the command in a Windows ConPTY with the given dimensions.
|
||||
// ConPTY manages process creation internally, so this builds a command line from
|
||||
// the exec.Cmd and starts the process via ConPTY. After this call, cmd.Process
|
||||
// is set so callers can manage the process lifecycle.
|
||||
func startPTYWithSize(cmd *exec.Cmd, cols, rows int) (PtyHandle, error) {
|
||||
cmdLine := buildCmdLine(cmd.Args)
|
||||
if len(cmd.Args) == 0 {
|
||||
cmdLine = escapeArg(cmd.Path)
|
||||
}
|
||||
|
||||
opts := []conpty.ConPtyOption{
|
||||
conpty.ConPtyDimensions(cols, rows),
|
||||
}
|
||||
if cmd.Dir != "" {
|
||||
opts = append(opts, conpty.ConPtyWorkDir(cmd.Dir))
|
||||
}
|
||||
|
||||
// Pass environment variables directly to the child process via ConPTY.
|
||||
if cmd.Env != nil {
|
||||
opts = append(opts, conpty.ConPtyEnv(cmd.Env))
|
||||
}
|
||||
|
||||
cpty, err := conpty.Start(cmdLine, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Set cmd.Process so callers can use PID, Kill, Wait, etc.
|
||||
pid := cpty.Pid()
|
||||
proc, err := os.FindProcess(int(pid))
|
||||
if err != nil {
|
||||
_ = cpty.Close()
|
||||
return nil, fmt.Errorf("failed to find ConPTY process %d: %w", pid, err)
|
||||
}
|
||||
cmd.Process = proc
|
||||
|
||||
return &windowsPTY{cpty: cpty}, nil
|
||||
}
|
||||
|
||||
@@ -229,8 +229,9 @@ func (r *ProcessRunner) Start(ctx context.Context, req StartProcessRequest) (*Pr
|
||||
id := uuid.New().String()
|
||||
now := time.Now().UTC()
|
||||
|
||||
// Use "sh -lc" to execute command with login shell environment
|
||||
cmd := exec.CommandContext(ctx, "sh", "-lc", req.Command)
|
||||
// Execute command through the system shell (Unix: sh -lc, Windows: cmd /c)
|
||||
prog, shellArgs := shellExecArgs(req.Command)
|
||||
cmd := exec.CommandContext(ctx, prog, shellArgs...)
|
||||
if req.WorkingDir != "" {
|
||||
cmd.Dir = req.WorkingDir
|
||||
}
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
package process
|
||||
|
||||
import (
|
||||
"runtime"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestDefaultShellCommand_CustomShell(t *testing.T) {
|
||||
cmd := defaultShellCommand("/usr/local/bin/fish")
|
||||
if cmd[0] != "/usr/local/bin/fish" {
|
||||
t.Errorf("expected preferred shell as first element, got %q", cmd[0])
|
||||
}
|
||||
}
|
||||
|
||||
func TestDefaultShellCommand_EmptyPreferred(t *testing.T) {
|
||||
cmd := defaultShellCommand("")
|
||||
if len(cmd) == 0 {
|
||||
t.Fatal("expected non-empty command")
|
||||
}
|
||||
// On Unix, should fall back to $SHELL or /bin/sh with -l flag
|
||||
// On Windows, should fall back to %COMSPEC% or powershell.exe
|
||||
if runtime.GOOS != "windows" {
|
||||
if cmd[len(cmd)-1] != "-l" {
|
||||
t.Errorf("expected login flag -l on Unix, got %v", cmd)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestShellExecArgs(t *testing.T) {
|
||||
prog, args := shellExecArgs("echo hello")
|
||||
if prog == "" {
|
||||
t.Fatal("expected non-empty program")
|
||||
}
|
||||
if len(args) == 0 {
|
||||
t.Fatal("expected non-empty args")
|
||||
}
|
||||
// The command string should appear in the args
|
||||
found := false
|
||||
for _, a := range args {
|
||||
if a == "echo hello" {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
t.Errorf("expected command string in args, got prog=%q args=%v", prog, args)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
//go:build !windows
|
||||
|
||||
package process
|
||||
|
||||
import "os"
|
||||
|
||||
// defaultShellCommand returns the command and args for starting an interactive login shell.
|
||||
// On Unix, uses $SHELL (or /bin/sh) with the -l (login) flag.
|
||||
func defaultShellCommand(preferredShell string) []string {
|
||||
shell := preferredShell
|
||||
if shell == "" {
|
||||
shell = os.Getenv("SHELL")
|
||||
}
|
||||
if shell == "" {
|
||||
shell = "/bin/sh"
|
||||
}
|
||||
return []string{shell, "-l"}
|
||||
}
|
||||
|
||||
// shellExecArgs returns the program and arguments needed to execute a command
|
||||
// string through the system shell.
|
||||
// On Unix: sh -lc "command"
|
||||
func shellExecArgs(command string) (prog string, args []string) {
|
||||
return "sh", []string{"-lc", command}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
//go:build windows
|
||||
|
||||
package process
|
||||
|
||||
import "os"
|
||||
|
||||
// defaultShellCommand returns the command and args for starting an interactive shell.
|
||||
// On Windows, uses %COMSPEC% (typically cmd.exe) or falls back to powershell.exe.
|
||||
// No -l flag since Windows shells don't use login shell mode.
|
||||
func defaultShellCommand(preferredShell string) []string {
|
||||
shell := preferredShell
|
||||
if shell == "" {
|
||||
shell = os.Getenv("COMSPEC")
|
||||
}
|
||||
if shell == "" {
|
||||
shell = "powershell.exe"
|
||||
}
|
||||
return []string{shell}
|
||||
}
|
||||
|
||||
// shellExecArgs returns the program and arguments needed to execute a command
|
||||
// string through the system shell.
|
||||
// On Windows: cmd /c "command"
|
||||
func shellExecArgs(command string) (prog string, args []string) {
|
||||
return "cmd", []string{"/c", command}
|
||||
}
|
||||
@@ -5,6 +5,8 @@ package config
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@@ -174,13 +176,13 @@ func setDefaults(v *viper.Viper) {
|
||||
v.SetDefault("nats.clientId", "kandev-client")
|
||||
v.SetDefault("nats.maxReconnects", 10)
|
||||
|
||||
// Docker defaults
|
||||
// Docker defaults — platform-aware host and volume path
|
||||
v.SetDefault("docker.enabled", true) // Docker runtime enabled by default if Docker is available
|
||||
v.SetDefault("docker.host", "unix:///var/run/docker.sock")
|
||||
v.SetDefault("docker.host", DefaultDockerHost())
|
||||
v.SetDefault("docker.apiVersion", "1.41")
|
||||
v.SetDefault("docker.tlsVerify", false)
|
||||
v.SetDefault("docker.defaultNetwork", "kandev-network")
|
||||
v.SetDefault("docker.volumeBasePath", "/var/lib/kandev/volumes")
|
||||
v.SetDefault("docker.volumeBasePath", defaultDockerVolumePath())
|
||||
|
||||
// Agent defaults (runtime selection is now per-task based on executor type)
|
||||
v.SetDefault("agent.standaloneHost", "localhost")
|
||||
@@ -209,6 +211,30 @@ func setDefaults(v *viper.Viper) {
|
||||
v.SetDefault("worktree.cleanupOnRemove", true)
|
||||
}
|
||||
|
||||
// DefaultDockerHost returns the platform-appropriate Docker socket path.
|
||||
// Respects DOCKER_HOST env var as override (standard Docker convention).
|
||||
func DefaultDockerHost() string {
|
||||
if host := os.Getenv("DOCKER_HOST"); host != "" {
|
||||
return host
|
||||
}
|
||||
if runtime.GOOS == "windows" {
|
||||
return "npipe:////./pipe/docker_engine"
|
||||
}
|
||||
return "unix:///var/run/docker.sock"
|
||||
}
|
||||
|
||||
// defaultDockerVolumePath returns the platform-appropriate volume base path.
|
||||
func defaultDockerVolumePath() string {
|
||||
if runtime.GOOS == "windows" {
|
||||
localAppData := os.Getenv("LOCALAPPDATA")
|
||||
if localAppData == "" {
|
||||
localAppData = filepath.Join(os.Getenv("USERPROFILE"), "AppData", "Local")
|
||||
}
|
||||
return filepath.Join(localAppData, "kandev", "volumes")
|
||||
}
|
||||
return "/var/lib/kandev/volumes"
|
||||
}
|
||||
|
||||
// Load reads configuration from environment variables, config file, and defaults.
|
||||
// Environment variables use the prefix KANDEV_ with snake_case naming.
|
||||
// Config file should be named config.yaml and placed in the current directory or /etc/kandev/.
|
||||
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
|
||||
"github.com/google/uuid"
|
||||
|
||||
"github.com/kandev/kandev/internal/common/config"
|
||||
commonsqlite "github.com/kandev/kandev/internal/common/sqlite"
|
||||
"github.com/kandev/kandev/internal/task/models"
|
||||
)
|
||||
@@ -137,7 +138,7 @@ func (r *Repository) ensureDefaultExecutorsAndEnvironments() error {
|
||||
isSystem: false,
|
||||
resumable: true,
|
||||
config: map[string]string{
|
||||
"docker_host": "unix:///var/run/docker.sock",
|
||||
"docker_host": config.DefaultDockerHost(),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
@@ -141,5 +141,6 @@ scripts/release/publish-launcher.sh 0.1.0
|
||||
| macOS (Apple Silicon) | `kandev-macos-arm64.zip` |
|
||||
| macOS (Intel) | `kandev-macos-x64.zip` |
|
||||
| Linux (x64) | `kandev-linux-x64.zip` |
|
||||
| Linux (ARM64) | `kandev-linux-arm64.zip` |
|
||||
| Windows (x64) | `kandev-windows-x64.zip` |
|
||||
| Windows (ARM64) | `kandev-windows-arm64.zip` |
|
||||
| Windows (ARM64) | Falls back to `windows-x64` (x64 emulation) |
|
||||
|
||||
@@ -5,10 +5,10 @@ const isWindows = process.platform === "win32";
|
||||
export type PlatformArch = "x64" | "arm64";
|
||||
export type PlatformDir =
|
||||
| "linux-x64"
|
||||
| "linux-arm64"
|
||||
| "macos-x64"
|
||||
| "macos-arm64"
|
||||
| "windows-x64"
|
||||
| "windows-arm64";
|
||||
| "windows-x64";
|
||||
|
||||
export function getBinaryName(base: string): string {
|
||||
return isWindows ? `${base}.exe` : base;
|
||||
@@ -40,9 +40,11 @@ export function getPlatformDir(): PlatformDir {
|
||||
const platform = process.platform;
|
||||
const arch = getEffectiveArch();
|
||||
if (platform === "linux" && arch === "x64") return "linux-x64";
|
||||
if (platform === "linux" && arch === "arm64") return "linux-arm64";
|
||||
if (platform === "darwin" && arch === "x64") return "macos-x64";
|
||||
if (platform === "darwin" && arch === "arm64") return "macos-arm64";
|
||||
if (platform === "win32" && arch === "x64") return "windows-x64";
|
||||
if (platform === "win32" && arch === "arm64") return "windows-arm64";
|
||||
// Windows ARM64 runs x64 binaries via emulation — no native arm64 build yet
|
||||
if (platform === "win32" && arch === "arm64") return "windows-x64";
|
||||
throw new Error(`Unsupported platform: ${platform}-${arch}`);
|
||||
}
|
||||
|
||||
+16
-8
@@ -37,17 +37,23 @@ export function createProcessSupervisor(): {
|
||||
|
||||
const attachSignalHandlers = () => {
|
||||
process.on("SIGINT", onSignal);
|
||||
process.on("SIGTERM", onSignal);
|
||||
// SIGTERM is not available on Windows — only attach where supported
|
||||
if (process.platform !== "win32") {
|
||||
process.on("SIGTERM", onSignal);
|
||||
}
|
||||
};
|
||||
|
||||
return { children, shutdown, attachSignalHandlers };
|
||||
}
|
||||
|
||||
/**
|
||||
* Send SIGTERM to a process and wait for it to exit.
|
||||
* Falls back to SIGKILL if the process doesn't exit within the timeout.
|
||||
* Terminate a process and wait for it to exit.
|
||||
* On Unix: sends SIGTERM, falls back to SIGKILL after timeout.
|
||||
* On Windows: tree-kill uses taskkill (no SIGTERM/SIGKILL distinction).
|
||||
*/
|
||||
function waitForProcessExit(child: ChildLike, timeoutMs: number): Promise<void> {
|
||||
const isWindows = process.platform === "win32";
|
||||
|
||||
return new Promise<void>((resolve) => {
|
||||
const pid = child.pid as number;
|
||||
|
||||
@@ -68,10 +74,12 @@ function waitForProcessExit(child: ChildLike, timeoutMs: number): Promise<void>
|
||||
resolve();
|
||||
};
|
||||
|
||||
// Set up timeout for fallback SIGKILL
|
||||
// Set up timeout for force-kill fallback
|
||||
const timeout = setTimeout(() => {
|
||||
console.log(`[kandev] process ${pid} did not exit in time, sending SIGKILL`);
|
||||
kill(pid, "SIGKILL", done);
|
||||
console.log(`[kandev] process ${pid} did not exit in time, force killing`);
|
||||
// On Windows, tree-kill always force-kills (no signal distinction)
|
||||
// On Unix, escalate to SIGKILL
|
||||
kill(pid, isWindows ? undefined : "SIGKILL", done);
|
||||
}, timeoutMs);
|
||||
|
||||
// Listen for exit event if available
|
||||
@@ -82,8 +90,8 @@ function waitForProcessExit(child: ChildLike, timeoutMs: number): Promise<void>
|
||||
});
|
||||
}
|
||||
|
||||
// Send SIGTERM
|
||||
kill(pid, "SIGTERM", (err) => {
|
||||
// Graceful termination: SIGTERM on Unix, default kill on Windows
|
||||
kill(pid, isWindows ? undefined : "SIGTERM", (err) => {
|
||||
// If kill fails (process already gone), we're done
|
||||
if (err) {
|
||||
clearTimeout(timeout);
|
||||
|
||||
+20
-6
@@ -1,7 +1,22 @@
|
||||
import { spawn, type ChildProcess, type StdioOptions } from "node:child_process";
|
||||
import { readFileSync } from "node:fs";
|
||||
|
||||
import { createProcessSupervisor } from "./process";
|
||||
|
||||
let _isWSL: boolean | undefined;
|
||||
function isWSL(): boolean {
|
||||
if (_isWSL === undefined) {
|
||||
try {
|
||||
_isWSL = readFileSync("/proc/version", "utf8")
|
||||
.toLowerCase()
|
||||
.includes("microsoft");
|
||||
} catch {
|
||||
_isWSL = false;
|
||||
}
|
||||
}
|
||||
return _isWSL;
|
||||
}
|
||||
|
||||
export type WebLaunchOptions = {
|
||||
command: string;
|
||||
args: string[];
|
||||
@@ -18,18 +33,17 @@ export function openBrowser(url: string) {
|
||||
if (process.env.KANDEV_NO_BROWSER === "1") {
|
||||
return;
|
||||
}
|
||||
const useCmd = process.platform === "win32" || isWSL();
|
||||
const opener =
|
||||
process.platform === "darwin"
|
||||
? "open"
|
||||
: process.platform === "win32"
|
||||
? "cmd"
|
||||
: useCmd
|
||||
? "cmd.exe"
|
||||
: "xdg-open";
|
||||
const args =
|
||||
process.platform === "win32"
|
||||
? ["/c", "start", "", url]
|
||||
: [url];
|
||||
const args = useCmd ? ["/c", "start", "", url] : [url];
|
||||
try {
|
||||
const child = spawn(opener, args, { stdio: "ignore", detached: true });
|
||||
child.on("error", () => {}); // ignore async spawn errors (e.g. xdg-open missing)
|
||||
child.unref();
|
||||
} catch {
|
||||
// ignore browser launch errors
|
||||
|
||||
Generated
+73
-14
@@ -367,7 +367,7 @@ importers:
|
||||
version: 11.12.2
|
||||
next:
|
||||
specifier: 16.1.1
|
||||
version: 16.1.1(@babel/core@7.28.6)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
|
||||
version: 16.1.1(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
|
||||
next-themes:
|
||||
specifier: ^0.4.6
|
||||
version: 0.4.6(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
|
||||
@@ -432,6 +432,9 @@ importers:
|
||||
'@types/react-dom':
|
||||
specifier: ^19
|
||||
version: 19.2.3(@types/react@19.2.8)
|
||||
cross-env:
|
||||
specifier: ^7.0.3
|
||||
version: 7.0.3
|
||||
eslint:
|
||||
specifier: ^9
|
||||
version: 9.39.2(jiti@2.6.1)
|
||||
@@ -1178,89 +1181,105 @@ packages:
|
||||
resolution: {integrity: sha512-excjX8DfsIcJ10x1Kzr4RcWe1edC9PquDRRPx3YVCvQv+U5p7Yin2s32ftzikXojb1PIFc/9Mt28/y+iRklkrw==}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@img/sharp-libvips-linux-arm@1.2.4':
|
||||
resolution: {integrity: sha512-bFI7xcKFELdiNCVov8e44Ia4u2byA+l3XtsAj+Q8tfCwO6BQ8iDojYdvoPMqsKDkuoOo+X6HZA0s0q11ANMQ8A==}
|
||||
cpu: [arm]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@img/sharp-libvips-linux-ppc64@1.2.4':
|
||||
resolution: {integrity: sha512-FMuvGijLDYG6lW+b/UvyilUWu5Ayu+3r2d1S8notiGCIyYU/76eig1UfMmkZ7vwgOrzKzlQbFSuQfgm7GYUPpA==}
|
||||
cpu: [ppc64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@img/sharp-libvips-linux-riscv64@1.2.4':
|
||||
resolution: {integrity: sha512-oVDbcR4zUC0ce82teubSm+x6ETixtKZBh/qbREIOcI3cULzDyb18Sr/Wcyx7NRQeQzOiHTNbZFF1UwPS2scyGA==}
|
||||
cpu: [riscv64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@img/sharp-libvips-linux-s390x@1.2.4':
|
||||
resolution: {integrity: sha512-qmp9VrzgPgMoGZyPvrQHqk02uyjA0/QrTO26Tqk6l4ZV0MPWIW6LTkqOIov+J1yEu7MbFQaDpwdwJKhbJvuRxQ==}
|
||||
cpu: [s390x]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@img/sharp-libvips-linux-x64@1.2.4':
|
||||
resolution: {integrity: sha512-tJxiiLsmHc9Ax1bz3oaOYBURTXGIRDODBqhveVHonrHJ9/+k89qbLl0bcJns+e4t4rvaNBxaEZsFtSfAdquPrw==}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@img/sharp-libvips-linuxmusl-arm64@1.2.4':
|
||||
resolution: {integrity: sha512-FVQHuwx1IIuNow9QAbYUzJ+En8KcVm9Lk5+uGUQJHaZmMECZmOlix9HnH7n1TRkXMS0pGxIJokIVB9SuqZGGXw==}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@img/sharp-libvips-linuxmusl-x64@1.2.4':
|
||||
resolution: {integrity: sha512-+LpyBk7L44ZIXwz/VYfglaX/okxezESc6UxDSoyo2Ks6Jxc4Y7sGjpgU9s4PMgqgjj1gZCylTieNamqA1MF7Dg==}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@img/sharp-linux-arm64@0.34.5':
|
||||
resolution: {integrity: sha512-bKQzaJRY/bkPOXyKx5EVup7qkaojECG6NLYswgktOZjaXecSAeCWiZwwiFf3/Y+O1HrauiE3FVsGxFg8c24rZg==}
|
||||
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@img/sharp-linux-arm@0.34.5':
|
||||
resolution: {integrity: sha512-9dLqsvwtg1uuXBGZKsxem9595+ujv0sJ6Vi8wcTANSFpwV/GONat5eCkzQo/1O6zRIkh0m/8+5BjrRr7jDUSZw==}
|
||||
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
|
||||
cpu: [arm]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@img/sharp-linux-ppc64@0.34.5':
|
||||
resolution: {integrity: sha512-7zznwNaqW6YtsfrGGDA6BRkISKAAE1Jo0QdpNYXNMHu2+0dTrPflTLNkpc8l7MUP5M16ZJcUvysVWWrMefZquA==}
|
||||
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
|
||||
cpu: [ppc64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@img/sharp-linux-riscv64@0.34.5':
|
||||
resolution: {integrity: sha512-51gJuLPTKa7piYPaVs8GmByo7/U7/7TZOq+cnXJIHZKavIRHAP77e3N2HEl3dgiqdD/w0yUfiJnII77PuDDFdw==}
|
||||
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
|
||||
cpu: [riscv64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@img/sharp-linux-s390x@0.34.5':
|
||||
resolution: {integrity: sha512-nQtCk0PdKfho3eC5MrbQoigJ2gd1CgddUMkabUj+rBevs8tZ2cULOx46E7oyX+04WGfABgIwmMC0VqieTiR4jg==}
|
||||
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
|
||||
cpu: [s390x]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@img/sharp-linux-x64@0.34.5':
|
||||
resolution: {integrity: sha512-MEzd8HPKxVxVenwAa+JRPwEC7QFjoPWuS5NZnBt6B3pu7EG2Ge0id1oLHZpPJdn3OQK+BQDiw9zStiHBTJQQQQ==}
|
||||
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@img/sharp-linuxmusl-arm64@0.34.5':
|
||||
resolution: {integrity: sha512-fprJR6GtRsMt6Kyfq44IsChVZeGN97gTD331weR1ex1c1rypDEABN6Tm2xa1wE6lYb5DdEnk03NZPqA7Id21yg==}
|
||||
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@img/sharp-linuxmusl-x64@0.34.5':
|
||||
resolution: {integrity: sha512-Jg8wNT1MUzIvhBFxViqrEhWDGzqymo3sV7z7ZsaWbZNDLXRJZoRGrjulp60YYtV4wfY8VIKcWidjojlLcWrd8Q==}
|
||||
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@img/sharp-wasm32@0.34.5':
|
||||
resolution: {integrity: sha512-OdWTEiVkY2PHwqkbBI8frFxQQFekHaSSkUIJkwzclWZe64O1X4UlUjqqqLaPbUpMOQk6FBu/HtlGXNblIs0huw==}
|
||||
@@ -1521,24 +1540,28 @@ packages:
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@next/swc-linux-arm64-musl@16.1.1':
|
||||
resolution: {integrity: sha512-MFHrgL4TXNQbBPzkKKur4Fb5ICEJa87HM7fczFs2+HWblM7mMLdco3dvyTI+QmLBU9xgns/EeeINSZD6Ar+oLg==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@next/swc-linux-x64-gnu@16.1.1':
|
||||
resolution: {integrity: sha512-20bYDfgOQAPUkkKBnyP9PTuHiJGM7HzNBbuqmD0jiFVZ0aOldz+VnJhbxzjcSabYsnNjMPsE0cyzEudpYxsrUQ==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@next/swc-linux-x64-musl@16.1.1':
|
||||
resolution: {integrity: sha512-9pRbK3M4asAHQRkwaXwu601oPZHghuSC8IXNENgbBSyImHv/zY4K5udBusgdHkvJ/Tcr96jJwQYOll0qU8+fPA==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@next/swc-win32-arm64-msvc@16.1.1':
|
||||
resolution: {integrity: sha512-bdfQkggaLgnmYrFkSQfsHfOhk/mCYmjnrbRCGgkMcoOBZ4n+TRRSLmT/CU5SATzlBJ9TpioUyBW/vWFXTqQRiA==}
|
||||
@@ -2373,66 +2396,79 @@ packages:
|
||||
resolution: {integrity: sha512-Rn3n+FUk2J5VWx+ywrG/HGPTD9jXNbicRtTM11e/uorplArnXZYsVifnPPqNNP5BsO3roI4n8332ukpY/zN7rQ==}
|
||||
cpu: [arm]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@rollup/rollup-linux-arm-musleabihf@4.55.1':
|
||||
resolution: {integrity: sha512-grPNWydeKtc1aEdrJDWk4opD7nFtQbMmV7769hiAaYyUKCT1faPRm2av8CX1YJsZ4TLAZcg9gTR1KvEzoLjXkg==}
|
||||
cpu: [arm]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@rollup/rollup-linux-arm64-gnu@4.55.1':
|
||||
resolution: {integrity: sha512-a59mwd1k6x8tXKcUxSyISiquLwB5pX+fJW9TkWU46lCqD/GRDe9uDN31jrMmVP3feI3mhAdvcCClhV8V5MhJFQ==}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@rollup/rollup-linux-arm64-musl@4.55.1':
|
||||
resolution: {integrity: sha512-puS1MEgWX5GsHSoiAsF0TYrpomdvkaXm0CofIMG5uVkP6IBV+ZO9xhC5YEN49nsgYo1DuuMquF9+7EDBVYu4uA==}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@rollup/rollup-linux-loong64-gnu@4.55.1':
|
||||
resolution: {integrity: sha512-r3Wv40in+lTsULSb6nnoudVbARdOwb2u5fpeoOAZjFLznp6tDU8kd+GTHmJoqZ9lt6/Sys33KdIHUaQihFcu7g==}
|
||||
cpu: [loong64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@rollup/rollup-linux-loong64-musl@4.55.1':
|
||||
resolution: {integrity: sha512-MR8c0+UxAlB22Fq4R+aQSPBayvYa3+9DrwG/i1TKQXFYEaoW3B5b/rkSRIypcZDdWjWnpcvxbNaAJDcSbJU3Lw==}
|
||||
cpu: [loong64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@rollup/rollup-linux-ppc64-gnu@4.55.1':
|
||||
resolution: {integrity: sha512-3KhoECe1BRlSYpMTeVrD4sh2Pw2xgt4jzNSZIIPLFEsnQn9gAnZagW9+VqDqAHgm1Xc77LzJOo2LdigS5qZ+gw==}
|
||||
cpu: [ppc64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@rollup/rollup-linux-ppc64-musl@4.55.1':
|
||||
resolution: {integrity: sha512-ziR1OuZx0vdYZZ30vueNZTg73alF59DicYrPViG0NEgDVN8/Jl87zkAPu4u6VjZST2llgEUjaiNl9JM6HH1Vdw==}
|
||||
cpu: [ppc64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@rollup/rollup-linux-riscv64-gnu@4.55.1':
|
||||
resolution: {integrity: sha512-uW0Y12ih2XJRERZ4jAfKamTyIHVMPQnTZcQjme2HMVDAHY4amf5u414OqNYC+x+LzRdRcnIG1YodLrrtA8xsxw==}
|
||||
cpu: [riscv64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@rollup/rollup-linux-riscv64-musl@4.55.1':
|
||||
resolution: {integrity: sha512-u9yZ0jUkOED1BFrqu3BwMQoixvGHGZ+JhJNkNKY/hyoEgOwlqKb62qu+7UjbPSHYjiVy8kKJHvXKv5coH4wDeg==}
|
||||
cpu: [riscv64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@rollup/rollup-linux-s390x-gnu@4.55.1':
|
||||
resolution: {integrity: sha512-/0PenBCmqM4ZUd0190j7J0UsQ/1nsi735iPRakO8iPciE7BQ495Y6msPzaOmvx0/pn+eJVVlZrNrSh4WSYLxNg==}
|
||||
cpu: [s390x]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@rollup/rollup-linux-x64-gnu@4.55.1':
|
||||
resolution: {integrity: sha512-a8G4wiQxQG2BAvo+gU6XrReRRqj+pLS2NGXKm8io19goR+K8lw269eTrPkSdDTALwMmJp4th2Uh0D8J9bEV1vg==}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@rollup/rollup-linux-x64-musl@4.55.1':
|
||||
resolution: {integrity: sha512-bD+zjpFrMpP/hqkfEcnjXWHMw5BIghGisOKPj+2NaNDuVT+8Ds4mPf3XcPHuat1tz89WRL+1wbcxKY3WSbiT7w==}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@rollup/rollup-openbsd-x64@4.55.1':
|
||||
resolution: {integrity: sha512-eLXw0dOiqE4QmvikfQ6yjgkg/xDM+MdU9YJuP4ySTibXU0oAvnEWXt7UDJmD4UkYialMfOGFPJnIHSe/kdzPxg==}
|
||||
@@ -2550,24 +2586,28 @@ packages:
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@tailwindcss/oxide-linux-arm64-musl@4.1.18':
|
||||
resolution: {integrity: sha512-1px92582HkPQlaaCkdRcio71p8bc8i/ap5807tPRDK/uw953cauQBT8c5tVGkOwrHMfc2Yh6UuxaH4vtTjGvHg==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@tailwindcss/oxide-linux-x64-gnu@4.1.18':
|
||||
resolution: {integrity: sha512-v3gyT0ivkfBLoZGF9LyHmts0Isc8jHZyVcbzio6Wpzifg/+5ZJpDiRiUhDLkcr7f/r38SWNe7ucxmGW3j3Kb/g==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@tailwindcss/oxide-linux-x64-musl@4.1.18':
|
||||
resolution: {integrity: sha512-bhJ2y2OQNlcRwwgOAGMY0xTFStt4/wyU6pvI6LSuZpRgKQwxTec0/3Scu91O8ir7qCR3AuepQKLU/kX99FouqQ==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@tailwindcss/oxide-wasm32-wasi@4.1.18':
|
||||
resolution: {integrity: sha512-LffYTvPjODiP6PT16oNeUQJzNVyJl1cjIebq/rWWBF+3eDst5JGEFSc5cWxyRCJ0Mxl+KyIkqRxk1XPEs9x8TA==}
|
||||
@@ -3042,41 +3082,49 @@ packages:
|
||||
resolution: {integrity: sha512-34gw7PjDGB9JgePJEmhEqBhWvCiiWCuXsL9hYphDF7crW7UgI05gyBAi6MF58uGcMOiOqSJ2ybEeCvHcq0BCmQ==}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@unrs/resolver-binding-linux-arm64-musl@1.11.1':
|
||||
resolution: {integrity: sha512-RyMIx6Uf53hhOtJDIamSbTskA99sPHS96wxVE/bJtePJJtpdKGXO1wY90oRdXuYOGOTuqjT8ACccMc4K6QmT3w==}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@unrs/resolver-binding-linux-ppc64-gnu@1.11.1':
|
||||
resolution: {integrity: sha512-D8Vae74A4/a+mZH0FbOkFJL9DSK2R6TFPC9M+jCWYia/q2einCubX10pecpDiTmkJVUH+y8K3BZClycD8nCShA==}
|
||||
cpu: [ppc64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@unrs/resolver-binding-linux-riscv64-gnu@1.11.1':
|
||||
resolution: {integrity: sha512-frxL4OrzOWVVsOc96+V3aqTIQl1O2TjgExV4EKgRY09AJ9leZpEg8Ak9phadbuX0BA4k8U5qtvMSQQGGmaJqcQ==}
|
||||
cpu: [riscv64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@unrs/resolver-binding-linux-riscv64-musl@1.11.1':
|
||||
resolution: {integrity: sha512-mJ5vuDaIZ+l/acv01sHoXfpnyrNKOk/3aDoEdLO/Xtn9HuZlDD6jKxHlkN8ZhWyLJsRBxfv9GYM2utQ1SChKew==}
|
||||
cpu: [riscv64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@unrs/resolver-binding-linux-s390x-gnu@1.11.1':
|
||||
resolution: {integrity: sha512-kELo8ebBVtb9sA7rMe1Cph4QHreByhaZ2QEADd9NzIQsYNQpt9UkM9iqr2lhGr5afh885d/cB5QeTXSbZHTYPg==}
|
||||
cpu: [s390x]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@unrs/resolver-binding-linux-x64-gnu@1.11.1':
|
||||
resolution: {integrity: sha512-C3ZAHugKgovV5YvAMsxhq0gtXuwESUKc5MhEtjBpLoHPLYM+iuwSj3lflFwK3DPm68660rZ7G8BMcwSro7hD5w==}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@unrs/resolver-binding-linux-x64-musl@1.11.1':
|
||||
resolution: {integrity: sha512-rV0YSoyhK2nZ4vEswT/QwqzqQXw5I6CjoaYMOX0TqBlWhojUf8P94mvI7nuJTeaCkkds3QE4+zS8Ko+GdXuZtA==}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@unrs/resolver-binding-wasm32-wasi@1.11.1':
|
||||
resolution: {integrity: sha512-5u4RkfxJm+Ng7IWgkzi3qrFOvLvQYnPBmjmZQ8+szTK/b31fQCnleNl1GgEt7nIsZRIf5PLhPwT0WM+q45x/UQ==}
|
||||
@@ -3499,6 +3547,11 @@ packages:
|
||||
crelt@1.0.6:
|
||||
resolution: {integrity: sha512-VQ2MBenTq1fWZUH9DJNGti7kKv6EeAuYr3cLwxUWhIu1baTaXh4Ib5W2CqHVqib4/MqbYGJqiL3Zb8GJZr3l4g==}
|
||||
|
||||
cross-env@7.0.3:
|
||||
resolution: {integrity: sha512-+/HKd6EgcQCJGh2PSjZuUitQBQynKor4wrFbRg4DtAgS1aWO+gU52xpH7M9ScGgXSYmAVS9bIJ8EzuaGw0oNAw==}
|
||||
engines: {node: '>=10.14', npm: '>=6', yarn: '>=1'}
|
||||
hasBin: true
|
||||
|
||||
cross-spawn@7.0.6:
|
||||
resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==}
|
||||
engines: {node: '>= 8'}
|
||||
@@ -4756,24 +4809,28 @@ packages:
|
||||
engines: {node: '>= 12.0.0'}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
lightningcss-linux-arm64-musl@1.30.2:
|
||||
resolution: {integrity: sha512-5Vh9dGeblpTxWHpOx8iauV02popZDsCYMPIgiuw97OJ5uaDsL86cnqSFs5LZkG3ghHoX5isLgWzMs+eD1YzrnA==}
|
||||
engines: {node: '>= 12.0.0'}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
lightningcss-linux-x64-gnu@1.30.2:
|
||||
resolution: {integrity: sha512-Cfd46gdmj1vQ+lR6VRTTadNHu6ALuw2pKR9lYq4FnhvgBc4zWY1EtZcAc6EffShbb1MFrIPfLDXD6Xprbnni4w==}
|
||||
engines: {node: '>= 12.0.0'}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
lightningcss-linux-x64-musl@1.30.2:
|
||||
resolution: {integrity: sha512-XJaLUUFXb6/QG2lGIW6aIk6jKdtjtcffUT0NKvIqhSBY3hh9Ch+1LCeH80dR9q9LBjG3ewbDjnumefsLsP6aiA==}
|
||||
engines: {node: '>= 12.0.0'}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
lightningcss-win32-arm64-msvc@1.30.2:
|
||||
resolution: {integrity: sha512-FZn+vaj7zLv//D/192WFFVA0RgHawIcHqLX9xuWiQt7P0PtdFEVaxgF9rjM/IRYHQXNnk61/H/gb2Ei+kUQ4xQ==}
|
||||
@@ -9839,6 +9896,10 @@ snapshots:
|
||||
|
||||
crelt@1.0.6: {}
|
||||
|
||||
cross-env@7.0.3:
|
||||
dependencies:
|
||||
cross-spawn: 7.0.6
|
||||
|
||||
cross-spawn@7.0.6:
|
||||
dependencies:
|
||||
path-key: 3.1.1
|
||||
@@ -10375,8 +10436,8 @@ snapshots:
|
||||
'@next/eslint-plugin-next': 16.1.1
|
||||
eslint: 9.39.2(jiti@2.6.1)
|
||||
eslint-import-resolver-node: 0.3.9
|
||||
eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0)(eslint@9.39.2(jiti@2.6.1))
|
||||
eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.53.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.2(jiti@2.6.1))
|
||||
eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.53.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1)))(eslint@9.39.2(jiti@2.6.1))
|
||||
eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.53.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.53.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1)))(eslint@9.39.2(jiti@2.6.1)))(eslint@9.39.2(jiti@2.6.1))
|
||||
eslint-plugin-jsx-a11y: 6.10.2(eslint@9.39.2(jiti@2.6.1))
|
||||
eslint-plugin-react: 7.37.5(eslint@9.39.2(jiti@2.6.1))
|
||||
eslint-plugin-react-hooks: 7.0.1(eslint@9.39.2(jiti@2.6.1))
|
||||
@@ -10398,7 +10459,7 @@ snapshots:
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0)(eslint@9.39.2(jiti@2.6.1)):
|
||||
eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.53.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1)))(eslint@9.39.2(jiti@2.6.1)):
|
||||
dependencies:
|
||||
'@nolyfill/is-core-module': 1.0.39
|
||||
debug: 4.4.3
|
||||
@@ -10409,22 +10470,22 @@ snapshots:
|
||||
tinyglobby: 0.2.15
|
||||
unrs-resolver: 1.11.1
|
||||
optionalDependencies:
|
||||
eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.53.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.2(jiti@2.6.1))
|
||||
eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.53.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.53.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1)))(eslint@9.39.2(jiti@2.6.1)))(eslint@9.39.2(jiti@2.6.1))
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
eslint-module-utils@2.12.1(@typescript-eslint/parser@8.53.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.2(jiti@2.6.1)):
|
||||
eslint-module-utils@2.12.1(@typescript-eslint/parser@8.53.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.53.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1)))(eslint@9.39.2(jiti@2.6.1)))(eslint@9.39.2(jiti@2.6.1)):
|
||||
dependencies:
|
||||
debug: 3.2.7
|
||||
optionalDependencies:
|
||||
'@typescript-eslint/parser': 8.53.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)
|
||||
eslint: 9.39.2(jiti@2.6.1)
|
||||
eslint-import-resolver-node: 0.3.9
|
||||
eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0)(eslint@9.39.2(jiti@2.6.1))
|
||||
eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.53.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1)))(eslint@9.39.2(jiti@2.6.1))
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.53.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.2(jiti@2.6.1)):
|
||||
eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.53.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.53.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1)))(eslint@9.39.2(jiti@2.6.1)))(eslint@9.39.2(jiti@2.6.1)):
|
||||
dependencies:
|
||||
'@rtsao/scc': 1.1.0
|
||||
array-includes: 3.1.9
|
||||
@@ -10435,7 +10496,7 @@ snapshots:
|
||||
doctrine: 2.1.0
|
||||
eslint: 9.39.2(jiti@2.6.1)
|
||||
eslint-import-resolver-node: 0.3.9
|
||||
eslint-module-utils: 2.12.1(@typescript-eslint/parser@8.53.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.2(jiti@2.6.1))
|
||||
eslint-module-utils: 2.12.1(@typescript-eslint/parser@8.53.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.53.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1)))(eslint@9.39.2(jiti@2.6.1)))(eslint@9.39.2(jiti@2.6.1))
|
||||
hasown: 2.0.2
|
||||
is-core-module: 2.16.1
|
||||
is-glob: 4.0.3
|
||||
@@ -11877,7 +11938,7 @@ snapshots:
|
||||
react: 19.2.3
|
||||
react-dom: 19.2.3(react@19.2.3)
|
||||
|
||||
next@16.1.1(@babel/core@7.28.6)(react-dom@19.2.3(react@19.2.3))(react@19.2.3):
|
||||
next@16.1.1(react-dom@19.2.3(react@19.2.3))(react@19.2.3):
|
||||
dependencies:
|
||||
'@next/env': 16.1.1
|
||||
'@swc/helpers': 0.5.15
|
||||
@@ -11886,7 +11947,7 @@ snapshots:
|
||||
postcss: 8.4.31
|
||||
react: 19.2.3
|
||||
react-dom: 19.2.3(react@19.2.3)
|
||||
styled-jsx: 5.1.6(@babel/core@7.28.6)(react@19.2.3)
|
||||
styled-jsx: 5.1.6(react@19.2.3)
|
||||
optionalDependencies:
|
||||
'@next/swc-darwin-arm64': 16.1.1
|
||||
'@next/swc-darwin-x64': 16.1.1
|
||||
@@ -13005,12 +13066,10 @@ snapshots:
|
||||
dependencies:
|
||||
inline-style-parser: 0.2.7
|
||||
|
||||
styled-jsx@5.1.6(@babel/core@7.28.6)(react@19.2.3):
|
||||
styled-jsx@5.1.6(react@19.2.3):
|
||||
dependencies:
|
||||
client-only: 0.0.1
|
||||
react: 19.2.3
|
||||
optionalDependencies:
|
||||
'@babel/core': 7.28.6
|
||||
|
||||
stylis@4.3.6: {}
|
||||
|
||||
|
||||
@@ -4,8 +4,8 @@
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "next dev --hostname 0.0.0.0",
|
||||
"build": "NODE_ENV=production next build",
|
||||
"start": "NODE_ENV=production next start",
|
||||
"build": "cross-env NODE_ENV=production next build",
|
||||
"start": "cross-env NODE_ENV=production next start",
|
||||
"lint": "eslint",
|
||||
"test": "vitest run"
|
||||
},
|
||||
@@ -104,6 +104,7 @@
|
||||
"@types/react-dom": "^19",
|
||||
"eslint": "^9",
|
||||
"eslint-config-next": "16.1.1",
|
||||
"cross-env": "^7.0.3",
|
||||
"happy-dom": "^20.1.0",
|
||||
"tailwindcss": "^4",
|
||||
"typescript": "^5",
|
||||
|
||||
@@ -0,0 +1,112 @@
|
||||
# Windows Support (WSL)
|
||||
|
||||
Kandev runs on Windows through WSL (Windows Subsystem for Linux).
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- Windows 10 (version 2004+) or Windows 11
|
||||
- WSL 2 with a Linux distribution installed (Ubuntu recommended)
|
||||
|
||||
## Setup
|
||||
|
||||
### 1. Install WSL
|
||||
|
||||
From PowerShell (as Administrator):
|
||||
|
||||
```powershell
|
||||
wsl --install
|
||||
```
|
||||
|
||||
Restart your machine if prompted, then open your Linux distribution from the Start menu.
|
||||
|
||||
### 2. Install system dependencies
|
||||
|
||||
```bash
|
||||
sudo apt update
|
||||
sudo apt install build-essential libatomic1 git
|
||||
```
|
||||
|
||||
- `build-essential` provides `gcc`, required by `go-sqlite3` (CGO)
|
||||
- `libatomic1` is required by Node.js
|
||||
|
||||
### 3. Install mise (runtime manager)
|
||||
|
||||
```bash
|
||||
curl https://mise.run | sh
|
||||
```
|
||||
|
||||
Add mise to your shell profile:
|
||||
|
||||
```bash
|
||||
echo 'eval "$(~/.local/bin/mise activate bash)"' >> ~/.bashrc
|
||||
source ~/.bashrc
|
||||
```
|
||||
|
||||
### 4. Install runtimes
|
||||
|
||||
```bash
|
||||
mise use -g node
|
||||
mise use -g pnpm
|
||||
mise use -g go
|
||||
```
|
||||
|
||||
### 5. Clone and install
|
||||
|
||||
```bash
|
||||
git clone <repo-url> kandev
|
||||
cd kandev
|
||||
make install
|
||||
```
|
||||
|
||||
### 6. Run
|
||||
|
||||
```bash
|
||||
make dev # development mode
|
||||
make start # production mode
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### `libatomic.so.1: cannot open shared object file`
|
||||
|
||||
Node.js requires `libatomic1`. Install it:
|
||||
|
||||
```bash
|
||||
sudo apt install libatomic1
|
||||
```
|
||||
|
||||
### `pnpm: command not found`
|
||||
|
||||
Ensure pnpm is installed and activated via mise:
|
||||
|
||||
```bash
|
||||
mise use -g pnpm
|
||||
```
|
||||
|
||||
### `Binary was compiled with 'CGO_ENABLED=0', go-sqlite3 requires cgo`
|
||||
|
||||
The SQLite driver needs a C compiler. Install `build-essential`:
|
||||
|
||||
```bash
|
||||
sudo apt install build-essential
|
||||
```
|
||||
|
||||
### Browser doesn't auto-open
|
||||
|
||||
WSL interop must be enabled so the CLI can launch your Windows browser. Ensure `/etc/wsl.conf` contains:
|
||||
|
||||
```ini
|
||||
[interop]
|
||||
enabled=true
|
||||
appendWindowsPath=true
|
||||
```
|
||||
|
||||
Then restart WSL from PowerShell:
|
||||
|
||||
```powershell
|
||||
wsl --shutdown
|
||||
```
|
||||
|
||||
### `cd: can't cd to apps` during `make install`
|
||||
|
||||
This is a symptom of pnpm not being installed. Follow step 4 above.
|
||||
Reference in New Issue
Block a user