diff --git a/pkg/app/run.go b/pkg/app/run.go index 14afe93ae..9af47c5ca 100644 --- a/pkg/app/run.go +++ b/pkg/app/run.go @@ -129,7 +129,7 @@ func Run(args []string, env config.Environment, file config.File, configFilePath serviceVersionLock := serviceversion.NewLockCommand(serviceVersionRoot.CmdClause, &globals) computeRoot := compute.NewRootCommand(app, &globals) - computeInit := compute.NewInitCommand(computeRoot.CmdClause, &globals) + computeInit := compute.NewInitCommand(computeRoot.CmdClause, httpClient, &globals) computeBuild := compute.NewBuildCommand(computeRoot.CmdClause, httpClient, &globals) computeDeploy := compute.NewDeployCommand(computeRoot.CmdClause, httpClient, &globals) computeUpdate := compute.NewUpdateCommand(computeRoot.CmdClause, httpClient, &globals) diff --git a/pkg/compute/assemblyscript.go b/pkg/compute/assemblyscript.go index febbaaaed..21b5e3894 100644 --- a/pkg/compute/assemblyscript.go +++ b/pkg/compute/assemblyscript.go @@ -16,33 +16,9 @@ import ( // AssemblyScript implements Toolchain for the AssemblyScript language. type AssemblyScript struct{} -// Name implements the Toolchain interface and returns the name of the toolchain. -func (a AssemblyScript) Name() string { return "assemblyscript" } - -// DisplayName implements the Toolchain interface and returns the name of the -// toolchain suitable for displaying or printing to output. -func (a AssemblyScript) DisplayName() string { return "AssemblyScript (beta)" } - -// StarterKits implements the Toolchain interface and returns the list of -// starter kits that can be used to initialize a new package for the toolchain. -func (a AssemblyScript) StarterKits() []StarterKit { - return []StarterKit{ - { - Name: "Default", - Path: "https://github.com/fastly/compute-starter-kit-assemblyscript-default", - Tag: "v0.1.0", - }, - } -} - -// SourceDirectory implements the Toolchain interface and returns the source -// directory for AssemblyScript packages. -func (a AssemblyScript) SourceDirectory() string { return "src" } - -// IncludeFiles implements the Toolchain interface and returns a list of -// additional files to include in the package archive for AssemblyScript packages. -func (a AssemblyScript) IncludeFiles() []string { - return []string{"package.json"} +// NewAssemblyScript constructs a new AssemblyScript. +func NewAssemblyScript() *AssemblyScript { + return &AssemblyScript{} } // Verify implements the Toolchain interface and verifies whether the @@ -170,16 +146,16 @@ func (a AssemblyScript) Build(out io.Writer, verbose bool) error { // Check if bin directory exists and create if not. pwd, err := os.Getwd() if err != nil { - return fmt.Errorf("error getting current working directory: %w", err) + return fmt.Errorf("getting current working directory: %w", err) } binDir := filepath.Join(pwd, "bin") if err := common.MakeDirectoryIfNotExists(binDir); err != nil { - return fmt.Errorf("error making bin directory: %w", err) + return fmt.Errorf("making bin directory: %w", err) } npmdir, err := getNpmBinPath() if err != nil { - return err + return fmt.Errorf("getting npm path: %w", err) } args := []string{ @@ -207,7 +183,7 @@ func (a AssemblyScript) Build(out io.Writer, verbose bool) error { func getNpmBinPath() (string, error) { path, err := exec.Command("npm", "bin").Output() if err != nil { - return "", fmt.Errorf("error getting npm bin path: %w", err) + return "", err } return strings.TrimSpace(string(path)), nil } diff --git a/pkg/compute/build.go b/pkg/compute/build.go index b403ac84f..ffa8f9a57 100644 --- a/pkg/compute/build.go +++ b/pkg/compute/build.go @@ -23,16 +23,44 @@ const IgnoreFilePath = ".fastlyignore" // Toolchain abstracts a Compute@Edge source language toolchain. type Toolchain interface { - Name() string - DisplayName() string - StarterKits() []StarterKit - SourceDirectory() string - IncludeFiles() []string Initialize(out io.Writer) error Verify(out io.Writer) error Build(out io.Writer, verbose bool) error } +// Language models a Compute@Edge source language. +type Language struct { + Name string + DisplayName string + StarterKits []StarterKit + SourceDirectory string + IncludeFiles []string + + Toolchain +} + +// LanguageOptions models configuration options for a Language. +type LanguageOptions struct { + Name string + DisplayName string + StarterKits []StarterKit + SourceDirectory string + IncludeFiles []string + Toolchain Toolchain +} + +// NewLanguage constructs a new Language from a LangaugeOptions. +func NewLanguage(options *LanguageOptions) *Language { + return &Language{ + options.Name, + options.DisplayName, + options.StarterKits, + options.SourceDirectory, + options.IncludeFiles, + options.Toolchain, + } +} + // BuildCommand produces a deployable artifact from files on the local disk. type BuildCommand struct { common.Base @@ -103,12 +131,22 @@ func (c *BuildCommand) Exec(in io.Reader, out io.Writer) (err error) { } name = sanitize.BaseName(name) - var toolchain Toolchain + var language *Language switch lang { case "assemblyscript": - toolchain = &AssemblyScript{} + language = NewLanguage(&LanguageOptions{ + Name: "assemblyscript", + SourceDirectory: "src", + IncludeFiles: []string{"package.json"}, + Toolchain: NewAssemblyScript(), + }) case "rust": - toolchain = &Rust{c.client} + language = NewLanguage(&LanguageOptions{ + Name: "rust", + SourceDirectory: "src", + IncludeFiles: []string{"Cargo.toml"}, + Toolchain: NewRust(c.client), + }) default: return fmt.Errorf("unsupported language %s", lang) } @@ -116,7 +154,7 @@ func (c *BuildCommand) Exec(in io.Reader, out io.Writer) (err error) { if !c.force { progress.Step(fmt.Sprintf("Verifying local %s toolchain...", lang)) - err = toolchain.Verify(progress) + err = language.Verify(progress) if err != nil { return err } @@ -124,7 +162,7 @@ func (c *BuildCommand) Exec(in io.Reader, out io.Writer) (err error) { progress.Step(fmt.Sprintf("Building package using %s toolchain...", lang)) - if err := toolchain.Build(progress, c.Globals.Flag.Verbose); err != nil { + if err := language.Build(progress, c.Globals.Flag.Verbose); err != nil { return err } @@ -135,7 +173,7 @@ func (c *BuildCommand) Exec(in io.Reader, out io.Writer) (err error) { files := []string{ ManifestFilename, } - files = append(files, toolchain.IncludeFiles()...) + files = append(files, language.IncludeFiles...) ignoreFiles, err := getIgnoredFiles(IgnoreFilePath) if err != nil { @@ -149,7 +187,7 @@ func (c *BuildCommand) Exec(in io.Reader, out io.Writer) (err error) { files = append(files, binFiles...) if c.includeSrc { - srcFiles, err := getNonIgnoredFiles(toolchain.SourceDirectory(), ignoreFiles) + srcFiles, err := getNonIgnoredFiles(language.SourceDirectory, ignoreFiles) if err != nil { return err } diff --git a/pkg/compute/init.go b/pkg/compute/init.go index 01c5df308..7cd13a2c9 100644 --- a/pkg/compute/init.go +++ b/pkg/compute/init.go @@ -14,6 +14,7 @@ import ( "time" "github.com/dustinkirkland/golang-petname" + "github.com/fastly/cli/pkg/api" "github.com/fastly/cli/pkg/common" "github.com/fastly/cli/pkg/compute/manifest" "github.com/fastly/cli/pkg/config" @@ -34,9 +35,21 @@ var ( domainNameRegEx = regexp.MustCompile(`(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)+[a-z0-9][a-z0-9-]{0,61}[a-z0-9]`) fastlyOrgRegEx = regexp.MustCompile(`^https:\/\/github\.com\/fastly`) fastlyFileIgnoreListRegEx = regexp.MustCompile(`\.github|LICENSE|SECURITY\.md|CHANGELOG\.md|screenshot\.png`) - languages = []Toolchain{ - &Rust{}, - &AssemblyScript{}, + starterKits = map[string][]StarterKit{ + "assemblyscript": { + { + Name: "Default", + Path: "https://github.com/fastly/compute-starter-kit-assemblyscript-default", + Tag: "v0.1.0", + }, + }, + "rust": { + { + Name: "Default", + Path: "https://github.com/fastly/fastly-template-rust-default.git", + Branch: "0.4.0", + }, + }, } ) @@ -51,6 +64,7 @@ type StarterKit struct { // InitCommand initializes a Compute@Edge project package on the local machine. type InitCommand struct { common.Base + client api.HTTPClient manifest manifest.Data language string from string @@ -62,9 +76,10 @@ type InitCommand struct { } // NewInitCommand returns a usable command registered under the parent. -func NewInitCommand(parent common.Registerer, globals *config.Data) *InitCommand { +func NewInitCommand(parent common.Registerer, client api.HTTPClient, globals *config.Data) *InitCommand { var c InitCommand c.Globals = globals + c.client = client c.manifest.File.Read(manifest.Filename) c.CmdClause = parent.Command("init", "Initialize a new Compute@Edge package locally") c.CmdClause.Flag("service-id", "Existing service ID to use. By default, this command creates a new service").Short('s').StringVar(&c.manifest.Flag.ServiceID) @@ -119,9 +134,24 @@ func (c *InitCommand) Exec(in io.Reader, out io.Writer) (err error) { name string description string authors []string - language Toolchain + language *Language ) + languages := []*Language{ + NewLanguage(&LanguageOptions{ + Name: "rust", + DisplayName: "Rust", + StarterKits: starterKits["rust"], + Toolchain: NewRust(c.client), + }), + NewLanguage(&LanguageOptions{ + Name: "assemblyscript", + DisplayName: "AssemblyScript (beta)", + StarterKits: starterKits["assemblyscript"], + Toolchain: NewAssemblyScript(), + }), + } + name, _ = c.manifest.Name() description, _ = c.manifest.Description() authors, _ = c.manifest.Authors() @@ -194,9 +224,9 @@ func (c *InitCommand) Exec(in io.Reader, out io.Writer) (err error) { if c.language == "" { text.Output(out, "%s", text.Bold("Language:")) for i, lang := range languages { - text.Output(out, "[%d] %s", i+1, lang.DisplayName()) + text.Output(out, "[%d] %s", i+1, lang.DisplayName) } - option, err := text.Input(out, "Choose option: [1] ", in, validateLanguageOption) + option, err := text.Input(out, "Choose option: [1] ", in, validateLanguageOption(languages)) if err != nil { return fmt.Errorf("reading input %w", err) } @@ -210,7 +240,7 @@ func (c *InitCommand) Exec(in io.Reader, out io.Writer) (err error) { } } else { for _, l := range languages { - if strings.EqualFold(c.language, l.Name()) { + if strings.EqualFold(c.language, l.Name) { language = l } } @@ -218,10 +248,10 @@ func (c *InitCommand) Exec(in io.Reader, out io.Writer) (err error) { if c.from == "" && !c.manifest.File.Exists() { text.Output(out, "%s", text.Bold("Starter kit:")) - for i, kit := range language.StarterKits() { + for i, kit := range language.StarterKits { text.Output(out, "[%d] %s (%s)", i+1, kit.Name, kit.Path) } - option, err := text.Input(out, "Choose option or type URL: [1] ", in, validateTemplateOptionOrURL(language.StarterKits())) + option, err := text.Input(out, "Choose option or type URL: [1] ", in, validateTemplateOptionOrURL(language.StarterKits)) if err != nil { return fmt.Errorf("error reading input %w", err) } @@ -230,7 +260,7 @@ func (c *InitCommand) Exec(in io.Reader, out io.Writer) (err error) { } if i, err := strconv.Atoi(option); err == nil { - template := language.StarterKits()[i-1] + template := language.StarterKits[i-1] c.from = template.Path c.branch = template.Branch c.tag = template.Tag @@ -434,8 +464,8 @@ func (c *InitCommand) Exec(in io.Reader, out io.Writer) (err error) { fmt.Fprintf(progress, "Setting version in manifest to %d...\n", version) m.Version = version - fmt.Fprintf(progress, "Setting language in manifest to %s...\n", language.Name()) - m.Language = language.Name() + fmt.Fprintf(progress, "Setting language in manifest to %s...\n", language.Name) + m.Language = language.Name if err := m.Write(filepath.Join(c.path, ManifestFilename)); err != nil { return fmt.Errorf("error saving package manifest: %w", err) @@ -522,18 +552,20 @@ func tempDir(prefix string) (abspath string, err error) { return abspath, nil } -func validateLanguageOption(input string) error { - errMsg := fmt.Errorf("must be a valid option") - if input == "" { - return nil - } - if option, err := strconv.Atoi(input); err == nil { - if option > len(languages) { - return errMsg +func validateLanguageOption(languages []*Language) func(string) error { + return func(input string) error { + errMsg := fmt.Errorf("must be a valid option") + if input == "" { + return nil } - return nil + if option, err := strconv.Atoi(input); err == nil { + if option > len(languages) { + return errMsg + } + return nil + } + return errMsg } - return errMsg } func validateTemplateOptionOrURL(templates []StarterKit) func(string) error { diff --git a/pkg/compute/rust.go b/pkg/compute/rust.go index 0a0359826..850a337e5 100644 --- a/pkg/compute/rust.go +++ b/pkg/compute/rust.go @@ -81,23 +81,9 @@ type Rust struct { client api.HTTPClient } -// Name implements the Toolchain interface and returns the name of the toolchain. -func (r Rust) Name() string { return "rust" } - -// DisplayName implements the Toolchain interface and returns the name of the -// toolchain suitable for displaying or printing to output. -func (r Rust) DisplayName() string { return "Rust" } - -// StarterKits implements the Toolchain interface and returns the list of -// starter kits that can be used to initialize a new package for the toolchain. -func (r Rust) StarterKits() []StarterKit { - return []StarterKit{ - { - Name: "Default", - Path: "https://github.com/fastly/fastly-template-rust-default.git", - Branch: "0.4.0", - }, - } +// NewRust constructs a new Rust. +func NewRust(client api.HTTPClient) *Rust { + return &Rust{client} } // SourceDirectory implements the Toolchain interface and returns the source