github.com/fastly/cli@v1.7.2-0.20240304164155-9d0f1d77c3bf/pkg/commands/compute/language_assemblyscript.go (about)

     1  package compute
     2  
     3  import (
     4  	"encoding/json"
     5  	"errors"
     6  	"io"
     7  	"os"
     8  
     9  	fsterr "github.com/fastly/cli/pkg/errors"
    10  	"github.com/fastly/cli/pkg/text"
    11  )
    12  
    13  // AsDefaultBuildCommand is a build command compiled into the CLI binary so it
    14  // can be used as a fallback for customer's who have an existing Compute project and
    15  // are simply upgrading their CLI version and might not be familiar with the
    16  // changes in the 4.0.0 release with regards to how build logic has moved to the
    17  // fastly.toml manifest.
    18  //
    19  // NOTE: In the 5.x CLI releases we persisted the default to the fastly.toml
    20  // We no longer do that. In 6.x we use the default and just inform the user.
    21  // This makes the experience less confusing as users didn't expect file changes.
    22  const AsDefaultBuildCommand = "npm exec -- asc assembly/index.ts --outFile bin/main.wasm --optimize --noAssert"
    23  
    24  // AsDefaultBuildCommandForWebpack is a build command compiled into the CLI
    25  // binary so it can be used as a fallback for customer's who have an existing
    26  // Compute project using the 'default' JS Starter Kit, and are simply upgrading
    27  // their CLI version and might not be familiar with the changes in the 4.0.0
    28  // release with regards to how build logic has moved to the fastly.toml manifest.
    29  //
    30  // NOTE: For this variation of the build script to be added to the user's
    31  // fastly.toml will require a successful check for the webpack dependency.
    32  const AsDefaultBuildCommandForWebpack = "npm exec webpack && npm exec -- asc assembly/index.ts --outFile bin/main.wasm --optimize --noAssert"
    33  
    34  // AsSourceDirectory represents the source code directory.
    35  const AsSourceDirectory = "assembly"
    36  
    37  // NewAssemblyScript constructs a new AssemblyScript toolchain.
    38  func NewAssemblyScript(
    39  	c *BuildCommand,
    40  	in io.Reader,
    41  	manifestFilename string,
    42  	out io.Writer,
    43  	spinner text.Spinner,
    44  ) *AssemblyScript {
    45  	return &AssemblyScript{
    46  		Shell: Shell{},
    47  
    48  		build:                 c.Globals.Manifest.File.Scripts.Build,
    49  		errlog:                c.Globals.ErrLog,
    50  		input:                 in,
    51  		manifestFilename:      manifestFilename,
    52  		metadataFilterEnvVars: c.MetadataFilterEnvVars,
    53  		output:                out,
    54  		postBuild:             c.Globals.Manifest.File.Scripts.PostBuild,
    55  		spinner:               spinner,
    56  		timeout:               c.Flags.Timeout,
    57  		verbose:               c.Globals.Verbose(),
    58  	}
    59  }
    60  
    61  // AssemblyScript implements a Toolchain for the AssemblyScript language.
    62  type AssemblyScript struct {
    63  	Shell
    64  
    65  	// autoYes is the --auto-yes flag.
    66  	autoYes bool
    67  	// build is a shell command defined in fastly.toml using [scripts.build].
    68  	build string
    69  	// defaultBuild indicates if the default build script was used.
    70  	defaultBuild bool
    71  	// errlog is an abstraction for recording errors to disk.
    72  	errlog fsterr.LogInterface
    73  	// input is the user's terminal stdin stream
    74  	input io.Reader
    75  	// manifestFilename is the name of the manifest file.
    76  	manifestFilename string
    77  	// metadataFilterEnvVars is a comma-separated list of user defined env vars.
    78  	metadataFilterEnvVars string
    79  	// nonInteractive is the --non-interactive flag.
    80  	nonInteractive bool
    81  	// output is the users terminal stdout stream
    82  	output io.Writer
    83  	// postBuild is a custom script executed after the build but before the Wasm
    84  	// binary is added to the .tar.gz archive.
    85  	postBuild string
    86  	// spinner is a terminal progress status indicator.
    87  	spinner text.Spinner
    88  	// timeout is the build execution threshold.
    89  	timeout int
    90  	// verbose indicates if the user set --verbose
    91  	verbose bool
    92  }
    93  
    94  // DefaultBuildScript indicates if a custom build script was used.
    95  func (a *AssemblyScript) DefaultBuildScript() bool {
    96  	return a.defaultBuild
    97  }
    98  
    99  // Dependencies returns all dependencies used by the project.
   100  func (a *AssemblyScript) Dependencies() map[string]string {
   101  	deps := make(map[string]string)
   102  
   103  	lockfile := "npm-shrinkwrap.json"
   104  	_, err := os.Stat(lockfile)
   105  	if errors.Is(err, os.ErrNotExist) {
   106  		lockfile = "package-lock.json"
   107  	}
   108  
   109  	var jlf JavaScriptLockFile
   110  	if f, err := os.Open(lockfile); err == nil {
   111  		if err := json.NewDecoder(f).Decode(&jlf); err == nil {
   112  			for k, v := range jlf.Packages {
   113  				if k != "" { // avoid "root" package
   114  					deps[k] = v.Version
   115  				}
   116  			}
   117  		}
   118  	}
   119  
   120  	return deps
   121  }
   122  
   123  // Build compiles the user's source code into a Wasm binary.
   124  func (a *AssemblyScript) Build() error {
   125  	if !a.verbose {
   126  		text.Break(a.output)
   127  	}
   128  	text.Deprecated(a.output, "The Fastly AssemblyScript SDK is being deprecated in favor of the more up-to-date and feature-rich JavaScript SDK. You can learn more about the JavaScript SDK on our Developer Hub Page - https://developer.fastly.com/learning/compute/javascript/\n\n")
   129  
   130  	if a.build == "" {
   131  		a.build = AsDefaultBuildCommand
   132  		a.defaultBuild = true
   133  	}
   134  
   135  	usesWebpack, err := a.checkForWebpack()
   136  	if err != nil {
   137  		return err
   138  	}
   139  	if usesWebpack {
   140  		a.build = AsDefaultBuildCommandForWebpack
   141  	}
   142  
   143  	if a.defaultBuild && a.verbose {
   144  		text.Info(a.output, "No [scripts.build] found in %s. The following default build command for AssemblyScript will be used: `%s`\n\n", a.manifestFilename, a.build)
   145  	}
   146  
   147  	bt := BuildToolchain{
   148  		autoYes:               a.autoYes,
   149  		buildFn:               a.Shell.Build,
   150  		buildScript:           a.build,
   151  		errlog:                a.errlog,
   152  		in:                    a.input,
   153  		manifestFilename:      a.manifestFilename,
   154  		metadataFilterEnvVars: a.metadataFilterEnvVars,
   155  		nonInteractive:        a.nonInteractive,
   156  		out:                   a.output,
   157  		postBuild:             a.postBuild,
   158  		spinner:               a.spinner,
   159  		timeout:               a.timeout,
   160  		verbose:               a.verbose,
   161  	}
   162  
   163  	return bt.Build()
   164  }
   165  
   166  func (a AssemblyScript) checkForWebpack() (bool, error) {
   167  	wd, err := os.Getwd()
   168  	if err != nil {
   169  		return false, err
   170  	}
   171  
   172  	home, err := os.UserHomeDir()
   173  	if err != nil {
   174  		return false, err
   175  	}
   176  
   177  	found, path, err := search("package.json", wd, home)
   178  	if err != nil {
   179  		return false, err
   180  	}
   181  
   182  	if found {
   183  		// gosec flagged this:
   184  		// G304 (CWE-22): Potential file inclusion via variable
   185  		//
   186  		// Disabling as the path is determined by our own logic.
   187  		/* #nosec */
   188  		data, err := os.ReadFile(path)
   189  		if err != nil {
   190  			return false, err
   191  		}
   192  
   193  		var pkg NPMPackage
   194  
   195  		err = json.Unmarshal(data, &pkg)
   196  		if err != nil {
   197  			return false, err
   198  		}
   199  
   200  		for k := range pkg.DevDependencies {
   201  			if k == "webpack" {
   202  				return true, nil
   203  			}
   204  		}
   205  
   206  		for k := range pkg.Dependencies {
   207  			if k == "webpack" {
   208  				return true, nil
   209  			}
   210  		}
   211  	}
   212  
   213  	return false, nil
   214  }