-
Notifications
You must be signed in to change notification settings - Fork 7
/
main.go
186 lines (157 loc) · 5.1 KB
/
main.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
package main
import (
"bytes"
"fmt"
"io"
"os"
"os/exec"
"path/filepath"
"strings"
"github.com/bitrise-io/go-steputils/cache"
"github.com/bitrise-io/go-steputils/stepconf"
"github.com/bitrise-io/go-utils/command"
"github.com/bitrise-io/go-utils/errorutil"
"github.com/bitrise-io/go-utils/log"
"github.com/kballard/go-shellquote"
)
type config struct {
WorkingDir string `env:"workdir,dir"`
YarnCommand string `env:"command"`
YarnArgs string `env:"args"`
UseCache bool `env:"cache_local_deps,opt[yes,no]"`
IsDebugLog bool `env:"verbose_log,opt[yes,no]"`
}
func main() {
var config config
if err := stepconf.Parse(&config); err != nil {
failf("Process config: %s", err)
}
stepconf.Print(config)
fmt.Println()
log.SetEnableDebugLog(config.IsDebugLog)
absWorkingDir, err := filepath.Abs(config.WorkingDir)
if err != nil {
failf("Process config: failed to normalize working directory: %s", err)
}
commandParams, err := shellquote.Split(config.YarnCommand)
if err != nil {
failf("Process config: provided yarn command is not a valid CLI command: %s", err)
}
args, err := shellquote.Split(config.YarnArgs)
if err != nil {
failf("Process config: provided yarn arguments are not valid CLI arguments: %s", err)
}
validInstallation := validateYarnInstallation(absWorkingDir)
if !validInstallation {
if err := installYarn(); err != nil {
failf("Install dependencies: %s", err)
}
if err := printYarnVersion(absWorkingDir); err != nil {
failf("Install dependencies: %s", err)
}
}
yarnCmd := command.New("yarn", append(commandParams, args...)...)
var output bytes.Buffer
yarnCmd.SetDir(absWorkingDir)
yarnCmd.SetStdout(io.MultiWriter(os.Stdout, &output)).SetStderr(io.MultiWriter(os.Stderr, &output))
fmt.Println()
log.Donef("$ %s", yarnCmd.PrintableCommandArgs())
fmt.Println()
if err := yarnCmd.Run(); err != nil {
if errorutil.IsExitStatusError(err) {
if strings.Contains(output.String(), "There appears to be trouble with your network connection. Retrying...") {
fmt.Println()
log.Warnf(`Looks like you've got network issues while installing yarn.
Please try to increase the timeout with --registry https://registry.npmjs.org --network-timeout [NUMBER] command before using this step (recommended value is 100000).
If issue still persists, please try to debug the error or reach out to support.`)
}
failf("Run: provided yarn command failed: %s", err)
}
failf("Run: failed to run provided yarn command: %s", err)
}
if config.UseCache && (len(commandParams) == 0 || commandParams[0] == "install") {
if err := cacheYarn(absWorkingDir); err != nil {
log.Warnf("Failed to cache node_modules: %s", err)
}
}
}
func failf(format string, v ...interface{}) {
log.Errorf(format, v...)
os.Exit(1)
}
func getInstallYarnCommand() *command.Model {
return command.New("npm", "install", "--global", "yarn")
}
func cacheYarn(workingDir string) error {
yarnCache := cache.New()
var cachePaths []string
// Supporting yarn workspaces (https://yarnpkg.com/lang/en/docs/workspaces/), for this recursively look
// up all node_modules directories
if err := filepath.Walk(workingDir, func(path string, fileInfo os.FileInfo, err error) error {
if err != nil {
return err
}
if fileInfo.IsDir() && fileInfo.Name() == "node_modules" {
cachePaths = append(cachePaths, path)
return filepath.SkipDir
}
return nil
}); err != nil {
return fmt.Errorf("failed to find node_modules directories: %s", err)
}
log.Debugf("Cached paths: %s", cachePaths)
for _, path := range cachePaths {
yarnCache.IncludePath(path)
}
if err := yarnCache.Commit(); err != nil {
return fmt.Errorf("failed to mark node_modules directories to be cached: %s", err)
}
return nil
}
func validateYarnInstallation(workDir string) bool {
pth, err := exec.LookPath("yarn")
if err != nil {
log.Debugf("yarn is not installed to the PATH")
return false
}
versionCmd := command.New("yarn", "--version").SetDir(workDir)
out, err := versionCmd.RunAndReturnTrimmedCombinedOutput()
if err != nil {
log.Debugf("yarn version command failed: %s, out: %s", err, out)
return false
}
log.Infof("Yarn is already installed at: %s", pth)
fmt.Println()
log.Infof("Yarn version:")
log.Printf(out)
return true
}
func installYarn() error {
log.Infof("Yarn not installed. Installing...")
installCmd := getInstallYarnCommand()
fmt.Println()
log.Donef("$ %s", installCmd.PrintableCommandArgs())
fmt.Println()
if err := installCmd.Run(); err != nil {
if errorutil.IsExitStatusError(err) {
return fmt.Errorf("installing yarn failed: %s", err)
}
return fmt.Errorf("failed to run command: %s", err)
}
return nil
}
func printYarnVersion(workDir string) error {
log.Infof("Yarn version:")
versionCmd := command.New("yarn", "--version")
versionCmd.SetStdout(os.Stdout).SetStderr(os.Stderr).SetDir(workDir)
fmt.Println()
log.Donef("$ %s", versionCmd.PrintableCommandArgs())
fmt.Println()
if err := versionCmd.Run(); err != nil {
if errorutil.IsExitStatusError(err) {
return fmt.Errorf("yarn version command failed: %s", err)
}
return fmt.Errorf("failed to run command: %s", err)
}
return nil
}