fix(ax): align setup package docs and tests

Co-Authored-By: Virgil <virgil@lethean.io>
This commit is contained in:
Virgil 2026-03-29 21:34:46 +00:00
parent e8a46c2f95
commit ad3373b134
10 changed files with 161 additions and 206 deletions

View file

@ -7,7 +7,9 @@ import (
"gopkg.in/yaml.v3"
)
// ConfigData holds the data passed to config templates.
// ConfigData supplies values when setup renders workspace templates.
//
// data := setup.ConfigData{Name: "agent", Type: "go", Repository: "core/agent"}
type ConfigData struct {
Name string
Description string
@ -20,13 +22,17 @@ type ConfigData struct {
Env map[string]string
}
// Target is a build target (os/arch pair).
// Target describes one build target.
//
// target := setup.Target{OS: "linux", Arch: "amd64"}
type Target struct {
OS string
Arch string
}
// Command is a named runnable command.
// Command defines one named command in generated config.
//
// command := setup.Command{Name: "unit", Run: "go test ./..."}
type Command struct {
Name string
Run string
@ -42,9 +48,9 @@ type configValue struct {
Value any
}
// GenerateBuildConfig renders a build.yaml for the detected project type.
// GenerateBuildConfig renders `build.yaml` content for a detected repo type.
//
// content, err := setup.GenerateBuildConfig("/repo", setup.TypeGo)
// content, err := setup.GenerateBuildConfig("/srv/repos/agent", setup.TypeGo)
func GenerateBuildConfig(path string, projType ProjectType) (string, error) {
name := core.PathBase(path)
sections := []configSection{
@ -88,7 +94,7 @@ func GenerateBuildConfig(path string, projType ProjectType) (string, error) {
return renderConfig(core.Concat(name, " build configuration"), sections)
}
// GenerateTestConfig renders a test.yaml for the detected project type.
// GenerateTestConfig renders `test.yaml` content for a detected repo type.
//
// content, err := setup.GenerateTestConfig(setup.TypeGo)
func GenerateTestConfig(projType ProjectType) (string, error) {

View file

@ -6,34 +6,49 @@ import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestConfig_GenerateBuildConfig_Good(t *testing.T) {
func TestConfig_GenerateBuildConfig_Good_Go(t *testing.T) {
config, err := GenerateBuildConfig("/tmp/myapp", TypeGo)
assert.NoError(t, err)
require.NoError(t, err)
assert.Contains(t, config, "# myapp build configuration")
assert.Contains(t, config, "type: go")
assert.Contains(t, config, "name: myapp")
assert.Contains(t, config, "main: ./cmd/myapp")
assert.Contains(t, config, "cgo: false")
}
func TestConfig_GenerateBuildConfig_Bad_Unknown(t *testing.T) {
config, err := GenerateBuildConfig("/tmp/myapp", TypeUnknown)
assert.NoError(t, err)
require.NoError(t, err)
assert.NotEmpty(t, config)
}
func TestConfig_GenerateTestConfig_Good(t *testing.T) {
func TestConfig_GenerateTestConfig_Good_Go(t *testing.T) {
config, err := GenerateTestConfig(TypeGo)
assert.NoError(t, err)
require.NoError(t, err)
assert.Contains(t, config, "go test")
}
func TestConfig_ParseGitRemote_Good(t *testing.T) {
assert.Equal(t, "core/go-io", parseGitRemote("https://forge.lthn.ai/core/go-io.git"))
assert.Equal(t, "core/go-io", parseGitRemote("git@forge.lthn.ai:core/go-io.git"))
func TestConfig_ParseGitRemote_Good_CommonFormats(t *testing.T) {
tests := map[string]string{
"https://github.com/dAppCore/go-io.git": "dAppCore/go-io",
"git@github.com:dAppCore/go-io.git": "dAppCore/go-io",
"ssh://git@forge.lthn.ai:2223/core/agent.git": "core/agent",
"ssh://git@forge.lthn.ai:2223/core/agent": "core/agent",
"git@forge.lthn.ai:core/agent.git": "core/agent",
"/srv/git/core/agent.git": "srv/git/core/agent",
}
for remote, want := range tests {
assert.Equal(t, want, parseGitRemote(remote), remote)
}
}
func TestConfig_ParseGitRemote_Ugly_Empty(t *testing.T) {
func TestConfig_ParseGitRemote_Bad_Empty(t *testing.T) {
assert.Equal(t, "", parseGitRemote(""))
assert.Equal(t, "", parseGitRemote("origin"))
}
func TestConfig_TrimRemotePath_Good(t *testing.T) {
@ -48,4 +63,3 @@ func TestConfig_RenderConfig_Good(t *testing.T) {
assert.NoError(t, err)
assert.Contains(t, result, "name: test")
}

View file

@ -1,13 +1,18 @@
// SPDX-License-Identifier: EUPL-1.2
// Package setup provides workspace setup and scaffolding using lib templates.
// Package setup provisions `.core/` files and workspace scaffolds for a repo.
//
// svc := core.ServiceFor[*setup.Service](core.New(core.WithService(setup.Register)), "setup")
package setup
import (
core "dappco.re/go/core"
)
// ProjectType identifies what kind of project lives at a path.
// ProjectType records what setup detected in a repository path.
//
// projType := setup.Detect("/srv/repos/agent")
// if projType == setup.TypeGo { /* generate Go defaults */ }
type ProjectType string
const (
@ -21,7 +26,7 @@ const (
// fs provides unrestricted filesystem access for setup operations.
var fs = (&core.Fs{}).NewUnrestricted()
// Detect identifies the project type from files present at the given path.
// Detect inspects a repository path and returns the primary project type.
//
// projType := setup.Detect("./repo")
func Detect(path string) ProjectType {
@ -43,7 +48,7 @@ func Detect(path string) ProjectType {
return TypeUnknown
}
// DetectAll returns all project types found at the path (polyglot repos).
// DetectAll returns every detected project type for a polyglot repository.
//
// types := setup.DetectAll("./repo")
func DetectAll(path string) []ProjectType {

View file

@ -7,32 +7,51 @@ import (
core "dappco.re/go/core"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestDetect_Detect_Good_Go(t *testing.T) {
dir := t.TempDir()
fs.Write(core.JoinPath(dir, "go.mod"), "module test")
require.True(t, fs.WriteMode(core.JoinPath(dir, "go.mod"), "module test\n", 0644).OK)
assert.Equal(t, TypeGo, Detect(dir))
}
func TestDetect_Detect_Good_PHP(t *testing.T) {
dir := t.TempDir()
fs.Write(core.JoinPath(dir, "composer.json"), "{}")
require.True(t, fs.WriteMode(core.JoinPath(dir, "composer.json"), "{}", 0644).OK)
assert.Equal(t, TypePHP, Detect(dir))
}
func TestDetect_Detect_Good_Node(t *testing.T) {
dir := t.TempDir()
require.True(t, fs.WriteMode(core.JoinPath(dir, "package.json"), `{"name":"test"}`, 0644).OK)
assert.Equal(t, TypeNode, Detect(dir))
}
func TestDetect_Detect_Good_Wails(t *testing.T) {
dir := t.TempDir()
require.True(t, fs.WriteMode(core.JoinPath(dir, "wails.json"), `{}`, 0644).OK)
assert.Equal(t, TypeWails, Detect(dir))
}
func TestDetect_Detect_Bad_Unknown(t *testing.T) {
dir := t.TempDir()
assert.Equal(t, TypeUnknown, Detect(dir))
}
func TestDetect_DetectAll_Good(t *testing.T) {
func TestDetect_DetectAll_Good_Polyglot(t *testing.T) {
dir := t.TempDir()
fs.Write(core.JoinPath(dir, "go.mod"), "module test")
fs.Write(core.JoinPath(dir, "package.json"), "{}")
require.True(t, fs.WriteMode(core.JoinPath(dir, "go.mod"), "module test\n", 0644).OK)
require.True(t, fs.WriteMode(core.JoinPath(dir, "package.json"), `{"name":"test"}`, 0644).OK)
types := DetectAll(dir)
assert.Contains(t, types, TypeGo)
assert.Contains(t, types, TypeNode)
assert.NotContains(t, types, TypePHP)
}
func TestDetect_DetectAll_Bad_Empty(t *testing.T) {
dir := t.TempDir()
assert.Empty(t, DetectAll(dir))
}
func TestDetect_AbsolutePath_Ugly_Empty(t *testing.T) {

View file

@ -8,20 +8,23 @@ import (
core "dappco.re/go/core"
)
// SetupOptions configures the setup service.
// SetupOptions carries service-level setup configuration.
//
// opts := setup.SetupOptions{}
type SetupOptions struct{}
// Service provides workspace setup and scaffolding as a Core service.
// Registers as "setup" — use s.Core().Process() for git operations.
// Service exposes workspace setup through Core service registration.
//
// core.New(core.WithService(setup.Register))
// c := core.New(core.WithService(setup.Register))
// svc, _ := core.ServiceFor[*setup.Service](c, "setup")
type Service struct {
*core.ServiceRuntime[SetupOptions]
}
// Register is the WithService factory for setup.
// Register wires the setup service into Core.
//
// core.New(core.WithService(setup.Register))
// c := core.New(core.WithService(setup.Register))
// svc, _ := core.ServiceFor[*setup.Service](c, "setup")
func Register(c *core.Core) core.Result {
svc := &Service{
ServiceRuntime: core.NewServiceRuntime(c, SetupOptions{}),
@ -29,14 +32,16 @@ func Register(c *core.Core) core.Result {
return core.Result{Value: svc, OK: true}
}
// OnStartup implements core.Startable.
// OnStartup keeps the setup service ready for Core startup hooks.
//
// result := svc.OnStartup(context.Background())
func (s *Service) OnStartup(ctx context.Context) core.Result {
return core.Result{OK: true}
}
// DetectGitRemote extracts owner/repo from git remote origin via Core Process.
// DetectGitRemote reads `origin` and returns `owner/repo` when available.
//
// remote := svc.DetectGitRemote("/repo")
// remote := svc.DetectGitRemote("/srv/repos/agent")
func (s *Service) DetectGitRemote(path string) string {
r := s.Core().Process().RunIn(context.Background(), path, "git", "remote", "get-url", "origin")
if !r.OK {

View file

@ -3,6 +3,7 @@
package setup
import (
"context"
"testing"
core "dappco.re/go/core"
@ -16,7 +17,15 @@ func TestService_Register_Good(t *testing.T) {
assert.NotNil(t, svc)
}
func TestService_DetectGitRemote_Good(t *testing.T) {
func TestService_OnStartup_Good(t *testing.T) {
c := core.New()
svc := &Service{ServiceRuntime: core.NewServiceRuntime(c, SetupOptions{})}
result := svc.OnStartup(context.Background())
assert.True(t, result.OK)
}
func TestService_DetectGitRemote_Good_NonGitDir(t *testing.T) {
c := core.New()
svc := &Service{ServiceRuntime: core.NewServiceRuntime(c, SetupOptions{})}
// Non-git dir returns empty

View file

@ -7,9 +7,9 @@ import (
core "dappco.re/go/core"
)
// Options controls setup behaviour.
// Options controls one setup run.
//
// err := svc.Run(setup.Options{Path: ".", Force: true})
// err := svc.Run(setup.Options{Path: ".", Template: "auto", Force: true})
type Options struct {
Path string // Target directory (default: cwd)
DryRun bool // Preview only, don't write
@ -17,11 +17,9 @@ type Options struct {
Template string // Workspace template or compatibility alias (default, review, security, agent, go, php, gui, auto)
}
// Run performs the workspace setup at the given path.
// It detects the project type, generates .core/ configs,
// and optionally scaffolds a workspace from a dir template.
// Run generates `.core/` files and optional workspace scaffolding for a repo.
//
// svc.Run(setup.Options{Path: ".", Template: "auto"})
// err := svc.Run(setup.Options{Path: ".", Template: "auto"})
func (s *Service) Run(opts Options) error {
if opts.Path == "" {
opts.Path = core.Env("DIR_CWD")

View file

@ -6,31 +6,17 @@ import (
core "dappco.re/go/core"
)
func ExampleDetect() {
dir := (&core.Fs{}).NewUnrestricted().TempDir("example")
defer (&core.Fs{}).NewUnrestricted().DeleteAll(dir)
// Empty dir — unknown type
core.Println(Detect(dir))
// Output: unknown
func Example_defaultBuildCommand() {
core.Println(defaultBuildCommand(TypeGo))
core.Println(defaultBuildCommand(TypePHP))
// Output:
// go build ./...
// composer test
}
func ExampleDetectAll() {
dir := (&core.Fs{}).NewUnrestricted().TempDir("example")
defer (&core.Fs{}).NewUnrestricted().DeleteAll(dir)
// Create a Go project
(&core.Fs{}).NewUnrestricted().Write(core.JoinPath(dir, "go.mod"), "module test")
types := DetectAll(dir)
core.Println(types)
// Output: [go]
}
func ExampleRegister() {
c := core.New(core.WithService(Register))
svc, ok := core.ServiceFor[*Service](c, "setup")
core.Println(ok)
_ = svc
// Output: true
func Example_formatFlow() {
core.Println(formatFlow(TypeNode))
// Output:
// - Build: `npm run build`
// - Test: `npm test`
}

View file

@ -1,94 +0,0 @@
// SPDX-License-Identifier: EUPL-1.2
package setup
import (
"testing"
"github.com/stretchr/testify/assert"
)
// --- defaultBuildCommand ---
func TestSetup_DefaultBuildCommand_Good(t *testing.T) {
assert.Equal(t, "go build ./...", defaultBuildCommand(TypeGo))
assert.Equal(t, "go build ./...", defaultBuildCommand(TypeWails))
assert.Equal(t, "composer test", defaultBuildCommand(TypePHP))
assert.Equal(t, "npm run build", defaultBuildCommand(TypeNode))
assert.Equal(t, "make build", defaultBuildCommand(TypeUnknown))
}
// --- defaultTestCommand ---
func TestSetup_DefaultTestCommand_Good(t *testing.T) {
assert.Equal(t, "go test ./...", defaultTestCommand(TypeGo))
assert.Equal(t, "go test ./...", defaultTestCommand(TypeWails))
assert.Equal(t, "composer test", defaultTestCommand(TypePHP))
assert.Equal(t, "npm test", defaultTestCommand(TypeNode))
assert.Equal(t, "make test", defaultTestCommand(TypeUnknown))
}
// --- formatFlow ---
func TestSetup_FormatFlow_Good(t *testing.T) {
goFlow := formatFlow(TypeGo)
assert.Contains(t, goFlow, "go build ./...")
assert.Contains(t, goFlow, "go test ./...")
phpFlow := formatFlow(TypePHP)
assert.Contains(t, phpFlow, "composer test")
nodeFlow := formatFlow(TypeNode)
assert.Contains(t, nodeFlow, "npm run build")
assert.Contains(t, nodeFlow, "npm test")
}
// --- Detect ---
func TestSetup_DetectGo_Good(t *testing.T) {
dir := t.TempDir()
fs.Write(dir+"/go.mod", "module test\n")
assert.Equal(t, TypeGo, Detect(dir))
}
func TestSetup_DetectPHP_Good(t *testing.T) {
dir := t.TempDir()
fs.Write(dir+"/composer.json", `{"name":"test"}`)
assert.Equal(t, TypePHP, Detect(dir))
}
func TestSetup_DetectNode_Good(t *testing.T) {
dir := t.TempDir()
fs.Write(dir+"/package.json", `{"name":"test"}`)
assert.Equal(t, TypeNode, Detect(dir))
}
func TestSetup_DetectWails_Good(t *testing.T) {
dir := t.TempDir()
fs.Write(dir+"/wails.json", `{}`)
assert.Equal(t, TypeWails, Detect(dir))
}
func TestSetup_Detect_Bad(t *testing.T) {
dir := t.TempDir()
assert.Equal(t, TypeUnknown, Detect(dir))
}
// --- DetectAll ---
func TestSetup_DetectAll_Good(t *testing.T) {
dir := t.TempDir()
fs.Write(dir+"/go.mod", "module test\n")
fs.Write(dir+"/package.json", `{"name":"test"}`)
types := DetectAll(dir)
assert.Contains(t, types, TypeGo)
assert.Contains(t, types, TypeNode)
assert.NotContains(t, types, TypePHP)
}
func TestSetup_DetectAll_Bad(t *testing.T) {
dir := t.TempDir()
types := DetectAll(dir)
assert.Empty(t, types)
}

View file

@ -10,57 +10,16 @@ import (
"github.com/stretchr/testify/require"
)
// testSvc creates a setup Service for tests.
func testSvc() *Service {
func newSetupService() *Service {
c := core.New()
return &Service{ServiceRuntime: core.NewServiceRuntime(c, SetupOptions{})}
}
func TestSetup_Detect_Good(t *testing.T) {
func TestSetup_Run_Good_WritesCoreConfigs(t *testing.T) {
dir := t.TempDir()
require.True(t, fs.WriteMode(core.JoinPath(dir, "go.mod"), "module example.com/test\n", 0644).OK)
assert.Equal(t, TypeGo, Detect(dir))
assert.Equal(t, []ProjectType{TypeGo}, DetectAll(dir))
}
func TestSetup_GenerateBuildConfig_Good(t *testing.T) {
cfg, err := GenerateBuildConfig("/tmp/example", TypeGo)
require.NoError(t, err)
assert.Contains(t, cfg, "# example build configuration")
assert.Contains(t, cfg, "project:")
assert.Contains(t, cfg, "name: example")
assert.Contains(t, cfg, "type: go")
assert.Contains(t, cfg, "main: ./cmd/example")
assert.Contains(t, cfg, "cgo: false")
}
func TestSetup_ParseGitRemote_Good(t *testing.T) {
tests := map[string]string{
"https://github.com/dAppCore/go-io.git": "dAppCore/go-io",
"git@github.com:dAppCore/go-io.git": "dAppCore/go-io",
"ssh://git@forge.lthn.ai:2223/core/agent.git": "core/agent",
"ssh://git@forge.lthn.ai:2223/core/agent": "core/agent",
"git@forge.lthn.ai:core/agent.git": "core/agent",
"/srv/git/core/agent.git": "srv/git/core/agent",
}
for remote, want := range tests {
assert.Equal(t, want, parseGitRemote(remote), remote)
}
}
func TestSetup_ParseGitRemote_Bad(t *testing.T) {
assert.Equal(t, "", parseGitRemote(""))
assert.Equal(t, "", parseGitRemote("origin"))
}
func TestSetup_Run_Good(t *testing.T) {
dir := t.TempDir()
require.True(t, fs.WriteMode(core.JoinPath(dir, "go.mod"), "module example.com/test\n", 0644).OK)
err := testSvc().Run(Options{Path: dir})
err := newSetupService().Run(Options{Path: dir})
require.NoError(t, err)
build := fs.Read(core.JoinPath(dir, ".core", "build.yaml"))
@ -72,14 +31,62 @@ func TestSetup_Run_Good(t *testing.T) {
assert.Contains(t, test.Value.(string), "go test ./...")
}
func TestSetup_RunTemplateAlias_Good(t *testing.T) {
func TestSetup_Run_Good_TemplateAlias(t *testing.T) {
dir := t.TempDir()
require.True(t, fs.WriteMode(core.JoinPath(dir, "go.mod"), "module example.com/test\n", 0644).OK)
err := testSvc().Run(Options{Path: dir, Template: "agent"})
err := newSetupService().Run(Options{Path: dir, Template: "agent"})
require.NoError(t, err)
prompt := fs.Read(core.JoinPath(dir, "PROMPT.md"))
require.True(t, prompt.OK)
assert.Contains(t, prompt.Value.(string), "This workspace was scaffolded by pkg/setup.")
}
func TestSetup_ResolveTemplateName_Good_Auto(t *testing.T) {
name, err := resolveTemplateName("auto", TypeGo)
require.NoError(t, err)
assert.Equal(t, "default", name)
}
func TestSetup_ResolveTemplateName_Bad_Empty(t *testing.T) {
_, err := resolveTemplateName("", TypeGo)
require.Error(t, err)
}
func TestSetup_TemplateExists_Good_Default(t *testing.T) {
assert.True(t, templateExists("default"))
}
func TestSetup_TemplateExists_Bad_Missing(t *testing.T) {
assert.False(t, templateExists("missing-template"))
}
func TestSetup_DefaultBuildCommand_Good(t *testing.T) {
assert.Equal(t, "go build ./...", defaultBuildCommand(TypeGo))
assert.Equal(t, "go build ./...", defaultBuildCommand(TypeWails))
assert.Equal(t, "composer test", defaultBuildCommand(TypePHP))
assert.Equal(t, "npm run build", defaultBuildCommand(TypeNode))
assert.Equal(t, "make build", defaultBuildCommand(TypeUnknown))
}
func TestSetup_DefaultTestCommand_Good(t *testing.T) {
assert.Equal(t, "go test ./...", defaultTestCommand(TypeGo))
assert.Equal(t, "go test ./...", defaultTestCommand(TypeWails))
assert.Equal(t, "composer test", defaultTestCommand(TypePHP))
assert.Equal(t, "npm test", defaultTestCommand(TypeNode))
assert.Equal(t, "make test", defaultTestCommand(TypeUnknown))
}
func TestSetup_FormatFlow_Good(t *testing.T) {
goFlow := formatFlow(TypeGo)
assert.Contains(t, goFlow, "go build ./...")
assert.Contains(t, goFlow, "go test ./...")
phpFlow := formatFlow(TypePHP)
assert.Contains(t, phpFlow, "composer test")
nodeFlow := formatFlow(TypeNode)
assert.Contains(t, nodeFlow, "npm run build")
assert.Contains(t, nodeFlow, "npm test")
}