-
Notifications
You must be signed in to change notification settings - Fork 0
/
magefile.go
336 lines (274 loc) · 10.3 KB
/
magefile.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
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
//go:build mage
// +build mage
package main
import (
"bytes"
"fmt"
"html/template"
"os"
"os/exec"
"regexp"
"strings"
"github.com/magefile/mage/mg"
"github.com/magefile/mage/sh"
"github.com/playwright-community/playwright-go"
)
// Updates the JavaScript glue code inside the example directory using the code provided in the $GOROOT.
// Will look up the JavaScript glue code needed to properly execute a WASM script in the `/misc/wasm/go_js_wasm_exec`
// directory of the $GOROOT and copy it to `./example` as `index.js`.
func UpdateGlue() error {
goroot := os.Getenv("GOROOT")
if goroot == "" {
return fmt.Errorf("missing $GOROOT environement variable, it was either empty or missing")
}
filepath := fmt.Sprintf("%s/misc/wasm/wasm_exec.js", goroot)
if _, err := os.Stat(filepath); os.IsNotExist(err) {
return fmt.Errorf("JavaScript glue code file was missing from GOROOT, make sure %s exists", filepath)
}
fmt.Printf("Copying %s to `./example/index.js`\n", filepath)
cmd := exec.Command("cp", filepath, "./example/index.js")
return cmd.Run()
}
// Runs the provided example by building it to WASM and running the serve tool in the example directory.
// RunExample will look up the example as a subdirectory in the example directory and build all go files it finds
// there to main.wasm in the example dir. It will then run the main command of serve.go to run the example
// as a web server on port 8080.
func RunExample(example string) error {
mg.Deps(InstallDeps, mg.F(BuildExample, example))
fmt.Printf("Executing the webserver and serving %s\n", example)
exampleDir := fmt.Sprintf("example/%s", example)
if _, err := os.Stat(exampleDir); os.IsNotExist(err) {
return fmt.Errorf("%s is not a valid example, make sure a directory exists with the same name under `./example` and that it has a main function defined", example)
}
var errOutput bytes.Buffer
cmd := exec.Command("go", "run", "./example/serve.go")
cmd.Stdout = os.Stdout
cmd.Stderr = &errOutput
err := cmd.Run()
if err != nil {
return fmt.Errorf("failed to run build command %s, %s. Error was %w", cmd.String(), errOutput.String(), err)
}
return nil
}
// Builds the provided example as a wasm file at the root of the example directory.
// BuildExample will look up the example as a subdirectory in the example directory and build all go files
// it finds there to main.wasm at the root of the example directory.
func BuildExample(example string) error {
mg.Deps(InstallDeps, UpdateGlue)
fmt.Printf("Building %s to main.wasm\n", example)
exampleDir := fmt.Sprintf("./example/%s", example)
if _, err := os.Stat(exampleDir); os.IsNotExist(err) {
return fmt.Errorf("%s is not a valid example, make sure a directory exists with the same name under `./example` and that it has a main function defined")
}
var errOutput bytes.Buffer
cmd := exec.Command("go", "build", "-o", "example/main.wasm", exampleDir)
cmd.Env = os.Environ()
cmd.Env = append(cmd.Env, "GOOS=js", "GOARCH=wasm")
cmd.Stdout = os.Stdout
cmd.Stderr = &errOutput
err := cmd.Run()
if err != nil {
return fmt.Errorf("failed to run build command %s, %s. Error was %w", cmd.String(), errOutput.String(), err)
}
return nil
}
type exampleData struct {
Examples []struct {
Path string
Active bool
Name string
}
Path string
Name string
Url string
}
var matchFirstCap = regexp.MustCompile("(.)([A-Z][a-z]+)")
var matchAllCap = regexp.MustCompile("([a-z0-9])([A-Z])")
func toSentenceCase(str string) string {
sentence := matchFirstCap.ReplaceAllString(str, "${1} ${2}")
sentence = matchAllCap.ReplaceAllString(sentence, "${1} ${2}")
return strings.ToTitle(strings.ToLower(sentence))
}
// Builds the all the examples from the example directory into a special viewer.
// BuildExampleViewer will look up the examples as subdirectories in the example directory
// and build all go files it finds there to the build directory of the `example/.exampleSwitcher`
// directory. It will generate the necessary code for the switcher to work as intended.
func BuildExampleViewer() error {
mg.Deps(InstallDeps, UpdateGlue)
fmt.Println("Creating the build directory for the example viewer")
if err := os.RemoveAll("./example/.exampleSwitcher/build"); err != nil {
return err
}
if err := os.MkdirAll("./example/.exampleSwitcher/build", os.ModePerm); err != nil {
return err
}
rootTemplate, err := template.ParseFiles("./example/.exampleSwitcher/root.html.tpl")
if err != nil {
return fmt.Errorf("failed to read index.html remplate. Error was %w", err)
}
indexTemplate, err := template.ParseFiles("./example/.exampleSwitcher/index.html.tpl")
if err != nil {
return fmt.Errorf("failed to read example index.html remplate. Error was %w", err)
}
fmt.Printf("Listing all directories of the example directory, except for the viewer\n")
examples, _ := os.ReadDir("./example")
var exampleDefs []exampleData
for _, dir := range examples {
if dir.IsDir() && dir.Name() != ".exampleSwitcher" {
example := dir.Name()
exampleDefs = append(exampleDefs, exampleData{
Examples: []struct {
Path string
Active bool
Name string
}{},
Path: example,
Name: toSentenceCase(example),
Url: fmt.Sprintf("https://github.com/Minivera/go-lander/tree/main/example/%s/main.go", example),
})
}
}
fmt.Printf("Executing all found examples and creating their build files\n")
for _, exampleDef := range exampleDefs {
example := exampleDef.Path
exampleBuildDir := fmt.Sprintf("./example/.exampleSwitcher/build/%s", example)
if err := os.MkdirAll(exampleBuildDir, os.ModePerm); err != nil {
return err
}
fmt.Printf("Copying glue to example build directory %s\n", exampleBuildDir)
cmd := exec.Command("cp", "./example/index.js", exampleBuildDir+"/index.js")
if err := cmd.Run(); err != nil {
return err
}
fmt.Printf("Building %s to main.wasm in the build directory %s\n", example, exampleBuildDir)
exampleDir := fmt.Sprintf("./example/%s", example)
if _, err := os.Stat(exampleDir); os.IsNotExist(err) {
return fmt.Errorf("%s is not a valid example, make sure a directory exists with the same name under `./example` and that it has a main function defined", example)
}
var errOutput bytes.Buffer
cmd = exec.Command("go", "build", "-o", exampleBuildDir+"/main.wasm", exampleDir)
cmd.Env = os.Environ()
cmd.Env = append(cmd.Env, "GOOS=js", "GOARCH=wasm")
cmd.Stdout = os.Stdout
cmd.Stderr = &errOutput
err := cmd.Run()
if err != nil {
return fmt.Errorf("failed to run build command %s, %s. Error was %w", cmd.String(), errOutput.String(), err)
}
fmt.Printf("Writing index.html in the build directory %s\n", exampleBuildDir)
indexFile, err := os.Create(exampleBuildDir + "/index.html")
if err != nil {
return fmt.Errorf("failed to create index file for example %s build. Error was %w", example, err)
}
for _, subDef := range exampleDefs {
exampleDef.Examples = append(exampleDef.Examples, struct {
Path string
Active bool
Name string
}{
Path: subDef.Path,
Active: subDef.Path == exampleDef.Path,
Name: subDef.Name,
})
}
err = indexTemplate.Execute(indexFile, exampleDef)
if err != nil {
return fmt.Errorf("failed to create index file for example %s build. Error was %w", example, err)
}
err = indexFile.Close()
if err != nil {
return err
}
}
fmt.Printf("Writing root index.html in the build directory\n")
rootFile, err := os.Create("./example/.exampleSwitcher/build/index.html")
if err != nil {
return fmt.Errorf("failed to create index file for root. Error was %w", err)
}
rootDef := exampleData{
Examples: []struct {
Path string
Active bool
Name string
}{},
}
for _, subDef := range exampleDefs {
rootDef.Examples = append(rootDef.Examples, struct {
Path string
Active bool
Name string
}{
Path: subDef.Path,
Active: subDef.Path == rootDef.Path,
Name: subDef.Name,
})
}
err = rootTemplate.Execute(rootFile, rootDef)
if err != nil {
return fmt.Errorf("failed to create root index file for build. Error was %w", err)
}
return rootFile.Close()
}
// Runs the examples by building them to WASM in a common viewer directory and then serving them as HTML files.
// RunExampleViewer will look up the examples as subdirectories in the example directory and build all go
// example it finds there to their respective index.html and main.wasm files. It will then run the main
// command of serve.go to run the .exampleSwitcher directory as a web server on port 8080.
func RunExampleViewer() error {
mg.Deps(InstallDeps, BuildExampleViewer)
fmt.Println("Executing the webserver and serving")
var errOutput bytes.Buffer
cmd := exec.Command("go", "run", "./example/.exampleSwitcher/serve.go")
cmd.Stdout = os.Stdout
cmd.Stderr = &errOutput
err := cmd.Run()
if err != nil {
return fmt.Errorf("failed to run command %s, %s. Error was %w", cmd.String(), errOutput.String(), err)
}
return nil
}
// Install all dependencies of the project in one command
func InstallDeps() error {
fmt.Println("Installing all dependencies")
cmd := exec.Command("go", "get", "./...")
return cmd.Run()
}
// Installs playwright for end-to-end browser testing
func InstallPlaywright() error {
fmt.Println("Installing Playwright")
return playwright.Install()
}
// Run the end-to-end tests from the example directory
func Tests() error {
mg.Deps(InstallDeps, InstallPlaywright, BuildExampleViewer)
fmt.Println("Starting example viewer in the background")
// Run the example viewer in the background
var errOutput bytes.Buffer
cmd := exec.Command("go", "run", "./example/.exampleSwitcher/serve.go")
cmd.Stdout = os.Stdout
cmd.Stderr = &errOutput
err := cmd.Start()
if err != nil {
return fmt.Errorf("failed to run build command %s, %s. Error was %w", cmd.String(), errOutput.String(), err)
}
go func() {
err = cmd.Wait()
if err != nil {
fmt.Printf("Command finished with error: %v", err)
}
}()
result, err := sh.Output("go", "test", "./endToEnd")
if err != nil {
return fmt.Errorf("failed to run test command, %s. Error was %w", result, err)
}
err = cmd.Process.Kill()
if err != nil {
return fmt.Errorf("failed to run kill server. Error was %w", err)
}
return nil
}
// Cleans the WASm and build artifacts from the repository
func Clean() {
fmt.Println("Cleaning WASM and build artifacts")
os.RemoveAll("vendor")
os.Remove("example/main.wasm")
}