Node answers the type-check as lint always, unit where a package has a test script and integration where it has test:integration, kept in detected-steps by fingerprint as the Go module keeps it; the image answers none. The manifests serve it. The step ask's end (step-ended) of BLD7 and BLD8 waits for BLD3 and the build-step package of mesh-delivery-sdk. Builds: design 47 G §22, §307, §326, §328 Claude-Session: https://claude.ai/code/session_01JG73KEq4zoDfM9WtYpcQgZ
160 lines
5.6 KiB
Go
160 lines
5.6 KiB
Go
package main
|
|
|
|
// **The verb `steps`** (design 47 G §22, §307, §326, §328): which pipeline steps a TypeScript artifact can run, by the
|
|
// convention check.go tests by. The type-check is the lint step (§328) and is always one; the unit step is the packages
|
|
// holding a package.json script `test`, the integration step those holding `test:integration`. A level with none is not
|
|
// answered. The answer is kept in the state detected-steps by the artifact's fingerprint, replaced by a newer one and by
|
|
// again, as the Go build module keeps it.
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"sort"
|
|
"strings"
|
|
"time"
|
|
|
|
"git.novox.be/novox/mesh-catalog/modules/mesh-build-go/buildstep"
|
|
seat "git.novox.be/novox/mesh-delivery/contracts/go/seats/nodebuildnode"
|
|
sdkstep "git.novox.be/novox/mesh-delivery/sdk/buildstep"
|
|
stdio "git.novox.be/novox/mesh-sdk/go"
|
|
"git.novox.be/novox/mesh-sdk/go/forgekey"
|
|
)
|
|
|
|
// The pipeline steps `steps` answers (the contract's DetectedStep enum).
|
|
const (
|
|
StepLint = "lint"
|
|
StepUnit = "unit"
|
|
StepIntegration = "integration"
|
|
)
|
|
|
|
// DetectedStep is the contract's.
|
|
type DetectedStep = seat.DetectedStep
|
|
|
|
// StepsAnswer is what `steps` answers.
|
|
type StepsAnswer struct {
|
|
Artifact string `json:"artifact"`
|
|
Fingerprint string `json:"fingerprint,omitempty"`
|
|
Steps []DetectedStep `json:"steps"`
|
|
Kept bool `json:"kept,omitempty"`
|
|
}
|
|
|
|
// detectSteps finds the steps of the package directories under dir.
|
|
func detectSteps(dir string) ([]DetectedStep, error) {
|
|
targets, err := testTargets(dir, ".")
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
unit, integration := []string{}, []string{}
|
|
for _, tg := range targets {
|
|
scripts, err := packageScripts(tg.dir)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if scripts["test"] != "" {
|
|
unit = append(unit, tg.where)
|
|
}
|
|
if scripts["test:integration"] != "" {
|
|
integration = append(integration, tg.where)
|
|
}
|
|
}
|
|
steps := []DetectedStep{{Step: StepLint}}
|
|
for _, l := range []struct {
|
|
step string
|
|
pkgs []string
|
|
}{{StepUnit, unit}, {StepIntegration, integration}} {
|
|
if len(l.pkgs) > 0 {
|
|
sort.Strings(l.pkgs)
|
|
steps = append(steps, DetectedStep{Step: l.step, Packages: l.pkgs})
|
|
}
|
|
}
|
|
return steps, nil
|
|
}
|
|
|
|
// stepsTool is the seat's verb `steps`.
|
|
func (m *Module) stepsTool() stdio.Tool {
|
|
return stdio.Tool{Name: Seat + "." + seat.VerbSteps,
|
|
Description: "Which pipeline steps a TypeScript artifact can run: the type-check as lint, always; unit tests where a package has a test script; " +
|
|
"integration tests where one has a test:integration script. A level with no test is not answered.",
|
|
Input: map[string]any{"type": "object", "required": []string{"module", "artifact", "fetch"}, "properties": map[string]any{
|
|
"module": map[string]any{"type": "string"},
|
|
"artifact": map[string]any{"type": "string", "description": "the artifact reference, <module>:<artifact>"},
|
|
"fingerprint": map[string]any{"type": "string"},
|
|
"fetch": map[string]any{"type": "object", "description": "what the fetch tool is asked: the repository, the full commit and the module's directory"},
|
|
"again": map[string]any{"type": "boolean", "description": "find them anew, not from the kept answer"},
|
|
}},
|
|
Run: m.Short(m.steps)}
|
|
}
|
|
|
|
func (m *Module) steps(ctx context.Context, args map[string]any) (any, error) {
|
|
str := func(k string) string { s, _ := args[k].(string); return s }
|
|
fetch, _ := args["fetch"].(map[string]any)
|
|
req, err := sdkstep.ParseFetchRequest(fetch)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if str("artifact") == "" || str("module") == "" {
|
|
return nil, fmt.Errorf("steps: the module and the artifact are asked")
|
|
}
|
|
artifact, fp := str("artifact"), str("fingerprint")
|
|
name := artifact
|
|
if _, n, ok := strings.Cut(artifact, ":"); ok {
|
|
name = n
|
|
}
|
|
key := seat.DetectedStepsByModuleArtifactKey(str("module"), name)
|
|
if again, _ := args["again"].(bool); !again && fp != "" && m.ReadDetected != nil {
|
|
if raw, err := m.ReadDetected(key); err == nil && len(raw) > 0 {
|
|
if kept, err := seat.DecodeDetectedStepsByModuleArtifactValue(raw); err == nil && kept.Fingerprint == fp {
|
|
return StepsAnswer{Artifact: artifact, Fingerprint: fp, Steps: kept.Steps, Kept: true}, nil
|
|
}
|
|
}
|
|
}
|
|
steps, err := m.stepsOfTree(ctx, req)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if fp != "" && m.Detected != nil {
|
|
at := time.Now()
|
|
if m.Clock != nil {
|
|
at = m.Clock()
|
|
}
|
|
if _, err := m.Detected(key, seat.DetectedSteps{V: 1, Fingerprint: fp, Steps: steps, At: &at}); err != nil {
|
|
return nil, fmt.Errorf("steps: keeping the answer: %w", err)
|
|
}
|
|
}
|
|
return StepsAnswer{Artifact: artifact, Fingerprint: fp, Steps: steps}, nil
|
|
}
|
|
|
|
// stepsOfTree fetches the commit alone (nothing installed) into a workspace removed at the end.
|
|
func (m *Module) stepsOfTree(ctx context.Context, req sdkstep.FetchRequest) ([]DetectedStep, error) {
|
|
if err := os.MkdirAll(m.Work, 0o700); err != nil {
|
|
return nil, err
|
|
}
|
|
root, err := os.MkdirTemp(m.Work, "steps-")
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer os.RemoveAll(root)
|
|
tree := filepath.Join(root, "tree")
|
|
if err := os.MkdirAll(tree, 0o700); err != nil {
|
|
return nil, err
|
|
}
|
|
gitRun := func(ctx context.Context, env []string, name string, a ...string) forgekey.Ran {
|
|
return m.Run(ctx, append(m.baseEnv(), env...), name, a...)
|
|
}
|
|
if err := buildstep.FetchCommit(ctx, gitRun, tree, m.remote(req.Repository), req.Commit, req.Ref, m.FetchOptions...); err != nil {
|
|
return nil, err
|
|
}
|
|
if len(req.Paths) > 0 {
|
|
if err := buildstep.Narrow(tree, req); err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
dir := tree
|
|
if req.Dir != "" && req.Dir != "." {
|
|
dir = filepath.Join(tree, filepath.FromSlash(req.Dir))
|
|
}
|
|
return detectSteps(dir)
|
|
}
|