mirror of
https://github.com/larksuite/cli.git
synced 2026-07-03 14:02:43 +08:00
feat: advance svglide run stages
This commit is contained in:
89
internal/svglide/stage.go
Normal file
89
internal/svglide/stage.go
Normal file
@@ -0,0 +1,89 @@
|
||||
package svglide
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
type StageReceipt struct {
|
||||
Stage string `json:"stage"`
|
||||
Status string `json:"status"`
|
||||
Message string `json:"message,omitempty"`
|
||||
Artifacts []string `json:"artifacts,omitempty"`
|
||||
}
|
||||
|
||||
func CompleteCurrentStage(root string) (StatusReport, error) {
|
||||
safeRoot, run, err := readRun(root)
|
||||
if err != nil {
|
||||
return StatusReport{}, err
|
||||
}
|
||||
index, stage, err := currentStageWithIndex(run)
|
||||
if err != nil {
|
||||
return StatusReport{}, err
|
||||
}
|
||||
missingOutputs, err := missingRunPaths(safeRoot, stage.Outputs)
|
||||
if err != nil {
|
||||
return StatusReport{}, err
|
||||
}
|
||||
if len(missingOutputs) > 0 {
|
||||
return StatusReport{}, fmt.Errorf("current stage %q missing outputs: %s", stage.Name, strings.Join(missingOutputs, ", "))
|
||||
}
|
||||
|
||||
if err := writeStageReceipt(safeRoot, StageReceipt{
|
||||
Stage: stage.Name,
|
||||
Status: StatusDone,
|
||||
Artifacts: stage.Outputs,
|
||||
}); err != nil {
|
||||
return StatusReport{}, err
|
||||
}
|
||||
|
||||
run.Stages[index].Status = StatusDone
|
||||
if index < len(run.Stages)-1 {
|
||||
nextStage := &run.Stages[index+1]
|
||||
run.CurrentStage = nextStage.Name
|
||||
if nextStage.Status == "" {
|
||||
nextStage.Status = StatusPending
|
||||
}
|
||||
} else {
|
||||
run.CurrentStage = stage.Name
|
||||
}
|
||||
run.UpdatedAt = time.Now().Format(time.RFC3339)
|
||||
|
||||
if err := writeRunFile(safeRoot, run); err != nil {
|
||||
return StatusReport{}, err
|
||||
}
|
||||
return InspectStatus(root)
|
||||
}
|
||||
|
||||
func currentStageWithIndex(run Run) (int, Stage, error) {
|
||||
for i, stage := range run.Stages {
|
||||
if stage.Name == run.CurrentStage {
|
||||
return i, stage, nil
|
||||
}
|
||||
}
|
||||
return -1, Stage{}, fmt.Errorf("current stage %q not found in run", run.CurrentStage)
|
||||
}
|
||||
|
||||
func writeRunFile(safeRoot string, run Run) error {
|
||||
target, err := ensureRunFileTargetForWrite(safeRoot, "run.json")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return writeJSON(target, run)
|
||||
}
|
||||
|
||||
func writeStageReceipt(safeRoot string, receipt StageReceipt) error {
|
||||
if strings.TrimSpace(receipt.Stage) == "" {
|
||||
return fmt.Errorf("stage receipt stage must not be empty")
|
||||
}
|
||||
if strings.ContainsAny(receipt.Stage, `/\`) || receipt.Stage == "." || receipt.Stage == ".." {
|
||||
return fmt.Errorf("stage receipt stage %q must be a file name", receipt.Stage)
|
||||
}
|
||||
target, err := ensureRunFileTargetForWrite(safeRoot, filepath.Join("receipts", receipt.Stage+".json"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return writeJSON(target, receipt)
|
||||
}
|
||||
91
internal/svglide/stage_test.go
Normal file
91
internal/svglide/stage_test.go
Normal file
@@ -0,0 +1,91 @@
|
||||
package svglide
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestCompleteCurrentStageAdvancesToNextStage(t *testing.T) {
|
||||
initStatusTestRun(t)
|
||||
|
||||
status, err := CompleteCurrentStage("demo")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if status.CurrentStage != StageResearch {
|
||||
t.Fatalf("CurrentStage = %q, want %q", status.CurrentStage, StageResearch)
|
||||
}
|
||||
|
||||
run := readStatusTestRunFile(t)
|
||||
if run.CurrentStage != StageResearch {
|
||||
t.Fatalf("run.CurrentStage = %q, want %q", run.CurrentStage, StageResearch)
|
||||
}
|
||||
if got := stageStatus(t, run, StageRequest); got != StatusDone {
|
||||
t.Fatalf("request stage status = %q, want %q", got, StatusDone)
|
||||
}
|
||||
if got := stageStatus(t, run, StageResearch); got != StatusPending {
|
||||
t.Fatalf("research stage status = %q, want %q", got, StatusPending)
|
||||
}
|
||||
|
||||
raw, err := os.ReadFile(filepath.Join("demo", "receipts", "request.json"))
|
||||
if err != nil {
|
||||
t.Fatalf("missing request receipt: %v", err)
|
||||
}
|
||||
var receipt StageReceipt
|
||||
if err := json.Unmarshal(raw, &receipt); err != nil {
|
||||
t.Fatalf("invalid request receipt: %v", err)
|
||||
}
|
||||
if receipt.Stage != StageRequest || receipt.Status != StatusDone {
|
||||
t.Fatalf("receipt = %+v, want request done", receipt)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCompleteCurrentStageRejectsMissingOutput(t *testing.T) {
|
||||
initStatusTestRun(t)
|
||||
if err := os.Remove(filepath.Join("demo", "request", "source_manifest.json")); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
_, err := CompleteCurrentStage("demo")
|
||||
if err == nil {
|
||||
t.Fatal("expected missing output error")
|
||||
}
|
||||
|
||||
run := readStatusTestRunFile(t)
|
||||
if run.CurrentStage != StageRequest {
|
||||
t.Fatalf("run.CurrentStage = %q, want %q", run.CurrentStage, StageRequest)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCompleteCurrentStageDoesNotAdvanceRunWhenReceiptWriteFails(t *testing.T) {
|
||||
initStatusTestRun(t)
|
||||
if err := os.Mkdir(filepath.Join("demo", "receipts", "request.json"), 0o755); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
_, err := CompleteCurrentStage("demo")
|
||||
if err == nil {
|
||||
t.Fatal("expected receipt write error")
|
||||
}
|
||||
|
||||
run := readStatusTestRunFile(t)
|
||||
if run.CurrentStage != StageRequest {
|
||||
t.Fatalf("run.CurrentStage = %q, want %q", run.CurrentStage, StageRequest)
|
||||
}
|
||||
if got := stageStatus(t, run, StageRequest); got == StatusDone {
|
||||
t.Fatalf("request stage status = %q, want not %q", got, StatusDone)
|
||||
}
|
||||
}
|
||||
|
||||
func stageStatus(t *testing.T, run Run, name string) string {
|
||||
t.Helper()
|
||||
for _, stage := range run.Stages {
|
||||
if stage.Name == name {
|
||||
return stage.Status
|
||||
}
|
||||
}
|
||||
t.Fatalf("missing stage %q", name)
|
||||
return ""
|
||||
}
|
||||
Reference in New Issue
Block a user