feat(core-app): FrankenPHP + Wails v3 native desktop app
Single 53MB binary embedding PHP 8.4 ZTS runtime, Laravel 12, Livewire 4, and Octane worker mode inside a Wails v3 native desktop window. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
091b6a73b9
commit
c2715af316
34 changed files with 7554 additions and 0 deletions
61
Taskfile.yml
61
Taskfile.yml
|
|
@ -140,6 +140,67 @@ tasks:
|
||||||
cmds:
|
cmds:
|
||||||
- go run ./internal/tools/i18n-validate ./...
|
- go run ./internal/tools/i18n-validate ./...
|
||||||
|
|
||||||
|
# --- Core IDE (Wails v3) ---
|
||||||
|
ide:dev:
|
||||||
|
desc: "Run Core IDE in Wails dev mode"
|
||||||
|
dir: cmd/core-ide
|
||||||
|
cmds:
|
||||||
|
- cd frontend && npm install && npm run build
|
||||||
|
- wails3 dev
|
||||||
|
|
||||||
|
ide:build:
|
||||||
|
desc: "Build Core IDE production binary"
|
||||||
|
dir: cmd/core-ide
|
||||||
|
cmds:
|
||||||
|
- cd frontend && npm install && npm run build
|
||||||
|
- wails3 build
|
||||||
|
|
||||||
|
ide:frontend:
|
||||||
|
desc: "Build Core IDE frontend only"
|
||||||
|
dir: cmd/core-ide/frontend
|
||||||
|
cmds:
|
||||||
|
- npm install
|
||||||
|
- npm run build
|
||||||
|
|
||||||
|
# --- Core App (FrankenPHP + Wails v3) ---
|
||||||
|
app:setup:
|
||||||
|
desc: "Install PHP-ZTS build dependency for Core App"
|
||||||
|
cmds:
|
||||||
|
- brew tap shivammathur/php 2>/dev/null || true
|
||||||
|
- brew install shivammathur/php/php@8.4-zts
|
||||||
|
|
||||||
|
app:composer:
|
||||||
|
desc: "Install Laravel dependencies for Core App"
|
||||||
|
dir: cmd/core-app/laravel
|
||||||
|
cmds:
|
||||||
|
- composer install --no-dev --optimize-autoloader --no-interaction
|
||||||
|
|
||||||
|
app:build:
|
||||||
|
desc: "Build Core App (FrankenPHP + Laravel desktop binary)"
|
||||||
|
dir: cmd/core-app
|
||||||
|
env:
|
||||||
|
CGO_ENABLED: "1"
|
||||||
|
CGO_CFLAGS:
|
||||||
|
sh: /opt/homebrew/opt/php@8.4-zts/bin/php-config --includes
|
||||||
|
CGO_LDFLAGS:
|
||||||
|
sh: "echo -L/opt/homebrew/opt/php@8.4-zts/lib $(/opt/homebrew/opt/php@8.4-zts/bin/php-config --ldflags) $(/opt/homebrew/opt/php@8.4-zts/bin/php-config --libs)"
|
||||||
|
cmds:
|
||||||
|
- go build -tags nowatcher -o ../../bin/core-app .
|
||||||
|
|
||||||
|
app:dev:
|
||||||
|
desc: "Build and run Core App"
|
||||||
|
dir: cmd/core-app
|
||||||
|
env:
|
||||||
|
CGO_ENABLED: "1"
|
||||||
|
CGO_CFLAGS:
|
||||||
|
sh: /opt/homebrew/opt/php@8.4-zts/bin/php-config --includes
|
||||||
|
CGO_LDFLAGS:
|
||||||
|
sh: "echo -L/opt/homebrew/opt/php@8.4-zts/lib $(/opt/homebrew/opt/php@8.4-zts/bin/php-config --ldflags) $(/opt/homebrew/opt/php@8.4-zts/bin/php-config --libs)"
|
||||||
|
DYLD_LIBRARY_PATH: "/opt/homebrew/opt/php@8.4-zts/lib"
|
||||||
|
cmds:
|
||||||
|
- go build -tags nowatcher -o ../../bin/core-app .
|
||||||
|
- ../../bin/core-app
|
||||||
|
|
||||||
# --- Multi-repo (when in workspace) ---
|
# --- Multi-repo (when in workspace) ---
|
||||||
dev:health:
|
dev:health:
|
||||||
desc: "Check health of all repos"
|
desc: "Check health of all repos"
|
||||||
|
|
|
||||||
37
cmd/core-app/Taskfile.yml
Normal file
37
cmd/core-app/Taskfile.yml
Normal file
|
|
@ -0,0 +1,37 @@
|
||||||
|
version: '3'
|
||||||
|
|
||||||
|
vars:
|
||||||
|
PHP_CONFIG: /opt/homebrew/opt/php@8.4-zts/bin/php-config
|
||||||
|
CGO_CFLAGS:
|
||||||
|
sh: "{{.PHP_CONFIG}} --includes"
|
||||||
|
CGO_LDFLAGS:
|
||||||
|
sh: "echo -L/opt/homebrew/opt/php@8.4-zts/lib $({{.PHP_CONFIG}} --ldflags) $({{.PHP_CONFIG}} --libs)"
|
||||||
|
|
||||||
|
tasks:
|
||||||
|
setup:
|
||||||
|
desc: "Install PHP-ZTS build dependency"
|
||||||
|
cmds:
|
||||||
|
- brew tap shivammathur/php 2>/dev/null || true
|
||||||
|
- brew install shivammathur/php/php@8.4-zts
|
||||||
|
|
||||||
|
build:
|
||||||
|
desc: "Build core-app binary"
|
||||||
|
env:
|
||||||
|
CGO_ENABLED: "1"
|
||||||
|
CGO_CFLAGS: "{{.CGO_CFLAGS}}"
|
||||||
|
CGO_LDFLAGS: "{{.CGO_LDFLAGS}}"
|
||||||
|
cmds:
|
||||||
|
- go build -tags nowatcher -o ../../bin/core-app .
|
||||||
|
|
||||||
|
dev:
|
||||||
|
desc: "Build and run core-app"
|
||||||
|
deps: [build]
|
||||||
|
env:
|
||||||
|
DYLD_LIBRARY_PATH: "/opt/homebrew/opt/php@8.4-zts/lib"
|
||||||
|
cmds:
|
||||||
|
- ../../bin/core-app
|
||||||
|
|
||||||
|
clean:
|
||||||
|
desc: "Remove build artifacts"
|
||||||
|
cmds:
|
||||||
|
- rm -f ../../bin/core-app
|
||||||
48
cmd/core-app/app_service.go
Normal file
48
cmd/core-app/app_service.go
Normal file
|
|
@ -0,0 +1,48 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/wailsapp/wails/v3/pkg/application"
|
||||||
|
)
|
||||||
|
|
||||||
|
// AppService provides native desktop capabilities to the Wails frontend.
|
||||||
|
// These methods are callable via window.go.main.AppService.{Method}()
|
||||||
|
// from any JavaScript/webview context.
|
||||||
|
type AppService struct {
|
||||||
|
app *application.App
|
||||||
|
env *AppEnvironment
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewAppService(env *AppEnvironment) *AppService {
|
||||||
|
return &AppService{env: env}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ServiceStartup is called by Wails when the application starts.
|
||||||
|
func (s *AppService) ServiceStartup(app *application.App) {
|
||||||
|
s.app = app
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetVersion returns the application version.
|
||||||
|
func (s *AppService) GetVersion() string {
|
||||||
|
return "0.1.0"
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetDataDir returns the persistent data directory path.
|
||||||
|
func (s *AppService) GetDataDir() string {
|
||||||
|
return s.env.DataDir
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetDatabasePath returns the SQLite database file path.
|
||||||
|
func (s *AppService) GetDatabasePath() string {
|
||||||
|
return s.env.DatabasePath
|
||||||
|
}
|
||||||
|
|
||||||
|
// ShowWindow shows and focuses the main application window.
|
||||||
|
func (s *AppService) ShowWindow(name string) {
|
||||||
|
if s.app == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if w, ok := s.app.Window.Get(name); ok {
|
||||||
|
w.Show()
|
||||||
|
w.Focus()
|
||||||
|
}
|
||||||
|
}
|
||||||
52
cmd/core-app/embed.go
Normal file
52
cmd/core-app/embed.go
Normal file
|
|
@ -0,0 +1,52 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"embed"
|
||||||
|
"fmt"
|
||||||
|
"io/fs"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
)
|
||||||
|
|
||||||
|
//go:embed all:laravel
|
||||||
|
var laravelFiles embed.FS
|
||||||
|
|
||||||
|
// extractLaravel copies the embedded Laravel app to a temporary directory.
|
||||||
|
// FrankenPHP needs real filesystem paths — it cannot serve from embed.FS.
|
||||||
|
// Returns the path to the extracted Laravel root.
|
||||||
|
func extractLaravel() (string, error) {
|
||||||
|
tmpDir, err := os.MkdirTemp("", "core-app-laravel-*")
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("create temp dir: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = fs.WalkDir(laravelFiles, "laravel", func(path string, d fs.DirEntry, err error) error {
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
relPath, err := filepath.Rel("laravel", path)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
targetPath := filepath.Join(tmpDir, relPath)
|
||||||
|
|
||||||
|
if d.IsDir() {
|
||||||
|
return os.MkdirAll(targetPath, 0o755)
|
||||||
|
}
|
||||||
|
|
||||||
|
data, err := laravelFiles.ReadFile(path)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("read embedded %s: %w", path, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return os.WriteFile(targetPath, data, 0o644)
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
os.RemoveAll(tmpDir)
|
||||||
|
return "", fmt.Errorf("extract Laravel: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return tmpDir, nil
|
||||||
|
}
|
||||||
166
cmd/core-app/env.go
Normal file
166
cmd/core-app/env.go
Normal file
|
|
@ -0,0 +1,166 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/rand"
|
||||||
|
"encoding/base64"
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"runtime"
|
||||||
|
)
|
||||||
|
|
||||||
|
// AppEnvironment holds the resolved paths for the running application.
|
||||||
|
type AppEnvironment struct {
|
||||||
|
// DataDir is the persistent data directory (survives app updates).
|
||||||
|
DataDir string
|
||||||
|
// LaravelRoot is the extracted Laravel app in the temp directory.
|
||||||
|
LaravelRoot string
|
||||||
|
// DatabasePath is the full path to the SQLite database file.
|
||||||
|
DatabasePath string
|
||||||
|
}
|
||||||
|
|
||||||
|
// PrepareEnvironment creates data directories, generates .env, and symlinks
|
||||||
|
// storage so Laravel can write to persistent locations.
|
||||||
|
func PrepareEnvironment(laravelRoot string) (*AppEnvironment, error) {
|
||||||
|
dataDir, err := resolveDataDir()
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("resolve data dir: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
env := &AppEnvironment{
|
||||||
|
DataDir: dataDir,
|
||||||
|
LaravelRoot: laravelRoot,
|
||||||
|
DatabasePath: filepath.Join(dataDir, "core-app.sqlite"),
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create persistent directories
|
||||||
|
dirs := []string{
|
||||||
|
dataDir,
|
||||||
|
filepath.Join(dataDir, "storage", "app"),
|
||||||
|
filepath.Join(dataDir, "storage", "framework", "cache", "data"),
|
||||||
|
filepath.Join(dataDir, "storage", "framework", "sessions"),
|
||||||
|
filepath.Join(dataDir, "storage", "framework", "views"),
|
||||||
|
filepath.Join(dataDir, "storage", "logs"),
|
||||||
|
}
|
||||||
|
for _, dir := range dirs {
|
||||||
|
if err := os.MkdirAll(dir, 0o755); err != nil {
|
||||||
|
return nil, fmt.Errorf("create dir %s: %w", dir, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create empty SQLite database if it doesn't exist
|
||||||
|
if _, err := os.Stat(env.DatabasePath); os.IsNotExist(err) {
|
||||||
|
if err := os.WriteFile(env.DatabasePath, nil, 0o644); err != nil {
|
||||||
|
return nil, fmt.Errorf("create database: %w", err)
|
||||||
|
}
|
||||||
|
log.Printf("Created new database: %s", env.DatabasePath)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Replace the extracted storage/ with a symlink to the persistent one
|
||||||
|
extractedStorage := filepath.Join(laravelRoot, "storage")
|
||||||
|
os.RemoveAll(extractedStorage)
|
||||||
|
persistentStorage := filepath.Join(dataDir, "storage")
|
||||||
|
if err := os.Symlink(persistentStorage, extractedStorage); err != nil {
|
||||||
|
return nil, fmt.Errorf("symlink storage: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate .env file with resolved paths
|
||||||
|
if err := writeEnvFile(laravelRoot, env); err != nil {
|
||||||
|
return nil, fmt.Errorf("write .env: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return env, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// resolveDataDir returns the OS-appropriate persistent data directory.
|
||||||
|
func resolveDataDir() (string, error) {
|
||||||
|
var base string
|
||||||
|
switch runtime.GOOS {
|
||||||
|
case "darwin":
|
||||||
|
home, err := os.UserHomeDir()
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
base = filepath.Join(home, "Library", "Application Support", "core-app")
|
||||||
|
case "linux":
|
||||||
|
if xdg := os.Getenv("XDG_DATA_HOME"); xdg != "" {
|
||||||
|
base = filepath.Join(xdg, "core-app")
|
||||||
|
} else {
|
||||||
|
home, err := os.UserHomeDir()
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
base = filepath.Join(home, ".local", "share", "core-app")
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
home, err := os.UserHomeDir()
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
base = filepath.Join(home, ".core-app")
|
||||||
|
}
|
||||||
|
return base, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// writeEnvFile generates the Laravel .env with resolved runtime paths.
|
||||||
|
func writeEnvFile(laravelRoot string, env *AppEnvironment) error {
|
||||||
|
appKey, err := loadOrGenerateAppKey(env.DataDir)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("app key: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
content := fmt.Sprintf(`APP_NAME="Core App"
|
||||||
|
APP_ENV=production
|
||||||
|
APP_KEY=%s
|
||||||
|
APP_DEBUG=false
|
||||||
|
APP_URL=http://localhost
|
||||||
|
|
||||||
|
DB_CONNECTION=sqlite
|
||||||
|
DB_DATABASE="%s"
|
||||||
|
|
||||||
|
CACHE_STORE=file
|
||||||
|
SESSION_DRIVER=file
|
||||||
|
LOG_CHANNEL=single
|
||||||
|
LOG_LEVEL=warning
|
||||||
|
`, appKey, env.DatabasePath)
|
||||||
|
|
||||||
|
return os.WriteFile(filepath.Join(laravelRoot, ".env"), []byte(content), 0o644)
|
||||||
|
}
|
||||||
|
|
||||||
|
// loadOrGenerateAppKey loads an existing APP_KEY from the data dir,
|
||||||
|
// or generates a new one and persists it.
|
||||||
|
func loadOrGenerateAppKey(dataDir string) (string, error) {
|
||||||
|
keyFile := filepath.Join(dataDir, ".app-key")
|
||||||
|
|
||||||
|
data, err := os.ReadFile(keyFile)
|
||||||
|
if err == nil && len(data) > 0 {
|
||||||
|
return string(data), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate a new 32-byte key
|
||||||
|
key := make([]byte, 32)
|
||||||
|
if _, err := rand.Read(key); err != nil {
|
||||||
|
return "", fmt.Errorf("generate key: %w", err)
|
||||||
|
}
|
||||||
|
appKey := "base64:" + base64.StdEncoding.EncodeToString(key)
|
||||||
|
|
||||||
|
if err := os.WriteFile(keyFile, []byte(appKey), 0o600); err != nil {
|
||||||
|
return "", fmt.Errorf("save key: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Printf("Generated new APP_KEY (saved to %s)", keyFile)
|
||||||
|
return appKey, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// appendEnv appends a key=value pair to the Laravel .env file.
|
||||||
|
func appendEnv(laravelRoot, key, value string) error {
|
||||||
|
envFile := filepath.Join(laravelRoot, ".env")
|
||||||
|
f, err := os.OpenFile(envFile, os.O_APPEND|os.O_WRONLY, 0o644)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
_, err = fmt.Fprintf(f, "%s=\"%s\"\n", key, value)
|
||||||
|
return err
|
||||||
|
}
|
||||||
67
cmd/core-app/go.mod
Normal file
67
cmd/core-app/go.mod
Normal file
|
|
@ -0,0 +1,67 @@
|
||||||
|
module github.com/host-uk/core/cmd/core-app
|
||||||
|
|
||||||
|
go 1.25.5
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/dunglas/frankenphp v1.5.0
|
||||||
|
github.com/wailsapp/wails/v3 v3.0.0-alpha.64
|
||||||
|
)
|
||||||
|
|
||||||
|
require (
|
||||||
|
dario.cat/mergo v1.0.2 // indirect
|
||||||
|
github.com/Microsoft/go-winio v0.6.2 // indirect
|
||||||
|
github.com/ProtonMail/go-crypto v1.3.0 // indirect
|
||||||
|
github.com/adrg/xdg v0.5.3 // indirect
|
||||||
|
github.com/beorn7/perks v1.0.1 // indirect
|
||||||
|
github.com/bep/debounce v1.2.1 // indirect
|
||||||
|
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
||||||
|
github.com/cloudflare/circl v1.6.3 // indirect
|
||||||
|
github.com/coder/websocket v1.8.14 // indirect
|
||||||
|
github.com/cyphar/filepath-securejoin v0.6.1 // indirect
|
||||||
|
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
|
||||||
|
github.com/dolthub/maphash v0.1.0 // indirect
|
||||||
|
github.com/ebitengine/purego v0.9.1 // indirect
|
||||||
|
github.com/emirpasic/gods v1.18.1 // indirect
|
||||||
|
github.com/gammazero/deque v1.0.0 // indirect
|
||||||
|
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect
|
||||||
|
github.com/go-git/go-billy/v5 v5.7.0 // indirect
|
||||||
|
github.com/go-git/go-git/v5 v5.16.4 // indirect
|
||||||
|
github.com/go-ole/go-ole v1.3.0 // indirect
|
||||||
|
github.com/godbus/dbus/v5 v5.2.2 // indirect
|
||||||
|
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect
|
||||||
|
github.com/google/uuid v1.6.0 // indirect
|
||||||
|
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect
|
||||||
|
github.com/jchv/go-winloader v0.0.0-20250406163304-c1995be93bd1 // indirect
|
||||||
|
github.com/kevinburke/ssh_config v1.4.0 // indirect
|
||||||
|
github.com/klauspost/cpuid/v2 v2.3.0 // indirect
|
||||||
|
github.com/leaanthony/go-ansi-parser v1.6.1 // indirect
|
||||||
|
github.com/leaanthony/u v1.1.1 // indirect
|
||||||
|
github.com/lmittmann/tint v1.1.2 // indirect
|
||||||
|
github.com/mattn/go-colorable v0.1.14 // indirect
|
||||||
|
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||||
|
github.com/maypok86/otter v1.2.4 // indirect
|
||||||
|
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
|
||||||
|
github.com/pjbgf/sha1cd v0.5.0 // indirect
|
||||||
|
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect
|
||||||
|
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
|
||||||
|
github.com/prometheus/client_golang v1.21.1 // indirect
|
||||||
|
github.com/prometheus/client_model v0.6.1 // indirect
|
||||||
|
github.com/prometheus/common v0.63.0 // indirect
|
||||||
|
github.com/prometheus/procfs v0.16.0 // indirect
|
||||||
|
github.com/rivo/uniseg v0.4.7 // indirect
|
||||||
|
github.com/samber/lo v1.52.0 // indirect
|
||||||
|
github.com/sergi/go-diff v1.4.0 // indirect
|
||||||
|
github.com/skeema/knownhosts v1.3.2 // indirect
|
||||||
|
github.com/wailsapp/go-webview2 v1.0.23 // indirect
|
||||||
|
github.com/xanzy/ssh-agent v0.3.3 // indirect
|
||||||
|
go.uber.org/multierr v1.11.0 // indirect
|
||||||
|
go.uber.org/zap v1.27.0 // indirect
|
||||||
|
golang.org/x/crypto v0.47.0 // indirect
|
||||||
|
golang.org/x/net v0.49.0 // indirect
|
||||||
|
golang.org/x/sys v0.40.0 // indirect
|
||||||
|
golang.org/x/text v0.33.0 // indirect
|
||||||
|
google.golang.org/protobuf v1.36.10 // indirect
|
||||||
|
gopkg.in/warnings.v0 v0.1.2 // indirect
|
||||||
|
)
|
||||||
|
|
||||||
|
replace github.com/host-uk/core => ../..
|
||||||
185
cmd/core-app/go.sum
Normal file
185
cmd/core-app/go.sum
Normal file
|
|
@ -0,0 +1,185 @@
|
||||||
|
dario.cat/mergo v1.0.2 h1:85+piFYR1tMbRrLcDwR18y4UKJ3aH1Tbzi24VRW1TK8=
|
||||||
|
dario.cat/mergo v1.0.2/go.mod h1:E/hbnu0NxMFBjpMIE34DRGLWqDy0g5FuKDhCb31ngxA=
|
||||||
|
github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY=
|
||||||
|
github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
|
||||||
|
github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
|
||||||
|
github.com/ProtonMail/go-crypto v1.3.0 h1:ILq8+Sf5If5DCpHQp4PbZdS1J7HDFRXz/+xKBiRGFrw=
|
||||||
|
github.com/ProtonMail/go-crypto v1.3.0/go.mod h1:9whxjD8Rbs29b4XWbB8irEcE8KHMqaR2e7GWU1R+/PE=
|
||||||
|
github.com/adrg/xdg v0.5.3 h1:xRnxJXne7+oWDatRhR1JLnvuccuIeCoBu2rtuLqQB78=
|
||||||
|
github.com/adrg/xdg v0.5.3/go.mod h1:nlTsY+NNiCBGCK2tpm09vRqfVzrc2fLmXGpBLF0zlTQ=
|
||||||
|
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/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio=
|
||||||
|
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs=
|
||||||
|
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
|
||||||
|
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
|
||||||
|
github.com/bep/debounce v1.2.1 h1:v67fRdBA9UQu2NhLFXrSg0Brw7CexQekrBwDMM8bzeY=
|
||||||
|
github.com/bep/debounce v1.2.1/go.mod h1:H8yggRPQKLUhUoqrJC1bO2xNya7vanpDl7xR3ISbCJ0=
|
||||||
|
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
|
||||||
|
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||||
|
github.com/cloudflare/circl v1.6.3 h1:9GPOhQGF9MCYUeXyMYlqTR6a5gTrgR/fBLXvUgtVcg8=
|
||||||
|
github.com/cloudflare/circl v1.6.3/go.mod h1:2eXP6Qfat4O/Yhh8BznvKnJ+uzEoTQ6jVKJRn81BiS4=
|
||||||
|
github.com/coder/websocket v1.8.14 h1:9L0p0iKiNOibykf283eHkKUHHrpG7f65OE3BhhO7v9g=
|
||||||
|
github.com/coder/websocket v1.8.14/go.mod h1:NX3SzP+inril6yawo5CQXx8+fk145lPDC6pumgx0mVg=
|
||||||
|
github.com/cyphar/filepath-securejoin v0.6.1 h1:5CeZ1jPXEiYt3+Z6zqprSAgSWiggmpVyciv8syjIpVE=
|
||||||
|
github.com/cyphar/filepath-securejoin v0.6.1/go.mod h1:A8hd4EnAeyujCJRrICiOWqjS1AX0a9kM5XL+NwKoYSc=
|
||||||
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
|
||||||
|
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/dolthub/maphash v0.1.0 h1:bsQ7JsF4FkkWyrP3oCnFJgrCUAFbFf3kOl4L/QxPDyQ=
|
||||||
|
github.com/dolthub/maphash v0.1.0/go.mod h1:gkg4Ch4CdCDu5h6PMriVLawB7koZ+5ijb9puGMV50a4=
|
||||||
|
github.com/dunglas/frankenphp v1.5.0 h1:mrkJNe2gxlqYijGSpYIVbbRYxjYw2bmgAeDFqwREEk4=
|
||||||
|
github.com/dunglas/frankenphp v1.5.0/go.mod h1:tU9EirkVR0EuIr69IT1XBjSE6YfQY88tZlgkAvLPdOw=
|
||||||
|
github.com/ebitengine/purego v0.9.1 h1:a/k2f2HQU3Pi399RPW1MOaZyhKJL9w/xFpKAg4q1s0A=
|
||||||
|
github.com/ebitengine/purego v0.9.1/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ=
|
||||||
|
github.com/elazarl/goproxy v1.7.2 h1:Y2o6urb7Eule09PjlhQRGNsqRfPmYI3KKQLFpCAV3+o=
|
||||||
|
github.com/elazarl/goproxy v1.7.2/go.mod h1:82vkLNir0ALaW14Rc399OTTjyNREgmdL2cVoIbS6XaE=
|
||||||
|
github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc=
|
||||||
|
github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ=
|
||||||
|
github.com/gammazero/deque v1.0.0 h1:LTmimT8H7bXkkCy6gZX7zNLtkbz4NdS2z8LZuor3j34=
|
||||||
|
github.com/gammazero/deque v1.0.0/go.mod h1:iflpYvtGfM3U8S8j+sZEKIak3SAKYpA5/SQewgfXDKo=
|
||||||
|
github.com/gliderlabs/ssh v0.3.8 h1:a4YXD1V7xMF9g5nTkdfnja3Sxy1PVDCj1Zg4Wb8vY6c=
|
||||||
|
github.com/gliderlabs/ssh v0.3.8/go.mod h1:xYoytBv1sV0aL3CavoDuJIQNURXkkfPA/wxQ1pL1fAU=
|
||||||
|
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI=
|
||||||
|
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376/go.mod h1:an3vInlBmSxCcxctByoQdvwPiA7DTK7jaaFDBTtu0ic=
|
||||||
|
github.com/go-git/go-billy/v5 v5.7.0 h1:83lBUJhGWhYp0ngzCMSgllhUSuoHP1iEWYjsPl9nwqM=
|
||||||
|
github.com/go-git/go-billy/v5 v5.7.0/go.mod h1:/1IUejTKH8xipsAcdfcSAlUlo2J7lkYV8GTKxAT/L3E=
|
||||||
|
github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399 h1:eMje31YglSBqCdIqdhKBW8lokaMrL3uTkpGYlE2OOT4=
|
||||||
|
github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399/go.mod h1:1OCfN199q1Jm3HZlxleg+Dw/mwps2Wbk9frAWm+4FII=
|
||||||
|
github.com/go-git/go-git/v5 v5.16.4 h1:7ajIEZHZJULcyJebDLo99bGgS0jRrOxzZG4uCk2Yb2Y=
|
||||||
|
github.com/go-git/go-git/v5 v5.16.4/go.mod h1:4Ge4alE/5gPs30F2H1esi2gPd69R0C39lolkucHBOp8=
|
||||||
|
github.com/go-json-experiment/json v0.0.0-20251027170946-4849db3c2f7e h1:Lf/gRkoycfOBPa42vU2bbgPurFong6zXeFtPoxholzU=
|
||||||
|
github.com/go-json-experiment/json v0.0.0-20251027170946-4849db3c2f7e/go.mod h1:uNVvRXArCGbZ508SxYYTC5v1JWoz2voff5pm25jU1Ok=
|
||||||
|
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/godbus/dbus/v5 v5.2.2 h1:TUR3TgtSVDmjiXOgAAyaZbYmIeP3DPkld3jgKGV8mXQ=
|
||||||
|
github.com/godbus/dbus/v5 v5.2.2/go.mod h1:3AAv2+hPq5rdnr5txxxRwiGjPXamgoIHgz9FPBfOp3c=
|
||||||
|
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 h1:f+oWsMOmNPc8JmEHVZIycC7hBoQxHH9pNKQORJNozsQ=
|
||||||
|
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8/go.mod h1:wcDNUvekVysuuOpQKo3191zZyTpiI6se1N1ULghS0sw=
|
||||||
|
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
||||||
|
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
||||||
|
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/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A=
|
||||||
|
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo=
|
||||||
|
github.com/jchv/go-winloader v0.0.0-20250406163304-c1995be93bd1 h1:njuLRcjAuMKr7kI3D85AXWkw6/+v9PwtV6M6o11sWHQ=
|
||||||
|
github.com/jchv/go-winloader v0.0.0-20250406163304-c1995be93bd1/go.mod h1:alcuEEnZsY1WQsagKhZDsoPCRoOijYqhZvPwLG0kzVs=
|
||||||
|
github.com/kevinburke/ssh_config v1.4.0 h1:6xxtP5bZ2E4NF5tuQulISpTO2z8XbtH8cg1PWkxoFkQ=
|
||||||
|
github.com/kevinburke/ssh_config v1.4.0/go.mod h1:q2RIzfka+BXARoNexmF9gkxEX7DmvbW9P4hIVx2Kg4M=
|
||||||
|
github.com/klauspost/compress v1.18.3 h1:9PJRvfbmTabkOX8moIpXPbMMbYN60bWImDDU7L+/6zw=
|
||||||
|
github.com/klauspost/compress v1.18.3/go.mod h1:R0h/fSBs8DE4ENlcrlib3PsXS61voFxhIs2DeRhCvJ4=
|
||||||
|
github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y=
|
||||||
|
github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
|
||||||
|
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||||
|
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||||
|
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||||
|
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||||
|
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||||
|
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||||
|
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||||
|
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
|
||||||
|
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
|
||||||
|
github.com/leaanthony/go-ansi-parser v1.6.1 h1:xd8bzARK3dErqkPFtoF9F3/HgN8UQk0ed1YDKpEz01A=
|
||||||
|
github.com/leaanthony/go-ansi-parser v1.6.1/go.mod h1:+vva/2y4alzVmmIEpk9QDhA7vLC5zKDTRwfZGOp3IWU=
|
||||||
|
github.com/leaanthony/u v1.1.1 h1:TUFjwDGlNX+WuwVEzDqQwC2lOv0P4uhTQw7CMFdiK7M=
|
||||||
|
github.com/leaanthony/u v1.1.1/go.mod h1:9+o6hejoRljvZ3BzdYlVL0JYCwtnAsVuN9pVTQcaRfI=
|
||||||
|
github.com/lmittmann/tint v1.1.2 h1:2CQzrL6rslrsyjqLDwD11bZ5OpLBPU+g3G/r5LSfS8w=
|
||||||
|
github.com/lmittmann/tint v1.1.2/go.mod h1:HIS3gSy7qNwGCj+5oRjAutErFBl4BzdQP6cJZ0NfMwE=
|
||||||
|
github.com/matryer/is v1.4.0/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwMRU=
|
||||||
|
github.com/matryer/is v1.4.1 h1:55ehd8zaGABKLXQUe2awZ99BD/PTc2ls+KV/dXphgEQ=
|
||||||
|
github.com/matryer/is v1.4.1/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwMRU=
|
||||||
|
github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE=
|
||||||
|
github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=
|
||||||
|
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||||
|
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||||
|
github.com/maypok86/otter v1.2.4 h1:HhW1Pq6VdJkmWwcZZq19BlEQkHtI8xgsQzBVXJU0nfc=
|
||||||
|
github.com/maypok86/otter v1.2.4/go.mod h1:mKLfoI7v1HOmQMwFgX4QkRk23mX6ge3RDvjdHOWG4R4=
|
||||||
|
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/onsi/gomega v1.34.1 h1:EUMJIKUjM8sKjYbtxQI9A4z2o+rruxnzNvpknOXie6k=
|
||||||
|
github.com/onsi/gomega v1.34.1/go.mod h1:kU1QgUvBDLXBJq618Xvm2LUX6rSAfRaFRTcdOeDLwwY=
|
||||||
|
github.com/pjbgf/sha1cd v0.5.0 h1:a+UkboSi1znleCDUNT3M5YxjOnN1fz2FhN48FlwCxs0=
|
||||||
|
github.com/pjbgf/sha1cd v0.5.0/go.mod h1:lhpGlyHLpQZoxMv8HcgXvZEhcGs0PG/vsZnEJ7H0iCM=
|
||||||
|
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ=
|
||||||
|
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU=
|
||||||
|
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||||
|
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
|
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
|
||||||
|
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
|
github.com/prometheus/client_golang v1.21.1 h1:DOvXXTqVzvkIewV/CDPFdejpMCGeMcbGCQ8YOmu+Ibk=
|
||||||
|
github.com/prometheus/client_golang v1.21.1/go.mod h1:U9NM32ykUErtVBxdvD3zfi+EuFkkaBvMb09mIfe0Zgg=
|
||||||
|
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.63.0 h1:YR/EIY1o3mEFP/kZCD7iDMnLPlGyuU2Gb3HIcXnA98k=
|
||||||
|
github.com/prometheus/common v0.63.0/go.mod h1:VVFF/fBIoToEnWRVkYoXEkq3R3paCoxG9PXP74SnV18=
|
||||||
|
github.com/prometheus/procfs v0.16.0 h1:xh6oHhKwnOJKMYiYBDWmkHqQPyiY40sny36Cmx2bbsM=
|
||||||
|
github.com/prometheus/procfs v0.16.0/go.mod h1:8veyXUu3nGP7oaCxhX6yeaM5u4stL2FeMXnCqhDthZg=
|
||||||
|
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||||
|
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
|
||||||
|
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
|
||||||
|
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
|
||||||
|
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
|
||||||
|
github.com/samber/lo v1.52.0 h1:Rvi+3BFHES3A8meP33VPAxiBZX/Aws5RxrschYGjomw=
|
||||||
|
github.com/samber/lo v1.52.0/go.mod h1:4+MXEGsJzbKGaUEQFKBq2xtfuznW9oz/WrgyzMzRoM0=
|
||||||
|
github.com/sergi/go-diff v1.4.0 h1:n/SP9D5ad1fORl+llWyN+D6qoUETXNZARKjyY2/KVCw=
|
||||||
|
github.com/sergi/go-diff v1.4.0/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4=
|
||||||
|
github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
|
||||||
|
github.com/skeema/knownhosts v1.3.2 h1:EDL9mgf4NzwMXCTfaxSD/o/a5fxDw/xL9nkU28JjdBg=
|
||||||
|
github.com/skeema/knownhosts v1.3.2/go.mod h1:bEg3iQAuw+jyiw+484wwFJoKSLwcfd7fqRy+N0QTiow=
|
||||||
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
|
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||||
|
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||||
|
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
|
||||||
|
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
||||||
|
github.com/wailsapp/go-webview2 v1.0.23 h1:jmv8qhz1lHibCc79bMM/a/FqOnnzOGEisLav+a0b9P0=
|
||||||
|
github.com/wailsapp/go-webview2 v1.0.23/go.mod h1:qJmWAmAmaniuKGZPWwne+uor3AHMB5PFhqiK0Bbj8kc=
|
||||||
|
github.com/wailsapp/wails/v3 v3.0.0-alpha.64 h1:xAhLFVfdbg7XdZQ5mMQmBv2BglWu8hMqe50Z+3UJvBs=
|
||||||
|
github.com/wailsapp/wails/v3 v3.0.0-alpha.64/go.mod h1:zvgNL/mlFcX8aRGu6KOz9AHrMmTBD+4hJRQIONqF/Yw=
|
||||||
|
github.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM=
|
||||||
|
github.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw=
|
||||||
|
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
|
||||||
|
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
|
||||||
|
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
|
||||||
|
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
|
||||||
|
go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8=
|
||||||
|
go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
|
||||||
|
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||||
|
golang.org/x/crypto v0.47.0 h1:V6e3FRj+n4dbpw86FJ8Fv7XVOql7TEwpHapKoMJ/GO8=
|
||||||
|
golang.org/x/crypto v0.47.0/go.mod h1:ff3Y9VzzKbwSSEzWqJsJVBnWmRwRSHt/6Op5n9bQc4A=
|
||||||
|
golang.org/x/exp v0.0.0-20260112195511-716be5621a96 h1:Z/6YuSHTLOHfNFdb8zVZomZr7cqNgTJvA8+Qz75D8gU=
|
||||||
|
golang.org/x/exp v0.0.0-20260112195511-716be5621a96/go.mod h1:nzimsREAkjBCIEFtHiYkrJyT+2uy9YZJB7H1k68CXZU=
|
||||||
|
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||||
|
golang.org/x/net v0.49.0 h1:eeHFmOGUTtaaPSGNmjBKpbng9MulQsJURQUAfUwY++o=
|
||||||
|
golang.org/x/net v0.49.0/go.mod h1:/ysNB2EvaqvesRkuLAyjI1ycPZlQHM3q01F02UY/MV8=
|
||||||
|
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20200810151505-1b9f1253b3ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/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.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ=
|
||||||
|
golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||||
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
|
golang.org/x/term v0.39.0 h1:RclSuaJf32jOqZz74CkPA9qFuVTX7vhLlpfj/IGWlqY=
|
||||||
|
golang.org/x/term v0.39.0/go.mod h1:yxzUCTP/U+FzoxfdKmLaA0RV1WgE0VY7hXBwKtY/4ww=
|
||||||
|
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
|
golang.org/x/text v0.33.0 h1:B3njUFyqtHDUI5jMn1YIr5B0IE2U0qck04r6d4KPAxE=
|
||||||
|
golang.org/x/text v0.33.0/go.mod h1:LuMebE6+rBincTi9+xWTY8TztLzKHc/9C1uBCG27+q8=
|
||||||
|
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
|
google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE=
|
||||||
|
google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
|
||||||
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||||
|
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||||
|
gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME=
|
||||||
|
gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI=
|
||||||
|
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
|
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=
|
||||||
137
cmd/core-app/handler.go
Normal file
137
cmd/core-app/handler.go
Normal file
|
|
@ -0,0 +1,137 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/dunglas/frankenphp"
|
||||||
|
)
|
||||||
|
|
||||||
|
// PHPHandler implements http.Handler by delegating to FrankenPHP.
|
||||||
|
// It resolves URLs to files (like Caddy's try_files) before passing
|
||||||
|
// requests to the PHP runtime.
|
||||||
|
type PHPHandler struct {
|
||||||
|
docRoot string
|
||||||
|
laravelRoot string
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewPHPHandler extracts the embedded Laravel app, prepares the environment,
|
||||||
|
// initialises FrankenPHP with worker mode, and returns the handler.
|
||||||
|
func NewPHPHandler() (*PHPHandler, *AppEnvironment, func(), error) {
|
||||||
|
// Extract embedded Laravel to temp directory
|
||||||
|
laravelRoot, err := extractLaravel()
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, nil, fmt.Errorf("extract Laravel: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prepare persistent environment
|
||||||
|
env, err := PrepareEnvironment(laravelRoot)
|
||||||
|
if err != nil {
|
||||||
|
os.RemoveAll(laravelRoot)
|
||||||
|
return nil, nil, nil, fmt.Errorf("prepare environment: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
docRoot := filepath.Join(laravelRoot, "public")
|
||||||
|
|
||||||
|
log.Printf("Laravel root: %s", laravelRoot)
|
||||||
|
log.Printf("Document root: %s", docRoot)
|
||||||
|
log.Printf("Data directory: %s", env.DataDir)
|
||||||
|
log.Printf("Database: %s", env.DatabasePath)
|
||||||
|
|
||||||
|
// Try Octane worker mode first, fall back to standard mode.
|
||||||
|
// Worker mode keeps Laravel booted in memory — sub-ms response times.
|
||||||
|
workerScript := filepath.Join(laravelRoot, "vendor", "laravel", "octane", "bin", "frankenphp-worker.php")
|
||||||
|
workerEnv := map[string]string{
|
||||||
|
"APP_BASE_PATH": laravelRoot,
|
||||||
|
"FRANKENPHP_WORKER": "1",
|
||||||
|
}
|
||||||
|
|
||||||
|
workerMode := false
|
||||||
|
if _, err := os.Stat(workerScript); err == nil {
|
||||||
|
if err := frankenphp.Init(
|
||||||
|
frankenphp.WithNumThreads(4),
|
||||||
|
frankenphp.WithWorkers("laravel", workerScript, 2, workerEnv, nil),
|
||||||
|
frankenphp.WithPhpIni(map[string]string{
|
||||||
|
"display_errors": "Off",
|
||||||
|
"opcache.enable": "1",
|
||||||
|
}),
|
||||||
|
); err != nil {
|
||||||
|
log.Printf("Worker mode init failed (%v), falling back to standard mode", err)
|
||||||
|
} else {
|
||||||
|
workerMode = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !workerMode {
|
||||||
|
if err := frankenphp.Init(
|
||||||
|
frankenphp.WithNumThreads(4),
|
||||||
|
frankenphp.WithPhpIni(map[string]string{
|
||||||
|
"display_errors": "Off",
|
||||||
|
"opcache.enable": "1",
|
||||||
|
}),
|
||||||
|
); err != nil {
|
||||||
|
os.RemoveAll(laravelRoot)
|
||||||
|
return nil, nil, nil, fmt.Errorf("init FrankenPHP: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if workerMode {
|
||||||
|
log.Println("FrankenPHP initialised (Octane worker mode, 2 workers)")
|
||||||
|
} else {
|
||||||
|
log.Println("FrankenPHP initialised (standard mode, 4 threads)")
|
||||||
|
}
|
||||||
|
|
||||||
|
cleanup := func() {
|
||||||
|
frankenphp.Shutdown()
|
||||||
|
os.RemoveAll(laravelRoot)
|
||||||
|
}
|
||||||
|
|
||||||
|
handler := &PHPHandler{
|
||||||
|
docRoot: docRoot,
|
||||||
|
laravelRoot: laravelRoot,
|
||||||
|
}
|
||||||
|
|
||||||
|
return handler, env, cleanup, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *PHPHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||||
|
urlPath := r.URL.Path
|
||||||
|
filePath := filepath.Join(h.docRoot, filepath.Clean(urlPath))
|
||||||
|
|
||||||
|
info, err := os.Stat(filePath)
|
||||||
|
if err == nil && info.IsDir() {
|
||||||
|
// Directory → try index.php inside it
|
||||||
|
urlPath = strings.TrimRight(urlPath, "/") + "/index.php"
|
||||||
|
} else if err != nil && !strings.HasSuffix(urlPath, ".php") {
|
||||||
|
// File not found and not a .php request → front controller
|
||||||
|
urlPath = "/index.php"
|
||||||
|
}
|
||||||
|
|
||||||
|
// Serve static assets directly (CSS, JS, images)
|
||||||
|
if !strings.HasSuffix(urlPath, ".php") {
|
||||||
|
staticPath := filepath.Join(h.docRoot, filepath.Clean(urlPath))
|
||||||
|
if info, err := os.Stat(staticPath); err == nil && !info.IsDir() {
|
||||||
|
http.ServeFile(w, r, staticPath)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Route to FrankenPHP
|
||||||
|
r.URL.Path = urlPath
|
||||||
|
|
||||||
|
req, err := frankenphp.NewRequestWithContext(r,
|
||||||
|
frankenphp.WithRequestDocumentRoot(h.docRoot, false),
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, fmt.Sprintf("FrankenPHP request error: %v", err), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := frankenphp.ServeHTTP(w, req); err != nil {
|
||||||
|
http.Error(w, fmt.Sprintf("FrankenPHP serve error: %v", err), http.StatusInternalServerError)
|
||||||
|
}
|
||||||
|
}
|
||||||
BIN
cmd/core-app/icons/appicon.png
Normal file
BIN
cmd/core-app/icons/appicon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 76 B |
24
cmd/core-app/icons/icons.go
Normal file
24
cmd/core-app/icons/icons.go
Normal file
|
|
@ -0,0 +1,24 @@
|
||||||
|
// Package icons provides embedded icon assets for the Core App.
|
||||||
|
package icons
|
||||||
|
|
||||||
|
import _ "embed"
|
||||||
|
|
||||||
|
// TrayTemplate is the template icon for macOS systray (22x22 PNG, black on transparent).
|
||||||
|
//
|
||||||
|
//go:embed tray-template.png
|
||||||
|
var TrayTemplate []byte
|
||||||
|
|
||||||
|
// TrayLight is the light mode icon for Windows/Linux systray.
|
||||||
|
//
|
||||||
|
//go:embed tray-light.png
|
||||||
|
var TrayLight []byte
|
||||||
|
|
||||||
|
// TrayDark is the dark mode icon for Windows/Linux systray.
|
||||||
|
//
|
||||||
|
//go:embed tray-dark.png
|
||||||
|
var TrayDark []byte
|
||||||
|
|
||||||
|
// AppIcon is the main application icon.
|
||||||
|
//
|
||||||
|
//go:embed appicon.png
|
||||||
|
var AppIcon []byte
|
||||||
BIN
cmd/core-app/icons/tray-dark.png
Normal file
BIN
cmd/core-app/icons/tray-dark.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 76 B |
BIN
cmd/core-app/icons/tray-light.png
Normal file
BIN
cmd/core-app/icons/tray-light.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 76 B |
BIN
cmd/core-app/icons/tray-template.png
Normal file
BIN
cmd/core-app/icons/tray-template.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 76 B |
13
cmd/core-app/laravel/.env.example
Normal file
13
cmd/core-app/laravel/.env.example
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
APP_NAME="Core App"
|
||||||
|
APP_ENV=production
|
||||||
|
APP_KEY=
|
||||||
|
APP_DEBUG=false
|
||||||
|
APP_URL=http://localhost
|
||||||
|
|
||||||
|
DB_CONNECTION=sqlite
|
||||||
|
DB_DATABASE=/tmp/core-app/database.sqlite
|
||||||
|
|
||||||
|
CACHE_STORE=file
|
||||||
|
SESSION_DRIVER=file
|
||||||
|
LOG_CHANNEL=single
|
||||||
|
LOG_LEVEL=warning
|
||||||
5
cmd/core-app/laravel/.gitignore
vendored
Normal file
5
cmd/core-app/laravel/.gitignore
vendored
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
/vendor/
|
||||||
|
/node_modules/
|
||||||
|
/.env
|
||||||
|
/bootstrap/cache/*.php
|
||||||
|
/storage/*.key
|
||||||
27
cmd/core-app/laravel/app/Livewire/Counter.php
Normal file
27
cmd/core-app/laravel/app/Livewire/Counter.php
Normal file
|
|
@ -0,0 +1,27 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Livewire;
|
||||||
|
|
||||||
|
use Livewire\Component;
|
||||||
|
|
||||||
|
class Counter extends Component
|
||||||
|
{
|
||||||
|
public int $count = 0;
|
||||||
|
|
||||||
|
public function increment(): void
|
||||||
|
{
|
||||||
|
$this->count++;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function decrement(): void
|
||||||
|
{
|
||||||
|
$this->count--;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function render()
|
||||||
|
{
|
||||||
|
return view('livewire.counter');
|
||||||
|
}
|
||||||
|
}
|
||||||
21
cmd/core-app/laravel/artisan
Normal file
21
cmd/core-app/laravel/artisan
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
#!/usr/bin/env php
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
define('LARAVEL_START', microtime(true));
|
||||||
|
|
||||||
|
require __DIR__.'/vendor/autoload.php';
|
||||||
|
|
||||||
|
$app = require_once __DIR__.'/bootstrap/app.php';
|
||||||
|
|
||||||
|
$kernel = $app->make(Illuminate\Contracts\Console\Kernel::class);
|
||||||
|
|
||||||
|
$status = $kernel->handle(
|
||||||
|
$input = new Symfony\Component\Console\Input\ArgvInput,
|
||||||
|
new Symfony\Component\Console\Output\ConsoleOutput
|
||||||
|
);
|
||||||
|
|
||||||
|
$kernel->terminate($input, $status);
|
||||||
|
|
||||||
|
exit($status);
|
||||||
19
cmd/core-app/laravel/bootstrap/app.php
Normal file
19
cmd/core-app/laravel/bootstrap/app.php
Normal file
|
|
@ -0,0 +1,19 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
use Illuminate\Foundation\Application;
|
||||||
|
use Illuminate\Foundation\Configuration\Exceptions;
|
||||||
|
use Illuminate\Foundation\Configuration\Middleware;
|
||||||
|
|
||||||
|
return Application::configure(basePath: dirname(__DIR__))
|
||||||
|
->withRouting(
|
||||||
|
web: __DIR__.'/../routes/web.php',
|
||||||
|
)
|
||||||
|
->withMiddleware(function (Middleware $middleware) {
|
||||||
|
//
|
||||||
|
})
|
||||||
|
->withExceptions(function (Exceptions $exceptions) {
|
||||||
|
//
|
||||||
|
})
|
||||||
|
->create();
|
||||||
29
cmd/core-app/laravel/composer.json
Normal file
29
cmd/core-app/laravel/composer.json
Normal file
|
|
@ -0,0 +1,29 @@
|
||||||
|
{
|
||||||
|
"name": "host-uk/core-app",
|
||||||
|
"description": "Embedded Laravel application for Core App desktop",
|
||||||
|
"license": "EUPL-1.2",
|
||||||
|
"type": "project",
|
||||||
|
"require": {
|
||||||
|
"php": "^8.4",
|
||||||
|
"laravel/framework": "^12.0",
|
||||||
|
"laravel/octane": "^2.0",
|
||||||
|
"livewire/livewire": "^4.0"
|
||||||
|
},
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"App\\": "app/"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"optimize-autoloader": true,
|
||||||
|
"preferred-install": "dist",
|
||||||
|
"sort-packages": true
|
||||||
|
},
|
||||||
|
"minimum-stability": "stable",
|
||||||
|
"prefer-stable": true,
|
||||||
|
"scripts": {
|
||||||
|
"post-autoload-dump": [
|
||||||
|
"@php artisan package:discover --ansi"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
6149
cmd/core-app/laravel/composer.lock
generated
Normal file
6149
cmd/core-app/laravel/composer.lock
generated
Normal file
File diff suppressed because it is too large
Load diff
19
cmd/core-app/laravel/config/app.php
Normal file
19
cmd/core-app/laravel/config/app.php
Normal file
|
|
@ -0,0 +1,19 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
return [
|
||||||
|
'name' => env('APP_NAME', 'Core App'),
|
||||||
|
'env' => env('APP_ENV', 'production'),
|
||||||
|
'debug' => (bool) env('APP_DEBUG', false),
|
||||||
|
'url' => env('APP_URL', 'http://localhost'),
|
||||||
|
'timezone' => 'UTC',
|
||||||
|
'locale' => 'en',
|
||||||
|
'fallback_locale' => 'en',
|
||||||
|
'faker_locale' => 'en_GB',
|
||||||
|
'cipher' => 'AES-256-CBC',
|
||||||
|
'key' => env('APP_KEY'),
|
||||||
|
'maintenance' => [
|
||||||
|
'driver' => 'file',
|
||||||
|
],
|
||||||
|
];
|
||||||
21
cmd/core-app/laravel/config/cache.php
Normal file
21
cmd/core-app/laravel/config/cache.php
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
return [
|
||||||
|
'default' => env('CACHE_STORE', 'file'),
|
||||||
|
|
||||||
|
'stores' => [
|
||||||
|
'file' => [
|
||||||
|
'driver' => 'file',
|
||||||
|
'path' => storage_path('framework/cache/data'),
|
||||||
|
'lock_path' => storage_path('framework/cache/data'),
|
||||||
|
],
|
||||||
|
'array' => [
|
||||||
|
'driver' => 'array',
|
||||||
|
'serialize' => false,
|
||||||
|
],
|
||||||
|
],
|
||||||
|
|
||||||
|
'prefix' => env('CACHE_PREFIX', 'core_app_cache_'),
|
||||||
|
];
|
||||||
25
cmd/core-app/laravel/config/database.php
Normal file
25
cmd/core-app/laravel/config/database.php
Normal file
|
|
@ -0,0 +1,25 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
return [
|
||||||
|
'default' => 'sqlite',
|
||||||
|
|
||||||
|
'connections' => [
|
||||||
|
'sqlite' => [
|
||||||
|
'driver' => 'sqlite',
|
||||||
|
'url' => env('DB_URL'),
|
||||||
|
'database' => env('DB_DATABASE', database_path('database.sqlite')),
|
||||||
|
'prefix' => '',
|
||||||
|
'foreign_key_constraints' => true,
|
||||||
|
'busy_timeout' => 5000,
|
||||||
|
'journal_mode' => 'wal',
|
||||||
|
'synchronous' => 'normal',
|
||||||
|
],
|
||||||
|
],
|
||||||
|
|
||||||
|
'migrations' => [
|
||||||
|
'table' => 'migrations',
|
||||||
|
'update_date_on_publish' => true,
|
||||||
|
],
|
||||||
|
];
|
||||||
25
cmd/core-app/laravel/config/logging.php
Normal file
25
cmd/core-app/laravel/config/logging.php
Normal file
|
|
@ -0,0 +1,25 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
return [
|
||||||
|
'default' => env('LOG_CHANNEL', 'single'),
|
||||||
|
|
||||||
|
'channels' => [
|
||||||
|
'single' => [
|
||||||
|
'driver' => 'single',
|
||||||
|
'path' => storage_path('logs/laravel.log'),
|
||||||
|
'level' => env('LOG_LEVEL', 'warning'),
|
||||||
|
'replace_placeholders' => true,
|
||||||
|
],
|
||||||
|
'stderr' => [
|
||||||
|
'driver' => 'monolog',
|
||||||
|
'level' => env('LOG_LEVEL', 'debug'),
|
||||||
|
'handler' => Monolog\Handler\StreamHandler::class,
|
||||||
|
'with' => [
|
||||||
|
'stream' => 'php://stderr',
|
||||||
|
],
|
||||||
|
'processors' => [Monolog\Processor\PsrLogMessageProcessor::class],
|
||||||
|
],
|
||||||
|
],
|
||||||
|
];
|
||||||
22
cmd/core-app/laravel/config/session.php
Normal file
22
cmd/core-app/laravel/config/session.php
Normal file
|
|
@ -0,0 +1,22 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
return [
|
||||||
|
'driver' => env('SESSION_DRIVER', 'file'),
|
||||||
|
'lifetime' => env('SESSION_LIFETIME', 120),
|
||||||
|
'expire_on_close' => true,
|
||||||
|
'encrypt' => false,
|
||||||
|
'files' => storage_path('framework/sessions'),
|
||||||
|
'connection' => env('SESSION_CONNECTION'),
|
||||||
|
'table' => 'sessions',
|
||||||
|
'store' => env('SESSION_STORE'),
|
||||||
|
'lottery' => [2, 100],
|
||||||
|
'cookie' => env('SESSION_COOKIE', 'core_app_session'),
|
||||||
|
'path' => '/',
|
||||||
|
'domain' => null,
|
||||||
|
'secure' => false,
|
||||||
|
'http_only' => true,
|
||||||
|
'same_site' => 'lax',
|
||||||
|
'partitioned' => false,
|
||||||
|
];
|
||||||
10
cmd/core-app/laravel/config/view.php
Normal file
10
cmd/core-app/laravel/config/view.php
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
return [
|
||||||
|
'paths' => [
|
||||||
|
resource_path('views'),
|
||||||
|
],
|
||||||
|
'compiled' => env('VIEW_COMPILED_PATH', realpath(storage_path('framework/views'))),
|
||||||
|
];
|
||||||
19
cmd/core-app/laravel/public/index.php
Normal file
19
cmd/core-app/laravel/public/index.php
Normal file
|
|
@ -0,0 +1,19 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
|
||||||
|
define('LARAVEL_START', microtime(true));
|
||||||
|
|
||||||
|
// Determine if the application is in maintenance mode...
|
||||||
|
if (file_exists($maintenance = __DIR__.'/../storage/framework/maintenance.php')) {
|
||||||
|
require $maintenance;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Register the Composer autoloader...
|
||||||
|
require __DIR__.'/../vendor/autoload.php';
|
||||||
|
|
||||||
|
// Bootstrap Laravel and handle the request...
|
||||||
|
(require_once __DIR__.'/../bootstrap/app.php')
|
||||||
|
->handleRequest(Request::capture());
|
||||||
107
cmd/core-app/laravel/resources/views/components/layout.blade.php
Normal file
107
cmd/core-app/laravel/resources/views/components/layout.blade.php
Normal file
|
|
@ -0,0 +1,107 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Core App</title>
|
||||||
|
<style>
|
||||||
|
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||||
|
body {
|
||||||
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
|
||||||
|
background: #0d1117;
|
||||||
|
color: #e6edf3;
|
||||||
|
min-height: 100vh;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 32px;
|
||||||
|
padding: 32px;
|
||||||
|
}
|
||||||
|
.card {
|
||||||
|
background: #161b22;
|
||||||
|
border: 1px solid #30363d;
|
||||||
|
border-radius: 12px;
|
||||||
|
padding: 48px;
|
||||||
|
text-align: center;
|
||||||
|
max-width: 600px;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
h1 { font-size: 32px; margin-bottom: 8px; }
|
||||||
|
h2 { font-size: 20px; margin-bottom: 16px; color: #8b949e; font-weight: 400; }
|
||||||
|
.accent { color: #39d0d8; }
|
||||||
|
.subtitle { color: #8b949e; font-size: 16px; margin-bottom: 24px; }
|
||||||
|
.info-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr 1fr;
|
||||||
|
gap: 12px;
|
||||||
|
margin-top: 24px;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
.info-item {
|
||||||
|
background: #21262d;
|
||||||
|
border: 1px solid #30363d;
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 12px 16px;
|
||||||
|
}
|
||||||
|
.info-item__label { font-size: 11px; color: #8b949e; text-transform: uppercase; letter-spacing: 0.5px; }
|
||||||
|
.info-item__value { font-size: 14px; margin-top: 4px; font-family: monospace; }
|
||||||
|
.badge {
|
||||||
|
display: inline-block;
|
||||||
|
background: #238636;
|
||||||
|
color: #fff;
|
||||||
|
border-radius: 12px;
|
||||||
|
padding: 4px 12px;
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 600;
|
||||||
|
margin-top: 20px;
|
||||||
|
}
|
||||||
|
.counter { text-align: center; }
|
||||||
|
.counter__display {
|
||||||
|
font-size: 72px;
|
||||||
|
font-weight: 700;
|
||||||
|
font-variant-numeric: tabular-nums;
|
||||||
|
color: #39d0d8;
|
||||||
|
line-height: 1;
|
||||||
|
margin-bottom: 24px;
|
||||||
|
}
|
||||||
|
.counter__controls {
|
||||||
|
display: flex;
|
||||||
|
gap: 16px;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
.counter__hint {
|
||||||
|
margin-top: 16px;
|
||||||
|
font-size: 12px;
|
||||||
|
color: #8b949e;
|
||||||
|
}
|
||||||
|
.btn {
|
||||||
|
appearance: none;
|
||||||
|
border: 1px solid #30363d;
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 12px 32px;
|
||||||
|
font-size: 20px;
|
||||||
|
font-weight: 600;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.15s;
|
||||||
|
}
|
||||||
|
.btn:active { transform: scale(0.96); }
|
||||||
|
.btn--primary {
|
||||||
|
background: #238636;
|
||||||
|
color: #fff;
|
||||||
|
border-color: #2ea043;
|
||||||
|
}
|
||||||
|
.btn--primary:hover { background: #2ea043; }
|
||||||
|
.btn--secondary {
|
||||||
|
background: #21262d;
|
||||||
|
color: #e6edf3;
|
||||||
|
}
|
||||||
|
.btn--secondary:hover { background: #30363d; }
|
||||||
|
</style>
|
||||||
|
@livewireStyles
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
{{ $slot }}
|
||||||
|
@livewireScripts
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
|
@ -0,0 +1,8 @@
|
||||||
|
<div class="counter">
|
||||||
|
<div class="counter__display">{{ $count }}</div>
|
||||||
|
<div class="counter__controls">
|
||||||
|
<button wire:click="decrement" class="btn btn--secondary">−</button>
|
||||||
|
<button wire:click="increment" class="btn btn--primary">+</button>
|
||||||
|
</div>
|
||||||
|
<p class="counter__hint">Livewire {{ \Livewire\Livewire::VERSION }} · Server-rendered, no page reload</p>
|
||||||
|
</div>
|
||||||
40
cmd/core-app/laravel/resources/views/welcome.blade.php
Normal file
40
cmd/core-app/laravel/resources/views/welcome.blade.php
Normal file
|
|
@ -0,0 +1,40 @@
|
||||||
|
<x-layout>
|
||||||
|
<div class="card">
|
||||||
|
<h1><span class="accent">Core App</span></h1>
|
||||||
|
<p class="subtitle">Laravel {{ app()->version() }} running inside a native desktop window</p>
|
||||||
|
|
||||||
|
<div class="info-grid">
|
||||||
|
<div class="info-item">
|
||||||
|
<div class="info-item__label">PHP</div>
|
||||||
|
<div class="info-item__value">{{ PHP_VERSION }}</div>
|
||||||
|
</div>
|
||||||
|
<div class="info-item">
|
||||||
|
<div class="info-item__label">Thread Safety</div>
|
||||||
|
<div class="info-item__value">{{ PHP_ZTS ? 'ZTS (Yes)' : 'NTS (No)' }}</div>
|
||||||
|
</div>
|
||||||
|
<div class="info-item">
|
||||||
|
<div class="info-item__label">SAPI</div>
|
||||||
|
<div class="info-item__value">{{ php_sapi_name() }}</div>
|
||||||
|
</div>
|
||||||
|
<div class="info-item">
|
||||||
|
<div class="info-item__label">Platform</div>
|
||||||
|
<div class="info-item__value">{{ PHP_OS }} {{ php_uname('m') }}</div>
|
||||||
|
</div>
|
||||||
|
<div class="info-item">
|
||||||
|
<div class="info-item__label">Database</div>
|
||||||
|
<div class="info-item__value">SQLite {{ \SQLite3::version()['versionString'] }}</div>
|
||||||
|
</div>
|
||||||
|
<div class="info-item">
|
||||||
|
<div class="info-item__label">Mode</div>
|
||||||
|
<div class="info-item__value">{{ env('FRANKENPHP_WORKER') ? 'Octane Worker' : 'Standard' }}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="badge">Single Binary · No Server · No Config</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card">
|
||||||
|
<h2>Livewire Reactivity Test</h2>
|
||||||
|
<livewire:counter />
|
||||||
|
</div>
|
||||||
|
</x-layout>
|
||||||
9
cmd/core-app/laravel/routes/web.php
Normal file
9
cmd/core-app/laravel/routes/web.php
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
use Illuminate\Support\Facades\Route;
|
||||||
|
|
||||||
|
Route::get('/', function () {
|
||||||
|
return view('welcome');
|
||||||
|
});
|
||||||
102
cmd/core-app/main.go
Normal file
102
cmd/core-app/main.go
Normal file
|
|
@ -0,0 +1,102 @@
|
||||||
|
// Package main provides the Core App — a native desktop application
|
||||||
|
// embedding Laravel via FrankenPHP inside a Wails v3 window.
|
||||||
|
//
|
||||||
|
// A single Go binary that boots the PHP runtime, extracts the embedded
|
||||||
|
// Laravel application, and serves it through FrankenPHP's ServeHTTP into
|
||||||
|
// a native webview via Wails v3's AssetOptions.Handler.
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"log"
|
||||||
|
"runtime"
|
||||||
|
|
||||||
|
"github.com/host-uk/core/cmd/core-app/icons"
|
||||||
|
"github.com/wailsapp/wails/v3/pkg/application"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
// Set up PHP handler (extracts Laravel, prepares env, inits FrankenPHP).
|
||||||
|
handler, env, cleanup, err := NewPHPHandler()
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("Failed to initialise PHP handler: %v", err)
|
||||||
|
}
|
||||||
|
defer cleanup()
|
||||||
|
|
||||||
|
// Create the app service and native bridge.
|
||||||
|
appService := NewAppService(env)
|
||||||
|
bridge, err := NewNativeBridge(appService)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("Failed to start native bridge: %v", err)
|
||||||
|
}
|
||||||
|
defer bridge.Shutdown(context.Background())
|
||||||
|
|
||||||
|
// Inject the bridge URL into the Laravel .env so PHP can call Go.
|
||||||
|
if err := appendEnv(handler.laravelRoot, "NATIVE_BRIDGE_URL", bridge.URL()); err != nil {
|
||||||
|
log.Printf("Warning: couldn't inject bridge URL into .env: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
app := application.New(application.Options{
|
||||||
|
Name: "Core App",
|
||||||
|
Description: "Host UK Native App — Laravel powered by FrankenPHP",
|
||||||
|
Services: []application.Service{
|
||||||
|
application.NewService(appService),
|
||||||
|
},
|
||||||
|
Assets: application.AssetOptions{
|
||||||
|
Handler: handler,
|
||||||
|
},
|
||||||
|
Mac: application.MacOptions{
|
||||||
|
ActivationPolicy: application.ActivationPolicyAccessory,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
appService.app = app
|
||||||
|
|
||||||
|
setupSystemTray(app)
|
||||||
|
|
||||||
|
// Main application window
|
||||||
|
app.Window.NewWithOptions(application.WebviewWindowOptions{
|
||||||
|
Name: "main",
|
||||||
|
Title: "Core App",
|
||||||
|
Width: 1200,
|
||||||
|
Height: 800,
|
||||||
|
URL: "/",
|
||||||
|
BackgroundColour: application.NewRGB(13, 17, 23),
|
||||||
|
})
|
||||||
|
|
||||||
|
log.Println("Starting Core App...")
|
||||||
|
|
||||||
|
if err := app.Run(); err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// setupSystemTray configures the system tray icon and menu.
|
||||||
|
func setupSystemTray(app *application.App) {
|
||||||
|
systray := app.SystemTray.New()
|
||||||
|
systray.SetTooltip("Core App")
|
||||||
|
|
||||||
|
if runtime.GOOS == "darwin" {
|
||||||
|
systray.SetTemplateIcon(icons.TrayTemplate)
|
||||||
|
} else {
|
||||||
|
systray.SetDarkModeIcon(icons.TrayDark)
|
||||||
|
systray.SetIcon(icons.TrayLight)
|
||||||
|
}
|
||||||
|
|
||||||
|
trayMenu := app.Menu.New()
|
||||||
|
|
||||||
|
trayMenu.Add("Open Core App").OnClick(func(ctx *application.Context) {
|
||||||
|
if w, ok := app.Window.Get("main"); ok {
|
||||||
|
w.Show()
|
||||||
|
w.Focus()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
trayMenu.AddSeparator()
|
||||||
|
|
||||||
|
trayMenu.Add("Quit").OnClick(func(ctx *application.Context) {
|
||||||
|
app.Quit()
|
||||||
|
})
|
||||||
|
|
||||||
|
systray.SetMenu(trayMenu)
|
||||||
|
}
|
||||||
96
cmd/core-app/native_bridge.go
Normal file
96
cmd/core-app/native_bridge.go
Normal file
|
|
@ -0,0 +1,96 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"net"
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
// NativeBridge provides a localhost HTTP API that PHP code can call
|
||||||
|
// to access native desktop capabilities (file dialogs, notifications, etc.).
|
||||||
|
//
|
||||||
|
// Livewire renders server-side in PHP, so it can't call Wails bindings
|
||||||
|
// (window.go.*) directly. Instead, PHP makes HTTP requests to this bridge.
|
||||||
|
// The bridge port is injected into Laravel's .env as NATIVE_BRIDGE_URL.
|
||||||
|
type NativeBridge struct {
|
||||||
|
server *http.Server
|
||||||
|
port int
|
||||||
|
app *AppService
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewNativeBridge creates and starts the bridge on a random available port.
|
||||||
|
func NewNativeBridge(appService *AppService) (*NativeBridge, error) {
|
||||||
|
mux := http.NewServeMux()
|
||||||
|
bridge := &NativeBridge{app: appService}
|
||||||
|
|
||||||
|
// Register bridge endpoints
|
||||||
|
mux.HandleFunc("POST /bridge/version", bridge.handleVersion)
|
||||||
|
mux.HandleFunc("POST /bridge/data-dir", bridge.handleDataDir)
|
||||||
|
mux.HandleFunc("POST /bridge/show-window", bridge.handleShowWindow)
|
||||||
|
mux.HandleFunc("GET /bridge/health", bridge.handleHealth)
|
||||||
|
|
||||||
|
// Listen on a random available port (localhost only)
|
||||||
|
listener, err := net.Listen("tcp", "127.0.0.1:0")
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("listen: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
bridge.port = listener.Addr().(*net.TCPAddr).Port
|
||||||
|
bridge.server = &http.Server{Handler: mux}
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
if err := bridge.server.Serve(listener); err != nil && err != http.ErrServerClosed {
|
||||||
|
log.Printf("Native bridge error: %v", err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
log.Printf("Native bridge listening on http://127.0.0.1:%d", bridge.port)
|
||||||
|
return bridge, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Port returns the port the bridge is listening on.
|
||||||
|
func (b *NativeBridge) Port() int {
|
||||||
|
return b.port
|
||||||
|
}
|
||||||
|
|
||||||
|
// URL returns the full base URL of the bridge.
|
||||||
|
func (b *NativeBridge) URL() string {
|
||||||
|
return fmt.Sprintf("http://127.0.0.1:%d", b.port)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Shutdown gracefully stops the bridge server.
|
||||||
|
func (b *NativeBridge) Shutdown(ctx context.Context) error {
|
||||||
|
return b.server.Shutdown(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *NativeBridge) handleHealth(w http.ResponseWriter, r *http.Request) {
|
||||||
|
writeJSON(w, map[string]string{"status": "ok"})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *NativeBridge) handleVersion(w http.ResponseWriter, r *http.Request) {
|
||||||
|
writeJSON(w, map[string]string{"version": b.app.GetVersion()})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *NativeBridge) handleDataDir(w http.ResponseWriter, r *http.Request) {
|
||||||
|
writeJSON(w, map[string]string{"path": b.app.GetDataDir()})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *NativeBridge) handleShowWindow(w http.ResponseWriter, r *http.Request) {
|
||||||
|
var req struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
}
|
||||||
|
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||||
|
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
b.app.ShowWindow(req.Name)
|
||||||
|
writeJSON(w, map[string]string{"status": "ok"})
|
||||||
|
}
|
||||||
|
|
||||||
|
func writeJSON(w http.ResponseWriter, v any) {
|
||||||
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
json.NewEncoder(w).Encode(v)
|
||||||
|
}
|
||||||
11
go.work
Normal file
11
go.work
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
go 1.25.5
|
||||||
|
|
||||||
|
use (
|
||||||
|
.
|
||||||
|
./cmd/bugseti
|
||||||
|
./cmd/core-app
|
||||||
|
./cmd/core-ide
|
||||||
|
./internal/bugseti
|
||||||
|
./internal/bugseti/updater
|
||||||
|
./internal/core-ide
|
||||||
|
)
|
||||||
Loading…
Add table
Reference in a new issue