Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions internal/simulator/simulator.go
Original file line number Diff line number Diff line change
Expand Up @@ -70,3 +70,18 @@
}
return devices, nil
}

func ResetIOSUniversalLinksCache(device string) error {
targetDevice := device
if targetDevice == "" {
targetDevice = "booted"
}

cmdKillInside := exec.Command("xcrun", "simctl", "spawn", targetDevice, "killall", "swcd")

Check failure on line 80 in internal/simulator/simulator.go

View workflow job for this annotation

GitHub Actions / format-and-lint

G204: Subprocess launched with variable (gosec)
if err := cmdKillInside.Run(); err != nil {
cmdKillHost := exec.Command("pkill", "-9", "swcd")
_ = cmdKillHost.Run()
}

return nil
}
110 changes: 110 additions & 0 deletions pkg/cmd/cache/cache.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
package cache

import (
"encoding/json"
"fmt"
"strings"

"github.com/space-code/linkctl/internal/simulator"
"github.com/space-code/linkctl/pkg/cmdutil"
"github.com/spf13/cobra"
)

type Options struct {
Factory *cmdutil.Factory

Platform string
Device string
Package string
JSONOutput bool
}

type ResetResult struct {
Success bool `json:"success"`
Platform string `json:"platform"`
Device string `json:"device,omitempty"`
Message string `json:"message"`
Error string `json:"error,omitempty"`
}

func NewCmdCacheReset(f *cmdutil.Factory) *cobra.Command {
opts := &Options{
Factory: f,
}

cmd := &cobra.Command{
Use: "cache-reset",
Short: "Reset Universal Links (iOS) or App Links (Android) cache on simulator/emulator",
Args: cobra.NoArgs,
RunE: func(cmd *cobra.Command, args []string) error {
return runCacheReset(opts)
},
}

cmd.Flags().StringVarP(&opts.Platform, "platform", "p", "ios", "Platform to reset cache for ios")
cmd.Flags().StringVarP(&opts.Device, "device", "d", "", "Target simulator/emulator ID or name")
cmd.Flags().BoolVar(&opts.JSONOutput, "json", false, "Output result as JSON")

_ = cmd.MarkFlagRequired("platform")

return cmd
}

func runCacheReset(opts *Options) error {
platform := strings.ToLower(strings.TrimSpace(opts.Platform))
result := &ResetResult{
Platform: platform,
Device: opts.Device,
}

switch platform {
case "ios":
err := simulator.ResetIOSUniversalLinksCache(opts.Device)
if err != nil {
result.Success = false
result.Error = err.Error()
result.Message = "Failed to reset iOS Universal Links cache"
} else {
result.Success = true
result.Message = "Successfully reset iOS Universal Links cache"
}
default:
err := fmt.Errorf("unsupported platform '%s': must be 'ios'", opts.Platform)
result.Success = false
result.Error = err.Error()
result.Message = "Validation error"
_ = printResult(opts, result)
return err
}

return printResult(opts, result)
}

func printResult(opts *Options, result *ResetResult) error {
ios := opts.Factory.IOStreams

if opts.JSONOutput {
data, err := json.MarshalIndent(result, "", " ")
if err != nil {
return fmt.Errorf("failed to marshal JSON output: %w", err)
}
fmt.Fprintln(ios.Out, string(data))
return nil
}

cs := ios.ColorScheme()

if result.Success {
fmt.Fprintf(ios.Out, "✔ %s\n", cs.Green(result.Message))
if result.Device != "" {
fmt.Fprintf(ios.Out, " └─ Target Device: %s\n", result.Device)
}
} else {
fmt.Fprintf(ios.Out, "✖ %s\n", cs.Red(result.Message))
if result.Error != "" {
fmt.Fprintf(ios.Out, " └─ Details: %s\n", result.Error)
}
}

return nil
}
54 changes: 54 additions & 0 deletions pkg/cmd/cache/cache_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package cache_test

import (
"bytes"
"testing"

"github.com/space-code/linkctl/pkg/cmd/cache"
"github.com/space-code/linkctl/pkg/cmdutil"
"github.com/space-code/linkctl/pkg/iostreams"
)

func newFactory(t *testing.T) (*cmdutil.Factory, *bytes.Buffer) {
t.Helper()

ios, _, stdout, _ := iostreams.Test()

f := &cmdutil.Factory{
AppVersion: "1.0.0",
ExecutableName: "linkctl",
IOStreams: ios,
}

return f, stdout
}

func TestCacheResetCmd_MissingRequiredPlatformFlag(t *testing.T) {
f, _ := newFactory(t)
cmd := cache.NewCmdCacheReset(f)
cmd.SetArgs([]string{})

if err := cmd.Execute(); err == nil {
t.Fatal("expected error when --platform flag is missing")
}
}

func TestCacheResetCmd_UnknownFlag(t *testing.T) {
f, _ := newFactory(t)
cmd := cache.NewCmdCacheReset(f)
cmd.SetArgs([]string{"--platform", "ios", "--unknown-flag"})

if err := cmd.Execute(); err == nil {
t.Fatal("expected error for unknown flag")
}
}

func TestCacheResetCmd_InvalidPlatform(t *testing.T) {
f, _ := newFactory(t)
cmd := cache.NewCmdCacheReset(f)
cmd.SetArgs([]string{"--platform", "windows"})

if err := cmd.Execute(); err == nil {
t.Fatal("expected error for unsupported platform")
}
}
2 changes: 2 additions & 0 deletions pkg/cmd/root/root.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package root

import (
cacheCmd "github.com/space-code/linkctl/pkg/cmd/cache"
checkCmd "github.com/space-code/linkctl/pkg/cmd/check"
devicesCmd "github.com/space-code/linkctl/pkg/cmd/devices"
scanCmd "github.com/space-code/linkctl/pkg/cmd/scan"
Expand All @@ -25,6 +26,7 @@ func NewCmdRoot(f *cmdutil.Factory, appVersion string) (*cobra.Command, error) {
cmd.AddCommand(checkCmd.NewCmdCheck(f))
cmd.AddCommand(scanCmd.NewCmdScan(f))
cmd.AddCommand(validateCmd.NewCmdValidate(f))
cmd.AddCommand(cacheCmd.NewCmdCacheReset(f))

return cmd, nil
}
Loading