Skip to content
Merged
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
50 changes: 50 additions & 0 deletions .goreleaser.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
version: 2

project_name: foundry

builds:
- main: ./main.go
goos:
- linux
- darwin
- windows
goarch:
- amd64
- arm64
flags:
- -ldflags="-s -w -X main.version={{ .Version }} -X main.commit={{ .Commit }}"

archives:
- formats: [tar.gz]
name_template: >-
{{ .ProjectName }}_
{{- title .Os }}_
{{- if eq .Arch "amd64" }}x86_64
{{- else if eq .Arch "386" }}i386
{{- else }}{{ .Arch }}{{ end }}
format_overrides:
- goos: windows
formats: [zip]
files:
- ./*

changelog:
sort: asc
filters:
exclude:
- "^docs:"
- "^test:"

release:
github:
owner: Nykenik24
name: foundry
footer: >-
---
Released by [GoReleaser](https://github.com/goreleaser/goreleaser).

source:
enabled: true

sboms:
- artifacts: archive
95 changes: 95 additions & 0 deletions cmd/runs.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
/*
Copyright © 2026 Luca A. nykenik24@proton.me
*/

package cmd

import (
"fmt"
"time"

"github.com/Nykenik24/foundry/core/runs"
"github.com/Nykenik24/foundry/core/utils"
"github.com/spf13/cobra"
)

var runsCmd = &cobra.Command{
Use: "runs",
Short: "Manage runs",
Run: func(cmd *cobra.Command, args []string) {
cmd.Usage()
},
}

var runsAllCmd = &cobra.Command{
Use: "all",
Short: "List all runs",
Run: func(cmd *cobra.Command, args []string) {
list := runs.RetrieveRuns()
runs.PrintRuns(list, fmt.Sprintf("Found %d run(s)", len(list)))
},
}

var runsSuccessfulCmd = &cobra.Command{
Use: "success",
Short: "List successful runs",
Run: func(cmd *cobra.Command, args []string) {
list := runs.QuerySuccessful(runs.RetrieveRuns()).Result
runs.PrintRuns(list, fmt.Sprintf("Found %d succesful run(s)", len(list)))
},
}

var runsFailedCmd = &cobra.Command{
Use: "failed",
Short: "List failed runs",
Run: func(cmd *cobra.Command, args []string) {
list := runs.QueryFailed(runs.RetrieveRuns()).Result
runs.PrintRuns(list, fmt.Sprintf("Found %d failed run(s)", len(list)))
},
}

var runsBeforeCmd = &cobra.Command{
Use: "before <timestamp>",
Args: cobra.ExactArgs(1),

Short: "List runs before a timestamp.",
Long: `List runs before a timestamp.

Format for timestamp is YEAR-MONTH-DAY HOUR:MINUTE:SECOND`,
Run: func(cmd *cobra.Command, args []string) {
ts, err := time.Parse(time.DateTime, args[0])
if err != nil {
utils.PrintFatal("Error when parsing timestamp: %v", err)
}
list := runs.QueryBefore(runs.RetrieveRuns(), ts).Result
runs.PrintRuns(list, fmt.Sprintf("Found %d run(s) before %s", len(list), args[0]))
},
}

var runsAfterCmd = &cobra.Command{
Use: "after <timestamp>",
Args: cobra.ExactArgs(1),

Short: "List runs after a timestamp.",
Long: `List runs after a timestamp.

Format for timestamp is YEAR-MONTH-DAY HOUR:MINUTE:SECOND`,
Run: func(cmd *cobra.Command, args []string) {
ts, err := time.Parse(time.DateTime, args[0])
if err != nil {
utils.PrintFatal("Error when parsing timestamp: %v", err)
}
list := runs.QueryAfter(runs.RetrieveRuns(), ts).Result
runs.PrintRuns(list, fmt.Sprintf("Found %d run(s) after %s", len(list), args[0]))
},
}

func init() {
runsCmd.AddCommand(runsAllCmd)
runsCmd.AddCommand(runsSuccessfulCmd)
runsCmd.AddCommand(runsFailedCmd)
runsCmd.AddCommand(runsBeforeCmd)
runsCmd.AddCommand(runsAfterCmd)

rootCmd.AddCommand(runsCmd)
}
64 changes: 53 additions & 11 deletions cmd/task.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,15 @@ Copyright © 2026 Luca A. <Nykenik24@proton.me>
package cmd

