Repository: buildsafedev/bsf (go)

Issue:
Add `date` and `time` column in `bsf search` GUI
When searching for a new package using `bsf search`, we should show the `date` and `time` in the TUI as well.

Pull request description:
feat: add the date and time field to the search gui
Fixes #125 
Closes #125 

## Description
This PR adds the Date and Time field to the `bsf search` GUI. (Format: 2006-01-02 15:04)

## Screenshot
![image](https://github.com/user-attachments/assets/01ba6803-7297-442d-9db6-d28a58ec819e)


Patch:
diff --git a/cmd/clean/clean.go b/cmd/clean/clean.go
new file mode 100644
index 0000000..8b609e4
--- /dev/null
+++ b/cmd/clean/clean.go
@@ -0,0 +1,25 @@
+package clean
+
+import (
+	"fmt"
+
+	in "github.com/buildsafedev/bsf/cmd/init"
+	"github.com/buildsafedev/bsf/cmd/styles"
+	"github.com/spf13/cobra"
+)
+
+var CleanCmd = &cobra.Command{
+	Use:   "clean",
+	Short: "cleans the autogenerated bsf files and directories",
+	Long: `cleans the autogenerated bsf files and directories.
+	Cleans bsf.hcl, bsf.lock, /bsf
+	`,
+	Run: func(cmd *cobra.Command, args []string) {
+		clean()
+	},
+}
+
+func clean() {
+	fmt.Println(styles.SucessStyle.Render("Cleaning autogenerated bsf files and directories"))
+	in.CleanUp()
+}
diff --git a/cmd/cmd.go b/cmd/cmd.go
index 02919c3..e8328b0 100644
--- a/cmd/cmd.go
+++ b/cmd/cmd.go
@@ -15,6 +15,7 @@ import (
 
 	"github.com/buildsafedev/bsf/cmd/attestation"
 	"github.com/buildsafedev/bsf/cmd/build"
+	"github.com/buildsafedev/bsf/cmd/clean"
 	"github.com/buildsafedev/bsf/cmd/configure"
 	"github.com/buildsafedev/bsf/cmd/develop"
 	"github.com/buildsafedev/bsf/cmd/direnv"
@@ -71,6 +72,7 @@ func Execute() {
 		rootCmd.AddCommand(configure.ConfigureCmd)
 		rootCmd.AddCommand(nixgenerate.NixGenCmd)
 		rootCmd.AddCommand(precheck.PreCheckCmd)
+		rootCmd.AddCommand(clean.CleanCmd)
 	}
 	rootCmd.AddCommand(oci.OCICmd)
 	rootCmd.AddCommand(dockerfile.DFCmd)
diff --git a/cmd/init/config.go b/cmd/init/config.go
index df1f091..fc71277 100644
--- a/cmd/init/config.go
+++ b/cmd/init/config.go
@@ -5,6 +5,7 @@ import (
 	"fmt"
 	"os"
 	"regexp"
+	"sort"
 	"strings"
 
 	"github.com/buildsafedev/bsf/pkg/hcl2nix"
@@ -64,8 +65,8 @@ func generateEmptyConf(imageName string, addCommonDeps bool, commonDepsType stri
 	}
 	return hcl2nix.Config{
 		Packages: hcl2nix.Packages{
-			Development: devDeps,
-			Runtime:     commonRTDeps,
+			Development: sortPackages(devDeps),
+			Runtime:     sortPackages(commonRTDeps),
 		},
 		OCIArtifact: []hcl2nix.OCIArtifact{
 			{
@@ -109,8 +110,8 @@ func genRustCargoConf() (hcl2nix.Config, error) {
 	rustDevDeps := append(commonDevDeps, rustDeps...)
 	return hcl2nix.Config{
 		Packages: hcl2nix.Packages{
-			Development: rustDevDeps,
-			Runtime:     commonRTDeps,
+			Development: sortPackages(rustDevDeps),
+			Runtime:     sortPackages(commonRTDeps),
 		},
 		RustApp: &hcl2nix.RustApp{
 			WorkspaceSrc: "./.",
@@ -126,8 +127,8 @@ func genPythonPoetryConf() hcl2nix.Config {
 	poetryDevDeps := append(commonDevDeps, pythonDeps...)
 	return hcl2nix.Config{
 		Packages: hcl2nix.Packages{
-			Development: poetryDevDeps,
-			Runtime:     commonRTDeps,
+			Development: sortPackages(poetryDevDeps),
+			Runtime:     sortPackages(commonRTDeps),
 		},
 		PoetryApp: &hcl2nix.PoetryApp{
 			ProjectDir:   "./.",
@@ -157,9 +158,9 @@ func genGoModuleConf(pd *langdetect.ProjectDetails) hcl2nix.Config {
 	goDevDeps := append(commonDevDeps, goDeps...)
 	return hcl2nix.Config{
 		Packages: hcl2nix.Packages{
-			Development: goDevDeps,
+			Development: sortPackages(goDevDeps),
 			// todo: maybe we should dynamically inject the latest version of such runtime packages(cacert)?
-			Runtime: commonRTDeps,
+			Runtime: sortPackages(commonRTDeps),
 		},
 		GoModule: &hcl2nix.GoModule{
 			Name:       name,
@@ -188,8 +189,8 @@ func genJsNpmConf() (hcl2nix.Config, error) {
 	nodeDevDeps := append(commonDevDeps, jsNpmDeps...)
 	return hcl2nix.Config{
 		Packages: hcl2nix.Packages{
-			Development: nodeDevDeps,
-			Runtime:     commonRTDeps,
+			Development: sortPackages(nodeDevDeps),
+			Runtime:     sortPackages(commonRTDeps),
 		},
 		JsNpmApp: &hcl2nix.JsNpmApp{
 			PackageName: name,
@@ -197,3 +198,10 @@ func genJsNpmConf() (hcl2nix.Config, error) {
 		},
 	}, nil
 }
+
+func sortPackages(packages []string) []string {
+	sort.Slice(packages, func(i, j int) bool {
+		return strings.Split(packages[i], "@")[0] < strings.Split(packages[j], "@")[0]
+	})
+	return packages
+}
diff --git a/cmd/init/init.go b/cmd/init/init.go
index 96ecec2..c1af213 100644
--- a/cmd/init/init.go
+++ b/cmd/init/init.go
@@ -147,7 +147,7 @@ func GetBSFInitializers() (bsfv1.SearchServiceClient, *hcl2nix.FileHandlers, err
 }
 
 // CleanUp removes the bsf config if any error occurs in init process (ctrl+c or any init process stage)
-func cleanUp() {
+func CleanUp() {
 	configs := []string{"bsf", "bsf.hcl", "bsf.lock"}
 
 	for _, f := range configs {
diff --git a/cmd/init/model.go b/cmd/init/model.go
index 71f621c..3198f33 100644
--- a/cmd/init/model.go
+++ b/cmd/init/model.go
@@ -64,7 +64,7 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
 	case tea.KeyMsg:
 		switch msg.String() {
 		case "ctrl+c", "q", "esc":
-			cleanUp()
+			CleanUp()
 			return m, tea.Quit
 		}
 	}
@@ -76,7 +76,7 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
 	}
 	err = m.processStages(m.stage)
 	if err != nil {
-		cleanUp()
+		CleanUp()
 		return m, tea.Quit
 	}
 	m.stage++
diff --git a/cmd/search/versionList.go b/cmd/search/versionList.go
index 8fafe41..ef0d260 100644
--- a/cmd/search/versionList.go
+++ b/cmd/search/versionList.go
@@ -4,6 +4,7 @@ import (
 	"context"
 	"fmt"
 	"strings"
+	"time"
 
 	"github.com/charmbracelet/bubbles/key"
 	"github.com/charmbracelet/bubbles/list"
@@ -36,6 +37,7 @@ func convFPR2Rows(versions *buildsafev1.FetchPackagesResponse) []table.Row {
 			pkg.SpdxId,
 			free,
 			pkg.Homepage,
+			time.Unix(int64(pkg.EpochSeconds), 0).Format("2006-01-02 15:04"),
 		})
 	}
 	return items
@@ -50,6 +52,7 @@ func initVersionTable(searchList list.Model, versions *buildsafev1.FetchPackages
 		{Title: "License", Width: frameWidth / cols},
 		{Title: "Free", Width: frameWidth / cols},
 		{Title: "Homepage", Width: frameWidth * 2 / cols},
+		{Title: "Date", Width: frameWidth / cols},
 	}
 
 	rows := convFPR2Rows(versions)
diff --git a/pkg/strings/set.go b/pkg/strings/set.go
index 82688ad..b120119 100644
--- a/pkg/strings/set.go
+++ b/pkg/strings/set.go
@@ -1,5 +1,10 @@
 package strings
 
+import (
+	"sort"
+	"strings"
+)
+
 type parse func(string) string
 
 // SliceToSet converts a slice of strings to a set of strings
@@ -42,6 +47,10 @@ func PreferNewSliceElements(existing []string, new []string, parseFunc parse) []
 		finalEle = append(finalEle, v)
 	}
 
+	sort.Slice(finalEle, func(i, j int) bool {
+		return strings.Split(finalEle[i], "@")[0] < strings.Split(finalEle[j], "@")[0]
+	})
+
 	return finalEle
 }
 


Test patch:
diff --git a/pkg/strings/set_test.go b/pkg/strings/set_test.go
index a342c95..475ea7c 100644
--- a/pkg/strings/set_test.go
+++ b/pkg/strings/set_test.go
@@ -68,7 +68,7 @@ func TestPreferNewSliceElements(t *testing.T) {
 			name:     "Test 3",
 			existing: []string{"a", "b", "c"},
 			new:      []string{"a", "b", "d"},
-			want:     []string{"c", "a", "b", "d"},
+			want:     []string{"a", "b", "c", "d"},
 		},
 	}