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() {