|
| 1 | +package compute |
| 2 | + |
| 3 | +import ( |
| 4 | + "fmt" |
| 5 | + "io" |
| 6 | + "os" |
| 7 | + "os/exec" |
| 8 | + "path/filepath" |
| 9 | + "strings" |
| 10 | + |
| 11 | + "github.com/fastly/cli/pkg/common" |
| 12 | + "github.com/fastly/cli/pkg/errors" |
| 13 | + "github.com/fastly/cli/pkg/text" |
| 14 | +) |
| 15 | + |
| 16 | +// AssemblyScript implements Toolchain for the AssemblyScript language. |
| 17 | +type AssemblyScript struct{} |
| 18 | + |
| 19 | +// NewAssemblyScript constructs a new AssemblyScript. |
| 20 | +func NewAssemblyScript() *AssemblyScript { |
| 21 | + return &AssemblyScript{} |
| 22 | +} |
| 23 | + |
| 24 | +// Verify implements the Toolchain interface and verifies whether the |
| 25 | +// AssemblyScript language toolchain is correctly configured on the host. |
| 26 | +func (a AssemblyScript) Verify(out io.Writer) error { |
| 27 | + // 1) Check `npm` is on $PATH |
| 28 | + // |
| 29 | + // npm is Node/AssemblyScript's toolchain installer and manager, it is |
| 30 | + // needed to assert that the correct versions of the asc compiler and |
| 31 | + // @fastly/as-compute package are installed. We only check whether the |
| 32 | + // binary exists on the users $PATH and error with installation help text. |
| 33 | + fmt.Fprintf(out, "Checking if npm is installed...\n") |
| 34 | + |
| 35 | + p, err := exec.LookPath("npm") |
| 36 | + if err != nil { |
| 37 | + return errors.RemediationError{ |
| 38 | + Inner: fmt.Errorf("`npm` not found in $PATH"), |
| 39 | + Remediation: fmt.Sprintf("To fix this error, install Node.js and npm by visiting:\n\n\t$ %s", text.Bold("https://nodejs.org/")), |
| 40 | + } |
| 41 | + } |
| 42 | + |
| 43 | + fmt.Fprintf(out, "Found npm at %s\n", p) |
| 44 | + |
| 45 | + // 2) Check package.json file exists in $PWD |
| 46 | + // |
| 47 | + // A valid npm package is needed for compilation and to assert whether the |
| 48 | + // required dependencies are installed locally. Therefore, we first assert |
| 49 | + // whether one exists in the current $PWD. |
| 50 | + fpath, err := filepath.Abs("package.json") |
| 51 | + if err != nil { |
| 52 | + return fmt.Errorf("getting package.json path: %w", err) |
| 53 | + } |
| 54 | + |
| 55 | + if !common.FileExists(fpath) { |
| 56 | + return errors.RemediationError{ |
| 57 | + Inner: fmt.Errorf("package.json not found"), |
| 58 | + Remediation: fmt.Sprintf("To fix this error, run the following command:\n\n\t$ %s", text.Bold("npm init")), |
| 59 | + } |
| 60 | + } |
| 61 | + |
| 62 | + fmt.Fprintf(out, "Found package.json at %s\n", fpath) |
| 63 | + |
| 64 | + // 3) Check if `asc` is installed. |
| 65 | + // |
| 66 | + // asc is the AssemblyScript compiler. We first check if it exists in the |
| 67 | + // package.json and then whether the binary exists in the npm bin directory. |
| 68 | + fmt.Fprintf(out, "Checking if AssemblyScript is installed...\n") |
| 69 | + if !checkPackageDependencyExists("assemblyscript") { |
| 70 | + return errors.RemediationError{ |
| 71 | + Inner: fmt.Errorf("`assemblyscript` not found in package.json"), |
| 72 | + Remediation: fmt.Sprintf("To fix this error, run the following command:\n\n\t$ %s", text.Bold("npm install --save-dev assemblyscript")), |
| 73 | + } |
| 74 | + } |
| 75 | + |
| 76 | + p, err = getNpmBinPath() |
| 77 | + if err != nil { |
| 78 | + return errors.RemediationError{ |
| 79 | + Inner: fmt.Errorf("could not determine npm bin path"), |
| 80 | + Remediation: fmt.Sprintf("To fix this error, run the following command:\n\n\t$ %s", text.Bold("npm install --global npm@latest")), |
| 81 | + } |
| 82 | + } |
| 83 | + |
| 84 | + path, err := exec.LookPath(filepath.Join(p, "asc")) |
| 85 | + if err != nil { |
| 86 | + return fmt.Errorf("getting asc path: %w", err) |
| 87 | + } |
| 88 | + if !common.FileExists(path) { |
| 89 | + return errors.RemediationError{ |
| 90 | + Inner: fmt.Errorf("`asc` binary not found in %s", p), |
| 91 | + Remediation: fmt.Sprintf("To fix this error, run the following command:\n\n\t$ %s", text.Bold("npm install --save-dev assemblyscript")), |
| 92 | + } |
| 93 | + } |
| 94 | + |
| 95 | + fmt.Fprintf(out, "Found asc at %s\n", path) |
| 96 | + |
| 97 | + return nil |
| 98 | +} |
| 99 | + |
| 100 | +// Initialize implements the Toolchain interface and initializes a newly cloned |
| 101 | +// package by installing required dependencies. |
| 102 | +func (a AssemblyScript) Initialize(out io.Writer) error { |
| 103 | + // 1) Check `npm` is on $PATH |
| 104 | + // |
| 105 | + // npm is Node/AssemblyScript's toolchain package manager, it is needed to |
| 106 | + // install the package dependencies on initialization. We only check whether |
| 107 | + // the binary exists on the users $PATH and error with installation help text. |
| 108 | + fmt.Fprintf(out, "Checking if npm is installed...\n") |
| 109 | + |
| 110 | + p, err := exec.LookPath("npm") |
| 111 | + if err != nil { |
| 112 | + return errors.RemediationError{ |
| 113 | + Inner: fmt.Errorf("`npm` not found in $PATH"), |
| 114 | + Remediation: fmt.Sprintf("To fix this error, install Node.js and npm by visiting:\n\n\t$ %s", text.Bold("https://nodejs.org/")), |
| 115 | + } |
| 116 | + } |
| 117 | + |
| 118 | + fmt.Fprintf(out, "Found npm at %s\n", p) |
| 119 | + |
| 120 | + // 2) Check package.json file exists in $PWD |
| 121 | + // |
| 122 | + // A valid npm package manifest file is needed for the install command to |
| 123 | + // work. Therefore, we first assert whether one exists in the current $PWD. |
| 124 | + fpath, err := filepath.Abs("package.json") |
| 125 | + if err != nil { |
| 126 | + return fmt.Errorf("getting package.json path: %w", err) |
| 127 | + } |
| 128 | + |
| 129 | + if !common.FileExists(fpath) { |
| 130 | + return errors.RemediationError{ |
| 131 | + Inner: fmt.Errorf("package.json not found"), |
| 132 | + Remediation: fmt.Sprintf("To fix this error, run the following command:\n\n\t$ %s", text.Bold("npm init")), |
| 133 | + } |
| 134 | + } |
| 135 | + |
| 136 | + fmt.Fprintf(out, "Found package.json at %s\n", fpath) |
| 137 | + |
| 138 | + // Call npm install. |
| 139 | + cmd := common.NewStreamingExec("npm", []string{"install"}, []string{}, false, out) |
| 140 | + return cmd.Exec() |
| 141 | +} |
| 142 | + |
| 143 | +// Build implements the Toolchain interface and attempts to compile the package |
| 144 | +// AssemblyScript source to a Wasm binary. |
| 145 | +func (a AssemblyScript) Build(out io.Writer, verbose bool) error { |
| 146 | + // Check if bin directory exists and create if not. |
| 147 | + pwd, err := os.Getwd() |
| 148 | + if err != nil { |
| 149 | + return fmt.Errorf("getting current working directory: %w", err) |
| 150 | + } |
| 151 | + binDir := filepath.Join(pwd, "bin") |
| 152 | + if err := common.MakeDirectoryIfNotExists(binDir); err != nil { |
| 153 | + return fmt.Errorf("making bin directory: %w", err) |
| 154 | + } |
| 155 | + |
| 156 | + npmdir, err := getNpmBinPath() |
| 157 | + if err != nil { |
| 158 | + return fmt.Errorf("getting npm path: %w", err) |
| 159 | + } |
| 160 | + |
| 161 | + args := []string{ |
| 162 | + "assembly/index.ts", |
| 163 | + "--binaryFile", |
| 164 | + filepath.Join(binDir, "main.wasm"), |
| 165 | + "--optimize", |
| 166 | + "--noAssert", |
| 167 | + } |
| 168 | + if verbose { |
| 169 | + args = append(args, "--verbose") |
| 170 | + } |
| 171 | + |
| 172 | + fmt.Fprintf(out, "Installing package dependencies...\n") |
| 173 | + |
| 174 | + // Call asc with the build arguments. |
| 175 | + cmd := common.NewStreamingExec(filepath.Join(npmdir, "asc"), args, []string{}, verbose, out) |
| 176 | + if err := cmd.Exec(); err != nil { |
| 177 | + return err |
| 178 | + } |
| 179 | + |
| 180 | + return nil |
| 181 | +} |
| 182 | + |
| 183 | +func getNpmBinPath() (string, error) { |
| 184 | + path, err := exec.Command("npm", "bin").Output() |
| 185 | + if err != nil { |
| 186 | + return "", err |
| 187 | + } |
| 188 | + return strings.TrimSpace(string(path)), nil |
| 189 | +} |
| 190 | + |
| 191 | +func checkPackageDependencyExists(name string) bool { |
| 192 | + // gosec flagged this: |
| 193 | + // G204 (CWE-78): Subprocess launched with variable |
| 194 | + // Disabling as the variables come from trusted sources. |
| 195 | + /* #nosec */ |
| 196 | + err := exec.Command("npm", "list", "--json", "--depth", "0", name).Run() |
| 197 | + return err == nil |
| 198 | +} |
0 commit comments