Phase 1: go-io/go-log → core.Fs{}, core.E(), core.Error/Info/Warn
Phase 2: strings/fmt → core.Contains, core.Sprintf, core.Split etc
Phase 3: embed.FS → core.Mount/core.Embed, core.Extract
Phase 4: cmd/main.go → core.Command(), c.Cli().Run(), no cli package
All packages migrated:
- pkg/lib (Codex): core.Mount, core.Extract, Result returns, AX comments
- pkg/setup (Codex): core.Fs, core.E, fixed missing lib helpers
- pkg/brain (Codex): Core primitives, AX comments
- pkg/monitor (Codex): Core string/logging primitives
- pkg/agentic (Codex): 20 files, Core primitives throughout
- cmd/main.go: pure Core CLI, no fmt/log/filepath/strings/cli
Remaining stdlib: path/filepath (Core doesn't wrap OS paths),
fmt.Sscanf/strings.Map (no Core equivalent).
Co-Authored-By: Virgil <virgil@lethean.io>
208 lines
4.6 KiB
Go
208 lines
4.6 KiB
Go
// SPDX-License-Identifier: EUPL-1.2
|
|
|
|
package setup
|
|
|
|
import (
|
|
"os/exec"
|
|
"path/filepath"
|
|
"strings"
|
|
|
|
core "dappco.re/go/core"
|
|
"gopkg.in/yaml.v3"
|
|
)
|
|
|
|
// ConfigData holds the data passed to config templates.
|
|
type ConfigData struct {
|
|
Name string
|
|
Description string
|
|
Type string
|
|
Module string
|
|
Repository string
|
|
GoVersion string
|
|
Targets []Target
|
|
Commands []Command
|
|
Env map[string]string
|
|
}
|
|
|
|
// Target is a build target (os/arch pair).
|
|
type Target struct {
|
|
OS string
|
|
Arch string
|
|
}
|
|
|
|
// Command is a named runnable command.
|
|
type Command struct {
|
|
Name string
|
|
Run string
|
|
}
|
|
|
|
type configSection struct {
|
|
Key string
|
|
Values []configValue
|
|
}
|
|
|
|
type configValue struct {
|
|
Key string
|
|
Value any
|
|
}
|
|
|
|
// GenerateBuildConfig renders a build.yaml for the detected project type.
|
|
//
|
|
// content, err := setup.GenerateBuildConfig("/repo", setup.TypeGo)
|
|
func GenerateBuildConfig(path string, projType ProjectType) (string, error) {
|
|
name := filepath.Base(path)
|
|
sections := []configSection{
|
|
{
|
|
Key: "project",
|
|
Values: []configValue{
|
|
{Key: "name", Value: name},
|
|
{Key: "type", Value: string(projType)},
|
|
},
|
|
},
|
|
}
|
|
|
|
switch projType {
|
|
case TypeGo, TypeWails:
|
|
sections = append(sections, configSection{
|
|
Key: "build",
|
|
Values: []configValue{
|
|
{Key: "main", Value: "./cmd/" + name},
|
|
{Key: "binary", Value: name},
|
|
{Key: "cgo", Value: false},
|
|
},
|
|
})
|
|
case TypePHP:
|
|
sections = append(sections, configSection{
|
|
Key: "build",
|
|
Values: []configValue{
|
|
{Key: "dockerfile", Value: "Dockerfile"},
|
|
{Key: "image", Value: name},
|
|
},
|
|
})
|
|
case TypeNode:
|
|
sections = append(sections, configSection{
|
|
Key: "build",
|
|
Values: []configValue{
|
|
{Key: "script", Value: "npm run build"},
|
|
{Key: "output", Value: "dist"},
|
|
},
|
|
})
|
|
}
|
|
|
|
return renderConfig(name+" build configuration", sections)
|
|
}
|
|
|
|
// GenerateTestConfig renders a test.yaml for the detected project type.
|
|
//
|
|
// content, err := setup.GenerateTestConfig(setup.TypeGo)
|
|
func GenerateTestConfig(projType ProjectType) (string, error) {
|
|
var sections []configSection
|
|
|
|
switch projType {
|
|
case TypeGo, TypeWails:
|
|
sections = []configSection{
|
|
{
|
|
Key: "commands",
|
|
Values: []configValue{
|
|
{Key: "unit", Value: "go test ./..."},
|
|
{Key: "coverage", Value: "go test -coverprofile=coverage.out ./..."},
|
|
{Key: "race", Value: "go test -race ./..."},
|
|
},
|
|
},
|
|
}
|
|
case TypePHP:
|
|
sections = []configSection{
|
|
{
|
|
Key: "commands",
|
|
Values: []configValue{
|
|
{Key: "unit", Value: "vendor/bin/pest --parallel"},
|
|
{Key: "lint", Value: "vendor/bin/pint --test"},
|
|
},
|
|
},
|
|
}
|
|
case TypeNode:
|
|
sections = []configSection{
|
|
{
|
|
Key: "commands",
|
|
Values: []configValue{
|
|
{Key: "unit", Value: "npm test"},
|
|
{Key: "lint", Value: "npm run lint"},
|
|
},
|
|
},
|
|
}
|
|
}
|
|
|
|
return renderConfig("Test configuration", sections)
|
|
}
|
|
|
|
func renderConfig(comment string, sections []configSection) (string, error) {
|
|
var builder strings.Builder
|
|
|
|
if comment != "" {
|
|
builder.WriteString("# ")
|
|
builder.WriteString(comment)
|
|
builder.WriteString("\n\n")
|
|
}
|
|
|
|
for idx, section := range sections {
|
|
builder.WriteString(section.Key)
|
|
builder.WriteString(":\n")
|
|
|
|
for _, value := range section.Values {
|
|
scalar, err := yaml.Marshal(value.Value)
|
|
if err != nil {
|
|
return "", core.E("setup.renderConfig", "marshal "+section.Key+"."+value.Key, err)
|
|
}
|
|
|
|
builder.WriteString(" ")
|
|
builder.WriteString(value.Key)
|
|
builder.WriteString(": ")
|
|
builder.WriteString(strings.TrimSpace(string(scalar)))
|
|
builder.WriteString("\n")
|
|
}
|
|
|
|
if idx < len(sections)-1 {
|
|
builder.WriteString("\n")
|
|
}
|
|
}
|
|
|
|
return builder.String(), nil
|
|
}
|
|
|
|
// detectGitRemote extracts owner/repo from git remote origin.
|
|
func detectGitRemote(path string) string {
|
|
cmd := exec.Command("git", "remote", "get-url", "origin")
|
|
cmd.Dir = path
|
|
output, err := cmd.Output()
|
|
if err != nil {
|
|
return ""
|
|
}
|
|
url := strings.TrimSpace(string(output))
|
|
|
|
// SSH: git@github.com:owner/repo.git or ssh://git@forge.lthn.ai:2223/core/agent.git
|
|
if strings.Contains(url, ":") {
|
|
parts := strings.SplitN(url, ":", 2)
|
|
if len(parts) == 2 {
|
|
repo := parts[1]
|
|
repo = strings.TrimSuffix(repo, ".git")
|
|
// Handle port in SSH URL (ssh://git@host:port/path)
|
|
if strings.Contains(repo, "/") {
|
|
segments := strings.SplitN(repo, "/", 2)
|
|
if len(segments) == 2 && strings.ContainsAny(segments[0], "0123456789") {
|
|
repo = segments[1]
|
|
}
|
|
}
|
|
return repo
|
|
}
|
|
}
|
|
|
|
// HTTPS: https://github.com/owner/repo.git
|
|
for _, host := range []string{"github.com/", "forge.lthn.ai/"} {
|
|
if idx := strings.Index(url, host); idx >= 0 {
|
|
repo := url[idx+len(host):]
|
|
return strings.TrimSuffix(repo, ".git")
|
|
}
|
|
}
|
|
|
|
return ""
|
|
}
|