gitlink-cli/shortcuts/workflow/state.go

99 lines
2.4 KiB
Go

package workflow
import (
"crypto/md5"
"encoding/json"
"fmt"
"os"
"path/filepath"
"time"
"github.com/gitlink-org/gitlink-cli/internal/config"
)
// LoadState reads the persisted workflow state from disk.
func LoadState(name string) (*WorkflowState, error) {
path := statePath(name)
data, err := os.ReadFile(path)
if err != nil {
if os.IsNotExist(err) {
return &WorkflowState{
Workflow: name,
Snapshots: make(map[string]string),
PhaseLastRun: make(map[string]string),
PhaseUpstream: make(map[string]string),
}, nil
}
return nil, err
}
var s WorkflowState
if err := json.Unmarshal(data, &s); err != nil {
return nil, fmt.Errorf("parse state file %s: %w", path, err)
}
if s.Snapshots == nil {
s.Snapshots = make(map[string]string)
}
if s.PhaseLastRun == nil {
s.PhaseLastRun = make(map[string]string)
}
if s.PhaseUpstream == nil {
s.PhaseUpstream = make(map[string]string)
}
return &s, nil
}
// Save persists the workflow state to disk.
func (s *WorkflowState) Save() error {
s.LastRun = time.Now().Format(time.RFC3339)
path := statePath(s.Workflow)
dir := filepath.Dir(path)
if err := os.MkdirAll(dir, 0700); err != nil {
return err
}
data, err := json.MarshalIndent(s, "", " ")
if err != nil {
return err
}
return os.WriteFile(path, data, 0600)
}
// Diff compares current step results against stored snapshots.
// Does NOT mutate snapshots — call UpdateSnapshots separately to persist.
func (s *WorkflowState) Diff(results []StepResult) []string {
changed := []string{}
for _, sr := range results {
if !sr.OK || sr.Data == nil {
continue
}
hash := hashData(sr.Data)
if prev, ok := s.Snapshots[sr.Step]; ok && prev != hash {
changed = append(changed, sr.Step)
}
}
return changed
}
// UpdateSnapshots stores hashes of current step results for future diff.
func (s *WorkflowState) UpdateSnapshots(results []StepResult) {
for _, sr := range results {
if !sr.OK || sr.Data == nil {
continue
}
s.Snapshots[sr.Step] = hashData(sr.Data)
}
}
// hashData computes an MD5 hash of the JSON-encoded data.
func hashData(data interface{}) string {
b, err := json.Marshal(data)
if err != nil {
return ""
}
return fmt.Sprintf("%x", md5.Sum(b))
}
// statePath returns the file path for a workflow's state file.
func statePath(name string) string {
return filepath.Join(config.ConfigDir(), fmt.Sprintf("workflow-%s-state.json", name))
}