diff --git a/.github/workflows/benchmark.yml b/.github/workflows/benchmark.yml index 26d85fc8e..b3e966e62 100644 --- a/.github/workflows/benchmark.yml +++ b/.github/workflows/benchmark.yml @@ -29,7 +29,7 @@ jobs: steps: - name: Check out code - uses: actions/checkout@v4 + uses: actions/checkout@v6 - name: Set up Go uses: actions/setup-go@v5 diff --git a/.github/workflows/ci-wfctl.yml.example b/.github/workflows/ci-wfctl.yml.example index af7ec7dbf..0c515c2e6 100644 --- a/.github/workflows/ci-wfctl.yml.example +++ b/.github/workflows/ci-wfctl.yml.example @@ -50,7 +50,7 @@ jobs: validate: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - uses: actions/setup-go@v5 with: go-version: '1.26' # bumped from generated 1.22 to match repo standard @@ -78,7 +78,7 @@ jobs: # contents: read # packages: write # steps: -# - uses: actions/checkout@v4 +# - uses: actions/checkout@v6 # - uses: actions/setup-go@v5 # with: # go-version: '1.26' diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 129e9060f..553f354a9 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -34,7 +34,7 @@ jobs: steps: - name: Check out code - uses: actions/checkout@v4 + uses: actions/checkout@v6 - name: Set up Go uses: actions/setup-go@v5 @@ -95,7 +95,7 @@ jobs: steps: - name: Check out code - uses: actions/checkout@v4 + uses: actions/checkout@v6 - name: Set up Go uses: actions/setup-go@v5 @@ -133,7 +133,7 @@ jobs: steps: - name: Check out code - uses: actions/checkout@v4 + uses: actions/checkout@v6 - name: Set up Go uses: actions/setup-go@v5 @@ -178,7 +178,7 @@ jobs: steps: - name: Check out code - uses: actions/checkout@v4 + uses: actions/checkout@v6 - name: Set up Go uses: actions/setup-go@v5 @@ -201,7 +201,7 @@ jobs: steps: - name: Check out code - uses: actions/checkout@v4 + uses: actions/checkout@v6 - name: Set up Go uses: actions/setup-go@v5 @@ -225,7 +225,7 @@ jobs: steps: - name: Check out code - uses: actions/checkout@v4 + uses: actions/checkout@v6 with: token: ${{ secrets.GITHUB_TOKEN }} @@ -272,7 +272,7 @@ jobs: steps: - name: Check out code - uses: actions/checkout@v4 + uses: actions/checkout@v6 - name: Set up Node.js uses: actions/setup-node@v4 @@ -307,7 +307,7 @@ jobs: steps: - name: Check out code - uses: actions/checkout@v4 + uses: actions/checkout@v6 - name: Set up Go uses: actions/setup-go@v5 @@ -381,7 +381,7 @@ jobs: name: Verify godo is not imported (issue #617) runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - name: Grep gate — *.go files must not import godo run: | ! grep -rn --include="*.go" \ @@ -398,7 +398,7 @@ jobs: name: Cloud-SDK inventory + k8s-backend init() partition + asymmetric graph audit runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - uses: actions/setup-go@v5 with: go-version-file: go.mod @@ -448,7 +448,7 @@ jobs: name: Verify removed AWS SDK packages are not imported (issue #653) runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - name: Grep gate — no *.go file (repo-wide) may import fully-removed AWS service packages # Scans the whole repo. service/eks is allowed only in provider/ (ECS/EKS deploy pipeline). # platform/providers/aws/ was deleted in Phase 3; provider/aws/ (deploy pipeline) is kept. diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 15301308e..27971e049 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -39,7 +39,7 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v4 + uses: actions/checkout@v6 - name: Set up Go if: matrix.language == 'go' diff --git a/.github/workflows/conformance-budget-check.yml b/.github/workflows/conformance-budget-check.yml index 364e39e97..9757061a4 100644 --- a/.github/workflows/conformance-budget-check.yml +++ b/.github/workflows/conformance-budget-check.yml @@ -37,7 +37,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Check out workflow repo - uses: actions/checkout@v4 + uses: actions/checkout@v6 # Compute the hour-bucket as a step output so the cache step # can reference it. Hourly TTL: same PR series re-checking diff --git a/.github/workflows/conformance-leak-scrubber.yml b/.github/workflows/conformance-leak-scrubber.yml index 4a0ad62da..e9125f620 100644 --- a/.github/workflows/conformance-leak-scrubber.yml +++ b/.github/workflows/conformance-leak-scrubber.yml @@ -46,7 +46,7 @@ jobs: DAILY_SCRUB_THRESHOLD: 3 # > 3 scrub events / day → file budget incident too steps: - name: Check out workflow repo - uses: actions/checkout@v4 + uses: actions/checkout@v6 # Detect unconfigured secret. The cron fires on a fixed # schedule regardless of secret provisioning state; if the diff --git a/.github/workflows/conformance-smoke.yml b/.github/workflows/conformance-smoke.yml index f528e0484..245892c63 100644 --- a/.github/workflows/conformance-smoke.yml +++ b/.github/workflows/conformance-smoke.yml @@ -72,7 +72,7 @@ jobs: CONFORMANCE_TAG: conformance-pr-${{ github.event.pull_request.number }} steps: - name: Check out workflow repo - uses: actions/checkout@v4 + uses: actions/checkout@v6 - name: Set up Go uses: actions/setup-go@v5 diff --git a/.github/workflows/copilot-setup-steps.yml b/.github/workflows/copilot-setup-steps.yml index a82bf8c62..67a6987f7 100644 --- a/.github/workflows/copilot-setup-steps.yml +++ b/.github/workflows/copilot-setup-steps.yml @@ -26,7 +26,7 @@ jobs: # If you do not check out your code, Copilot will do this for you. steps: - name: Checkout code - uses: actions/checkout@v4 + uses: actions/checkout@v6 # Setup Go environment for development and testing - name: Setup Go diff --git a/.github/workflows/create-release.yml b/.github/workflows/create-release.yml index e58d27241..3aefec155 100644 --- a/.github/workflows/create-release.yml +++ b/.github/workflows/create-release.yml @@ -31,7 +31,7 @@ jobs: steps: - name: Check out code - uses: actions/checkout@v4 + uses: actions/checkout@v6 with: fetch-depth: 0 diff --git a/.github/workflows/cross-plugin-build-test.yml b/.github/workflows/cross-plugin-build-test.yml index e8404c885..b4de78686 100644 --- a/.github/workflows/cross-plugin-build-test.yml +++ b/.github/workflows/cross-plugin-build-test.yml @@ -52,9 +52,9 @@ jobs: matrix: plugin: [workflow-plugin-aws, workflow-plugin-gcp, workflow-plugin-azure] steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 with: { path: workflow } - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 with: repository: GoCodeAlone/${{ matrix.plugin }} path: ${{ matrix.plugin }} @@ -93,7 +93,7 @@ jobs: permissions: contents: read steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - uses: actions/setup-go@v5 with: { go-version-file: go.mod } - name: Typed-IaC E2E test (in-process gRPC roundtrip) diff --git a/.github/workflows/dependency-update.yml b/.github/workflows/dependency-update.yml index 407a0f1d3..ab384d7c3 100644 --- a/.github/workflows/dependency-update.yml +++ b/.github/workflows/dependency-update.yml @@ -23,7 +23,7 @@ jobs: steps: - name: Check out code - uses: actions/checkout@v4 + uses: actions/checkout@v6 with: token: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/helm-lint.yml b/.github/workflows/helm-lint.yml index 772d82e29..5d0c4fc2d 100644 --- a/.github/workflows/helm-lint.yml +++ b/.github/workflows/helm-lint.yml @@ -15,7 +15,7 @@ jobs: steps: - name: Check out code - uses: actions/checkout@v4 + uses: actions/checkout@v6 - name: Set up Helm uses: azure/setup-helm@v4 diff --git a/.github/workflows/pre-release.yml b/.github/workflows/pre-release.yml index 1d3933436..3145ccd19 100644 --- a/.github/workflows/pre-release.yml +++ b/.github/workflows/pre-release.yml @@ -25,7 +25,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Check out code - uses: actions/checkout@v4 + uses: actions/checkout@v6 - name: Set up Go uses: actions/setup-go@v5 @@ -58,7 +58,7 @@ jobs: steps: - name: Check out code - uses: actions/checkout@v4 + uses: actions/checkout@v6 with: fetch-depth: 0 @@ -153,7 +153,7 @@ jobs: steps: - name: Check out code - uses: actions/checkout@v4 + uses: actions/checkout@v6 - name: Set up Go uses: actions/setup-go@v5 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index cc69c9a80..abde39365 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -32,7 +32,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Check out code - uses: actions/checkout@v4 + uses: actions/checkout@v6 - name: Set up Go uses: actions/setup-go@v5 @@ -65,7 +65,7 @@ jobs: steps: - name: Check out code - uses: actions/checkout@v4 + uses: actions/checkout@v6 - name: Set up Node.js uses: actions/setup-node@v4 @@ -106,7 +106,7 @@ jobs: steps: - name: Check out code - uses: actions/checkout@v4 + uses: actions/checkout@v6 with: fetch-depth: 0 @@ -188,7 +188,7 @@ jobs: steps: - name: Check out code - uses: actions/checkout@v4 + uses: actions/checkout@v6 - name: Set up Go uses: actions/setup-go@v5 @@ -244,7 +244,7 @@ jobs: steps: - name: Check out code - uses: actions/checkout@v4 + uses: actions/checkout@v6 with: fetch-depth: 0 diff --git a/cigen/render_gha.go b/cigen/render_gha.go index 83073e85f..9d0f93176 100644 --- a/cigen/render_gha.go +++ b/cigen/render_gha.go @@ -146,7 +146,7 @@ func renderGHAWorkflow(p *CIPlan, name string) (string, error) { // writeCheckoutStep emits the checkout step. func writeCheckoutStep(b *strings.Builder) { - b.WriteString(" - uses: actions/checkout@v4\n") + b.WriteString(" - uses: actions/checkout@v6\n") } // writeSetupWfctlStep emits the setup-wfctl action step. diff --git a/cigen/testdata/multisite/generated-infra.yml b/cigen/testdata/multisite/generated-infra.yml index b77e85e60..faeab5856 100644 --- a/cigen/testdata/multisite/generated-infra.yml +++ b/cigen/testdata/multisite/generated-infra.yml @@ -18,7 +18,7 @@ jobs: if: github.event_name == 'pull_request' runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - name: Install wfctl uses: GoCodeAlone/setup-wfctl@v1 with: @@ -58,7 +58,7 @@ jobs: SPACES_access_key: ${{ secrets.SPACES_access_key }} SPACES_secret_key: ${{ secrets.SPACES_secret_key }} steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - name: Install wfctl uses: GoCodeAlone/setup-wfctl@v1 with: @@ -97,7 +97,7 @@ jobs: SPACES_access_key: ${{ secrets.SPACES_access_key }} SPACES_secret_key: ${{ secrets.SPACES_secret_key }} steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - name: Install wfctl uses: GoCodeAlone/setup-wfctl@v1 with: diff --git a/cigen/validate.go b/cigen/validate.go new file mode 100644 index 000000000..e41ebff8e --- /dev/null +++ b/cigen/validate.go @@ -0,0 +1,205 @@ +package cigen + +import ( + "fmt" + "io" + "sort" + "strings" + + "github.com/rhysd/actionlint" + "gopkg.in/yaml.v3" +) + +// ValidationFinding describes one CI artifact validation problem. +type ValidationFinding struct { + Path string `json:"path"` + Code string `json:"code"` + Message string `json:"message"` +} + +// ValidationMessages formats validation findings for CLI output and tests. +func ValidationMessages(findings []ValidationFinding) []string { + messages := make([]string, 0, len(findings)) + for _, finding := range findings { + if finding.Path != "" { + messages = append(messages, fmt.Sprintf("%s: %s", finding.Path, finding.Message)) + continue + } + messages = append(messages, finding.Message) + } + return messages +} + +// ValidateRenderedFiles validates rendered CI provider artifacts for a platform. +func ValidateRenderedFiles(platform string, files map[string]string) []ValidationFinding { + if len(files) == 0 { + return []ValidationFinding{{Code: "missing_ci_artifact", Message: "no CI artifact files provided"}} + } + var findings []ValidationFinding + for _, path := range sortedFileKeys(files) { + content := files[path] + switch platform { + case "github_actions": + findings = append(findings, validateGitHubActions(path, content)...) + case "gitlab_ci": + findings = append(findings, validateGitLabCI(path, content)...) + case "jenkins": + findings = append(findings, validateJenkins(path, content)...) + case "circleci": + findings = append(findings, validateCircleCI(path, content)...) + default: + return []ValidationFinding{{ + Code: "unsupported_ci_platform", + Message: fmt.Sprintf("unsupported platform %q (supported: github_actions, gitlab_ci, jenkins, circleci)", platform), + }} + } + } + return findings +} + +func validateGitHubActions(path, content string) []ValidationFinding { + findings := lintGitHubActions(path, content) + node, yamlFindings := parseYAMLArtifact(path, content) + if len(yamlFindings) > 0 { + return append(findings, yamlFindings...) + } + for _, key := range []string{"on", "jobs"} { + if !yamlMapHasKey(node, key) { + findings = append(findings, ciFinding(path, "missing_github_actions_"+key, "GitHub Actions workflow missing top-level "+key)) + } + } + if jobs := yamlMapValue(node, "jobs"); jobs != nil && len(jobs.Content) == 0 { + findings = append(findings, ciFinding(path, "empty_github_actions_jobs", "GitHub Actions workflow jobs must not be empty")) + } + return findings +} + +func lintGitHubActions(path, content string) []ValidationFinding { + linter, err := actionlint.NewLinter(io.Discard, &actionlint.LinterOptions{ + Oneline: true, + Shellcheck: "", + Pyflakes: "", + }) + if err != nil { + return []ValidationFinding{ciFinding(path, "github_actions_linter_unavailable", fmt.Sprintf("GitHub Actions linter unavailable: %v", err))} + } + lintErrors, err := linter.Lint(path, []byte(content), nil) + if err != nil { + return []ValidationFinding{ciFinding(path, "github_actions_lint_error", fmt.Sprintf("GitHub Actions lint failed: %v", err))} + } + findings := make([]ValidationFinding, 0, len(lintErrors)) + for _, lintError := range lintErrors { + code := "actionlint" + if lintError.Kind != "" { + code = "actionlint_" + lintError.Kind + } + findings = append(findings, ciFinding(path, code, lintError.Error())) + } + return findings +} + +func validateGitLabCI(path, content string) []ValidationFinding { + node, findings := parseYAMLArtifact(path, content) + if len(findings) > 0 { + return findings + } + if !yamlMapHasKey(node, "stages") { + findings = append(findings, ciFinding(path, "missing_gitlab_stages", "GitLab CI config missing top-level stages")) + } + if !yamlMapHasAnyJob(node, "stages", "variables", "before_script", "default", "include", "workflow") { + findings = append(findings, ciFinding(path, "missing_gitlab_jobs", "GitLab CI config missing at least one job")) + } + if strings.Contains(content, "\nonly:") || strings.Contains(content, "\n only:") { + findings = append(findings, ciFinding(path, "deprecated_gitlab_only", "GitLab CI config uses deprecated only: syntax; use rules:")) + } + return findings +} + +func validateJenkins(path, content string) []ValidationFinding { + var findings []ValidationFinding + for _, want := range []string{"pipeline", "stages"} { + if !strings.Contains(content, want) { + findings = append(findings, ciFinding(path, "missing_jenkins_"+want, "Jenkinsfile missing "+want+" block")) + } + } + if strings.Contains(content, "wfctl deploy --image") || strings.Contains(content, "docker build") { + findings = append(findings, ciFinding(path, "legacy_jenkins_deploy", "Jenkinsfile contains retired template deploy/build stages")) + } + return findings +} + +func validateCircleCI(path, content string) []ValidationFinding { + node, findings := parseYAMLArtifact(path, content) + if len(findings) > 0 { + return findings + } + for _, key := range []string{"version", "jobs", "workflows"} { + if !yamlMapHasKey(node, key) { + findings = append(findings, ciFinding(path, "missing_circleci_"+key, "CircleCI config missing top-level "+key)) + } + } + if strings.Contains(content, "\nneeds:") || strings.Contains(content, "\n needs:") { + findings = append(findings, ciFinding(path, "github_actions_needs_in_circleci", "CircleCI config must use requires:, not GitHub Actions needs:")) + } + return findings +} + +func parseYAMLArtifact(path, content string) (*yaml.Node, []ValidationFinding) { + var node yaml.Node + if err := yaml.Unmarshal([]byte(content), &node); err != nil { + return nil, []ValidationFinding{ciFinding(path, "invalid_yaml", fmt.Sprintf("invalid YAML: %v", err))} + } + if len(node.Content) == 0 || node.Content[0].Kind != yaml.MappingNode { + return nil, []ValidationFinding{ciFinding(path, "invalid_yaml_document", "CI config must be a YAML mapping")} + } + return node.Content[0], nil +} + +func yamlMapHasKey(node *yaml.Node, key string) bool { + return yamlMapValue(node, key) != nil +} + +func yamlMapValue(node *yaml.Node, key string) *yaml.Node { + if node == nil || node.Kind != yaml.MappingNode { + return nil + } + for i := 0; i+1 < len(node.Content); i += 2 { + if node.Content[i].Value == key { + return node.Content[i+1] + } + } + return nil +} + +func yamlMapHasAnyJob(node *yaml.Node, reserved ...string) bool { + reservedSet := make(map[string]struct{}, len(reserved)) + for _, key := range reserved { + reservedSet[key] = struct{}{} + } + if node == nil || node.Kind != yaml.MappingNode { + return false + } + for i := 0; i+1 < len(node.Content); i += 2 { + key := node.Content[i].Value + if _, ok := reservedSet[key]; ok { + continue + } + if node.Content[i+1].Kind == yaml.MappingNode { + return true + } + } + return false +} + +func sortedFileKeys(files map[string]string) []string { + keys := make([]string, 0, len(files)) + for key := range files { + keys = append(keys, key) + } + sort.Strings(keys) + return keys +} + +func ciFinding(path, code, message string) ValidationFinding { + return ValidationFinding{Path: path, Code: code, Message: message} +} diff --git a/cigen/validate_test.go b/cigen/validate_test.go new file mode 100644 index 000000000..4bfb7e1f4 --- /dev/null +++ b/cigen/validate_test.go @@ -0,0 +1,71 @@ +package cigen_test + +import ( + "strings" + "testing" + + "github.com/GoCodeAlone/workflow/cigen" +) + +func TestValidateRenderedFilesAcceptsGeneratedPlatforms(t *testing.T) { + plan := richCIPlan() + cases := []struct { + platform string + render func(*cigen.CIPlan) (map[string]string, error) + }{ + {platform: "github_actions", render: cigen.RenderGitHubActions}, + {platform: "gitlab_ci", render: cigen.RenderGitLabCI}, + {platform: "jenkins", render: cigen.RenderJenkins}, + {platform: "circleci", render: cigen.RenderCircleCI}, + } + + for _, tc := range cases { + t.Run(tc.platform, func(t *testing.T) { + files, err := tc.render(plan) + if err != nil { + t.Fatalf("render: %v", err) + } + if findings := cigen.ValidateRenderedFiles(tc.platform, files); len(findings) > 0 { + t.Fatalf("expected generated %s files to validate, got %v", tc.platform, findings) + } + }) + } +} + +func TestValidateRenderedFilesRejectsWrongProviderShape(t *testing.T) { + files := map[string]string{ + ".github/workflows/deploy.yml": "stages:\n - plan\n", + } + findings := cigen.ValidateRenderedFiles("github_actions", files) + if len(findings) == 0 { + t.Fatal("expected GitHub Actions validation findings") + } + if got := strings.Join(cigen.ValidationMessages(findings), "\n"); !strings.Contains(got, "jobs") { + t.Fatalf("expected findings to mention missing jobs, got:\n%s", got) + } +} + +func TestValidateRenderedFilesUsesActionlintForGitHubActions(t *testing.T) { + files := map[string]string{ + ".github/workflows/deploy.yml": `name: deploy +on: + pull_request: +jobs: + plan: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v6 + run: echo invalid +`, + } + findings := cigen.ValidateRenderedFiles("github_actions", files) + if len(findings) == 0 { + t.Fatal("expected GitHub Actions validation findings") + } + for _, finding := range findings { + if strings.HasPrefix(finding.Code, "actionlint") { + return + } + } + t.Fatalf("expected actionlint finding, got %#v", findings) +} diff --git a/cmd/wfctl/ci.go b/cmd/wfctl/ci.go index e51e10732..72ce35362 100644 --- a/cmd/wfctl/ci.go +++ b/cmd/wfctl/ci.go @@ -46,7 +46,7 @@ Actions: plan Analyze config and emit a CIPlan JSON (platform-neutral) run Run CI phases (build, test, deploy) from workflow config init Generate bootstrap CI YAML for GitHub Actions or GitLab CI - validate Validate CI config sections + validate Validate workflow CI config sections or rendered CI provider artifacts Options: --platform CI platform: github_actions, gitlab_ci, jenkins, circleci (required for generate) @@ -172,6 +172,9 @@ func runCIGenerate(args []string) error { if renderErr != nil { return renderErr } + if findings := cigen.ValidateRenderedFiles(resolvedPlatform, files); len(findings) > 0 { + return fmt.Errorf("ci generate: rendered %s artifact failed validation: %s", resolvedPlatform, strings.Join(cigen.ValidationMessages(findings), "; ")) + } // --diff mode: print diff and optionally exit 1 if different if *diff { diff --git a/cmd/wfctl/ci_init.go b/cmd/wfctl/ci_init.go index 4b1464fa9..d114e086b 100644 --- a/cmd/wfctl/ci_init.go +++ b/cmd/wfctl/ci_init.go @@ -122,7 +122,7 @@ func generateGHABootstrap(cfg *config.WorkflowConfig) string { sb.WriteString(" build-test:\n") sb.WriteString(" runs-on: ubuntu-latest\n") sb.WriteString(" steps:\n") - sb.WriteString(" - uses: actions/checkout@v4\n") + sb.WriteString(" - uses: actions/checkout@v6\n") sb.WriteString(" - uses: GoCodeAlone/setup-wfctl@v1\n") sb.WriteString(" - run: wfctl ci run --phase build,test\n") @@ -145,7 +145,7 @@ func generateGHABootstrap(cfg *config.WorkflowConfig) string { sb.WriteString(" environment: " + envName + "\n") } sb.WriteString(" steps:\n") - sb.WriteString(" - uses: actions/checkout@v4\n") + sb.WriteString(" - uses: actions/checkout@v6\n") sb.WriteString(" - uses: GoCodeAlone/setup-wfctl@v1\n") if configHasMigrations(cfg) { sb.WriteString(" - run: mkdir -p .wfctl\n") @@ -270,7 +270,7 @@ func generateGHADeploy(cfg *config.WorkflowConfig) string { sb.WriteString(" if: github.event.workflow_run.conclusion == 'success'\n") sb.WriteString(" runs-on: ubuntu-latest\n") sb.WriteString(" steps:\n") - sb.WriteString(" - uses: actions/checkout@v4\n") + sb.WriteString(" - uses: actions/checkout@v6\n") sb.WriteString(" with:\n") sb.WriteString(" ref: " + sha + "\n") sb.WriteString(" - uses: GoCodeAlone/setup-wfctl@v1\n") @@ -299,7 +299,7 @@ func generateGHADeploy(cfg *config.WorkflowConfig) string { sb.WriteString(" environment: " + envName + "\n") } sb.WriteString(" steps:\n") - sb.WriteString(" - uses: actions/checkout@v4\n") + sb.WriteString(" - uses: actions/checkout@v6\n") sb.WriteString(" with:\n") sb.WriteString(" ref: " + sha + "\n") sb.WriteString(" - uses: GoCodeAlone/setup-wfctl@v1\n") @@ -358,7 +358,7 @@ func generateRetentionYML(cfg *config.WorkflowConfig) string { sb.WriteString(" prune:\n") sb.WriteString(" runs-on: ubuntu-latest\n") sb.WriteString(" steps:\n") - sb.WriteString(" - uses: actions/checkout@v4\n") + sb.WriteString(" - uses: actions/checkout@v6\n") sb.WriteString(" - uses: GoCodeAlone/setup-wfctl@v1\n") for _, e := range entries { sb.WriteString(" - run: wfctl registry prune --registry " + e.name + "\n") diff --git a/cmd/wfctl/ci_test.go b/cmd/wfctl/ci_test.go index 315959610..6926ac1eb 100644 --- a/cmd/wfctl/ci_test.go +++ b/cmd/wfctl/ci_test.go @@ -39,7 +39,7 @@ func TestGenerateGitHubActions(t *testing.T) { } markers := []string{ - "actions/checkout@v4", + "actions/checkout@v6", "GoCodeAlone/setup-wfctl@v1", "wfctl infra plan", "permissions", diff --git a/cmd/wfctl/ci_validate.go b/cmd/wfctl/ci_validate.go index e7f3e0b92..cb42f7724 100644 --- a/cmd/wfctl/ci_validate.go +++ b/cmd/wfctl/ci_validate.go @@ -9,6 +9,7 @@ import ( "strings" "time" + "github.com/GoCodeAlone/workflow/cigen" "github.com/GoCodeAlone/workflow/config" "github.com/GoCodeAlone/workflow/internal/legacyaws" "github.com/GoCodeAlone/workflow/internal/legacydo" @@ -32,11 +33,14 @@ func runCIValidate(args []string) error { override := fs.String("override", "", "Override token to bypass failed checks (3-word passphrase)") format := fs.String("format", "text", "Output format: text or json") pluginDir := fs.String("plugin-dir", "", "Directory of installed external plugins") + platform := fs.String("platform", "", "Validate rendered CI artifact platform: github_actions, gitlab_ci, jenkins, circleci") fs.Usage = func() { fmt.Fprintf(fs.Output(), `Usage: wfctl ci validate [options] + wfctl ci validate --platform Run full validation suite on a workflow config for CI pipelines. Includes structural validation, immutability checks, and pipeline reference analysis. +With --platform, validates rendered provider CI artifacts instead. Options: `) @@ -52,6 +56,10 @@ Options: return fmt.Errorf("at least one config file is required") } + if *platform != "" { + return runCIValidateArtifacts(*platform, files, *format) + } + if *pluginDir != "" { if err := schema.LoadPluginTypesFromDir(*pluginDir); err != nil { return fmt.Errorf("failed to load plugins: %w", err) @@ -119,6 +127,48 @@ Options: return nil } +func runCIValidateArtifacts(platform string, files []string, format string) error { + rendered := make(map[string]string, len(files)) + for _, file := range files { + data, err := os.ReadFile(file) + if err != nil { + rendered[file] = "" + continue + } + rendered[file] = string(data) + } + findings := cigen.ValidateRenderedFiles(platform, rendered) + for _, file := range files { + if _, err := os.Stat(file); err != nil { + findings = append(findings, cigen.ValidationFinding{ + Path: file, + Code: "read_ci_artifact", + Message: fmt.Sprintf("read CI artifact: %v", err), + }) + } + } + passed := len(findings) == 0 + switch format { + case "json": + enc := json.NewEncoder(os.Stdout) + enc.SetIndent("", " ") + if err := enc.Encode(map[string]any{"platform": platform, "findings": findings, "passed": passed}); err != nil { + return err + } + default: + for _, file := range files { + fmt.Printf(" VALIDATE %s (%s)\n", file, platform) + } + for _, finding := range findings { + fmt.Printf(" %s: %s\n", finding.Code, finding.Message) + } + } + if !passed { + return fmt.Errorf("%d file(s) failed ci validate", len(files)) + } + return nil +} + // ciValidateFile runs all validation checks on a single config file. func ciValidateFile(cfgPath string, strict, immutableConfig bool, immutableSections string) []error { var errs []error diff --git a/cmd/wfctl/ci_validate_artifacts_test.go b/cmd/wfctl/ci_validate_artifacts_test.go new file mode 100644 index 000000000..2d23f3129 --- /dev/null +++ b/cmd/wfctl/ci_validate_artifacts_test.go @@ -0,0 +1,24 @@ +package main + +import ( + "os" + "path/filepath" + "strings" + "testing" +) + +func TestRunCIValidatePlatformArtifactRejectsInvalidGitHubActions(t *testing.T) { + dir := t.TempDir() + path := filepath.Join(dir, "deploy.yml") + if err := os.WriteFile(path, []byte("stages:\n - plan\n"), 0600); err != nil { + t.Fatalf("write artifact: %v", err) + } + + err := runCIValidate([]string{"--platform", "github_actions", path}) + if err == nil { + t.Fatal("expected invalid GitHub Actions artifact to fail") + } + if !strings.Contains(err.Error(), "ci validate") { + t.Fatalf("expected ci validate error, got %v", err) + } +} diff --git a/cmd/wfctl/generate.go b/cmd/wfctl/generate.go index c77c0b773..087242919 100644 --- a/cmd/wfctl/generate.go +++ b/cmd/wfctl/generate.go @@ -163,7 +163,7 @@ func writeCIWorkflow(path string, features *projectFeatures) error { b.WriteString(" validate:\n") b.WriteString(" runs-on: ubuntu-latest\n") b.WriteString(" steps:\n") - b.WriteString(" - uses: actions/checkout@v4\n") + b.WriteString(" - uses: actions/checkout@v6\n") b.WriteString(" - uses: actions/setup-go@v5\n") b.WriteString(" with:\n") b.WriteString(" go-version: '1.22'\n") @@ -216,7 +216,7 @@ func writeCDWorkflow(path string, features *projectFeatures, registry, platforms b.WriteString(" contents: read\n") b.WriteString(" packages: write\n") b.WriteString(" steps:\n") - b.WriteString(" - uses: actions/checkout@v4\n") + b.WriteString(" - uses: actions/checkout@v6\n") b.WriteString(" - uses: actions/setup-go@v5\n") b.WriteString(" with:\n") b.WriteString(" go-version: '1.22'\n") @@ -266,7 +266,7 @@ jobs: permissions: contents: write steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - uses: actions/setup-go@v5 with: go-version: '1.22' diff --git a/cmd/wfctl/generate_test.go b/cmd/wfctl/generate_test.go index fa176c3aa..01a68e0f0 100644 --- a/cmd/wfctl/generate_test.go +++ b/cmd/wfctl/generate_test.go @@ -397,8 +397,8 @@ func TestCIWorkflowContent(t *testing.T) { } content := string(data) - if !strings.Contains(content, "actions/checkout@v4") { - t.Error("ci.yml should use actions/checkout@v4") + if !strings.Contains(content, "actions/checkout@v6") { + t.Error("ci.yml should use actions/checkout@v6") } if !strings.Contains(content, "actions/setup-go@v5") { t.Error("ci.yml should use actions/setup-go@v5") diff --git a/cmd/wfctl/templates/api-service/.github/workflows/ci.yml.tmpl b/cmd/wfctl/templates/api-service/.github/workflows/ci.yml.tmpl index e9cf4e9b0..5dbd6dca3 100644 --- a/cmd/wfctl/templates/api-service/.github/workflows/ci.yml.tmpl +++ b/cmd/wfctl/templates/api-service/.github/workflows/ci.yml.tmpl @@ -9,7 +9,7 @@ jobs: validate: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - uses: actions/setup-go@v5 with: go-version: '1.22' diff --git a/cmd/wfctl/templates/event-processor/.github/workflows/ci.yml.tmpl b/cmd/wfctl/templates/event-processor/.github/workflows/ci.yml.tmpl index e9cf4e9b0..5dbd6dca3 100644 --- a/cmd/wfctl/templates/event-processor/.github/workflows/ci.yml.tmpl +++ b/cmd/wfctl/templates/event-processor/.github/workflows/ci.yml.tmpl @@ -9,7 +9,7 @@ jobs: validate: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - uses: actions/setup-go@v5 with: go-version: '1.22' diff --git a/cmd/wfctl/templates/full-stack/.github/workflows/ci.yml.tmpl b/cmd/wfctl/templates/full-stack/.github/workflows/ci.yml.tmpl index 24b8a3a46..4476463be 100644 --- a/cmd/wfctl/templates/full-stack/.github/workflows/ci.yml.tmpl +++ b/cmd/wfctl/templates/full-stack/.github/workflows/ci.yml.tmpl @@ -9,7 +9,7 @@ jobs: validate: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - uses: actions/setup-go@v5 with: go-version: '1.22' diff --git a/cmd/wfctl/templates/plugin/.github/workflows/release.yml.tmpl b/cmd/wfctl/templates/plugin/.github/workflows/release.yml.tmpl index b7f85f0a8..0ac71fd60 100644 --- a/cmd/wfctl/templates/plugin/.github/workflows/release.yml.tmpl +++ b/cmd/wfctl/templates/plugin/.github/workflows/release.yml.tmpl @@ -9,7 +9,7 @@ jobs: permissions: contents: write steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - uses: actions/setup-go@v5 with: go-version: '1.22' diff --git a/cmd/wfctl/templates/ui-plugin/.github/workflows/release.yml.tmpl b/cmd/wfctl/templates/ui-plugin/.github/workflows/release.yml.tmpl index 086a31072..d9108440c 100644 --- a/cmd/wfctl/templates/ui-plugin/.github/workflows/release.yml.tmpl +++ b/cmd/wfctl/templates/ui-plugin/.github/workflows/release.yml.tmpl @@ -9,7 +9,7 @@ jobs: permissions: contents: write steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - uses: actions/setup-go@v5 with: go-version: '1.22' diff --git a/data/registry/.github/workflows/validate.yml b/data/registry/.github/workflows/validate.yml index 5e86fbcbb..36cfbdb8d 100644 --- a/data/registry/.github/workflows/validate.yml +++ b/data/registry/.github/workflows/validate.yml @@ -13,7 +13,7 @@ jobs: permissions: contents: read steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - name: Set up Node.js uses: actions/setup-node@v4 @@ -32,7 +32,7 @@ jobs: permissions: contents: read steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - name: Validate template YAML files run: bash scripts/validate-templates.sh diff --git a/docs/PLUGIN_RELEASE_GATES.md b/docs/PLUGIN_RELEASE_GATES.md index 69f823be9..936cb61fb 100644 --- a/docs/PLUGIN_RELEASE_GATES.md +++ b/docs/PLUGIN_RELEASE_GATES.md @@ -63,7 +63,7 @@ jobs: release: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 with: { fetch-depth: 0 } - uses: actions/setup-go@v5 with: { go-version-file: go.mod } diff --git a/docs/WFCTL.md b/docs/WFCTL.md index 1e5d7747c..5cbf41e53 100644 --- a/docs/WFCTL.md +++ b/docs/WFCTL.md @@ -2149,6 +2149,10 @@ Analyze a workflow config with the `cigen` engine (config → `CIPlan` → rende - A healthz smoke job when a `/healthz` endpoint is derivable Emits warnings for secret names that cannot be automatically derived. +Generated artifacts are validated before they are written. GitHub Actions uses +the embedded Go `actionlint` dependency; GitLab CI, Jenkins, and CircleCI use +offline provider-shape checks. Built-in validation does not require external +lint executables or live provider services. ``` wfctl ci generate [options] @@ -2229,6 +2233,39 @@ wfctl ci generate --platform github_actions --from-plan plan.json --write --- +### `ci validate` + +Validate workflow configs for CI use, or validate rendered CI provider artifacts +when `--platform` is supplied. GitHub Actions validation uses the embedded Go +`actionlint` dependency. GitLab CI, Jenkins, and CircleCI validation performs +offline structural checks without requiring provider CLIs or live lint APIs. + +``` +wfctl ci validate [options] +wfctl ci validate --platform +``` + +| Flag | Default | Description | +|------|---------|-------------| +| `--platform` | _(none)_ | Rendered CI artifact platform: `github_actions`, `gitlab_ci`, `jenkins`, `circleci` | +| `--format` | `text` | Output format: `text` or `json` | +| `--strict` | `false` | Strict workflow-config validation mode | +| `--immutable-config` | `false` | Fail workflow-config validation if the `ci:` section is absent or invalid | +| `--immutable-sections` | _(none)_ | Comma-separated workflow-config sections that must not be empty | +| `--plugin-dir` | _(none)_ | Directory of installed external plugins for workflow-config validation | + +Examples: + +```bash +wfctl ci validate deploy.yaml +wfctl ci validate --platform github_actions .github/workflows/deploy.yml +wfctl ci validate --platform gitlab_ci .gitlab-ci.yml +wfctl ci validate --platform jenkins Jenkinsfile +wfctl ci validate --platform circleci .circleci/config.yml +``` + +--- + ### `ci run` Execute CI phases (build, test, deploy) defined in the `ci:` section of a workflow config. @@ -2684,7 +2721,7 @@ Plugin CI should generate evidence with the released artifact, then update the r ```yaml steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - uses: GoCodeAlone/workflow/.github/actions/setup-wfctl@main with: version: v0.51.2 diff --git a/docs/manual/build-deploy/03-ci-deploy-environments.md b/docs/manual/build-deploy/03-ci-deploy-environments.md index 4b4b96094..296747c8b 100644 --- a/docs/manual/build-deploy/03-ci-deploy-environments.md +++ b/docs/manual/build-deploy/03-ci-deploy-environments.md @@ -73,7 +73,7 @@ deploy-staging: needs: [build-image] runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 with: ref: ${{ github.event.workflow_run.head_sha || github.sha }} - uses: GoCodeAlone/setup-wfctl@v1 @@ -96,7 +96,7 @@ repair-staging-migrations: environment: staging runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - uses: GoCodeAlone/setup-wfctl@v1 - run: | wfctl migrate repair-dirty --config infra.yaml --env staging \ diff --git a/docs/tutorials/deploy-pipeline.md b/docs/tutorials/deploy-pipeline.md index f28c4ec6a..4c7b6e3be 100644 --- a/docs/tutorials/deploy-pipeline.md +++ b/docs/tutorials/deploy-pipeline.md @@ -355,21 +355,21 @@ jobs: build-test: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - uses: GoCodeAlone/setup-wfctl@v1 - run: wfctl ci run --phase build,test deploy-staging: runs-on: ubuntu-latest needs: [build-test] steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - uses: GoCodeAlone/setup-wfctl@v1 - run: wfctl ci run --phase deploy --env staging deploy-prod: runs-on: ubuntu-latest needs: [build-test] steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - uses: GoCodeAlone/setup-wfctl@v1 - run: wfctl ci run --phase deploy --env prod ``` @@ -389,7 +389,7 @@ Insert a `build-image` job between `build-test` and the deploy jobs. This compil outputs: sha: ${{ steps.meta.outputs.sha }} steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - uses: actions/setup-go@v5 with: go-version: '1.26' @@ -413,7 +413,7 @@ Reference the SHA in your deploy jobs via the `IMAGE_SHA` environment variable ( deploy-staging: needs: [build-image] steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - uses: GoCodeAlone/setup-wfctl@v1 - run: wfctl ci run --phase deploy --env staging env: @@ -453,7 +453,7 @@ Change `deploy-prod.needs` from `[build-test]` to `[deploy-staging]`. This makes runs-on: ubuntu-latest needs: [build-image] # waits for image push steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - uses: GoCodeAlone/setup-wfctl@v1 - run: wfctl ci run --phase deploy --env staging env: @@ -464,7 +464,7 @@ Change `deploy-prod.needs` from `[build-test]` to `[deploy-staging]`. This makes runs-on: ubuntu-latest needs: [deploy-staging] # auto-promotes after staging succeeds steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - uses: GoCodeAlone/setup-wfctl@v1 - run: wfctl ci run --phase deploy --env prod env: @@ -508,7 +508,7 @@ When `requireApproval: true`, `wfctl ci init` emits `environment: prod` in the g needs: [deploy-staging] environment: prod # GitHub waits for environment protection approval steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - uses: GoCodeAlone/setup-wfctl@v1 - run: wfctl ci run --phase deploy --env prod ``` diff --git a/go.mod b/go.mod index fae652894..6bb3b0109 100644 --- a/go.mod +++ b/go.mod @@ -38,6 +38,7 @@ require ( github.com/prometheus/client_golang v1.23.2 github.com/prometheus/client_model v0.6.2 github.com/redis/go-redis/v9 v9.19.0 + github.com/rhysd/actionlint v1.7.12 github.com/santhosh-tekuri/jsonschema/v6 v6.0.2 github.com/stretchr/testify v1.11.1 github.com/stripe/stripe-go/v82 v82.5.1 @@ -96,6 +97,7 @@ require ( github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/bits-and-blooms/bitset v1.24.4 // indirect + github.com/bmatcuk/doublestar/v4 v4.10.0 // indirect github.com/bytedance/gopkg v0.1.4 // indirect github.com/bytedance/sonic v1.15.1 // indirect github.com/bytedance/sonic/loader v0.5.1 // indirect @@ -197,6 +199,7 @@ require ( github.com/mailru/easyjson v0.9.2 // indirect github.com/mattn/go-colorable v0.1.14 // indirect github.com/mattn/go-runewidth v0.0.23 // indirect + github.com/mattn/go-shellwords v1.0.12 // indirect github.com/miekg/dns v1.1.72 // indirect github.com/mitchellh/go-homedir v1.1.0 // indirect github.com/mitchellh/go-testing-interface v1.14.1 // indirect @@ -262,6 +265,7 @@ require ( go.uber.org/zap v1.28.0 // indirect go.yaml.in/yaml/v2 v2.4.4 // indirect go.yaml.in/yaml/v3 v3.0.4 // indirect + go.yaml.in/yaml/v4 v4.0.0-rc.3 // indirect golang.org/x/arch v0.27.0 // indirect golang.org/x/net v0.54.0 // indirect golang.org/x/sys v0.45.0 // indirect diff --git a/go.sum b/go.sum index 7ccab5c80..099e7dc78 100644 --- a/go.sum +++ b/go.sum @@ -99,6 +99,8 @@ github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6r github.com/bits-and-blooms/bitset v1.12.0/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8= github.com/bits-and-blooms/bitset v1.24.4 h1:95H15Og1clikBrKr/DuzMXkQzECs1M6hhoGXLwLQOZE= github.com/bits-and-blooms/bitset v1.24.4/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8= +github.com/bmatcuk/doublestar/v4 v4.10.0 h1:zU9WiOla1YA122oLM6i4EXvGW62DvKZVxIe6TYWexEs= +github.com/bmatcuk/doublestar/v4 v4.10.0/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTSPVIjEY1Wr7jzc= github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs= github.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c= github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA= @@ -483,6 +485,8 @@ github.com/mattn/go-isatty v0.0.22 h1:j8l17JJ9i6VGPUFUYoTUKPSgKe/83EYU2zBC7YNKMw github.com/mattn/go-isatty v0.0.22/go.mod h1:ZXfXG4SQHsB/w3ZeOYbR0PrPwLy+n6xiMrJlRFqopa4= github.com/mattn/go-runewidth v0.0.23 h1:7ykA0T0jkPpzSvMS5i9uoNn2Xy3R383f9HDx3RybWcw= github.com/mattn/go-runewidth v0.0.23/go.mod h1:XBkDxAl56ILZc9knddidhrOlY5R/pDhgLpndooCuJAs= +github.com/mattn/go-shellwords v1.0.12 h1:M2zGm7EW6UQJvDeQxo4T51eKPurbeFbe8WtebGE2xrk= +github.com/mattn/go-shellwords v1.0.12/go.mod h1:EZzvwXDESEeg03EKmM+RmDnNOPKG4lLtQsUlTZDWQ8Y= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/miekg/dns v1.1.72 h1:vhmr+TF2A3tuoGNkLDFK9zi36F2LS+hKTRW0Uf8kbzI= github.com/miekg/dns v1.1.72/go.mod h1:+EuEPhdHOsfk6Wk5TT2CzssZdqkmFhf8r+aVyDEToIs= @@ -605,6 +609,8 @@ github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94 github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= github.com/reugn/go-quartz v0.15.2 h1:IQUnwTtNURVtdcwH4CJhFH3dXAUwP2fXZaNjPp+sJAY= github.com/reugn/go-quartz v0.15.2/go.mod h1:00DVnBKq2Fxag/HlR9mGXjmHNlMFQ1n/LNM+Fn0jUaE= +github.com/rhysd/actionlint v1.7.12 h1:vQ4GeJN86C0QH+gTUQcs8McmK62OLT3kmakPMtEWYnY= +github.com/rhysd/actionlint v1.7.12/go.mod h1:krOUhujIsJusovkaYzQ/VNH8PFexjNKqU0q5XI/4w+g= github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs= @@ -775,6 +781,8 @@ go.yaml.in/yaml/v2 v2.4.4 h1:tuyd0P+2Ont/d6e2rl3be67goVK4R6deVxCUX5vyPaQ= go.yaml.in/yaml/v2 v2.4.4/go.mod h1:gMZqIpDtDqOfM0uNfy0SkpRhvUryYH0Z6wdMYcacYXQ= go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc= go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= +go.yaml.in/yaml/v4 v4.0.0-rc.3 h1:3h1fjsh1CTAPjW7q/EMe+C8shx5d8ctzZTrLcs/j8Go= +go.yaml.in/yaml/v4 v4.0.0-rc.3/go.mod h1:aZqd9kCMsGL7AuUv/m/PvWLdg5sjJsZ4oHDEnfPPfY0= golang.org/x/arch v0.27.0 h1:0WNVcR8u9yFz8j5FvdHpgwNp3FS5U4guYdzHwEiGjoU= golang.org/x/arch v0.27.0/go.mod h1:0X+GdSIP+kL5wPmpK7sdkEVTt2XoYP0cSjQSbZBwOi8= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= diff --git a/mcp/scaffold_tools.go b/mcp/scaffold_tools.go index 6cb63b1d6..990020d21 100644 --- a/mcp/scaffold_tools.go +++ b/mcp/scaffold_tools.go @@ -721,7 +721,7 @@ func generateGitHubActionsBootstrap(cfg *config.WorkflowConfig) string { b.WriteString(" build-test:\n") b.WriteString(" runs-on: ubuntu-latest\n") b.WriteString(" steps:\n") - b.WriteString(" - uses: actions/checkout@v4\n") + b.WriteString(" - uses: actions/checkout@v6\n") b.WriteString(" - uses: GoCodeAlone/setup-wfctl@v1\n") b.WriteString(" - run: wfctl ci run --phase build,test\n") @@ -737,7 +737,7 @@ func generateGitHubActionsBootstrap(cfg *config.WorkflowConfig) string { } b.WriteString(" if: github.ref == 'refs/heads/main'\n") b.WriteString(" steps:\n") - b.WriteString(" - uses: actions/checkout@v4\n") + b.WriteString(" - uses: actions/checkout@v6\n") b.WriteString(" - uses: GoCodeAlone/setup-wfctl@v1\n") fmt.Fprintf(&b, " - run: wfctl ci run --phase deploy --env %s\n", envName) } diff --git a/mcp/scaffold_tools_test.go b/mcp/scaffold_tools_test.go index 287205dcb..d4cd0c814 100644 --- a/mcp/scaffold_tools_test.go +++ b/mcp/scaffold_tools_test.go @@ -211,7 +211,7 @@ ci: if !strings.Contains(text, "wfctl ci run") { t.Error("expected wfctl ci run call") } - if !strings.Contains(text, "actions/checkout@v4") { + if !strings.Contains(text, "actions/checkout@v6") { t.Error("expected checkout action") } } diff --git a/mcp/wfctl_tools.go b/mcp/wfctl_tools.go index a968306a7..590ee4ed5 100644 --- a/mcp/wfctl_tools.go +++ b/mcp/wfctl_tools.go @@ -1711,7 +1711,7 @@ func mcpGenerateCDWorkflow(features *mcpProjectFeatures, registry, platforms str b.WriteString(" contents: read\n") b.WriteString(" packages: write\n") b.WriteString(" steps:\n") - b.WriteString(" - uses: actions/checkout@v4\n") + b.WriteString(" - uses: actions/checkout@v6\n") b.WriteString(" - uses: actions/setup-go@v5\n") b.WriteString(" with:\n") b.WriteString(" go-version: '1.22'\n") @@ -1914,7 +1914,7 @@ jobs: permissions: contents: write steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - uses: actions/setup-go@v5 with: go-version: '1.22' diff --git a/plugin/sdk/generator.go b/plugin/sdk/generator.go index aad80efdb..5d90f03a4 100644 --- a/plugin/sdk/generator.go +++ b/plugin/sdk/generator.go @@ -589,7 +589,7 @@ jobs: test: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - uses: actions/setup-go@v5 with: go-version: '%s' @@ -612,7 +612,7 @@ jobs: release: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 with: fetch-depth: 0 - uses: actions/setup-go@v5