feat: initial commit
This commit is contained in:
22
pkg/wails3kit/updates/platform/BUILD.bazel
Normal file
22
pkg/wails3kit/updates/platform/BUILD.bazel
Normal file
@@ -0,0 +1,22 @@
|
||||
load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test")
|
||||
|
||||
go_library(
|
||||
name = "platform",
|
||||
srcs = [
|
||||
"apply.go",
|
||||
"archive.go",
|
||||
"installer.go",
|
||||
],
|
||||
importpath = "github.com/Eriyc/rules_wails/pkg/wails3kit/updates/platform",
|
||||
visibility = ["//visibility:public"],
|
||||
deps = [
|
||||
"//pkg/wails3kit/updates",
|
||||
],
|
||||
)
|
||||
|
||||
go_test(
|
||||
name = "platform_test",
|
||||
srcs = ["platform_test.go"],
|
||||
embed = [":platform"],
|
||||
deps = ["//pkg/wails3kit/updates"],
|
||||
)
|
||||
119
pkg/wails3kit/updates/platform/apply.go
Normal file
119
pkg/wails3kit/updates/platform/apply.go
Normal file
@@ -0,0 +1,119 @@
|
||||
package platform
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
|
||||
"github.com/Eriyc/rules_wails/pkg/wails3kit/updates"
|
||||
)
|
||||
|
||||
type restorePoint struct {
|
||||
path string
|
||||
backup string
|
||||
hadPrior bool
|
||||
}
|
||||
|
||||
func applyBundle(request helperRequest) error {
|
||||
backupDir, err := os.MkdirTemp("", "wails3kit-backup-*")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
restores := make([]restorePoint, 0, len(request.Bundle.Files))
|
||||
for _, file := range request.Bundle.Files {
|
||||
source := filepath.Join(request.StagedPath, filepath.FromSlash(file.Path))
|
||||
target := filepath.Join(request.InstallRoot, filepath.FromSlash(file.Path))
|
||||
modeValue, err := strconv.ParseUint(file.Mode, 8, 32)
|
||||
if err != nil {
|
||||
rollback(restores)
|
||||
return err
|
||||
}
|
||||
mode := os.FileMode(modeValue)
|
||||
|
||||
restore, err := backupTarget(backupDir, target)
|
||||
if err != nil {
|
||||
rollback(restores)
|
||||
return err
|
||||
}
|
||||
restores = append(restores, restore)
|
||||
|
||||
if err := os.MkdirAll(filepath.Dir(target), 0o755); err != nil {
|
||||
rollback(restores)
|
||||
return err
|
||||
}
|
||||
if err := os.RemoveAll(target); err != nil {
|
||||
rollback(restores)
|
||||
return err
|
||||
}
|
||||
if err := copyFile(source, target, mode); err != nil {
|
||||
rollback(restores)
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func backupTarget(backupDir string, target string) (restorePoint, error) {
|
||||
point := restorePoint{path: target}
|
||||
info, err := os.Stat(target)
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
return point, nil
|
||||
}
|
||||
return point, err
|
||||
}
|
||||
if info.IsDir() {
|
||||
return point, fmt.Errorf("%w: target %s is a directory", updates.ErrInvalidArtifact, target)
|
||||
}
|
||||
|
||||
backupPath := filepath.Join(backupDir, filepath.Base(target))
|
||||
if err := copyFile(target, backupPath, info.Mode()); err != nil {
|
||||
return point, err
|
||||
}
|
||||
point.hadPrior = true
|
||||
point.backup = backupPath
|
||||
return point, nil
|
||||
}
|
||||
|
||||
func rollback(restores []restorePoint) {
|
||||
for index := len(restores) - 1; index >= 0; index-- {
|
||||
restore := restores[index]
|
||||
if restore.hadPrior {
|
||||
info, err := os.Stat(restore.backup)
|
||||
if err == nil {
|
||||
_ = os.RemoveAll(restore.path)
|
||||
_ = os.MkdirAll(filepath.Dir(restore.path), 0o755)
|
||||
_ = copyFile(restore.backup, restore.path, info.Mode())
|
||||
}
|
||||
continue
|
||||
}
|
||||
_ = os.RemoveAll(restore.path)
|
||||
}
|
||||
}
|
||||
|
||||
func copyFile(sourcePath string, destinationPath string, mode os.FileMode) error {
|
||||
source, err := os.Open(sourcePath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer source.Close()
|
||||
|
||||
return writeFile(destinationPath, source, mode)
|
||||
}
|
||||
|
||||
func writeFile(path string, source io.Reader, mode os.FileMode) error {
|
||||
file, err := os.Create(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
if _, err := io.Copy(file, source); err != nil {
|
||||
return err
|
||||
}
|
||||
return file.Chmod(mode)
|
||||
}
|
||||
188
pkg/wails3kit/updates/platform/archive.go
Normal file
188
pkg/wails3kit/updates/platform/archive.go
Normal file
@@ -0,0 +1,188 @@
|
||||
package platform
|
||||
|
||||
import (
|
||||
"archive/tar"
|
||||
"archive/zip"
|
||||
"compress/gzip"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/Eriyc/rules_wails/pkg/wails3kit/updates"
|
||||
)
|
||||
|
||||
func extractBundle(_ context.Context, archivePath string, format updates.ArtifactFormat) (string, updates.BundleManifest, error) {
|
||||
stageDir, err := os.MkdirTemp("", "wails3kit-stage-*")
|
||||
if err != nil {
|
||||
return "", updates.BundleManifest{}, err
|
||||
}
|
||||
|
||||
switch format {
|
||||
case updates.ArtifactFormatZip:
|
||||
err = extractZip(archivePath, stageDir)
|
||||
case updates.ArtifactFormatTarGz:
|
||||
err = extractTarGz(archivePath, stageDir)
|
||||
default:
|
||||
err = fmt.Errorf("%w: unsupported format %s", updates.ErrInvalidArtifact, format)
|
||||
}
|
||||
if err != nil {
|
||||
_ = os.RemoveAll(stageDir)
|
||||
return "", updates.BundleManifest{}, err
|
||||
}
|
||||
|
||||
manifestPath := filepath.Join(stageDir, "bundle.json")
|
||||
bytes, err := os.ReadFile(manifestPath)
|
||||
if err != nil {
|
||||
_ = os.RemoveAll(stageDir)
|
||||
return "", updates.BundleManifest{}, err
|
||||
}
|
||||
|
||||
var manifest updates.BundleManifest
|
||||
if err := json.Unmarshal(bytes, &manifest); err != nil {
|
||||
_ = os.RemoveAll(stageDir)
|
||||
return "", updates.BundleManifest{}, err
|
||||
}
|
||||
|
||||
if err := validateBundle(stageDir, manifest); err != nil {
|
||||
_ = os.RemoveAll(stageDir)
|
||||
return "", updates.BundleManifest{}, err
|
||||
}
|
||||
|
||||
return stageDir, manifest, nil
|
||||
}
|
||||
|
||||
func extractZip(archivePath string, targetDir string) error {
|
||||
reader, err := zip.OpenReader(archivePath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer reader.Close()
|
||||
|
||||
for _, file := range reader.File {
|
||||
path, err := safeArchivePath(targetDir, file.Name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if file.FileInfo().IsDir() {
|
||||
if err := os.MkdirAll(path, 0o755); err != nil {
|
||||
return err
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
source, err := file.Open()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := writeFile(path, source, file.Mode()); err != nil {
|
||||
_ = source.Close()
|
||||
return err
|
||||
}
|
||||
_ = source.Close()
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func extractTarGz(archivePath string, targetDir string) error {
|
||||
file, err := os.Open(archivePath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
gzipReader, err := gzip.NewReader(file)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer gzipReader.Close()
|
||||
|
||||
reader := tar.NewReader(gzipReader)
|
||||
for {
|
||||
header, err := reader.Next()
|
||||
if err == io.EOF {
|
||||
return nil
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
path, err := safeArchivePath(targetDir, header.Name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
switch header.Typeflag {
|
||||
case tar.TypeDir:
|
||||
if err := os.MkdirAll(path, 0o755); err != nil {
|
||||
return err
|
||||
}
|
||||
case tar.TypeReg:
|
||||
if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := writeFile(path, reader, os.FileMode(header.Mode)); err != nil {
|
||||
return err
|
||||
}
|
||||
default:
|
||||
return fmt.Errorf("%w: unsupported tar entry %s", updates.ErrInvalidArtifact, header.Name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func validateBundle(stageDir string, manifest updates.BundleManifest) error {
|
||||
if manifest.SchemaVersion != 1 {
|
||||
return fmt.Errorf("%w: unsupported bundle schema %d", updates.ErrInvalidArtifact, manifest.SchemaVersion)
|
||||
}
|
||||
if !isSafeRelative(manifest.EntryPoint) {
|
||||
return fmt.Errorf("%w: invalid entrypoint", updates.ErrInvalidArtifact)
|
||||
}
|
||||
if len(manifest.Files) == 0 {
|
||||
return fmt.Errorf("%w: bundle contains no files", updates.ErrInvalidArtifact)
|
||||
}
|
||||
|
||||
for _, file := range manifest.Files {
|
||||
if !isSafeRelative(file.Path) {
|
||||
return fmt.Errorf("%w: invalid bundle path %s", updates.ErrInvalidArtifact, file.Path)
|
||||
}
|
||||
if _, err := strconv.ParseUint(file.Mode, 8, 32); err != nil {
|
||||
return fmt.Errorf("%w: invalid mode %s", updates.ErrInvalidArtifact, file.Mode)
|
||||
}
|
||||
info, err := os.Stat(filepath.Join(stageDir, filepath.FromSlash(file.Path)))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if info.IsDir() {
|
||||
return fmt.Errorf("%w: file entry %s is a directory", updates.ErrInvalidArtifact, file.Path)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func safeArchivePath(root string, name string) (string, error) {
|
||||
if !isSafeRelative(name) && strings.TrimSpace(name) != "bundle.json" {
|
||||
return "", fmt.Errorf("%w: unsafe archive path %s", updates.ErrInvalidArtifact, name)
|
||||
}
|
||||
return filepath.Join(root, filepath.FromSlash(name)), nil
|
||||
}
|
||||
|
||||
func isSafeRelative(path string) bool {
|
||||
cleaned := filepath.Clean(filepath.FromSlash(path))
|
||||
if cleaned == "." || cleaned == "" {
|
||||
return false
|
||||
}
|
||||
if filepath.IsAbs(cleaned) {
|
||||
return false
|
||||
}
|
||||
return !strings.HasPrefix(cleaned, "..")
|
||||
}
|
||||
12
pkg/wails3kit/updates/platform/darwin/BUILD.bazel
Normal file
12
pkg/wails3kit/updates/platform/darwin/BUILD.bazel
Normal file
@@ -0,0 +1,12 @@
|
||||
load("@io_bazel_rules_go//go:def.bzl", "go_library")
|
||||
|
||||
go_library(
|
||||
name = "darwin",
|
||||
srcs = ["darwin.go"],
|
||||
importpath = "github.com/Eriyc/rules_wails/pkg/wails3kit/updates/platform/darwin",
|
||||
visibility = ["//visibility:public"],
|
||||
deps = [
|
||||
"//pkg/wails3kit/updates",
|
||||
"//pkg/wails3kit/updates/platform",
|
||||
],
|
||||
)
|
||||
31
pkg/wails3kit/updates/platform/darwin/darwin.go
Normal file
31
pkg/wails3kit/updates/platform/darwin/darwin.go
Normal file
@@ -0,0 +1,31 @@
|
||||
package darwin
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/Eriyc/rules_wails/pkg/wails3kit/updates"
|
||||
"github.com/Eriyc/rules_wails/pkg/wails3kit/updates/platform"
|
||||
)
|
||||
|
||||
func New() updates.PlatformInstaller {
|
||||
return platform.New(DetectInstallRoot)
|
||||
}
|
||||
|
||||
func DetectInstallRoot(app updates.AppDescriptor) (updates.InstallRoot, error) {
|
||||
if app.ExecutablePath == "" {
|
||||
return updates.InstallRoot{}, errors.New("missing executable path")
|
||||
}
|
||||
|
||||
current := filepath.Dir(app.ExecutablePath)
|
||||
for {
|
||||
if filepath.Ext(current) == ".app" {
|
||||
return updates.InstallRoot{Path: current}, nil
|
||||
}
|
||||
parent := filepath.Dir(current)
|
||||
if parent == current {
|
||||
return updates.InstallRoot{}, errors.New("unable to locate .app bundle")
|
||||
}
|
||||
current = parent
|
||||
}
|
||||
}
|
||||
172
pkg/wails3kit/updates/platform/installer.go
Normal file
172
pkg/wails3kit/updates/platform/installer.go
Normal file
@@ -0,0 +1,172 @@
|
||||
package platform
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"time"
|
||||
|
||||
"github.com/Eriyc/rules_wails/pkg/wails3kit/updates"
|
||||
)
|
||||
|
||||
const helperFlag = "--wails3kit-update-helper"
|
||||
|
||||
type Detector func(app updates.AppDescriptor) (updates.InstallRoot, error)
|
||||
|
||||
type Installer struct {
|
||||
detector Detector
|
||||
}
|
||||
|
||||
func New(detector Detector) *Installer {
|
||||
return &Installer{detector: detector}
|
||||
}
|
||||
|
||||
func (installer *Installer) DetectInstallRoot(app updates.AppDescriptor) (updates.InstallRoot, error) {
|
||||
if installer.detector == nil {
|
||||
return updates.InstallRoot{}, errors.New("missing install root detector")
|
||||
}
|
||||
return installer.detector(app)
|
||||
}
|
||||
|
||||
func (installer *Installer) Stage(ctx context.Context, root updates.InstallRoot, file updates.DownloadedFile, release updates.Release) (updates.StagedArtifact, error) {
|
||||
stageDir, bundleManifest, err := extractBundle(ctx, file.Path, release.Artifact.Format)
|
||||
if err != nil {
|
||||
return updates.StagedArtifact{}, err
|
||||
}
|
||||
|
||||
return updates.StagedArtifact{
|
||||
Path: stageDir,
|
||||
Root: root,
|
||||
Release: release,
|
||||
Bundle: bundleManifest,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (installer *Installer) SpawnApplyAndRestart(_ context.Context, req updates.ApplyRequest) error {
|
||||
helperDir, err := os.MkdirTemp("", "wails3kit-helper-*")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
helperBinary := filepath.Join(helperDir, filepath.Base(req.App.ExecutablePath))
|
||||
if err := copyFile(req.App.ExecutablePath, helperBinary, 0o755); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
payload := helperRequest{
|
||||
InstallRoot: req.Root.Path,
|
||||
StagedPath: req.Staged.Path,
|
||||
Bundle: req.Staged.Bundle,
|
||||
Args: append([]string(nil), req.App.Args...),
|
||||
WorkingDirectory: req.App.WorkingDirectory,
|
||||
}
|
||||
|
||||
requestPath := filepath.Join(helperDir, "request.json")
|
||||
encoded, err := json.Marshal(payload)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := os.WriteFile(requestPath, encoded, 0o600); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
command := exec.Command(helperBinary, helperFlag, requestPath)
|
||||
command.Dir = helperDir
|
||||
command.Stdout = os.Stdout
|
||||
command.Stderr = os.Stderr
|
||||
return command.Start()
|
||||
}
|
||||
|
||||
func MaybeRun(args []string) (bool, error) {
|
||||
if len(args) < 3 || args[1] != helperFlag {
|
||||
return false, nil
|
||||
}
|
||||
return true, runHelper(args[2])
|
||||
}
|
||||
|
||||
func NewCurrent() (*Installer, error) {
|
||||
switch runtime.GOOS {
|
||||
case "darwin":
|
||||
return New(detectDarwinInstallRoot), nil
|
||||
case "windows":
|
||||
return New(detectDirectoryInstallRoot), nil
|
||||
case "linux":
|
||||
return New(detectDirectoryInstallRoot), nil
|
||||
default:
|
||||
return nil, fmt.Errorf("unsupported platform: %s", runtime.GOOS)
|
||||
}
|
||||
}
|
||||
|
||||
type helperRequest struct {
|
||||
InstallRoot string `json:"installRoot"`
|
||||
StagedPath string `json:"stagedPath"`
|
||||
Bundle updates.BundleManifest `json:"bundle"`
|
||||
Args []string `json:"args,omitempty"`
|
||||
WorkingDirectory string `json:"workingDirectory,omitempty"`
|
||||
}
|
||||
|
||||
func runHelper(requestPath string) error {
|
||||
bytes, err := os.ReadFile(requestPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var request helperRequest
|
||||
if err := json.Unmarshal(bytes, &request); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
deadline := time.Now().Add(15 * time.Second)
|
||||
var applyErr error
|
||||
for time.Now().Before(deadline) {
|
||||
applyErr = applyBundle(request)
|
||||
if applyErr == nil {
|
||||
break
|
||||
}
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
}
|
||||
if applyErr != nil {
|
||||
return applyErr
|
||||
}
|
||||
|
||||
relaunch := filepath.Join(request.InstallRoot, filepath.FromSlash(request.Bundle.EntryPoint))
|
||||
command := exec.Command(relaunch, request.Args...)
|
||||
if request.WorkingDirectory != "" {
|
||||
command.Dir = request.WorkingDirectory
|
||||
} else {
|
||||
command.Dir = request.InstallRoot
|
||||
}
|
||||
command.Stdout = os.Stdout
|
||||
command.Stderr = os.Stderr
|
||||
return command.Start()
|
||||
}
|
||||
|
||||
func detectDirectoryInstallRoot(app updates.AppDescriptor) (updates.InstallRoot, error) {
|
||||
if app.ExecutablePath == "" {
|
||||
return updates.InstallRoot{}, errors.New("missing executable path")
|
||||
}
|
||||
return updates.InstallRoot{Path: filepath.Dir(app.ExecutablePath)}, nil
|
||||
}
|
||||
|
||||
func detectDarwinInstallRoot(app updates.AppDescriptor) (updates.InstallRoot, error) {
|
||||
if app.ExecutablePath == "" {
|
||||
return updates.InstallRoot{}, errors.New("missing executable path")
|
||||
}
|
||||
|
||||
current := filepath.Dir(app.ExecutablePath)
|
||||
for {
|
||||
if filepath.Ext(current) == ".app" {
|
||||
return updates.InstallRoot{Path: current}, nil
|
||||
}
|
||||
parent := filepath.Dir(current)
|
||||
if parent == current {
|
||||
return updates.InstallRoot{}, fmt.Errorf("unable to locate .app bundle from %s", app.ExecutablePath)
|
||||
}
|
||||
current = parent
|
||||
}
|
||||
}
|
||||
12
pkg/wails3kit/updates/platform/linux/BUILD.bazel
Normal file
12
pkg/wails3kit/updates/platform/linux/BUILD.bazel
Normal file
@@ -0,0 +1,12 @@
|
||||
load("@io_bazel_rules_go//go:def.bzl", "go_library")
|
||||
|
||||
go_library(
|
||||
name = "linux",
|
||||
srcs = ["linux.go"],
|
||||
importpath = "github.com/Eriyc/rules_wails/pkg/wails3kit/updates/platform/linux",
|
||||
visibility = ["//visibility:public"],
|
||||
deps = [
|
||||
"//pkg/wails3kit/updates",
|
||||
"//pkg/wails3kit/updates/platform",
|
||||
],
|
||||
)
|
||||
20
pkg/wails3kit/updates/platform/linux/linux.go
Normal file
20
pkg/wails3kit/updates/platform/linux/linux.go
Normal file
@@ -0,0 +1,20 @@
|
||||
package linux
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/Eriyc/rules_wails/pkg/wails3kit/updates"
|
||||
"github.com/Eriyc/rules_wails/pkg/wails3kit/updates/platform"
|
||||
)
|
||||
|
||||
func New() updates.PlatformInstaller {
|
||||
return platform.New(DetectInstallRoot)
|
||||
}
|
||||
|
||||
func DetectInstallRoot(app updates.AppDescriptor) (updates.InstallRoot, error) {
|
||||
if app.ExecutablePath == "" {
|
||||
return updates.InstallRoot{}, errors.New("missing executable path")
|
||||
}
|
||||
return updates.InstallRoot{Path: filepath.Dir(app.ExecutablePath)}, nil
|
||||
}
|
||||
134
pkg/wails3kit/updates/platform/platform_test.go
Normal file
134
pkg/wails3kit/updates/platform/platform_test.go
Normal file
@@ -0,0 +1,134 @@
|
||||
package platform
|
||||
|
||||
import (
|
||||
"archive/zip"
|
||||
"encoding/json"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/Eriyc/rules_wails/pkg/wails3kit/updates"
|
||||
)
|
||||
|
||||
func TestExtractBundleRejectsTraversal(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
archivePath := filepath.Join(t.TempDir(), "bundle.zip")
|
||||
file, err := os.Create(archivePath)
|
||||
if err != nil {
|
||||
t.Fatalf("Create returned error: %v", err)
|
||||
}
|
||||
writer := zip.NewWriter(file)
|
||||
entry, err := writer.Create("../escape")
|
||||
if err != nil {
|
||||
t.Fatalf("Create entry returned error: %v", err)
|
||||
}
|
||||
if _, err := entry.Write([]byte("bad")); err != nil {
|
||||
t.Fatalf("Write returned error: %v", err)
|
||||
}
|
||||
if err := writer.Close(); err != nil {
|
||||
t.Fatalf("Close writer returned error: %v", err)
|
||||
}
|
||||
if err := file.Close(); err != nil {
|
||||
t.Fatalf("Close file returned error: %v", err)
|
||||
}
|
||||
|
||||
if _, _, err := extractBundle(t.Context(), archivePath, updates.ArtifactFormatZip); err == nil {
|
||||
t.Fatal("expected traversal archive to fail")
|
||||
}
|
||||
}
|
||||
|
||||
func TestApplyBundleReplacesFilesAndPreservesArgs(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
root := t.TempDir()
|
||||
stage := t.TempDir()
|
||||
if err := os.MkdirAll(filepath.Join(root, "resources"), 0o755); err != nil {
|
||||
t.Fatalf("MkdirAll returned error: %v", err)
|
||||
}
|
||||
if err := os.WriteFile(filepath.Join(root, "App"), []byte("old"), 0o755); err != nil {
|
||||
t.Fatalf("WriteFile returned error: %v", err)
|
||||
}
|
||||
if err := os.WriteFile(filepath.Join(root, "resources", "config.json"), []byte("old"), 0o644); err != nil {
|
||||
t.Fatalf("WriteFile returned error: %v", err)
|
||||
}
|
||||
if err := os.MkdirAll(filepath.Join(stage, "resources"), 0o755); err != nil {
|
||||
t.Fatalf("MkdirAll returned error: %v", err)
|
||||
}
|
||||
if err := os.WriteFile(filepath.Join(stage, "App"), []byte("new"), 0o755); err != nil {
|
||||
t.Fatalf("WriteFile returned error: %v", err)
|
||||
}
|
||||
if err := os.WriteFile(filepath.Join(stage, "resources", "config.json"), []byte("new"), 0o644); err != nil {
|
||||
t.Fatalf("WriteFile returned error: %v", err)
|
||||
}
|
||||
|
||||
request := helperRequest{
|
||||
InstallRoot: root,
|
||||
StagedPath: stage,
|
||||
Bundle: updates.BundleManifest{
|
||||
SchemaVersion: 1,
|
||||
EntryPoint: "App",
|
||||
Files: []updates.BundleFile{
|
||||
{Path: "App", Mode: "0755"},
|
||||
{Path: "resources/config.json", Mode: "0644"},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
if err := applyBundle(request); err != nil {
|
||||
t.Fatalf("applyBundle returned error: %v", err)
|
||||
}
|
||||
|
||||
bytes, err := os.ReadFile(filepath.Join(root, "App"))
|
||||
if err != nil {
|
||||
t.Fatalf("ReadFile returned error: %v", err)
|
||||
}
|
||||
if string(bytes) != "new" {
|
||||
t.Fatalf("unexpected executable contents: %s", string(bytes))
|
||||
}
|
||||
bytes, err = os.ReadFile(filepath.Join(root, "resources", "config.json"))
|
||||
if err != nil {
|
||||
t.Fatalf("ReadFile returned error: %v", err)
|
||||
}
|
||||
if string(bytes) != "new" {
|
||||
t.Fatalf("unexpected resource contents: %s", string(bytes))
|
||||
}
|
||||
}
|
||||
|
||||
func TestMaybeRunIgnoresNormalArgs(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
handled, err := MaybeRun([]string{"app"})
|
||||
if err != nil {
|
||||
t.Fatalf("MaybeRun returned error: %v", err)
|
||||
}
|
||||
if handled {
|
||||
t.Fatal("expected MaybeRun to ignore non-helper args")
|
||||
}
|
||||
}
|
||||
|
||||
func TestHelperRequestJSONRoundTrip(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
request := helperRequest{
|
||||
InstallRoot: "/tmp/app",
|
||||
StagedPath: "/tmp/stage",
|
||||
Bundle: updates.BundleManifest{
|
||||
SchemaVersion: 1,
|
||||
EntryPoint: "App",
|
||||
Files: []updates.BundleFile{{Path: "App", Mode: "0755"}},
|
||||
},
|
||||
Args: []string{"--flag"},
|
||||
}
|
||||
bytes, err := json.Marshal(request)
|
||||
if err != nil {
|
||||
t.Fatalf("Marshal returned error: %v", err)
|
||||
}
|
||||
var decoded helperRequest
|
||||
if err := json.Unmarshal(bytes, &decoded); err != nil {
|
||||
t.Fatalf("Unmarshal returned error: %v", err)
|
||||
}
|
||||
if decoded.Bundle.EntryPoint != "App" {
|
||||
t.Fatalf("unexpected entrypoint: %s", decoded.Bundle.EntryPoint)
|
||||
}
|
||||
}
|
||||
12
pkg/wails3kit/updates/platform/windows/BUILD.bazel
Normal file
12
pkg/wails3kit/updates/platform/windows/BUILD.bazel
Normal file
@@ -0,0 +1,12 @@
|
||||
load("@io_bazel_rules_go//go:def.bzl", "go_library")
|
||||
|
||||
go_library(
|
||||
name = "windows",
|
||||
srcs = ["windows.go"],
|
||||
importpath = "github.com/Eriyc/rules_wails/pkg/wails3kit/updates/platform/windows",
|
||||
visibility = ["//visibility:public"],
|
||||
deps = [
|
||||
"//pkg/wails3kit/updates",
|
||||
"//pkg/wails3kit/updates/platform",
|
||||
],
|
||||
)
|
||||
20
pkg/wails3kit/updates/platform/windows/windows.go
Normal file
20
pkg/wails3kit/updates/platform/windows/windows.go
Normal file
@@ -0,0 +1,20 @@
|
||||
package windows
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/Eriyc/rules_wails/pkg/wails3kit/updates"
|
||||
"github.com/Eriyc/rules_wails/pkg/wails3kit/updates/platform"
|
||||
)
|
||||
|
||||
func New() updates.PlatformInstaller {
|
||||
return platform.New(DetectInstallRoot)
|
||||
}
|
||||
|
||||
func DetectInstallRoot(app updates.AppDescriptor) (updates.InstallRoot, error) {
|
||||
if app.ExecutablePath == "" {
|
||||
return updates.InstallRoot{}, errors.New("missing executable path")
|
||||
}
|
||||
return updates.InstallRoot{Path: filepath.Dir(app.ExecutablePath)}, nil
|
||||
}
|
||||
Reference in New Issue
Block a user