import (
"bytes"
"fmt"
"log"
"os"
"os/exec"
"strings"
"time"

"github.com/Nykenik24/foundry/core/runs"
"github.com/Nykenik24/foundry/core/task"
"github.com/Nykenik24/foundry/core/utils"
"github.com/pelletier/go-toml/v2"
Expand All @@ -25,6 +29,7 @@ var taskCmd = &cobra.Command{
}

var newTaskCommand string
var newTaskStoreRuns bool

var taskNewCmd = &cobra.Command{
Use: "new <name>",
Expand Down Expand Up @@ -62,6 +67,7 @@ var taskNewCmd = &cobra.Command{
}

tasks.Tasks[name] = task.NewTask(name, newTaskCommand)
tasks.Tasks[name].StoreRuns = newTaskStoreRuns

newSrc, err := toml.Marshal(tasks)
if err != nil {
Expand Down Expand Up @@ -123,18 +129,18 @@ var taskRunCmd = &cobra.Command{
Short: "Run a task",
Run: func(cmd *cobra.Command, args []string) {
if !utils.PathExists(".foundry") {
log.Fatalf("Not a foundry project")
utils.PrintFatal("Not a foundry project")
}

if !utils.PathExists(".foundry/tasks.toml") {
if err := os.WriteFile(".foundry/tasks.toml", []byte(""), 0644); err != nil {
log.Fatalf("Error when writing tasks document: %v", err)
utils.PrintFatal("Error when writing tasks document: %v", err)
}
}

taskSrc, err := os.ReadFile(".foundry/tasks.toml")
if err != nil {
log.Fatalf("Error when reading .foundry/tasks.toml: %v", err)
utils.PrintFatal("Error when reading .foundry/tasks.toml: %v", err)
}

name := args[0]
Expand All @@ -148,19 +154,54 @@ var taskRunCmd = &cobra.Command{
}

if _, exists := tasks.Tasks[name]; !exists {
log.Fatalf("Task '%s' doesn't exist", name)
utils.PrintFatal("Task '%s' doesn't exist", name)
}

cmdStr := exec.Command("sh", "-c", tasks.Tasks[name].Cmd)
targetTask := tasks.Tasks[name]
cmdStr := exec.Command("sh", "-c", targetTask.Cmd)

var stdout bytes.Buffer
var stderr bytes.Buffer

cmdStr.Stdout = os.Stdout
cmdStr.Stderr = os.Stderr
cmdStr.Stdout = &stdout
cmdStr.Stderr = &stderr
cmdStr.Stdin = os.Stdin

err = cmdStr.Run()
os.Stdout.Write([]byte(stdout.String()))
os.Stderr.Write([]byte(stderr.String()))

if err != nil {
log.Printf("Error when running task: %v\n", err)
log.Fatalf("Command was '%s'", tasks.Tasks[name].Cmd)
utils.PrintFatal("Command was '%s'", tasks.Tasks[name].Cmd)
}

if targetTask.StoreRuns {
utils.GlobalLogger.Log(fmt.Sprintf("Storing %s's run", targetTask.Name), utils.LogInfo)
if !utils.PathExists(".foundry/runs") {
os.Mkdir(".foundry/runs", 0755)
}

taskLogs := strings.Split(stdout.String()+"\n"+stderr.String(), "\n")

runInfo := runs.RunInfo{
RanBy: "task/" + targetTask.Name,
Logs: taskLogs,
Success: true,
}

runToml, err := toml.Marshal(runInfo)
if err != nil {
utils.PrintFatal("Error when marshaling run: %v", err)
}

utils.GlobalLogger.Log(fmt.Sprintf("TOML for run: %s", string(runToml)), utils.LogInfo)
utils.GlobalLogger.Log(fmt.Sprintf("Name for run: %s.run", time.Now().Format(time.RFC3339)), utils.LogInfo)

err = os.WriteFile(fmt.Sprintf(".foundry/runs/%s.toml", time.Now().Format(time.RFC3339)), runToml, 0644)
if err != nil {
utils.PrintFatal("Error when creating run file: %v", err)
}
}
},
}
Expand All @@ -170,18 +211,18 @@ var taskListCmd = &cobra.Command{
Short: "List all tasks",
Run: func(cmd *cobra.Command, args []string) {
if !utils.PathExists(".foundry") {
log.Fatalf("Not a foundry project")
utils.PrintFatal("Not a foundry project")
}

if !utils.PathExists(".foundry/tasks.toml") {
if err := os.WriteFile(".foundry/tasks.toml", []byte(""), 0644); err != nil {
log.Fatalf("Error when writing tasks document: %v", err)
utils.PrintFatal("Error when writing tasks document: %v", err)
}
}

taskSrc, err := os.ReadFile(".foundry/tasks.toml")
if err != nil {
log.Fatalf("Error when reading .foundry/tasks.toml: %v", err)
utils.PrintFatal("Error when reading .foundry/tasks.toml: %v", err)
}

tasks, err := task.RetrieveTasks(string(taskSrc))
Expand All @@ -202,6 +243,7 @@ var taskListCmd = &cobra.Command{

func init() {
taskNewCmd.Flags().StringVar(&newTaskCommand, "cmd", "", "The command the task will have")
taskNewCmd.Flags().BoolVar(&newTaskStoreRuns, "store-runs", false, "Store runs of the task")

taskCmd.AddCommand(taskNewCmd)
taskCmd.AddCommand(taskRemoveCmd)
Expand Down
64 changes: 64 additions & 0 deletions core/runs/query.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
package runs

import "time"

type QueryType int

const (
QSuccesful QueryType = iota
QFailed
QBefore
QAfter
)

type RunQuery struct {
Result []*Run
Type QueryType
}

func newRunQuery(runs []*Run, kind QueryType) *RunQuery {
return &RunQuery{
Result: runs,
Type: kind,
}
}

func QuerySuccessful(runs []*Run) *RunQuery {
var queried []*Run
for _, run := range runs {
if run.Info.Success {
queried = append(queried, run)
}
}
return newRunQuery(queried, QSuccesful)
}

func QueryFailed(runs []*Run) *RunQuery {
var queried []*Run
for _, run := range runs {
if !run.Info.Success {
queried = append(queried, run)
}
}
return newRunQuery(queried, QFailed)
}

func QueryBefore(runs []*Run, timestamp time.Time) *RunQuery {
var queried []*Run
for _, run := range runs {
if timestamp.After(run.Timestamp) {
queried = append(queried, run)
}
}
return newRunQuery(queried, QBefore)
}

func QueryAfter(runs []*Run, timestamp time.Time) *RunQuery {
var queried []*Run
for _, run := range runs {
if timestamp.Before(run.Timestamp) {
queried = append(queried, run)
}
}
return newRunQuery(queried, QAfter)
}
Loading