forked from paketo-buildpacks/yarn-install
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathinstall_process.go
207 lines (169 loc) · 6.15 KB
/
install_process.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
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
package yarninstall
import (
"bytes"
"errors"
"fmt"
"os"
"path/filepath"
"strings"
"github.com/paketo-buildpacks/packit/v2/fs"
"github.com/paketo-buildpacks/packit/v2/pexec"
"github.com/paketo-buildpacks/packit/v2/scribe"
)
//go:generate faux --interface Summer --output fakes/summer.go
type Summer interface {
Sum(paths ...string) (string, error)
}
//go:generate faux --interface Executable --output fakes/executable.go
type Executable interface {
Execute(pexec.Execution) error
}
type YarnInstallProcess struct {
executable Executable
summer Summer
logger scribe.Emitter
}
func NewYarnInstallProcess(executable Executable, summer Summer, logger scribe.Emitter) YarnInstallProcess {
return YarnInstallProcess{
executable: executable,
summer: summer,
logger: logger,
}
}
func (ip YarnInstallProcess) ShouldRun(workingDir string, metadata map[string]interface{}) (run bool, sha string, err error) {
ip.logger.Subprocess("Process inputs:")
_, err = os.Stat(filepath.Join(workingDir, "yarn.lock"))
if os.IsNotExist(err) {
ip.logger.Action("yarn.lock -> Not found")
ip.logger.Break()
return true, "", nil
} else if err != nil {
return true, "", fmt.Errorf("unable to read yarn.lock file: %w", err)
}
ip.logger.Action("yarn.lock -> Found")
ip.logger.Break()
buffer := bytes.NewBuffer(nil)
err = ip.executable.Execute(pexec.Execution{
Args: []string{"config", "list", "--silent"},
Stdout: buffer,
Stderr: buffer,
Dir: workingDir,
})
if err != nil {
return true, "", fmt.Errorf("failed to execute yarn config output:\n%s\nerror: %s", buffer.String(), err)
}
nodeEnv := os.Getenv("NODE_ENV")
buffer.WriteString(nodeEnv)
file, err := os.CreateTemp("", "config-file")
if err != nil {
return true, "", fmt.Errorf("failed to create temp file for %s: %w", file.Name(), err)
}
defer file.Close()
_, err = file.Write(buffer.Bytes())
if err != nil {
return true, "", fmt.Errorf("failed to write temp file for %s: %w", file.Name(), err)
}
sum, err := ip.summer.Sum(filepath.Join(workingDir, "yarn.lock"), filepath.Join(workingDir, "package.json"), file.Name())
if err != nil {
return true, "", fmt.Errorf("unable to sum config files: %w", err)
}
prevSHA, ok := metadata["cache_sha"].(string)
if (ok && sum != prevSHA) || !ok {
return true, sum, nil
}
return false, "", nil
}
func (ip YarnInstallProcess) SetupModules(workingDir, currentModulesLayerPath, nextModulesLayerPath string) (string, error) {
if currentModulesLayerPath != "" {
err := fs.Copy(filepath.Join(currentModulesLayerPath, "node_modules"), filepath.Join(nextModulesLayerPath, "node_modules"))
if err != nil {
return "", fmt.Errorf("failed to copy node_modules directory: %w", err)
}
} else {
file, err := os.Lstat(filepath.Join(workingDir, "node_modules"))
if err != nil {
if !errors.Is(err, os.ErrNotExist) {
return "", fmt.Errorf("failed to stat node_modules directory: %w", err)
}
}
if file != nil && file.Mode()&os.ModeSymlink == os.ModeSymlink {
err = os.RemoveAll(filepath.Join(workingDir, "node_modules"))
if err != nil {
//not tested
return "", fmt.Errorf("failed to remove node_modules symlink: %w", err)
}
}
err = os.MkdirAll(filepath.Join(workingDir, "node_modules"), os.ModePerm)
if err != nil {
//not directly tested
return "", fmt.Errorf("failed to create node_modules directory: %w", err)
}
// err = fs.Move(filepath.Join(workingDir, "node_modules"), filepath.Join(nextModulesLayerPath, "node_modules"))
// if err != nil {
// return "", fmt.Errorf("failed to move node_modules directory to layer: %w", err)
// }
// err = os.Symlink(filepath.Join(nextModulesLayerPath, "node_modules"), filepath.Join(workingDir, "node_modules"))
// if err != nil {
// return "", fmt.Errorf("failed to symlink node_modules into working directory: %w", err)
// }
}
return nextModulesLayerPath, nil
}
// The build process here relies on yarn install ... --frozen-lockfile note that
// even if we provide a node_modules directory we must run a 'yarn install' as
// this is the ONLY way to rebuild native extensions.
func (ip YarnInstallProcess) Execute(workingDir, modulesLayerPath string, launch bool) error {
environment := os.Environ()
environment = append(environment, fmt.Sprintf("PATH=%s%c%s", os.Getenv("PATH"), os.PathListSeparator, filepath.Join("node_modules", ".bin")))
buffer := bytes.NewBuffer(nil)
err := ip.executable.Execute(pexec.Execution{
Args: []string{"config", "get", "yarn-offline-mirror"},
Stdout: buffer,
Stderr: buffer,
Env: environment,
Dir: workingDir,
})
if err != nil {
return fmt.Errorf("failed to execute yarn config output:\n%s\nerror: %s", buffer.String(), err)
}
installArgs := []string{"install", "--ignore-engines", "--frozen-lockfile"}
if !launch {
installArgs = append(installArgs, "--production", "false")
}
// Parse yarn config get yarn-offline-mirror output
// in case there are any warning lines in the output like:
// warning You don't appear to have an internet connection.
var offlineMirrorDir string
for _, line := range strings.Split(buffer.String(), "\n") {
if strings.HasPrefix(strings.TrimSpace(line), "/") {
offlineMirrorDir = strings.TrimSpace(line)
break
}
}
info, err := os.Stat(offlineMirrorDir)
if err != nil && !errors.Is(err, os.ErrNotExist) {
return fmt.Errorf("failed to confirm existence of offline mirror directory: %w", err)
}
if info != nil && info.IsDir() {
installArgs = append(installArgs, "--offline")
}
// installArgs = append(installArgs, "--modules-folder", filepath.Join(modulesLayerPath, "node_modules"))
ip.logger.Subprocess("Running 'yarn %s'", strings.Join(installArgs, " "))
err = ip.executable.Execute(pexec.Execution{
Args: installArgs,
Env: environment,
Stdout: ip.logger.ActionWriter,
Stderr: ip.logger.ActionWriter,
Dir: workingDir,
})
if err != nil {
return fmt.Errorf("failed to execute yarn install: %w", err)
}
// Copy node_modules to layers folder for caching purpose
err = fs.Copy(filepath.Join(workingDir, "node_modules"), filepath.Join(modulesLayerPath, "node_modules"))
if err != nil {
fmt.Println(err)
}
fmt.Println("Copy successful.")
return nil
}