// Copyright (c) Tailscale Inc & AUTHORS // SPDX-License-Identifier: BSD-3-Clause // The build-webclient tool generates the static resources needed for the // web client (code at client/web). // // # Running // // Meant to be invoked from the tailscale/web-client-prebuilt repo when // updating the production built web client assets. To run it manually, // you can use `./tool/go run ./misc/build-webclient` package main import ( "flag" "fmt" "log" "os" "os/exec" "path/filepath" "tailscale.com/util/precompress" ) var ( outDir = flag.String("outDir", "build/", "path to output directory") ) func main() { flag.Parse() // The toolDir flag is relative to the current working directory, // so we need to resolve it to an absolute path. toolDir, err := filepath.Abs("./tool") if err != nil { log.Fatalf("Cannot resolve tool-dir: %v", err) } if err := build(toolDir, "client/web"); err != nil { log.Fatalf("%v", err) } } func build(toolDir, appDir string) error { if err := os.Chdir(appDir); err != nil { return fmt.Errorf("Cannot change cwd: %w", err) } if err := yarn(toolDir); err != nil { return fmt.Errorf("install failed: %w", err) } if err := yarn(toolDir, "lint"); err != nil { return fmt.Errorf("lint failed: %w", err) } if err := yarn(toolDir, "build", "--outDir="+*outDir, "--emptyOutDir"); err != nil { return fmt.Errorf("build failed: %w", err) } var compressedFiles []string if err := precompress.PrecompressDir(*outDir, precompress.Options{ ProgressFn: func(path string) { log.Printf("Pre-compressing %v\n", path) compressedFiles = append(compressedFiles, path) }, }); err != nil { return fmt.Errorf("Cannot precompress: %w", err) } // Cleanup pre-compressed files. for _, f := range compressedFiles { if err := os.Remove(f); err != nil { log.Printf("Failed to cleanup %q: %v", f, err) } // Removing intermediate ".br" version, we use ".gz" asset. if err := os.Remove(f + ".br"); err != nil { log.Printf("Failed to cleanup %q: %v", f+".gz", err) } } return nil } func yarn(toolDir string, args ...string) error { args = append([]string{"--silent", "--non-interactive"}, args...) return run(filepath.Join(toolDir, "yarn"), args...) } func run(name string, args ...string) error { cmd := exec.Command(name, args...) cmd.Stdout = os.Stdout cmd.Stderr = os.Stderr return cmd.Run() }