diff --git a/internal/simulator/simulator.go b/internal/simulator/simulator.go index 36c3734..5182aff 100644 --- a/internal/simulator/simulator.go +++ b/internal/simulator/simulator.go @@ -70,3 +70,18 @@ func GetBootedIOSDevices() ([]simctlDevice, error) { } return devices, nil } + +func ResetIOSUniversalLinksCache(device string) error { + targetDevice := device + if targetDevice == "" { + targetDevice = "booted" + } + + cmdKillInside := exec.Command("xcrun", "simctl", "spawn", targetDevice, "killall", "swcd") + if err := cmdKillInside.Run(); err != nil { + cmdKillHost := exec.Command("pkill", "-9", "swcd") + _ = cmdKillHost.Run() + } + + return nil +} diff --git a/pkg/cmd/cache/cache.go b/pkg/cmd/cache/cache.go new file mode 100644 index 0000000..3c60c1d --- /dev/null +++ b/pkg/cmd/cache/cache.go @@ -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 +} diff --git a/pkg/cmd/cache/cache_test.go b/pkg/cmd/cache/cache_test.go new file mode 100644 index 0000000..00ac48b --- /dev/null +++ b/pkg/cmd/cache/cache_test.go @@ -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") + } +} diff --git a/pkg/cmd/root/root.go b/pkg/cmd/root/root.go index 1a51ad4..c8232e7 100644 --- a/pkg/cmd/root/root.go +++ b/pkg/cmd/root/root.go @@ -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" @@ -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 }