diff --git a/.fastly/config.toml b/.fastly/config.toml index dcd9e100f..a8cc187ac 100644 --- a/.fastly/config.toml +++ b/.fastly/config.toml @@ -1,12 +1,14 @@ -config_version = 2 +config_version = 3 [fastly] api_endpoint = "https://api.fastly.com" [language] [language.go] -tinygo_constraint = ">= 0.26.0-0" # NOTE -0 indicates to the CLI's semver package that we accept pre-releases (TinyGo users commonly use pre-releases). -toolchain_constraint = ">= 1.18" +tinygo_constraint = ">= 0.28.1-0" # NOTE -0 indicates to the CLI's semver package that we accept pre-releases (TinyGo users commonly use pre-releases). +tinygo_constraint_fallback = ">= 0.26.0-0" # The Fastly Go SDK 0.2.0 requires `tinygo_constraint` but the 0.1.x SDK requires this constraint. +toolchain_constraint = ">= 1.21" # Go toolchain constraint for use with WASI support. +toolchain_constraint_tinygo = ">= 1.18" # Go toolchain constraint for use with TinyGo. [language.rust] toolchain_constraint = ">= 1.56.1" diff --git a/.github/workflows/pr_test.yml b/.github/workflows/pr_test.yml index 7c6ece293..53799655e 100644 --- a/.github/workflows/pr_test.yml +++ b/.github/workflows/pr_test.yml @@ -29,7 +29,7 @@ jobs: - name: Install Go uses: actions/setup-go@v4 with: - go-version: 1.20.x + go-version: 1.21.x # NOTE: Manage GitHub Actions cache via https://github.com/fastly/cli/actions/caches # This is useful if you need to clear the cache when a dependency doesn't update correctly. # @@ -87,7 +87,7 @@ jobs: strategy: matrix: tinygo-version: [0.27.0] - go-version: [1.20.x] + go-version: [1.21.x] node-version: [18] platform: [ubuntu-latest, macos-latest, windows-latest] runs-on: ${{ matrix.platform }} diff --git a/.github/workflows/tag_release.yml b/.github/workflows/tag_release.yml index cda4886f9..296c6b072 100644 --- a/.github/workflows/tag_release.yml +++ b/.github/workflows/tag_release.yml @@ -15,7 +15,7 @@ jobs: - name: "Install Go" uses: actions/setup-go@v4 with: - go-version: '1.20.x' + go-version: '1.21.x' - name: "Set GOHOSTOS and GOHOSTARCH" run: echo "GOHOSTOS=$(go env GOHOSTOS)" >> $GITHUB_ENV && echo "GOHOSTARCH=$(go env GOHOSTARCH)" >> $GITHUB_ENV - name: "Install Rust" diff --git a/RELEASE.md b/RELEASE.md index c4d13f1ee..93908ea98 100644 --- a/RELEASE.md +++ b/RELEASE.md @@ -1,19 +1,17 @@ # Release Process 1. Merge all PRs intended for the release. -2. Rebase latest remote main branch locally (`git pull --rebase origin main`). -3. Ensure all analysis checks and tests are passing (`time TEST_COMPUTE_INIT=1 TEST_COMPUTE_BUILD=1 TEST_COMPUTE_DEPLOY=1 make all`). -4. Ensure goreleaser builds locally (`make release GORELEASER_ARGS="--skip-validate --skip-post-hooks --clean"`). -5. Open a new PR to update CHANGELOG ([example](https://github.com/fastly/cli/pull/273))[1](#note1). -6. Merge CHANGELOG. -7. Rebase latest remote main branch locally (`git pull --rebase origin main`). -8. Tag a new release (`tag=vX.Y.Z && git tag -s $tag -m "$tag" && git push origin $tag`)[2](#note2). -9. Copy/paste CHANGELOG into the [draft release](https://github.com/fastly/cli/releases). -10. Publish draft release. -11. Communicate the release in the relevant Slack channels[3](#note3). +1. Rebase latest remote main branch locally (`git pull --rebase origin main`). +1. Ensure all analysis checks and tests are passing (`time TEST_COMPUTE_INIT=1 TEST_COMPUTE_BUILD=1 TEST_COMPUTE_DEPLOY=1 make all`). +1. Ensure goreleaser builds locally (`make release GORELEASER_ARGS="--skip-validate --skip-post-hooks --clean"`). +1. Open a new PR to update CHANGELOG ([example](https://github.com/fastly/cli/pull/273))[1](#note1). +1. Merge CHANGELOG. +1. Rebase latest remote main branch locally (`git pull --rebase origin main`). +1. Tag a new release (`tag=vX.Y.Z && git tag -s $tag -m "$tag" && git push origin $tag`)[2](#note2). +1. Copy/paste CHANGELOG into the [draft release](https://github.com/fastly/cli/releases). +1. Publish draft release. ## Footnotes -1. We utilize [semantic versioning](https://semver.org/) and only include relevant/significant changes within the CHANGELOG. -2. Triggers a [github action](https://github.com/fastly/cli/blob/main/.github/workflows/tag_release.yml) that produces a 'draft' release. -3. Fastly make internal announcements in the Slack channels: `#api-clients`, `#ecp-languages`. +1. We utilize [semantic versioning](https://semver.org/) and only include relevant/significant changes within the CHANGELOG (be sure to document changes to the app config if `config_version` has changed). +1. Triggers a [github action](https://github.com/fastly/cli/blob/main/.github/workflows/tag_release.yml) that produces a 'draft' release. diff --git a/pkg/commands/compute/build_test.go b/pkg/commands/compute/build_test.go index 75fd49ee7..d702850e5 100644 --- a/pkg/commands/compute/build_test.go +++ b/pkg/commands/compute/build_test.go @@ -248,34 +248,45 @@ func TestBuildGo(t *testing.T) { // // NOTE: This test passes --verbose so we can validate specific outputs. { - name: "build script inserted dynamically when missing", + name: "build success", args: args("compute build --verbose"), applicationConfig: config.File{ Language: config.Language{ Go: config.Go{ - TinyGoConstraint: ">= 0.26.0-0", - ToolchainConstraint: ">= 1.17", + TinyGoConstraint: ">= 0.26.0-0", + ToolchainConstraintTinyGo: ">= 1.18", + ToolchainConstraint: ">= 1.21", }, }, }, fastlyManifest: ` manifest_version = 2 name = "test" - language = "go"`, + language = "go" + [scripts] + build = "go build -o bin/main.wasm ./" + env_vars = ["GOARCH=wasm", "GOOS=wasip1"] + `, wantOutput: []string{ - "No [scripts.build] found in fastly.toml.", // requires --verbose - "The following default build command for", - "tinygo build", + "The Fastly CLI build step requires a go version '>= 1.21'", + "Build script to execute", + "Build environment variables set", + "GOARCH=wasm GOOS=wasip1", + "Creating ./bin directory (for Wasm binary)", + "Built package", }, }, + // The following test case is expected to fail because we specify a custom + // build script that doesn't actually produce a ./bin/main.wasm { name: "build error", args: args("compute build"), applicationConfig: config.File{ Language: config.Language{ Go: config.Go{ - TinyGoConstraint: ">= 0.26.0-0", - ToolchainConstraint: ">= 1.17", + TinyGoConstraint: ">= 0.26.0-0", + ToolchainConstraintTinyGo: ">= 1.18", + ToolchainConstraint: ">= 1.21", }, }, }, @@ -288,30 +299,6 @@ func TestBuildGo(t *testing.T) { build = "echo no compilation happening"`, wantRemediationError: compute.DefaultBuildErrorRemediation, }, - // NOTE: This test passes --verbose so we can validate specific outputs. - { - name: "successful build", - args: args("compute build --verbose"), - applicationConfig: config.File{ - Language: config.Language{ - Go: config.Go{ - TinyGoConstraint: ">= 0.26.0-0", - ToolchainConstraint: ">= 1.17", - }, - }, - }, - fastlyManifest: fmt.Sprintf(` - manifest_version = 2 - name = "test" - language = "go" - - [scripts] - build = "%s"`, compute.GoDefaultBuildCommand), - wantOutput: []string{ - "Creating ./bin directory (for Wasm binary)", - "Built package", - }, - }, } for testcaseIdx := range scenarios { testcase := &scenarios[testcaseIdx] @@ -687,8 +674,21 @@ func TestBuildOther(t *testing.T) { // NOTE: Our only requirement is that there be a bin directory. The custom // build script we're using in the test is not going to use any files in the // directory (the script will just `echo` a message). + // + // NOTE: We create a "valid" main.wasm file with a quick shell script. + // + // Previously we set the build script to "touch ./bin/main.wasm" but since + // adding Wasm validation this no longer works as it's an empty file. + // + // So we use the following script to produce a file that LOOKS valid but isn't. + // + // magic="\x00\x61\x73\x6d\x01\x00\x00\x00" + // printf "$magic" > ./pkg/commands/compute/testdata/main.wasm rootdir := testutil.NewEnv(testutil.EnvOpts{ T: t, + Copy: []testutil.FileIO{ + {Src: "./testdata/main.wasm", Dst: "bin/main.wasm"}, + }, Write: []testutil.FileIO{ {Src: "mock content", Dst: "bin/testfile"}, }, @@ -720,7 +720,7 @@ func TestBuildOther(t *testing.T) { manifest_version = 2 name = "test" [scripts] - build = "touch ./bin/main.wasm" + build = "ls ./bin" post_build = "echo doing a post build"`, stdin: "N", wantOutput: []string{ @@ -737,7 +737,7 @@ func TestBuildOther(t *testing.T) { manifest_version = 2 name = "test" [scripts] - build = "touch ./bin/main.wasm" + build = "ls ./bin" post_build = "echo doing a post build"`, stdin: "Y", wantOutput: []string{ @@ -754,7 +754,7 @@ func TestBuildOther(t *testing.T) { name = "test" language = "other" [scripts] - build = "touch ./bin/main.wasm" + build = "ls ./bin" post_build = "echo doing a post build"`, stdin: "Y", wantOutput: []string{ @@ -770,7 +770,7 @@ func TestBuildOther(t *testing.T) { manifest_version = 2 name = "test" [scripts] - build = "touch ./bin/main.wasm" + build = "ls ./bin" post_build = "echo doing a post build with no confirmation prompt && exit 1"`, // force an error so post_build is displayed to validate it was run. wantOutput: []string{ "doing a post build with no confirmation prompt", diff --git a/pkg/commands/compute/init.go b/pkg/commands/compute/init.go index 8786fb5c0..df127b73a 100644 --- a/pkg/commands/compute/init.go +++ b/pkg/commands/compute/init.go @@ -255,10 +255,22 @@ func (c *InitCommand) Exec(in io.Reader, out io.Writer) (err error) { s := Shell{} command, args := s.Build(postInit) - noTimeout := 0 // zero indicates no timeout - err := fstexec.Command( - command, args, msg, out, spinner, c.Globals.Flags.Verbose, noTimeout, c.Globals.ErrLog, - ) + // gosec flagged this: + // G204 (CWE-78): Subprocess launched with function call as argument or cmd arguments + // Disabling as we require the user to provide this command. + // #nosec + // nosemgrep: go.lang.security.audit.dangerous-exec-command.dangerous-exec-command + err := fstexec.Command(fstexec.CommandOpts{ + Args: args, + Command: command, + Env: c.manifest.File.Scripts.EnvVars, + ErrLog: c.Globals.ErrLog, + Output: out, + Spinner: spinner, + SpinnerMessage: msg, + Timeout: 0, // zero indicates no timeout + Verbose: c.Globals.Flags.Verbose, + }) if err != nil { return err } diff --git a/pkg/commands/compute/language_go.go b/pkg/commands/compute/language_go.go index c5af7e5e5..cb3409dcd 100644 --- a/pkg/commands/compute/language_go.go +++ b/pkg/commands/compute/language_go.go @@ -1,13 +1,16 @@ package compute import ( + "bufio" "fmt" "io" + "os" "os/exec" "regexp" "strings" "github.com/Masterminds/semver/v3" + "github.com/fastly/cli/pkg/config" fsterr "github.com/fastly/cli/pkg/errors" "github.com/fastly/cli/pkg/global" @@ -15,7 +18,7 @@ import ( "github.com/fastly/cli/pkg/text" ) -// GoDefaultBuildCommand is a build command compiled into the CLI binary so it +// TinyGoDefaultBuildCommand is a build command compiled into the CLI binary so it // can be used as a fallback for customer's who have an existing C@E project and // are simply upgrading their CLI version and might not be familiar with the // changes in the 4.0.0 release with regards to how build logic has moved to the @@ -24,7 +27,7 @@ import ( // NOTE: In the 5.x CLI releases we persisted the default to the fastly.toml // We no longer do that. In 6.x we use the default and just inform the user. // This makes the experience less confusing as users didn't expect file changes. -const GoDefaultBuildCommand = "tinygo build -target=wasi -gc=conservative -o bin/main.wasm ./" +const TinyGoDefaultBuildCommand = "tinygo build -target=wasi -gc=conservative -o bin/main.wasm ./" // GoSourceDirectory represents the source code directory. │ │ const GoSourceDirectory = "." @@ -44,6 +47,7 @@ func NewGo( autoYes: globals.Flags.AutoYes, build: fastlyManifest.Scripts.Build, config: globals.Config.Language.Go, + env: fastlyManifest.Scripts.EnvVars, errlog: globals.ErrLog, input: in, nonInteractive: globals.Flags.NonInteractive, @@ -70,6 +74,8 @@ type Go struct { build string // config is the Go specific application configuration. config config.Go + // env is environment variables to be set. + env []string // errlog is an abstraction for recording errors to disk. errlog fsterr.LogInterface // input is the user's terminal stdin stream @@ -91,27 +97,55 @@ type Go struct { // Build compiles the user's source code into a Wasm binary. func (g *Go) Build() error { - var noBuildScript bool + var ( + tinygoToolchain bool + toolchainConstraint string + ) + if g.build == "" { - g.build = GoDefaultBuildCommand - noBuildScript = true + g.build = TinyGoDefaultBuildCommand + tinygoToolchain = true + toolchainConstraint = g.config.ToolchainConstraintTinyGo + text.Info(g.output, "No [scripts.build] found in fastly.toml. Visit https://developer.fastly.com/learning/compute/go/ to learn how to target standard Go vs TinyGo.") + text.Break(g.output) + text.Description(g.output, "The following default build command for TinyGo will be used", g.build) } - if noBuildScript && g.verbose { - text.Info(g.output, "No [scripts.build] found in fastly.toml. The following default build command for Go will be used: `%s`\n", g.build) + if g.build != "" { + // IMPORTANT: All Fastly starter-kits for Go/TinyGo will have build script. + // + // So we'll need to parse the build script to identify if TinyGo is used so + // we can set the constraints appropriately. + if strings.Contains(g.build, "tinygo build") { + tinygoToolchain = true + toolchainConstraint = g.config.ToolchainConstraintTinyGo + } else { + toolchainConstraint = g.config.ToolchainConstraint + } } + // IMPORTANT: The Go SDK 0.2.0 bumps the tinygo requirement to 0.28.1 + // + // This means we need to check the go.mod of the user's project for + // `compute-sdk-go` and then parse the version and identify if it's less than + // 0.2.0 version. If it less than, change the TinyGo constraint to 0.26.0 + tinygoConstraint := identifyTinyGoConstraint(g.config.TinyGoConstraint, g.config.TinyGoConstraintFallback) + g.toolchainConstraint( - "go", `go version go(?P\d[^\s]+)`, g.config.ToolchainConstraint, - ) - g.toolchainConstraint( - "tinygo", `tinygo version (?P\d[^\s]+)`, g.config.TinyGoConstraint, + "go", `go version go(?P\d[^\s]+)`, toolchainConstraint, ) + if tinygoToolchain { + g.toolchainConstraint( + "tinygo", `tinygo version (?P\d[^\s]+)`, tinygoConstraint, + ) + } + bt := BuildToolchain{ autoYes: g.autoYes, buildFn: g.Shell.Build, buildScript: g.build, + env: g.env, errlog: g.errlog, in: g.input, nonInteractive: g.nonInteractive, @@ -125,6 +159,84 @@ func (g *Go) Build() error { return bt.Build() } +// identifyTinyGoConstraint checks the compute-sdk-go version used by the +// project and if it's less than 0.2.0 we'll change the TinyGo constraint to be +// version 0.26.0 +// +// We do this because the 0.2.0 release of the compute-sdk-go bumps the TinyGo +// version requirement to 0.28.1 and we want to avoid any scenarios where a +// bump in SDK version causes the user's build to break (which would happen for +// users with a pre-existing project who happen to update their CLI version: the +// new CLI version would have a TinyGo constraint that would be higher than +// before and would stop their build from working). +// +// NOTE: The `configConstraint` is the latest CLI application config version. +// If there are any errors trying to parse the go.mod we'll default to the +// config constraint. +func identifyTinyGoConstraint(configConstraint, fallback string) string { + moduleName := "github.com/fastly/compute-sdk-go" + version := "" + + f, err := os.Open("go.mod") + if err != nil { + return configConstraint + } + defer f.Close() + + scanner := bufio.NewScanner(f) + for scanner.Scan() { + line := scanner.Text() + parts := strings.Fields(line) + + // go.mod has two separate definition possibilities: + // + // 1. + // require github.com/fastly/compute-sdk-go v0.1.7 + // + // 2. + // require ( + // github.com/fastly/compute-sdk-go v0.1.7 + // ) + if len(parts) >= 2 { + // 1. require [github.com/fastly/compute-sdk-go] v0.1.7 + if parts[1] == moduleName { + version = strings.TrimPrefix(parts[2], "v") + break + } + // 2. [github.com/fastly/compute-sdk-go] v0.1.7 + if parts[0] == moduleName { + version = strings.TrimPrefix(parts[1], "v") + break + } + } + } + + if err := scanner.Err(); err != nil { + return configConstraint + } + + if version == "" { + return configConstraint + } + + gomodVersion, err := semver.NewVersion(version) + if err != nil { + return configConstraint + } + + // 0.2.0 introduces the break by bumping the TinyGo minimum version to 0.28.1 + breakingSDKVersion, err := semver.NewVersion("0.2.0") + if err != nil { + return configConstraint + } + + if gomodVersion.LessThan(breakingSDKVersion) { + return fallback + } + + return configConstraint +} + // toolchainConstraint warns the user if the required constraint is not met. // // NOTE: We don't stop the build as their toolchain may compile successfully. @@ -132,7 +244,7 @@ func (g *Go) Build() error { // the opportunity to do something about it if they choose. func (g *Go) toolchainConstraint(toolchain, pattern, constraint string) { if g.verbose { - text.Info(g.output, "The Fastly CLI requires a %s version '%s'. ", toolchain, constraint) + text.Info(g.output, "The Fastly CLI build step requires a %s version '%s'. ", toolchain, constraint) } versionCommand := fmt.Sprintf("%s version", toolchain) diff --git a/pkg/commands/compute/language_toolchain.go b/pkg/commands/compute/language_toolchain.go index cad5b68d0..9902d3434 100644 --- a/pkg/commands/compute/language_toolchain.go +++ b/pkg/commands/compute/language_toolchain.go @@ -1,9 +1,12 @@ package compute import ( + "bytes" + "encoding/binary" "fmt" "io" "os" + "strconv" "strings" fsterr "github.com/fastly/cli/pkg/errors" @@ -11,6 +14,14 @@ import ( "github.com/fastly/cli/pkg/text" ) +const ( + // https://webassembly.github.io/spec/core/binary/modules.html#binary-module + wasmBytes = 4 + + // Defining as a constant avoids gosec G304 issue with command execution. + binWasmPath = "./bin/main.wasm" +) + // DefaultBuildErrorRemediation is the message returned to a user when there is // a build error. var DefaultBuildErrorRemediation = func() string { @@ -41,6 +52,8 @@ type BuildToolchain struct { buildFn func(string) (string, []string) // buildScript is the [scripts.build] within the fastly.toml manifest. buildScript string + // env is environment variables to be set. + env []string // errlog is an abstraction for recording errors to disk. errlog fsterr.LogInterface // in is the user's terminal stdin stream @@ -69,6 +82,9 @@ func (bt BuildToolchain) Build() error { if bt.verbose { text.Break(bt.out) text.Description(bt.out, "Build script to execute", fmt.Sprintf("%s %s", cmd, strings.Join(args, " "))) + if len(bt.env) > 0 { + text.Description(bt.out, "Build environment variables set", strings.Join(bt.env, " ")) + } } var err error @@ -140,11 +156,16 @@ func (bt BuildToolchain) Build() error { // IMPORTANT: The stat check MUST come after the internalPostBuildCallback. // This is because for Rust it needs to move the binary first. - _, err = os.Stat("./bin/main.wasm") + _, err = os.Stat(binWasmPath) if err != nil { return bt.handleError(err) } + // NOTE: The logic for checking the Wasm binary is 'valid' is not exhaustive. + if err := bt.validateWasm(); err != nil { + return err + } + if bt.postBuild != "" { if !bt.autoYes && !bt.nonInteractive { msg := fmt.Sprintf(CustomPostScriptMessage, "build") @@ -179,6 +200,44 @@ func (bt BuildToolchain) Build() error { return nil } +// The encoding of a module starts with a preamble containing a 4-byte magic +// number (the string '\0asm') and a version field. +// +// Reference: +// https://webassembly.github.io/spec/core/binary/modules.html#binary-module +func (bt BuildToolchain) validateWasm() error { + f, err := os.Open(binWasmPath) + if err != nil { + return bt.handleError(err) + } + defer f.Close() + + // Parse the magic number + magic := make([]byte, wasmBytes) + _, err = f.Read(magic) + if err != nil { + return bt.handleError(err) + } + expectedMagic := []byte{0x00, 0x61, 0x73, 0x6d} + if !bytes.Equal(magic, expectedMagic) { + return bt.handleError(fmt.Errorf("unexpected magic: %#v", magic)) + } + if bt.verbose { + text.Break(bt.out) + text.Description(bt.out, "Wasm module 'magic'", fmt.Sprintf("%#v", magic)) + } + + // Parse the version + var version uint32 + if err := binary.Read(f, binary.LittleEndian, &version); err != nil { + return bt.handleError(err) + } + if bt.verbose { + text.Description(bt.out, "Wasm module 'version'", strconv.FormatUint(uint64(version), 10)) + } + return nil +} + func (bt BuildToolchain) handleError(err error) error { return fsterr.RemediationError{ Inner: err, @@ -194,9 +253,17 @@ func (bt BuildToolchain) handleError(err error) error { // This causes the spinner message to be displayed twice with different status. // By passing in the spinner and message we can short-circuit the spinner. func (bt BuildToolchain) execCommand(cmd string, args []string, spinMessage string) error { - return fstexec.Command( - cmd, args, spinMessage, bt.out, bt.spinner, bt.verbose, bt.timeout, bt.errlog, - ) + return fstexec.Command(fstexec.CommandOpts{ + Args: args, + Command: cmd, + Env: bt.env, + ErrLog: bt.errlog, + Output: bt.out, + Spinner: bt.spinner, + SpinnerMessage: spinMessage, + Timeout: bt.timeout, + Verbose: bt.verbose, + }) } // promptForPostBuildContinue ensures the user is happy to continue with the build diff --git a/pkg/commands/compute/testdata/main.wasm b/pkg/commands/compute/testdata/main.wasm new file mode 100644 index 000000000..d8fc92d02 Binary files /dev/null and b/pkg/commands/compute/testdata/main.wasm differ diff --git a/pkg/commands/config/root.go b/pkg/commands/config/root.go index 43c583be8..c0f922c64 100644 --- a/pkg/commands/config/root.go +++ b/pkg/commands/config/root.go @@ -6,6 +6,7 @@ import ( "os" "github.com/fastly/cli/pkg/cmd" + "github.com/fastly/cli/pkg/config" "github.com/fastly/cli/pkg/global" "github.com/fastly/cli/pkg/text" ) @@ -16,6 +17,7 @@ type RootCommand struct { cmd.Base location bool + reset bool } // NewRootCommand returns a new command registered in the parent. @@ -24,11 +26,16 @@ func NewRootCommand(parent cmd.Registerer, g *global.Data) *RootCommand { c.Globals = g c.CmdClause = parent.Command("config", "Display the Fastly CLI configuration") c.CmdClause.Flag("location", "Print the location of the CLI configuration file").Short('l').BoolVar(&c.location) + c.CmdClause.Flag("reset", "Reset the config to a version compatible with the current CLI version").Short('r').BoolVar(&c.reset) return &c } // Exec implements the command interface. func (c *RootCommand) Exec(_ io.Reader, out io.Writer) (err error) { + if c.reset { + c.Globals.Config.UseStatic(config.FilePath) + } + if c.location { if c.Globals.Flags.Verbose { text.Break(out) diff --git a/pkg/config/config.go b/pkg/config/config.go index c45ce89c4..b79c3b89a 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -88,11 +88,18 @@ type Go struct { // TinyGoConstraint is the `tinygo` version that we support. TinyGoConstraint string `toml:"tinygo_constraint"` - // ToolchainConstraint is the `go` version that we support. + // TinyGoConstraintFallback is a fallback `tinygo` version for users who have + // a pre-existing project with a 0.1.x Fastly Go SDK specified. + TinyGoConstraintFallback string `toml:"tinygo_constraint_fallback"` + + // ToolchainConstraint is the `go` version that we support with WASI. + ToolchainConstraint string `toml:"toolchain_constraint"` + + // ToolchainConstraintTinyGo is the `go` version that we support with TinyGo. // // We aim for go versions that support go modules by default. // https://go.dev/blog/using-go-modules - ToolchainConstraint string `toml:"toolchain_constraint"` + ToolchainConstraintTinyGo string `toml:"toolchain_constraint_tinygo"` } // Rust represents Rust C@E language specific configuration. @@ -132,9 +139,9 @@ type StarterKit struct { Branch string `toml:"branch"` } -// createConfigDir creates the application configuration directory if it +// ensureConfigDirExists creates the application configuration directory if it // doesn't already exist. -func createConfigDir(path string) error { +func ensureConfigDirExists(path string) error { basePath := filepath.Dir(path) return filesystem.MakeDirectoryIfNotExists(basePath) } @@ -251,7 +258,7 @@ func (f *File) Read( f = &staticConfig } - err = createConfigDir(path) + err = ensureConfigDirExists(path) if err != nil { errLog.Add(err) return err @@ -351,7 +358,7 @@ func (f *File) UseStatic(path string) error { f.CLI.Version = revision.SemVer(revision.AppVersion) f.MigrateLegacy() - err = createConfigDir(path) + err = ensureConfigDirExists(path) if err != nil { return err } diff --git a/pkg/exec/exec.go b/pkg/exec/exec.go index 1733e3712..b63b07889 100644 --- a/pkg/exec/exec.go +++ b/pkg/exec/exec.go @@ -23,17 +23,28 @@ const divider = "--------------------------------------------------------------- // compute commands can use this to standardize the flow control for each // compiler toolchain. type Streaming struct { - Args []string - Command string - Env []string - ForceOutput bool - Output io.Writer - Process *os.Process - SignalCh chan os.Signal - Spinner text.Spinner + // Args are the command positional arguments. + Args []string + // Command is the command to be executed. + Command string + // Env is the environment variables to set. + Env []string + // ForceOutput ensures output is displayed (default: only display on error). + ForceOutput bool + // Output is where to write output (e.g. stdout) + Output io.Writer + // Process is the process to terminal if signal received. + Process *os.Process + // SignalCh is a channel handling signal events. + SignalCh chan os.Signal + // Spinner is a specific spinner instance. + Spinner text.Spinner + // SpinnerMessage is the messaging to use. SpinnerMessage string - Timeout time.Duration - Verbose bool + // Timeout is the command timeout. + Timeout time.Duration + // Verbose outputs additional information. + Verbose bool } // MonitorSignals spawns a goroutine that configures signal handling so that @@ -157,35 +168,48 @@ func (s *Streaming) Signal(sig os.Signal) error { return nil } +// CommandOpts are arguments for executing a streaming command. +type CommandOpts struct { + // Args are the command positional arguments. + Args []string + // Command is the command to be executed. + Command string + // Env is the environment variables to set. + Env []string + // ErrLog provides an interface for recording errors to disk. + ErrLog fsterr.LogInterface + // Output is where to write output (e.g. stdout) + Output io.Writer + // Spinner is a specific spinner instance. + Spinner text.Spinner + // SpinnerMessage is the messaging to use. + SpinnerMessage string + // Timeout is the command timeout. + Timeout int + // Verbose outputs additional information. + Verbose bool +} + // Command is an abstraction over a Streaming type. It is used by both the // `compute init` and `compute build` commands to run post init/build scripts. -func Command( - cmd string, - args []string, - spinMessage string, - out io.Writer, - spinner text.Spinner, - verbose bool, - timeout int, - errLog fsterr.LogInterface, -) error { +func Command(opts CommandOpts) error { s := Streaming{ - Command: cmd, - Args: args, - Env: os.Environ(), - Output: out, - Spinner: spinner, - SpinnerMessage: spinMessage, - Verbose: verbose, + Command: opts.Command, + Args: opts.Args, + Env: opts.Env, + Output: opts.Output, + Spinner: opts.Spinner, + SpinnerMessage: opts.SpinnerMessage, + Verbose: opts.Verbose, } - if verbose { + if opts.Verbose { s.ForceOutput = true } - if timeout > 0 { - s.Timeout = time.Duration(timeout) * time.Second + if opts.Timeout > 0 { + s.Timeout = time.Duration(opts.Timeout) * time.Second } if err := s.Exec(); err != nil { - errLog.Add(err) + opts.ErrLog.Add(err) return err } return nil diff --git a/pkg/manifest/file.go b/pkg/manifest/file.go index b705c8662..4400fc295 100644 --- a/pkg/manifest/file.go +++ b/pkg/manifest/file.go @@ -188,7 +188,12 @@ func appendSpecRef(w io.Writer) error { // Scripts represents build configuration. type Scripts struct { - Build string `toml:"build,omitempty"` + // Build is a custom build script. + Build string `toml:"build,omitempty"` + // EnvVars contains build related environment variables. + EnvVars []string `toml:"env_vars,omitempty"` + // PostBuild is executed after the build step. PostBuild string `toml:"post_build,omitempty"` - PostInit string `toml:"post_init,omitempty"` + // PostInit is executed after the init step. + PostInit string `toml:"post_init,omitempty"` } diff --git a/scripts/config.sh b/scripts/config.sh index 1c7c36ec5..aea066f86 100755 --- a/scripts/config.sh +++ b/scripts/config.sh @@ -10,15 +10,16 @@ then fi kits=( + compute-starter-kit-assemblyscript-default + compute-starter-kit-go-default + compute-starter-kit-javascript-default + compute-starter-kit-javascript-empty compute-starter-kit-rust-default compute-starter-kit-rust-empty compute-starter-kit-rust-static-content compute-starter-kit-rust-websockets - compute-starter-kit-javascript-default - compute-starter-kit-javascript-empty + # compute-starter-kit-tinygo-default compute-starter-kit-typescript - compute-starter-kit-assemblyscript-default - compute-starter-kit-go-default ) function parse() {