From 3fdc12d0fe7015ae6b7880955fc3638de4e5b6bd Mon Sep 17 00:00:00 2001 From: Hayden <64056131+hay-kot@users.noreply.github.com> Date: Mon, 6 May 2024 19:37:34 -0500 Subject: [PATCH 01/23] simplify implementation --- .examples/cli/{{ .ProjectKebab }}/main.go | 57 +++-------------------- 1 file changed, 6 insertions(+), 51 deletions(-) diff --git a/.examples/cli/{{ .ProjectKebab }}/main.go b/.examples/cli/{{ .ProjectKebab }}/main.go index d180297..da63cf5 100644 --- a/.examples/cli/{{ .ProjectKebab }}/main.go +++ b/.examples/cli/{{ .ProjectKebab }}/main.go @@ -4,7 +4,6 @@ import ( "fmt" "os" - "github.com/rs/zerolog" "github.com/rs/zerolog/log" "github.com/urfave/cli/v2" ) @@ -26,52 +25,18 @@ func build() string { } func main() { - ctrl := &controller{} - app := &cli.App{ Name: "{{ .Project }}", Usage: "{{ .Scaffold.description }}", Version: build(), - Flags: []cli.Flag{ - &cli.StringFlag{ - Name: "cwd", - Usage: "current working directory", - Value: ".", - }, - &cli.StringFlag{ - Name: "log-level", - Usage: "log level (debug, info, warn, error, fatal, panic)", - Value: "panic", - }, - }, - Before: func(ctx *cli.Context) error { - log.Logger = log.Output(zerolog.ConsoleWriter{Out: os.Stderr}) - - ctrl.cwd = ctx.String("cwd") - ctrl.logLevel = ctx.String("log-level") - - switch ctrl.logLevel { - case "debug": - log.Level(zerolog.DebugLevel) - case "info": - log.Level(zerolog.InfoLevel) - case "warn": - log.Level(zerolog.WarnLevel) - case "error": - log.Level(zerolog.ErrorLevel) - case "fatal": - log.Level(zerolog.FatalLevel) - default: - log.Level(zerolog.PanicLevel) - } - - return nil - }, Commands: []*cli.Command{ { - Name: "hello", - Usage: "Says hello world", - Action: ctrl.HelloWorld, + Name: "hello", + Usage: "Says hello world", + Action: func(ctx *cli.Context) error { + fmt.Println("Hello, your favorite colors are {{ .Scaffold.colors | join `, ` }}") + return nil + }, }, }, } @@ -80,13 +45,3 @@ func main() { log.Fatal().Err(err).Msg("failed to run scaffold") } } - -type controller struct { - cwd string - logLevel string -} - -func (c *controller) HelloWorld(ctx *cli.Context) error { - fmt.Println("Hello, your favorite colors are {{ .Scaffold.colors | join `, ` }}") - return nil -} From 2bfc986389a463dd71117e0f6beb58d91c9f7197 Mon Sep 17 00:00:00 2001 From: Hayden <64056131+hay-kot@users.noreply.github.com> Date: Mon, 6 May 2024 19:38:17 -0500 Subject: [PATCH 02/23] add test mode to scaffold new command --- .examples/cli/scaffold.yaml | 4 + .examples/role/scaffold.yaml | 12 +- app/commands/controller.go | 3 +- app/commands/new.go | 176 ++++++++++++++------------ app/scaffold/merge.go | 9 ++ app/scaffold/project.go | 16 ++- app/scaffold/project_scaffold_file.go | 27 ++-- app/scaffold/rc.go | 2 +- main.go | 8 +- taskfile.yml | 13 +- 10 files changed, 146 insertions(+), 124 deletions(-) diff --git a/.examples/cli/scaffold.yaml b/.examples/cli/scaffold.yaml index 95a521e..99b5b29 100644 --- a/.examples/cli/scaffold.yaml +++ b/.examples/cli/scaffold.yaml @@ -24,3 +24,7 @@ questions: - "green" - "blue" - "yellow" + +test: + description: "Ths is a test description" + colors: ["red", "green"] diff --git a/.examples/role/scaffold.yaml b/.examples/role/scaffold.yaml index e337e45..a2cd299 100644 --- a/.examples/role/scaffold.yaml +++ b/.examples/role/scaffold.yaml @@ -23,10 +23,8 @@ rewrites: computed: snaked: "{{ snakecase .Scaffold.role_name }}" static: false -inject: - - name: "add role to site.yaml" - path: site.yaml - at: "# $Scaffold.role_name" - template: | - - name: {{ .Scaffold.role_name }} - role: {{ .Computed.snaked }} + +test: + role_name: "test_role" + toggle: true + description: "This is a test role" diff --git a/app/commands/controller.go b/app/commands/controller.go index bf6e3e4..82b4a92 100644 --- a/app/commands/controller.go +++ b/app/commands/controller.go @@ -14,6 +14,7 @@ type Flags struct { OutputDir string ScaffoldDirs []string Cwd string + NoInteractive bool } type Controller struct { @@ -22,7 +23,7 @@ type Controller struct { engine *engine.Engine rc *scaffold.ScaffoldRC - vars map[string]string + vars map[string]any prepared bool } diff --git a/app/commands/new.go b/app/commands/new.go index dfc7c63..8343a13 100644 --- a/app/commands/new.go +++ b/app/commands/new.go @@ -19,84 +19,9 @@ import ( "github.com/urfave/cli/v2" ) -func (ctrl *Controller) fuzzyFallBack(str string) ([]string, []string, error) { - systemScaffolds, err := pkgs.ListSystem(os.DirFS(ctrl.Flags.Cache)) - if err != nil { - return nil, nil, err - } - - localScaffolds, err := pkgs.ListLocal(os.DirFS(ctrl.Flags.OutputDir)) - if err != nil { - return nil, nil, err - } - - systemMatches := fuzzy.Find(str, systemScaffolds) - systemMatchesOutput := make([]string, len(systemMatches)) - for i, match := range systemMatches { - systemMatchesOutput[i] = match.Str - } - - localMatches := fuzzy.Find(str, localScaffolds) - localMatchesOutput := make([]string, len(localMatches)) - for i, match := range localMatches { - localMatchesOutput[i] = match.Str - } - - return systemMatchesOutput, localMatchesOutput, nil -} - -var ( - bold = lipgloss.NewStyle().Bold(true) - colorRed = lipgloss.NewStyle().Foreground(lipgloss.Color("#dc2626")) -) - -func didYouMeanPrompt(given, suggestion string) bool { - bldr := strings.Builder{} - - // Couldn't find a scaffold named: - // 'foo' - // - // Did you mean: - // 'bar'? - // - // [y/n]: +func (ctrl *Controller) New(ctx *cli.Context) error { + isTest := ctx.Bool("test") - bldr.WriteString("\n ") - bldr.WriteString(bold.Render(colorRed.Render("could not find a scaffold named"))) - bldr.WriteString("\n ") - bldr.WriteString(given) - bldr.WriteString("\n\n") - bldr.WriteString(" ") - bldr.WriteString(bold.Render("did you mean")) - bldr.WriteString("\n ") - bldr.WriteString(suggestion) - bldr.WriteString("?\n\n ") - bldr.WriteString("[y/n]: ") - - out := bldr.String() - - var resp string - - fmt.Print(out) - fmt.Scanln(&resp) - - return resp == "y" -} - -func basicAuthAuthorizer(pkgurl, username, password string) pkgs.AuthProviderFunc { - return func(url string) (transport.AuthMethod, bool) { - if url != pkgurl { - return nil, false - } - - return &http.BasicAuth{ - Username: username, - Password: password, - }, true - } -} - -func (ctrl *Controller) Project(ctx *cli.Context) error { argPath := ctx.Args().First() if argPath == "" { return fmt.Errorf("path is required") @@ -176,14 +101,16 @@ func (ctrl *Controller) Project(ctx *cli.Context) error { rest := ctx.Args().Tail() - ctrl.vars = make(map[string]string, len(rest)) for _, v := range rest { + if !strings.Contains(v, "=") { + return fmt.Errorf("variable %s is not in the form of key=value", v) + } + kv := strings.Split(v, "=") ctrl.vars[kv[0]] = kv[1] } pfs := os.DirFS(path) - p, err := scaffold.LoadProject(pfs, scaffold.Options{ NoClobber: ctrl.Flags.NoClobber, }) @@ -191,8 +118,6 @@ func (ctrl *Controller) Project(ctx *cli.Context) error { return err } - defaults := scaffold.MergeMaps(ctrl.vars, ctrl.rc.Defaults) - if p.Conf.Messages.Pre != "" { out, err := glamour.RenderWithEnvironmentConfig(p.Conf.Messages.Pre) if err != nil { @@ -202,9 +127,15 @@ func (ctrl *Controller) Project(ctx *cli.Context) error { fmt.Println(out) } - vars, err := p.AskQuestions(defaults, ctrl.engine) - if err != nil { - return err + vars := scaffold.MergeMaps(ctrl.vars, ctrl.rc.Defaults) + if isTest { + // explicitly ignore rc.Defaults here + vars = scaffold.MergeMaps(ctrl.vars, p.Conf.Test) + } else { + vars, err = p.AskQuestions(vars, ctrl.engine) + if err != nil { + return err + } } args := &scaffold.RWFSArgs{ @@ -267,3 +198,80 @@ func httpAuthPrompt() (username string, password string, err error) { return answers.Username, answers.Password, nil } + +func (ctrl *Controller) fuzzyFallBack(str string) ([]string, []string, error) { + systemScaffolds, err := pkgs.ListSystem(os.DirFS(ctrl.Flags.Cache)) + if err != nil { + return nil, nil, err + } + + localScaffolds, err := pkgs.ListLocal(os.DirFS(ctrl.Flags.OutputDir)) + if err != nil { + return nil, nil, err + } + + systemMatches := fuzzy.Find(str, systemScaffolds) + systemMatchesOutput := make([]string, len(systemMatches)) + for i, match := range systemMatches { + systemMatchesOutput[i] = match.Str + } + + localMatches := fuzzy.Find(str, localScaffolds) + localMatchesOutput := make([]string, len(localMatches)) + for i, match := range localMatches { + localMatchesOutput[i] = match.Str + } + + return systemMatchesOutput, localMatchesOutput, nil +} + +var ( + bold = lipgloss.NewStyle().Bold(true) + colorRed = lipgloss.NewStyle().Foreground(lipgloss.Color("#dc2626")) +) + +func didYouMeanPrompt(given, suggestion string) bool { + bldr := strings.Builder{} + + // Couldn't find a scaffold named: + // 'foo' + // + // Did you mean: + // 'bar'? + // + // [y/n]: + + bldr.WriteString("\n ") + bldr.WriteString(bold.Render(colorRed.Render("could not find a scaffold named"))) + bldr.WriteString("\n ") + bldr.WriteString(given) + bldr.WriteString("\n\n") + bldr.WriteString(" ") + bldr.WriteString(bold.Render("did you mean")) + bldr.WriteString("\n ") + bldr.WriteString(suggestion) + bldr.WriteString("?\n\n ") + bldr.WriteString("[y/n]: ") + + out := bldr.String() + + var resp string + + fmt.Print(out) + fmt.Scanln(&resp) + + return resp == "y" +} + +func basicAuthAuthorizer(pkgurl, username, password string) pkgs.AuthProviderFunc { + return func(url string) (transport.AuthMethod, bool) { + if url != pkgurl { + return nil, false + } + + return &http.BasicAuth{ + Username: username, + Password: password, + }, true + } +} diff --git a/app/scaffold/merge.go b/app/scaffold/merge.go index b4b5fd8..8ac65c3 100644 --- a/app/scaffold/merge.go +++ b/app/scaffold/merge.go @@ -11,3 +11,12 @@ func MergeMaps[T any](maps ...map[string]T) map[string]T { } return out } + +// CastMap casts a map[string]T to a man[string]any map. +func CastMap[T any](m map[string]T) map[string]any { + out := map[string]any{} + for k, v := range m { + out[k] = v + } + return out +} diff --git a/app/scaffold/project.go b/app/scaffold/project.go index ab398a0..1d0651a 100644 --- a/app/scaffold/project.go +++ b/app/scaffold/project.go @@ -4,6 +4,7 @@ package scaffold import ( "fmt" "io/fs" + "maps" "strconv" "github.com/AlecAivazis/survey/v2" @@ -94,7 +95,7 @@ func (p *Project) validate() (str string, err error) { return "", fmt.Errorf("{{ .Project }} directory does not exist") } -func (p *Project) AskQuestions(def map[string]string, e *engine.Engine) (map[string]any, error) { +func (p *Project) AskQuestions(def map[string]any, e *engine.Engine) (map[string]any, error) { projectMode := p.NameTemplate != "templates" if projectMode { @@ -116,15 +117,16 @@ func (p *Project) AskQuestions(def map[string]string, e *engine.Engine) (map[str p.Conf.Questions = append(pre, p.Conf.Questions...) case true: - p.Name = name + nameStr, ok := name.(string) + if !ok { + return nil, fmt.Errorf("Project name must be a string") + } + + p.Name = nameStr } } - vars := make(map[string]any) - // Copy default values - for k, v := range def { - vars[k] = v - } + vars := maps.Clone(def) for _, q := range p.Conf.Questions { if q.When != "" { diff --git a/app/scaffold/project_scaffold_file.go b/app/scaffold/project_scaffold_file.go index 27c8002..8554564 100644 --- a/app/scaffold/project_scaffold_file.go +++ b/app/scaffold/project_scaffold_file.go @@ -9,6 +9,20 @@ import ( "gopkg.in/yaml.v3" ) +type ProjectScaffoldFile struct { + Skip []string `yaml:"skip"` + Questions []Question `yaml:"questions"` + Rewrites []Rewrite `yaml:"rewrites"` + Computed map[string]string `yaml:"computed"` + Messages struct { + Pre string `yaml:"pre"` + Post string `yaml:"post"` + } `yaml:"messages"` + Inject []Injectable `yaml:"inject"` + Features []Feature `yaml:"features"` + Test map[string]any `yaml:"test"` +} + type Rewrite struct { From string `yaml:"from"` To string `yaml:"to"` @@ -26,19 +40,6 @@ type Feature struct { Globs []string `yaml:"globs"` } -type ProjectScaffoldFile struct { - Skip []string `yaml:"skip"` - Questions []Question `yaml:"questions"` - Rewrites []Rewrite `yaml:"rewrites"` - Computed map[string]string `yaml:"computed"` - Messages struct { - Pre string `yaml:"pre"` - Post string `yaml:"post"` - } `yaml:"messages"` - Inject []Injectable `yaml:"inject"` - Features []Feature `yaml:"features"` -} - func ReadScaffoldFile(reader io.Reader) (*ProjectScaffoldFile, error) { var out ProjectScaffoldFile diff --git a/app/scaffold/rc.go b/app/scaffold/rc.go index cdfb295..04f8a16 100644 --- a/app/scaffold/rc.go +++ b/app/scaffold/rc.go @@ -22,7 +22,7 @@ type ScaffoldRC struct { // // These are injected into the template as variables for // every scaffold. - Defaults map[string]string `yaml:"defaults"` + Defaults map[string]any `yaml:"defaults"` // Aliases define a alias for a repository. // or filepath. diff --git a/main.go b/main.go index e797619..a729043 100644 --- a/main.go +++ b/main.go @@ -165,7 +165,13 @@ func main() { Name: "new", Usage: "create a new project from a scaffold", UsageText: "scaffold new [scaffold (url | path)] [flags]", - Action: ctrl.Project, + Flags: []cli.Flag{ + &cli.BoolFlag{ + Name: "test", + Usage: "no prompts for user input and use test data from scaffold file", + }, + }, + Action: ctrl.New, }, { Name: "list", diff --git a/taskfile.yml b/taskfile.yml index 6464a0f..2c0c146 100644 --- a/taskfile.yml +++ b/taskfile.yml @@ -61,18 +61,11 @@ tasks: desc: Runs the main.go program with the cli scaffold cmds: - rm -rf ./gen/* - - | - go run main.go \ - new cli \ - "project=TEST_PROJECT" \ - "description"=TEST_PROJECT" \ - "colors=red" + - go run main.go new --test cli + - go run ./gen/main.go hello do:role: desc: Runs the main.go program with the role scaffold cmds: - rm -rf ./gen/* - - | - go run main.go \ - new role \ - "Use Github Actions=true" + - go run main.go new --test role From 929279612081a06b1cc99fd06ebe7f815594070a Mon Sep 17 00:00:00 2001 From: Hayden <64056131+hay-kot@users.noreply.github.com> Date: Mon, 6 May 2024 19:44:32 -0500 Subject: [PATCH 03/23] implement test example in ci --- .github/workflows/partial-tests.yml | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/.github/workflows/partial-tests.yml b/.github/workflows/partial-tests.yml index 206e67a..b23755a 100644 --- a/.github/workflows/partial-tests.yml +++ b/.github/workflows/partial-tests.yml @@ -26,3 +26,23 @@ jobs: - name: Test run: go test ./... -race + + - name: Test 'cli' example + run: | + go run main.go new --test hello + go run ./gen/main.go hello + + # Run the command and store the output in a variable + output=$(gen run ./gen/main.go hello) + + # Define the expected output + expected_output="Hello, your favorite colors are red, green" + + # Compare the actual output with the expected output + if [ "$output" = "$expected_output" ]; then + echo "Test passed: Output matches the expected string." + exit 0 + else + echo "Test failed: Output does not match the expected string." + exit 1 + fi From be2f8cffaf90c40f2cbdd55233a157055b1c462f Mon Sep 17 00:00:00 2001 From: Hayden <64056131+hay-kot@users.noreply.github.com> Date: Mon, 6 May 2024 19:45:38 -0500 Subject: [PATCH 04/23] change hello to cli --- .github/workflows/partial-tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/partial-tests.yml b/.github/workflows/partial-tests.yml index b23755a..a547d2e 100644 --- a/.github/workflows/partial-tests.yml +++ b/.github/workflows/partial-tests.yml @@ -29,7 +29,7 @@ jobs: - name: Test 'cli' example run: | - go run main.go new --test hello + go run main.go new --test cli go run ./gen/main.go hello # Run the command and store the output in a variable From 12098042d30804d462b5eae299c1bc576b70d1e6 Mon Sep 17 00:00:00 2001 From: Hayden <64056131+hay-kot@users.noreply.github.com> Date: Mon, 6 May 2024 19:47:42 -0500 Subject: [PATCH 05/23] fix cli --- .github/workflows/partial-tests.yml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.github/workflows/partial-tests.yml b/.github/workflows/partial-tests.yml index a547d2e..f4b40e6 100644 --- a/.github/workflows/partial-tests.yml +++ b/.github/workflows/partial-tests.yml @@ -7,7 +7,7 @@ jobs: Go: runs-on: ubuntu-latest steps: - - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4 + - uses: actions/checkout@4 # v4 - name: Set up Go uses: actions/setup-go@v5 @@ -28,6 +28,10 @@ jobs: run: go test ./... -race - name: Test 'cli' example + env: + SCAFFOLD_NO_CLOBBER: true + SCAFFOLD_OUT: "gen" + SCAFFOLD_DIR: ".scaffold,.examples" run: | go run main.go new --test cli go run ./gen/main.go hello From 48e46f84ff9d6b580b3fbbd39658d79dfc4e6817 Mon Sep 17 00:00:00 2001 From: Hayden <64056131+hay-kot@users.noreply.github.com> Date: Mon, 6 May 2024 19:49:17 -0500 Subject: [PATCH 06/23] use proper version --- .github/workflows/partial-tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/partial-tests.yml b/.github/workflows/partial-tests.yml index f4b40e6..aaae1c4 100644 --- a/.github/workflows/partial-tests.yml +++ b/.github/workflows/partial-tests.yml @@ -7,7 +7,7 @@ jobs: Go: runs-on: ubuntu-latest steps: - - uses: actions/checkout@4 # v4 + - uses: actions/checkout@v4 - name: Set up Go uses: actions/setup-go@v5 From 54711f49035fff8f846dc215ac8337cca208c162 Mon Sep 17 00:00:00 2001 From: Hayden <64056131+hay-kot@users.noreply.github.com> Date: Mon, 6 May 2024 19:50:21 -0500 Subject: [PATCH 07/23] duhhhh --- .github/workflows/partial-tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/partial-tests.yml b/.github/workflows/partial-tests.yml index aaae1c4..c6df9c5 100644 --- a/.github/workflows/partial-tests.yml +++ b/.github/workflows/partial-tests.yml @@ -37,7 +37,7 @@ jobs: go run ./gen/main.go hello # Run the command and store the output in a variable - output=$(gen run ./gen/main.go hello) + output=$(go run ./gen/main.go hello) # Define the expected output expected_output="Hello, your favorite colors are red, green" From 975d01ca71e048d0a865a0a02988a817fc4bb95b Mon Sep 17 00:00:00 2001 From: Hayden <64056131+hay-kot@users.noreply.github.com> Date: Mon, 6 May 2024 19:57:31 -0500 Subject: [PATCH 08/23] pull out test into file --- .github/workflows/partial-tests.yml | 23 +---------------------- taskfile.yml | 5 +++++ tests/cli.test.sh | 22 ++++++++++++++++++++++ 3 files changed, 28 insertions(+), 22 deletions(-) create mode 100755 tests/cli.test.sh diff --git a/.github/workflows/partial-tests.yml b/.github/workflows/partial-tests.yml index c6df9c5..3dccdf8 100644 --- a/.github/workflows/partial-tests.yml +++ b/.github/workflows/partial-tests.yml @@ -28,25 +28,4 @@ jobs: run: go test ./... -race - name: Test 'cli' example - env: - SCAFFOLD_NO_CLOBBER: true - SCAFFOLD_OUT: "gen" - SCAFFOLD_DIR: ".scaffold,.examples" - run: | - go run main.go new --test cli - go run ./gen/main.go hello - - # Run the command and store the output in a variable - output=$(go run ./gen/main.go hello) - - # Define the expected output - expected_output="Hello, your favorite colors are red, green" - - # Compare the actual output with the expected output - if [ "$output" = "$expected_output" ]; then - echo "Test passed: Output matches the expected string." - exit 0 - else - echo "Test failed: Output does not match the expected string." - exit 1 - fi + run: ./tests/cli.test.sh diff --git a/taskfile.yml b/taskfile.yml index 2c0c146..f5b1b2b 100644 --- a/taskfile.yml +++ b/taskfile.yml @@ -57,6 +57,11 @@ tasks: - task: lint - task: test + test:scripts: + desc: Runs all go tests for the scripts + cmds: + - ./tests/cli.test.sh + do:cli: desc: Runs the main.go program with the cli scaffold cmds: diff --git a/tests/cli.test.sh b/tests/cli.test.sh new file mode 100755 index 0000000..9d4da51 --- /dev/null +++ b/tests/cli.test.sh @@ -0,0 +1,22 @@ +#!/bin/bash +export SCAFFOLD_NO_CLOBBER="true" +export SCAFFOLD_OUT="gen" +export SCAFFOLD_DIR=".scaffold,.examples" + +go run main.go new --test cli +go run ./gen/main.go hello + +# Run the command and store the output in a variable +output=$(go run ./gen/main.go hello) + +# Define the expected output +expected_output="Hello, your favorite colors are red, green" + +# Compare the actual output with the expected output +if [ "$output" = "$expected_output" ]; then + echo "Test passed: Output matches the expected string." + exit 0 +else + echo "Test failed: Output does not match the expected string." + exit 1 +fi From 5d29bdfe8d82abff146621d4e1e9eb11104f4776 Mon Sep 17 00:00:00 2001 From: Hayden <64056131+hay-kot@users.noreply.github.com> Date: Mon, 6 May 2024 21:02:47 -0500 Subject: [PATCH 09/23] docs for testing --- docs/docs/testing-scaffolds.md | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 docs/docs/testing-scaffolds.md diff --git a/docs/docs/testing-scaffolds.md b/docs/docs/testing-scaffolds.md new file mode 100644 index 0000000..99f27d9 --- /dev/null +++ b/docs/docs/testing-scaffolds.md @@ -0,0 +1,17 @@ +--- +title: Testing Scaffolds +--- + +Scaffold has some built-in support for testing scaffolds. This is done through with the `--test` flag when running the `scaffold new --test