fix(setup): make setup APIs AX-native
Co-Authored-By: Virgil <virgil@lethean.io>
This commit is contained in:
parent
f8cd0ff11a
commit
feaa4dec5e
5 changed files with 153 additions and 110 deletions
|
|
@ -50,8 +50,9 @@ type configValue struct {
|
|||
|
||||
// GenerateBuildConfig renders `build.yaml` content for a detected repo type.
|
||||
//
|
||||
// content, err := setup.GenerateBuildConfig("/srv/repos/agent", setup.TypeGo)
|
||||
func GenerateBuildConfig(path string, projType ProjectType) (string, error) {
|
||||
// r := setup.GenerateBuildConfig("/srv/repos/agent", setup.TypeGo)
|
||||
// if r.OK { content := r.Value.(string) }
|
||||
func GenerateBuildConfig(path string, projType ProjectType) core.Result {
|
||||
name := core.PathBase(path)
|
||||
sections := []configSection{
|
||||
{
|
||||
|
|
@ -96,8 +97,9 @@ func GenerateBuildConfig(path string, projType ProjectType) (string, error) {
|
|||
|
||||
// GenerateTestConfig renders `test.yaml` content for a detected repo type.
|
||||
//
|
||||
// content, err := setup.GenerateTestConfig(setup.TypeGo)
|
||||
func GenerateTestConfig(projType ProjectType) (string, error) {
|
||||
// r := setup.GenerateTestConfig(setup.TypeGo)
|
||||
// if r.OK { content := r.Value.(string) }
|
||||
func GenerateTestConfig(projType ProjectType) core.Result {
|
||||
var sections []configSection
|
||||
|
||||
switch projType {
|
||||
|
|
@ -137,7 +139,7 @@ func GenerateTestConfig(projType ProjectType) (string, error) {
|
|||
return renderConfig("Test configuration", sections)
|
||||
}
|
||||
|
||||
func renderConfig(comment string, sections []configSection) (string, error) {
|
||||
func renderConfig(comment string, sections []configSection) core.Result {
|
||||
builder := core.NewBuilder()
|
||||
|
||||
if comment != "" {
|
||||
|
|
@ -151,15 +153,19 @@ func renderConfig(comment string, sections []configSection) (string, error) {
|
|||
builder.WriteString(":\n")
|
||||
|
||||
for _, value := range section.Values {
|
||||
scalar, err := marshalConfigValue(value.Value)
|
||||
if err != nil {
|
||||
return "", core.E("setup.renderConfig", core.Concat("marshal ", section.Key, ".", value.Key), err)
|
||||
scalar := marshalConfigValue(value.Value)
|
||||
if !scalar.OK {
|
||||
err, _ := scalar.Value.(error)
|
||||
return core.Result{
|
||||
Value: core.E("setup.renderConfig", core.Concat("marshal ", section.Key, ".", value.Key), err),
|
||||
OK: false,
|
||||
}
|
||||
}
|
||||
|
||||
builder.WriteString(" ")
|
||||
builder.WriteString(value.Key)
|
||||
builder.WriteString(": ")
|
||||
builder.WriteString(scalar)
|
||||
builder.WriteString(scalar.Value.(string))
|
||||
builder.WriteString("\n")
|
||||
}
|
||||
|
||||
|
|
@ -168,21 +174,27 @@ func renderConfig(comment string, sections []configSection) (string, error) {
|
|||
}
|
||||
}
|
||||
|
||||
return builder.String(), nil
|
||||
return core.Result{Value: builder.String(), OK: true}
|
||||
}
|
||||
|
||||
func marshalConfigValue(value any) (scalar string, err error) {
|
||||
func marshalConfigValue(value any) (result core.Result) {
|
||||
defer func() {
|
||||
if recovered := recover(); recovered != nil {
|
||||
err = core.E("setup.marshalConfigValue", core.Sprint(recovered), nil)
|
||||
result = core.Result{
|
||||
Value: core.E("setup.marshalConfigValue", core.Sprint(recovered), nil),
|
||||
OK: false,
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
data, err := yaml.Marshal(value)
|
||||
if err != nil {
|
||||
return "", err
|
||||
return core.Result{
|
||||
Value: core.E("setup.marshalConfigValue", "yaml marshal value", err),
|
||||
OK: false,
|
||||
}
|
||||
}
|
||||
return core.Trim(string(data)), nil
|
||||
return core.Result{Value: core.Trim(string(data)), OK: true}
|
||||
}
|
||||
|
||||
func parseGitRemote(remote string) string {
|
||||
|
|
|
|||
|
|
@ -10,18 +10,18 @@ func ExampleGenerateBuildConfig() {
|
|||
dir := (&core.Fs{}).NewUnrestricted().TempDir("example")
|
||||
defer (&core.Fs{}).NewUnrestricted().DeleteAll(dir)
|
||||
|
||||
config, err := GenerateBuildConfig(dir, TypeGo)
|
||||
core.Println(err == nil)
|
||||
core.Println(core.Contains(config, "type: go"))
|
||||
config := GenerateBuildConfig(dir, TypeGo)
|
||||
core.Println(config.OK)
|
||||
core.Println(core.Contains(config.Value.(string), "type: go"))
|
||||
// Output:
|
||||
// true
|
||||
// true
|
||||
}
|
||||
|
||||
func ExampleGenerateTestConfig() {
|
||||
config, err := GenerateTestConfig(TypeGo)
|
||||
core.Println(err == nil)
|
||||
core.Println(core.Contains(config, "go test"))
|
||||
config := GenerateTestConfig(TypeGo)
|
||||
core.Println(config.OK)
|
||||
core.Println(core.Contains(config.Value.(string), "go test"))
|
||||
// Output:
|
||||
// true
|
||||
// true
|
||||
|
|
|
|||
|
|
@ -10,47 +10,51 @@ import (
|
|||
)
|
||||
|
||||
func TestConfig_GenerateBuildConfig_Good_Go(t *testing.T) {
|
||||
config, err := GenerateBuildConfig("/tmp/myapp", TypeGo)
|
||||
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")
|
||||
config := GenerateBuildConfig("/tmp/myapp", TypeGo)
|
||||
require.True(t, config.OK)
|
||||
text := config.Value.(string)
|
||||
assert.Contains(t, text, "# myapp build configuration")
|
||||
assert.Contains(t, text, "type: go")
|
||||
assert.Contains(t, text, "name: myapp")
|
||||
assert.Contains(t, text, "main: ./cmd/myapp")
|
||||
assert.Contains(t, text, "cgo: false")
|
||||
}
|
||||
|
||||
func TestConfig_GenerateBuildConfig_Bad_Unknown(t *testing.T) {
|
||||
config, err := GenerateBuildConfig("/tmp/myapp", TypeUnknown)
|
||||
require.NoError(t, err)
|
||||
assert.NotEmpty(t, config)
|
||||
config := GenerateBuildConfig("/tmp/myapp", TypeUnknown)
|
||||
require.True(t, config.OK)
|
||||
assert.NotEmpty(t, config.Value.(string))
|
||||
}
|
||||
|
||||
func TestConfig_GenerateBuildConfig_Ugly_WailsNestedPath(t *testing.T) {
|
||||
config, err := GenerateBuildConfig("/tmp/workspaces/team-console", TypeWails)
|
||||
require.NoError(t, err)
|
||||
assert.Contains(t, config, "name: team-console")
|
||||
assert.Contains(t, config, "type: wails")
|
||||
assert.Contains(t, config, "main: ./cmd/team-console")
|
||||
config := GenerateBuildConfig("/tmp/workspaces/team-console", TypeWails)
|
||||
require.True(t, config.OK)
|
||||
text := config.Value.(string)
|
||||
assert.Contains(t, text, "name: team-console")
|
||||
assert.Contains(t, text, "type: wails")
|
||||
assert.Contains(t, text, "main: ./cmd/team-console")
|
||||
}
|
||||
|
||||
func TestConfig_GenerateTestConfig_Good_Go(t *testing.T) {
|
||||
config, err := GenerateTestConfig(TypeGo)
|
||||
require.NoError(t, err)
|
||||
assert.Contains(t, config, "go test")
|
||||
config := GenerateTestConfig(TypeGo)
|
||||
require.True(t, config.OK)
|
||||
assert.Contains(t, config.Value.(string), "go test")
|
||||
}
|
||||
|
||||
func TestConfig_GenerateTestConfig_Bad_Unknown(t *testing.T) {
|
||||
config, err := GenerateTestConfig(TypeUnknown)
|
||||
require.NoError(t, err)
|
||||
assert.Contains(t, config, "# Test configuration")
|
||||
assert.NotContains(t, config, "commands:")
|
||||
config := GenerateTestConfig(TypeUnknown)
|
||||
require.True(t, config.OK)
|
||||
text := config.Value.(string)
|
||||
assert.Contains(t, text, "# Test configuration")
|
||||
assert.NotContains(t, text, "commands:")
|
||||
}
|
||||
|
||||
func TestConfig_GenerateTestConfig_Ugly_WailsUsesGoSuite(t *testing.T) {
|
||||
config, err := GenerateTestConfig(TypeWails)
|
||||
require.NoError(t, err)
|
||||
assert.Contains(t, config, "go test ./...")
|
||||
assert.Contains(t, config, "go test -race ./...")
|
||||
config := GenerateTestConfig(TypeWails)
|
||||
require.True(t, config.OK)
|
||||
text := config.Value.(string)
|
||||
assert.Contains(t, text, "go test ./...")
|
||||
assert.Contains(t, text, "go test -race ./...")
|
||||
}
|
||||
|
||||
func TestConfig_ParseGitRemote_Good_CommonFormats(t *testing.T) {
|
||||
|
|
@ -94,22 +98,22 @@ func TestConfig_RenderConfig_Good_SingleSection(t *testing.T) {
|
|||
sections := []configSection{
|
||||
{Key: "project", Values: []configValue{{Key: "name", Value: "test"}}},
|
||||
}
|
||||
result, err := renderConfig("Test", sections)
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, result, "name: test")
|
||||
result := renderConfig("Test", sections)
|
||||
require.True(t, result.OK)
|
||||
assert.Contains(t, result.Value.(string), "name: test")
|
||||
}
|
||||
|
||||
func TestConfig_RenderConfig_Bad_UnsupportedValue(t *testing.T) {
|
||||
sections := []configSection{
|
||||
{Key: "project", Values: []configValue{{Key: "name", Value: func() {}}}},
|
||||
}
|
||||
result, err := renderConfig("Test", sections)
|
||||
assert.Error(t, err)
|
||||
assert.Empty(t, result)
|
||||
result := renderConfig("Test", sections)
|
||||
assert.False(t, result.OK)
|
||||
assert.Error(t, result.Value.(error))
|
||||
}
|
||||
|
||||
func TestConfig_RenderConfig_Ugly_EmptySections(t *testing.T) {
|
||||
result, err := renderConfig("", nil)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "", result)
|
||||
result := renderConfig("", nil)
|
||||
require.True(t, result.OK)
|
||||
assert.Equal(t, "", result.Value.(string))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,7 +9,8 @@ import (
|
|||
|
||||
// Options controls one setup run.
|
||||
//
|
||||
// err := svc.Run(setup.Options{Path: ".", Template: "auto", Force: true})
|
||||
// r := svc.Run(setup.Options{Path: ".", Template: "auto", Force: true})
|
||||
// if !r.OK { core.Print(nil, "%v", r.Value) }
|
||||
type Options struct {
|
||||
Path string // Target directory (default: cwd)
|
||||
DryRun bool // Preview only, don't write
|
||||
|
|
@ -19,8 +20,9 @@ type Options struct {
|
|||
|
||||
// Run generates `.core/` files and optional workspace scaffolding for a repo.
|
||||
//
|
||||
// err := svc.Run(setup.Options{Path: ".", Template: "auto"})
|
||||
func (s *Service) Run(opts Options) error {
|
||||
// r := svc.Run(setup.Options{Path: ".", Template: "auto"})
|
||||
// core.Println(r.OK)
|
||||
func (s *Service) Run(opts Options) core.Result {
|
||||
if opts.Path == "" {
|
||||
opts.Path = core.Env("DIR_CWD")
|
||||
}
|
||||
|
|
@ -37,19 +39,22 @@ func (s *Service) Run(opts Options) error {
|
|||
|
||||
var tmplName string
|
||||
if opts.Template != "" {
|
||||
var err error
|
||||
tmplName, err = resolveTemplateName(opts.Template, projType)
|
||||
if err != nil {
|
||||
return err
|
||||
templateResult := resolveTemplateName(opts.Template, projType)
|
||||
if !templateResult.OK {
|
||||
return templateResult
|
||||
}
|
||||
tmplName = templateResult.Value.(string)
|
||||
if !templateExists(tmplName) {
|
||||
return core.E("setup.Run", core.Concat("template not found: ", tmplName), nil)
|
||||
return core.Result{
|
||||
Value: core.E("setup.Run", core.Concat("template not found: ", tmplName), nil),
|
||||
OK: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Generate .core/ config files
|
||||
if err := setupCoreDir(opts, projType); err != nil {
|
||||
return err
|
||||
if result := setupCoreDir(opts, projType); !result.OK {
|
||||
return result
|
||||
}
|
||||
|
||||
// Scaffold from dir template if requested
|
||||
|
|
@ -57,11 +62,11 @@ func (s *Service) Run(opts Options) error {
|
|||
return s.scaffoldTemplate(opts, projType, tmplName)
|
||||
}
|
||||
|
||||
return nil
|
||||
return core.Result{Value: opts.Path, OK: true}
|
||||
}
|
||||
|
||||
// setupCoreDir creates .core/ with build.yaml and test.yaml.
|
||||
func setupCoreDir(opts Options, projType ProjectType) error {
|
||||
func setupCoreDir(opts Options, projType ProjectType) core.Result {
|
||||
coreDir := core.JoinPath(opts.Path, ".core")
|
||||
|
||||
if opts.DryRun {
|
||||
|
|
@ -70,33 +75,44 @@ func setupCoreDir(opts Options, projType ProjectType) error {
|
|||
} else {
|
||||
if r := fs.EnsureDir(coreDir); !r.OK {
|
||||
err, _ := r.Value.(error)
|
||||
return core.E("setup.setupCoreDir", "create .core directory", err)
|
||||
return core.Result{
|
||||
Value: core.E("setup.setupCoreDir", "create .core directory", err),
|
||||
OK: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// build.yaml
|
||||
buildConfig, err := GenerateBuildConfig(opts.Path, projType)
|
||||
if err != nil {
|
||||
return core.E("setup.setupCoreDir", "generate build config", err)
|
||||
buildConfig := GenerateBuildConfig(opts.Path, projType)
|
||||
if !buildConfig.OK {
|
||||
err, _ := buildConfig.Value.(error)
|
||||
return core.Result{
|
||||
Value: core.E("setup.setupCoreDir", "generate build config", err),
|
||||
OK: false,
|
||||
}
|
||||
}
|
||||
if err := writeConfig(core.JoinPath(coreDir, "build.yaml"), buildConfig, opts); err != nil {
|
||||
return err
|
||||
if result := writeConfig(core.JoinPath(coreDir, "build.yaml"), buildConfig.Value.(string), opts); !result.OK {
|
||||
return result
|
||||
}
|
||||
|
||||
// test.yaml
|
||||
testConfig, err := GenerateTestConfig(projType)
|
||||
if err != nil {
|
||||
return core.E("setup.setupCoreDir", "generate test config", err)
|
||||
testConfig := GenerateTestConfig(projType)
|
||||
if !testConfig.OK {
|
||||
err, _ := testConfig.Value.(error)
|
||||
return core.Result{
|
||||
Value: core.E("setup.setupCoreDir", "generate test config", err),
|
||||
OK: false,
|
||||
}
|
||||
}
|
||||
if err := writeConfig(core.JoinPath(coreDir, "test.yaml"), testConfig, opts); err != nil {
|
||||
return err
|
||||
if result := writeConfig(core.JoinPath(coreDir, "test.yaml"), testConfig.Value.(string), opts); !result.OK {
|
||||
return result
|
||||
}
|
||||
|
||||
return nil
|
||||
return core.Result{Value: coreDir, OK: true}
|
||||
}
|
||||
|
||||
// scaffoldTemplate extracts a dir template into the target path.
|
||||
func (s *Service) scaffoldTemplate(opts Options, projType ProjectType, tmplName string) error {
|
||||
func (s *Service) scaffoldTemplate(opts Options, projType ProjectType, tmplName string) core.Result {
|
||||
core.Print(nil, "Template: %s", tmplName)
|
||||
|
||||
data := &lib.WorkspaceData{
|
||||
|
|
@ -115,53 +131,62 @@ func (s *Service) scaffoldTemplate(opts Options, projType ProjectType, tmplName
|
|||
if opts.DryRun {
|
||||
core.Print(nil, "Would extract workspace/%s to %s", tmplName, opts.Path)
|
||||
core.Print(nil, " Template found: %s", tmplName)
|
||||
return nil
|
||||
return core.Result{Value: opts.Path, OK: true}
|
||||
}
|
||||
|
||||
if err := lib.ExtractWorkspace(tmplName, opts.Path, data); err != nil {
|
||||
return core.E("setup.scaffoldTemplate", core.Concat("extract workspace template ", tmplName), err)
|
||||
return core.Result{
|
||||
Value: core.E("setup.scaffoldTemplate", core.Concat("extract workspace template ", tmplName), err),
|
||||
OK: false,
|
||||
}
|
||||
}
|
||||
return nil
|
||||
return core.Result{Value: opts.Path, OK: true}
|
||||
}
|
||||
|
||||
func writeConfig(path, content string, opts Options) error {
|
||||
func writeConfig(path, content string, opts Options) core.Result {
|
||||
if opts.DryRun {
|
||||
core.Print(nil, " %s", path)
|
||||
return nil
|
||||
return core.Result{Value: path, OK: true}
|
||||
}
|
||||
|
||||
if !opts.Force && fs.Exists(path) {
|
||||
core.Print(nil, " skip %s (exists, use --force to overwrite)", core.PathBase(path))
|
||||
return nil
|
||||
return core.Result{Value: path, OK: true}
|
||||
}
|
||||
|
||||
if r := fs.WriteMode(path, content, 0644); !r.OK {
|
||||
err, _ := r.Value.(error)
|
||||
return core.E("setup.writeConfig", core.Concat("write ", core.PathBase(path)), err)
|
||||
return core.Result{
|
||||
Value: core.E("setup.writeConfig", core.Concat("write ", core.PathBase(path)), err),
|
||||
OK: false,
|
||||
}
|
||||
}
|
||||
core.Print(nil, " created %s", path)
|
||||
return nil
|
||||
return core.Result{Value: path, OK: true}
|
||||
}
|
||||
|
||||
func resolveTemplateName(name string, projType ProjectType) (string, error) {
|
||||
func resolveTemplateName(name string, projType ProjectType) core.Result {
|
||||
if name == "" {
|
||||
return "", core.E("setup.resolveTemplateName", "template is required", nil)
|
||||
return core.Result{
|
||||
Value: core.E("setup.resolveTemplateName", "template is required", nil),
|
||||
OK: false,
|
||||
}
|
||||
}
|
||||
|
||||
if name == "auto" {
|
||||
switch projType {
|
||||
case TypeGo, TypeWails, TypePHP, TypeNode, TypeUnknown:
|
||||
return "default", nil
|
||||
return core.Result{Value: "default", OK: true}
|
||||
}
|
||||
}
|
||||
|
||||
switch name {
|
||||
case "agent", "go", "php", "gui":
|
||||
return "default", nil
|
||||
return core.Result{Value: "default", OK: true}
|
||||
case "verify", "conventions":
|
||||
return "review", nil
|
||||
return core.Result{Value: "review", OK: true}
|
||||
default:
|
||||
return name, nil
|
||||
return core.Result{Value: name, OK: true}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -19,8 +19,8 @@ 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)
|
||||
|
||||
err := newSetupService().Run(Options{Path: dir})
|
||||
require.NoError(t, err)
|
||||
result := newSetupService().Run(Options{Path: dir})
|
||||
require.True(t, result.OK)
|
||||
|
||||
build := fs.Read(core.JoinPath(dir, ".core", "build.yaml"))
|
||||
require.True(t, build.OK)
|
||||
|
|
@ -35,8 +35,8 @@ 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 := newSetupService().Run(Options{Path: dir, Template: "agent"})
|
||||
require.NoError(t, err)
|
||||
result := newSetupService().Run(Options{Path: dir, Template: "agent"})
|
||||
require.True(t, result.OK)
|
||||
|
||||
prompt := fs.Read(core.JoinPath(dir, "PROMPT.md"))
|
||||
require.True(t, prompt.OK)
|
||||
|
|
@ -47,8 +47,9 @@ func TestSetup_Run_Bad_MissingTemplateDoesNotWrite(t *testing.T) {
|
|||
dir := t.TempDir()
|
||||
require.True(t, fs.WriteMode(core.JoinPath(dir, "go.mod"), "module example.com/test\n", 0644).OK)
|
||||
|
||||
err := newSetupService().Run(Options{Path: dir, Template: "missing-template"})
|
||||
require.Error(t, err)
|
||||
result := newSetupService().Run(Options{Path: dir, Template: "missing-template"})
|
||||
require.False(t, result.OK)
|
||||
require.Error(t, result.Value.(error))
|
||||
assert.False(t, fs.Exists(core.JoinPath(dir, ".core")))
|
||||
}
|
||||
|
||||
|
|
@ -56,27 +57,28 @@ func TestSetup_Run_Ugly_DryRunDoesNotWrite(t *testing.T) {
|
|||
dir := t.TempDir()
|
||||
require.True(t, fs.WriteMode(core.JoinPath(dir, "go.mod"), "module example.com/test\n", 0644).OK)
|
||||
|
||||
err := newSetupService().Run(Options{Path: dir, Template: "agent", DryRun: true})
|
||||
require.NoError(t, err)
|
||||
result := newSetupService().Run(Options{Path: dir, Template: "agent", DryRun: true})
|
||||
require.True(t, result.OK)
|
||||
assert.False(t, fs.Exists(core.JoinPath(dir, ".core")))
|
||||
assert.False(t, fs.Exists(core.JoinPath(dir, "PROMPT.md")))
|
||||
}
|
||||
|
||||
func TestSetup_ResolveTemplateName_Good_Auto(t *testing.T) {
|
||||
name, err := resolveTemplateName("auto", TypeGo)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, "default", name)
|
||||
name := resolveTemplateName("auto", TypeGo)
|
||||
require.True(t, name.OK)
|
||||
assert.Equal(t, "default", name.Value.(string))
|
||||
}
|
||||
|
||||
func TestSetup_ResolveTemplateName_Bad_Empty(t *testing.T) {
|
||||
_, err := resolveTemplateName("", TypeGo)
|
||||
require.Error(t, err)
|
||||
result := resolveTemplateName("", TypeGo)
|
||||
require.False(t, result.OK)
|
||||
require.Error(t, result.Value.(error))
|
||||
}
|
||||
|
||||
func TestSetup_ResolveTemplateName_Ugly_ConventionsAlias(t *testing.T) {
|
||||
name, err := resolveTemplateName("conventions", TypeGo)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, "review", name)
|
||||
name := resolveTemplateName("conventions", TypeGo)
|
||||
require.True(t, name.OK)
|
||||
assert.Equal(t, "review", name.Value.(string))
|
||||
}
|
||||
|
||||
func TestSetup_TemplateExists_Good_Default(t *testing.T) {
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue