11 Commits

Author SHA1 Message Date
pk
48b15de4c5 New version 2024-11-06 10:43:05 +01:00
pk
79afdd39b8 Fix missing dot 2024-09-03 14:40:50 +02:00
pk
6b14faa5b1 Fix survey options. 2024-09-03 14:39:41 +02:00
pk
93338b0712 Add templates for air. 2024-09-03 14:38:02 +02:00
pk
7f25c3f83c Improve template for docker_image.sh 2024-08-26 23:53:31 +02:00
pk
e87dfaf38b Add template support. 2024-08-26 23:12:33 +02:00
pk
30f8be3d5d Add post build command and exclude the esbuild outdir in the watcher. 2024-07-04 10:56:41 +02:00
pk
c356f34e21 Bump version 2024-01-04 21:56:44 +01:00
pk
734a6ce62e Fix linker to work correctly with sub paths. 2024-01-04 21:55:24 +01:00
pk
9e7f716be8 Add some features 2023-09-13 11:45:24 +02:00
pk
760decfb85 Ignore hidden files 2022-12-15 12:17:04 +01:00
26 changed files with 2107 additions and 273 deletions

34
.gowebbuild.yaml Executable file
View File

@ -0,0 +1,34 @@
- esbuild:
entryPoints:
- frontend/the-app.js
outdir: ./frontend-dist
sourcemap: 1
format: 3
splitting: true
platform: 0
bundle: true
write: true
logLevel: 3
purgeBeforeBuild: false
watch:
paths:
- ./frontend/src
exclude: []
# serve: # Uncomment and set a path to enable
# path: ""
# port: 8080
copy:
- src: ./frontend/index.html
dest: ./frontend-dist
# download:
# - url: https://example.com/some-file-or-asset.js
# dest: ./frontend/src/vendor/some-file-or-asset.js
# replace:
# - pattern: "*.go|*.js|*.html"
# search: "Something"
# replace: "This"
# link:
# from: ../../web/tp-elements
# to: ./web
# productionBuildOptions:
# cmdPostBuild: ""

16
.vscode/launch.json vendored Normal file
View File

@ -0,0 +1,16 @@
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"name": "Launch Package",
"type": "go",
"request": "launch",
"mode": "auto",
"program": "${workspaceFolder}",
"args": ["generate"]
}
]
}

View File

@ -1 +1,6 @@
gowebbuild
# NPM Proxy
The npm proxy is a small npm registry server that can be used to serve packages from the local filesystem instead of the default registry.
This allows to install packages that haven't been published yet.

View File

@ -1,7 +1,9 @@
package main
import (
"fmt"
"os"
"os/exec"
"path/filepath"
"github.com/evanw/esbuild/pkg/api"
@ -19,18 +21,38 @@ func buildAction(ctx *cli.Context) error {
opts := readCfg(cfgPath)
for _, o := range opts {
if ctx.Bool("p") {
download(o)
}
purge(o)
cp(o)
esBuildCfg := cfgToESBuildCfg(o)
if ctx.Bool("p") {
o.ESBuild.MinifyIdentifiers = true
o.ESBuild.MinifySyntax = true
o.ESBuild.MinifyWhitespace = true
o.ESBuild.Sourcemap = api.SourceMapNone
esBuildCfg.MinifyIdentifiers = true
esBuildCfg.MinifySyntax = true
esBuildCfg.MinifyWhitespace = true
esBuildCfg.Sourcemap = api.SourceMapNone
}
api.Build(o.ESBuild.BuildOptions)
api.Build(esBuildCfg)
replace(o)
if ctx.Bool("p") && o.ProductionBuildOptions.CmdPostBuild != "" {
defer func() {
fmt.Printf("Executing post production build command `%s`\n", o.ProductionBuildOptions.CmdPostBuild)
cmd := exec.Command("sh", "-c", o.ProductionBuildOptions.CmdPostBuild)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
err := cmd.Run()
if err != nil {
fmt.Printf("Failed to execute post production build command `%s`: %+v\n", o.ProductionBuildOptions.CmdPostBuild, err)
os.Exit(1)
}
}()
}
}
return nil

150
config.go Normal file
View File

@ -0,0 +1,150 @@
package main
import (
"encoding/json"
"fmt"
"os"
"path/filepath"
"strings"
"github.com/evanw/esbuild/pkg/api"
"gopkg.in/yaml.v3"
)
func cfgToESBuildCfg(cfg options) api.BuildOptions {
return api.BuildOptions{
EntryPoints: cfg.ESBuild.EntryPoints,
Outdir: cfg.ESBuild.Outdir,
Outfile: cfg.ESBuild.Outfile,
Sourcemap: api.SourceMap(cfg.ESBuild.Sourcemap),
Format: api.Format(cfg.ESBuild.Format),
Splitting: cfg.ESBuild.Splitting,
Platform: api.Platform(cfg.ESBuild.Platform),
Bundle: cfg.ESBuild.Bundle,
Write: cfg.ESBuild.Write,
LogLevel: api.LogLevel(cfg.ESBuild.LogLevel),
}
}
type options struct {
ESBuild struct {
EntryPoints []string `yaml:"entryPoints"`
Outdir string `yaml:"outdir"`
Outfile string `yaml:"outfile"`
Sourcemap int `yaml:"sourcemap"`
Format int `yaml:"format"`
Splitting bool `yaml:"splitting"`
Platform int `yaml:"platform"`
Bundle bool `yaml:"bundle"`
Write bool `yaml:"write"`
LogLevel int `yaml:"logLevel"`
PurgeBeforeBuild bool `yaml:"purgeBeforeBuild"`
} `yaml:"esbuild"`
Watch struct {
Paths []string `yaml:"paths"`
Exclude []string `yaml:"exclude"`
}
Serve struct {
Path string `yaml:"path"`
Port int `yaml:"port"`
} `yaml:"serve"`
Copy []struct {
Src string `yaml:"src"`
Dest string `yaml:"dest"`
} `yaml:"copy"`
Download []struct {
Url string `yaml:"url"`
Dest string `yaml:"dest"`
} `yaml:"download"`
Replace []struct {
Pattern string `yaml:"pattern"`
Search string `yaml:"search"`
Replace string `yaml:"replace"`
} `yaml:"replace"`
Link struct {
From string `yaml:"from"`
To string `yaml:"to"`
} `yaml:"link"`
ProductionBuildOptions struct {
CmdPostBuild string `yaml:"cmdPostBuild"`
} `yaml:"productionBuildOptions"`
NpmProxy struct {
Overrides []NpmProxyOverride
} `yaml:"npm_proxy"`
}
type NpmProxyOverride struct {
Namespace string `yaml:"namespace"`
Upstream string `yaml:"upstream"`
PackageRoot string `yaml:"packageRoot"`
}
func readCfg(cfgPath string) []options {
if filepath.Ext(cfgPath) == ".json" {
jsonOpts := readJsonCfg(cfgPath)
data, err := yaml.Marshal(jsonOpts)
if err != nil {
fmt.Printf("%+v\n", err)
os.Exit(1)
}
yamlPath := strings.TrimSuffix(cfgPath, ".json") + ".yaml"
err = os.WriteFile(yamlPath, data, 0755)
if err != nil {
fmt.Printf("%+v\n", err)
os.Exit(1)
}
cfgPath = yamlPath
}
cfgContent, err := os.ReadFile(cfgPath)
if err != nil {
fmt.Printf("%+v\n", err)
os.Exit(1)
}
optsSetups := []options{}
err = yaml.Unmarshal(cfgContent, &optsSetups)
if err != nil {
opt := options{}
err = yaml.Unmarshal(cfgContent, &opt)
if err != nil {
fmt.Printf("%+v\n", err)
os.Exit(1)
}
optsSetups = append(optsSetups, opt)
}
return optsSetups
}
func readJsonCfg(cfgPath string) []options {
cfgContent, err := os.ReadFile(cfgPath)
if err != nil {
fmt.Printf("%+v\n", err)
os.Exit(1)
}
optsSetups := []options{}
err = json.Unmarshal(cfgContent, &optsSetups)
if err != nil {
opt := options{}
err = json.Unmarshal(cfgContent, &opt)
if err != nil {
fmt.Printf("%+v\n", err)
os.Exit(1)
}
optsSetups = append(optsSetups, opt)
}
return optsSetups
}

48
fsutils/helpers.go Normal file
View File

@ -0,0 +1,48 @@
package fsutils
import (
"errors"
"io/fs"
"os"
"path/filepath"
"strings"
)
func FindFiles(root, name string) []string {
paths := []string{}
filepath.WalkDir(root, func(path string, d fs.DirEntry, err error) error {
if err != nil {
return nil
}
if !d.IsDir() && filepath.Base(path) == name && !strings.Contains(path, "node_modules") {
paths = append(paths, path)
}
return nil
})
return paths
}
func IsFile(path string) bool {
stat, err := os.Stat(path)
if errors.Is(err, os.ErrNotExist) {
return false
}
return !stat.IsDir()
}
func IsDir(path string) bool {
stat, err := os.Stat(path)
if errors.Is(err, os.ErrNotExist) {
os.MkdirAll(path, 0755)
return true
}
return err == nil && stat.IsDir()
}

89
go.mod
View File

@ -1,25 +1,86 @@
module github.com/trading-peter/gowebbuild
go 1.18
go 1.23.1
require (
github.com/evanw/esbuild v0.14.50
github.com/goyek/goyek v0.6.3
github.com/jaschaephraim/lrserver v0.0.0-20171129202958-50d19f603f71
github.com/otiai10/copy v1.7.0
github.com/Iilun/survey/v2 v2.5.1
github.com/Masterminds/semver/v3 v3.3.0
github.com/evanw/esbuild v0.23.0
github.com/jaschaephraim/lrserver v0.0.0-20240306232639-afed386b3640
github.com/kataras/golog v0.1.12
github.com/kataras/iris/v12 v12.2.11
github.com/mholt/archiver/v4 v4.0.0-alpha.8
github.com/otiai10/copy v1.14.0
github.com/radovskyb/watcher v1.0.7
github.com/tidwall/gjson v1.14.1
github.com/tidwall/gjson v1.17.1
github.com/urfave/cli/v2 v2.27.2
gopkg.in/yaml.v3 v3.0.1
)
require (
github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect
github.com/gorilla/websocket v1.5.0 // indirect
github.com/BurntSushi/toml v1.4.0 // indirect
github.com/CloudyKit/fastprinter v0.0.0-20200109182630-33d98a066a53 // indirect
github.com/CloudyKit/jet/v6 v6.2.0 // indirect
github.com/Joker/jade v1.1.3 // indirect
github.com/Shopify/goreferrer v0.0.0-20220729165902-8cddb4f5de06 // indirect
github.com/andybalholm/brotli v1.1.0 // indirect
github.com/aymerick/douceur v0.2.0 // indirect
github.com/bodgit/plumbing v1.2.0 // indirect
github.com/bodgit/sevenzip v1.3.0 // indirect
github.com/bodgit/windows v1.0.0 // indirect
github.com/connesc/cipherio v0.2.1 // indirect
github.com/cpuguy83/go-md2man/v2 v2.0.4 // indirect
github.com/dsnet/compress v0.0.1 // indirect
github.com/fatih/structs v1.1.0 // indirect
github.com/flosch/pongo2/v4 v4.0.2 // indirect
github.com/golang/snappy v0.0.4 // indirect
github.com/gomarkdown/markdown v0.0.0-20240626202925-2eda941fd024 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/gorilla/css v1.0.1 // indirect
github.com/gorilla/websocket v1.5.3 // indirect
github.com/hashicorp/errwrap v1.0.0 // indirect
github.com/hashicorp/go-multierror v1.1.1 // indirect
github.com/iris-contrib/schema v0.0.6 // indirect
github.com/josharian/intern v1.0.0 // indirect
github.com/kataras/blocks v0.0.8 // indirect
github.com/kataras/pio v0.0.13 // indirect
github.com/kataras/sitemap v0.0.6 // indirect
github.com/kataras/tunnel v0.0.4 // indirect
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect
github.com/klauspost/compress v1.17.9 // indirect
github.com/klauspost/pgzip v1.2.5 // indirect
github.com/kr/text v0.2.0 // indirect
github.com/mailgun/raymond/v2 v2.0.48 // indirect
github.com/mailru/easyjson v0.7.7 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b // indirect
github.com/microcosm-cc/bluemonday v1.0.27 // indirect
github.com/nwaples/rardecode/v2 v2.0.0-beta.2 // indirect
github.com/pierrec/lz4/v4 v4.1.15 // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/smartystreets/goconvey v1.7.2 // indirect
github.com/schollz/closestmatch v2.1.0+incompatible // indirect
github.com/sirupsen/logrus v1.9.3 // indirect
github.com/tdewolff/minify/v2 v2.20.37 // indirect
github.com/tdewolff/parse/v2 v2.7.15 // indirect
github.com/therootcompany/xz v1.0.1 // indirect
github.com/tidwall/match v1.1.1 // indirect
github.com/tidwall/pretty v1.2.0 // indirect
github.com/urfave/cli/v2 v2.16.3 // indirect
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect
golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10 // indirect
gopkg.in/fsnotify.v1 v1.4.7 // indirect
github.com/tidwall/pretty v1.2.1 // indirect
github.com/ulikunitz/xz v0.5.10 // indirect
github.com/valyala/bytebufferpool v1.0.0 // indirect
github.com/vmihailenco/msgpack/v5 v5.4.1 // indirect
github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect
github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 // indirect
github.com/yosssi/ace v0.0.5 // indirect
go4.org v0.0.0-20200411211856-f5505b9728dd // indirect
golang.org/x/crypto v0.25.0 // indirect
golang.org/x/exp v0.0.0-20240707233637-46b078467d37 // indirect
golang.org/x/net v0.27.0 // indirect
golang.org/x/sync v0.7.0 // indirect
golang.org/x/sys v0.22.0 // indirect
golang.org/x/term v0.22.0 // indirect
golang.org/x/text v0.16.0 // indirect
golang.org/x/time v0.5.0 // indirect
google.golang.org/protobuf v1.34.2 // indirect
gopkg.in/ini.v1 v1.67.0 // indirect
)

509
go.sum
View File

@ -1,49 +1,488 @@
github.com/cpuguy83/go-md2man/v2 v2.0.2 h1:p1EgwI/C7NhT0JmVkwCD2ZBK8j4aeHQX2pMHHBfMQ6w=
github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/evanw/esbuild v0.14.50 h1:h7sijkRPGB9ckpIOc6FMZ81/NMy/4g40LhsBAtPa3/I=
github.com/evanw/esbuild v0.14.50/go.mod h1:dkwI35DCMf0iR+tJDiCEiPKZ4A+AotmmeLpPEv3dl9k=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc=
github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/goyek/goyek v0.6.3 h1:t0h3gWdlvGeSChltiyAyka9Mlcp3CEPDRssRf0XHDTM=
github.com/goyek/goyek v0.6.3/go.mod h1:UGjZz3juJL2l2eMqRbxQYjG8ieyKb7WMYPv0KB0KVxA=
github.com/jaschaephraim/lrserver v0.0.0-20171129202958-50d19f603f71 h1:24NdJ5N6gtrcoeS4JwLMeruKFmg20QdF/5UnX5S/j18=
github.com/jaschaephraim/lrserver v0.0.0-20171129202958-50d19f603f71/go.mod h1:ozZLfjiLmXytkIUh200wMeuoQJ4ww06wN+KZtFP6j3g=
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU=
cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc=
cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0=
cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To=
cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M=
cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE=
cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw=
cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos=
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/toml v1.4.0 h1:kuoIxZQy2WRRk1pttg9asf+WVv6tWQuBNVmK8+nqPr0=
github.com/BurntSushi/toml v1.4.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
github.com/CloudyKit/fastprinter v0.0.0-20200109182630-33d98a066a53 h1:sR+/8Yb4slttB4vD+b9btVEnWgL3Q00OBTzVT8B9C0c=
github.com/CloudyKit/fastprinter v0.0.0-20200109182630-33d98a066a53/go.mod h1:+3IMCy2vIlbG1XG/0ggNQv0SvxCAIpPM5b1nCz56Xno=
github.com/CloudyKit/jet/v6 v6.2.0 h1:EpcZ6SR9n28BUGtNJSvlBqf90IpjeFr36Tizxhn/oME=
github.com/CloudyKit/jet/v6 v6.2.0/go.mod h1:d3ypHeIRNo2+XyqnGA8s+aphtcVpjP5hPwP/Lzo7Ro4=
github.com/Iilun/survey/v2 v2.5.1 h1:WBDYH5dHcH90iKu+4DmNhsyYGcNMmOCFnlELpBWeQGU=
github.com/Iilun/survey/v2 v2.5.1/go.mod h1:mZL+0ztzqErbD7saTLuQIznMP0L1M+ZEwXhnLip0IMs=
github.com/Joker/hpp v1.0.0 h1:65+iuJYdRXv/XyN62C1uEmmOx3432rNG/rKlX6V7Kkc=
github.com/Joker/hpp v1.0.0/go.mod h1:8x5n+M1Hp5hC0g8okX3sR3vFQwynaX/UgSOM9MeBKzY=
github.com/Joker/jade v1.1.3 h1:Qbeh12Vq6BxURXT1qZBRHsDxeURB8ztcL6f3EXSGeHk=
github.com/Joker/jade v1.1.3/go.mod h1:T+2WLyt7VH6Lp0TRxQrUYEs64nRc83wkMQrfeIQKduM=
github.com/Masterminds/semver/v3 v3.3.0 h1:B8LGeaivUe71a5qox1ICM/JLl0NqZSW5CHyL+hmvYS0=
github.com/Masterminds/semver/v3 v3.3.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM=
github.com/Netflix/go-expect v0.0.0-20220104043353-73e0943537d2 h1:+vx7roKuyA63nhn5WAunQHLTznkw5W8b1Xc0dNjp83s=
github.com/Netflix/go-expect v0.0.0-20220104043353-73e0943537d2/go.mod h1:HBCaDeC1lPdgDeDbhX8XFpy1jqjK0IBG8W5K+xYqA0w=
github.com/Shopify/goreferrer v0.0.0-20220729165902-8cddb4f5de06 h1:KkH3I3sJuOLP3TjA/dfr4NAY8bghDwnXiU7cTKxQqo0=
github.com/Shopify/goreferrer v0.0.0-20220729165902-8cddb4f5de06/go.mod h1:7erjKLwalezA0k99cWs5L11HWOAPNjdUZ6RxH1BXbbM=
github.com/ajg/form v1.5.1 h1:t9c7v8JUKu/XxOGBU0yjNpaMloxGEJhUkqFRq0ibGeU=
github.com/ajg/form v1.5.1/go.mod h1:uL1WgH+h2mgNtvBq0339dVnzXdBETtL2LeUXaIv25UY=
github.com/andybalholm/brotli v1.1.0 h1:eLKJA0d02Lf0mVpIDgYnqXcUn0GqVmEFny3VuID1U3M=
github.com/andybalholm/brotli v1.1.0/go.mod h1:sms7XGricyQI9K10gOSf56VKKWS4oLer58Q+mhRPtnY=
github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk=
github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4=
github.com/bodgit/plumbing v1.2.0 h1:gg4haxoKphLjml+tgnecR4yLBV5zo4HAZGCtAh3xCzM=
github.com/bodgit/plumbing v1.2.0/go.mod h1:b9TeRi7Hvc6Y05rjm8VML3+47n4XTZPtQ/5ghqic2n8=
github.com/bodgit/sevenzip v1.3.0 h1:1ljgELgtHqvgIp8W8kgeEGHIWP4ch3xGI8uOBZgLVKY=
github.com/bodgit/sevenzip v1.3.0/go.mod h1:omwNcgZTEooWM8gA/IJ2Nk/+ZQ94+GsytRzOJJ8FBlM=
github.com/bodgit/windows v1.0.0 h1:rLQ/XjsleZvx4fR1tB/UxQrK+SJ2OFHzfPjLWWOhDIA=
github.com/bodgit/windows v1.0.0/go.mod h1:a6JLwrB4KrTR5hBpp8FI9/9W9jJfeQ2h4XDXU74ZCdM=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/connesc/cipherio v0.2.1 h1:FGtpTPMbKNNWByNrr9aEBtaJtXjqOzkIXNYJp6OEycw=
github.com/connesc/cipherio v0.2.1/go.mod h1:ukY0MWJDFnJEbXMQtOcn2VmTpRfzcTz4OoVrWGGJZcA=
github.com/cpuguy83/go-md2man/v2 v2.0.4 h1:wfIWP927BUkWJb2NmU/kNDYIBTh/ziUX91+lVfRxZq4=
github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/creack/pty v1.1.17 h1:QeVUsEDNrLBW4tMgZHvxy18sKtr6VI492kBhUfhDJNI=
github.com/creack/pty v1.1.17/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dsnet/compress v0.0.1 h1:PlZu0n3Tuv04TzpfPbrnI0HW/YwodEXDS+oPKahKF0Q=
github.com/dsnet/compress v0.0.1/go.mod h1:Aw8dCMJ7RioblQeTqt88akK31OvO8Dhf5JflhBbQEHo=
github.com/dsnet/golib v0.0.0-20171103203638-1ea166775780/go.mod h1:Lj+Z9rebOhdfkVLjJ8T6VcRQv3SXugXy999NBtR9aFY=
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/evanw/esbuild v0.23.0 h1:PLUwTn2pzQfIBRrMKcD3M0g1ALOKIHMDefdFCk7avwM=
github.com/evanw/esbuild v0.23.0/go.mod h1:D2vIQZqV/vIf/VRHtViaUtViZmG7o+kKmlBfVQuRi48=
github.com/fatih/color v1.15.0 h1:kOqh6YHBtK8aywxGerMG2Eq3H6Qgoqeo13Bk2Mv/nBs=
github.com/fatih/color v1.15.0/go.mod h1:0h5ZqXfHYED7Bhv2ZJamyIOUej9KtShiJESRwBDUSsw=
github.com/fatih/structs v1.1.0 h1:Q7juDM0QtcnhCpeyLGQKyg4TOIghuNXrkL32pHAUMxo=
github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M=
github.com/flosch/pongo2/v4 v4.0.2 h1:gv+5Pe3vaSVmiJvh/BZa82b7/00YUGm0PIyVVLop0Hw=
github.com/flosch/pongo2/v4 v4.0.2/go.mod h1:B5ObFANs/36VwxxlgKpdchIJHMvHB562PW+BWPhwZD8=
github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA=
github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM=
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y=
github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
github.com/golang/mock v1.4.3 h1:GV+pQPG/EUUbkh47niozDcADz6go/dUwhVzdUQHIVRw=
github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM=
github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/gomarkdown/markdown v0.0.0-20240626202925-2eda941fd024 h1:saBP362Qm7zDdDXqv61kI4rzhmLFq3Z1gx34xpl6cWE=
github.com/gomarkdown/markdown v0.0.0-20240626202925-2eda941fd024/go.mod h1:JDGcbDT52eL4fju3sZ4TeHGsQwhG9nbDV21aMyhwPoA=
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8=
github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU=
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
github.com/gopherjs/gopherjs v1.17.2 h1:fQnZVsXk8uxXIStYb0N4bGk7jeyTalG/wsZjQ25dO0g=
github.com/gopherjs/gopherjs v1.17.2/go.mod h1:pRRIvn/QzFLrKfvEz3qUuEhtE/zLCWfreZ6J5gM2i+k=
github.com/gorilla/css v1.0.1 h1:ntNaBIghp6JmvWnxbZKANoLyuXTPZ4cAMlo6RyhlbO8=
github.com/gorilla/css v1.0.1/go.mod h1:BvnYkspnSzMmwRK+b8/xgNPLiIuNZr6vbZBTPQ2A3b0=
github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg=
github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA=
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo=
github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM=
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hinshun/vt10x v0.0.0-20220119200601-820417d04eec h1:qv2VnGeEQHchGaZ/u7lxST/RaJw+cv273q79D81Xbog=
github.com/hinshun/vt10x v0.0.0-20220119200601-820417d04eec/go.mod h1:Q48J4R4DvxnHolD5P8pOtXigYlRuPLGl6moFx3ulM68=
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/imkira/go-interpol v1.1.0 h1:KIiKr0VSG2CUW1hl1jpiyuzuJeKUUpC8iM1AIE7N1Vk=
github.com/imkira/go-interpol v1.1.0/go.mod h1:z0h2/2T3XF8kyEPpRgJ3kmNv+C43p+I/CoI+jC3w2iA=
github.com/iris-contrib/httpexpect/v2 v2.15.2 h1:T9THsdP1woyAqKHwjkEsbCnMefsAFvk8iJJKokcJ3Go=
github.com/iris-contrib/httpexpect/v2 v2.15.2/go.mod h1:JLDgIqnFy5loDSUv1OA2j0mb6p/rDhiCqigP22Uq9xE=
github.com/iris-contrib/schema v0.0.6 h1:CPSBLyx2e91H2yJzPuhGuifVRnZBBJ3pCOMbOvPZaTw=
github.com/iris-contrib/schema v0.0.6/go.mod h1:iYszG0IOsuIsfzjymw1kMzTL8YQcCWlm65f3wX8J5iA=
github.com/jaschaephraim/lrserver v0.0.0-20240306232639-afed386b3640 h1:qxoA9wh1IZAbMhfFSE81tn8RsB48LNd7ecH6lFpxucc=
github.com/jaschaephraim/lrserver v0.0.0-20240306232639-afed386b3640/go.mod h1:1Dkfm1/kgjeZc+2TBUAyZ3TJeQ/HaKbj8ig+7nAHkws=
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
github.com/otiai10/copy v1.7.0 h1:hVoPiN+t+7d2nzzwMiDHPSOogsWAStewq3TwU05+clE=
github.com/otiai10/copy v1.7.0/go.mod h1:rmRl6QPdJj6EiUqXQ/4Nn2lLXoNQjFCQbbNrxgc/t3U=
github.com/otiai10/curr v0.0.0-20150429015615-9b4961190c95/go.mod h1:9qAhocn7zKJG+0mI8eUu6xqkFDYS2kb2saOteoSB3cE=
github.com/otiai10/curr v1.0.0/go.mod h1:LskTG5wDwr8Rs+nNQ+1LlxRjAtTZZjtJW4rMXl6j4vs=
github.com/otiai10/mint v1.3.0/go.mod h1:F5AjcsTsWUqX+Na9fpHb52P8pcRX2CI6A3ctIT91xUo=
github.com/otiai10/mint v1.3.3 h1:7JgpsBaN0uMkyju4tbYHu0mnM55hNKVYLsXmwr15NQI=
github.com/otiai10/mint v1.3.3/go.mod h1:/yxELlJQ0ufhjUwhshSj+wFjZ78CnZ48/1wtmBH1OTc=
github.com/kataras/blocks v0.0.8 h1:MrpVhoFTCR2v1iOOfGng5VJSILKeZZI+7NGfxEh3SUM=
github.com/kataras/blocks v0.0.8/go.mod h1:9Jm5zx6BB+06NwA+OhTbHW1xkMOYxahnqTN5DveZ2Yg=
github.com/kataras/golog v0.1.12 h1:Bu7I/G4ilJlbfzjmU39O9N+2uO1pBcMK045fzZ4ytNg=
github.com/kataras/golog v0.1.12/go.mod h1:wrGSbOiBqbQSQznleVNX4epWM8rl9SJ/rmEacl0yqy4=
github.com/kataras/iris/v12 v12.2.11 h1:sGgo43rMPfzDft8rjVhPs6L3qDJy3TbBrMD/zGL1pzk=
github.com/kataras/iris/v12 v12.2.11/go.mod h1:uMAeX8OqG9vqdhyrIPv8Lajo/wXTtAF43wchP9WHt2w=
github.com/kataras/pio v0.0.13 h1:x0rXVX0fviDTXOOLOmr4MUxOabu1InVSTu5itF8CXCM=
github.com/kataras/pio v0.0.13/go.mod h1:k3HNuSw+eJ8Pm2lA4lRhg3DiCjVgHlP8hmXApSej3oM=
github.com/kataras/sitemap v0.0.6 h1:w71CRMMKYMJh6LR2wTgnk5hSgjVNB9KL60n5e2KHvLY=
github.com/kataras/sitemap v0.0.6/go.mod h1:dW4dOCNs896OR1HmG+dMLdT7JjDk7mYBzoIRwuj5jA4=
github.com/kataras/tunnel v0.0.4 h1:sCAqWuJV7nPzGrlb0os3j49lk2JhILT0rID38NHNLpA=
github.com/kataras/tunnel v0.0.4/go.mod h1:9FkU4LaeifdMWqZu7o20ojmW4B7hdhv2CMLwfnHGpYw=
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs=
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/klauspost/compress v1.4.1/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA=
github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw=
github.com/klauspost/cpuid v1.2.0/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
github.com/klauspost/pgzip v1.2.5 h1:qnWYvvKqedOF2ulHpMG72XQol4ILEJ8k2wwRl/Km8oE=
github.com/klauspost/pgzip v1.2.5/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/mailgun/raymond/v2 v2.0.48 h1:5dmlB680ZkFG2RN/0lvTAghrSxIESeu9/2aeDqACtjw=
github.com/mailgun/raymond/v2 v2.0.48/go.mod h1:lsgvL50kgt1ylcFJYZiULi5fjPBkkhNfj4KA0W54Z18=
github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b h1:j7+1HpAFS1zy5+Q4qx1fWh90gTKwiN4QCGoY9TWyyO4=
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE=
github.com/mholt/archiver/v4 v4.0.0-alpha.8 h1:tRGQuDVPh66WCOelqe6LIGh0gwmfwxUrSSDunscGsRM=
github.com/mholt/archiver/v4 v4.0.0-alpha.8/go.mod h1:5f7FUYGXdJWUjESffJaYR4R60VhnHxb2X3T1teMyv5A=
github.com/microcosm-cc/bluemonday v1.0.27 h1:MpEUotklkwCSLeH+Qdx1VJgNqLlpY2KXwXFM08ygZfk=
github.com/microcosm-cc/bluemonday v1.0.27/go.mod h1:jFi9vgW+H7c3V0lb6nR74Ib/DIB5OBs92Dimizgw2cA=
github.com/mitchellh/go-wordwrap v1.0.1 h1:TLuKupo69TCn6TQSyGxwI1EblZZEsQ0vMlAFQflz0v0=
github.com/mitchellh/go-wordwrap v1.0.1/go.mod h1:R62XHJLzvMFRBbcrT7m7WgmE1eOyTSsCt+hzestvNj0=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
github.com/nwaples/rardecode/v2 v2.0.0-beta.2 h1:e3mzJFJs4k83GXBEiTaQ5HgSc/kOK8q0rDaRO0MPaOk=
github.com/nwaples/rardecode/v2 v2.0.0-beta.2/go.mod h1:yntwv/HfMc/Hbvtq9I19D1n58te3h6KsqCf3GxyfBGY=
github.com/otiai10/copy v1.14.0 h1:dCI/t1iTdYGtkvCuBG2BgR6KZa83PTclw4U5n2wAllU=
github.com/otiai10/copy v1.14.0/go.mod h1:ECfuL02W+/FkTWZWgQqXPWZgW9oeKCSQ5qVfSc4qc4w=
github.com/otiai10/mint v1.5.1 h1:XaPLeE+9vGbuyEHem1JNk3bYc7KKqyI/na0/mLd/Kks=
github.com/otiai10/mint v1.5.1/go.mod h1:MJm72SBthJjz8qhefc4z1PYEieWmy8Bku7CjcAqyUSM=
github.com/pierrec/lz4/v4 v4.1.15 h1:MO0/ucJhngq7299dKLwIMtgTfbkoSPF6AoMYDd8Q4q0=
github.com/pierrec/lz4/v4 v4.1.15/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/radovskyb/watcher v1.0.7 h1:AYePLih6dpmS32vlHfhCeli8127LzkIgwJGcwwe8tUE=
github.com/radovskyb/watcher v1.0.7/go.mod h1:78okwvY5wPdzcb1UYnip1pvrZNIVEIh/Cm+ZuvsUYIg=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/smartystreets/assertions v1.2.0 h1:42S6lae5dvLc7BrLu/0ugRtcFVjoJNMC/N3yZFZkDFs=
github.com/smartystreets/assertions v1.2.0/go.mod h1:tcbTF8ujkAEcZ8TElKY+i30BzYlVhC/LOxJk7iOWnoo=
github.com/smartystreets/goconvey v1.7.2 h1:9RBaZCeXEQ3UselpuwUQHltGVXvdwm6cv1hgR6gDIPg=
github.com/smartystreets/goconvey v1.7.2/go.mod h1:Vw0tHAZW6lzCRk3xgdin6fKYcG+G3Pg9vgXWeJpQFMM=
github.com/tidwall/gjson v1.14.1 h1:iymTbGkQBhveq21bEvAQ81I0LEBork8BFe1CUZXdyuo=
github.com/tidwall/gjson v1.14.1/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
github.com/rwcarlsen/goexif v0.0.0-20190401172101-9e8deecbddbd/go.mod h1:hPqNNc0+uJM6H+SuU8sEs5K5IQeKccPqeSjfgcKGgPk=
github.com/sanity-io/litter v1.5.5 h1:iE+sBxPBzoK6uaEP5Lt3fHNgpKcHXc/A2HGETy0uJQo=
github.com/sanity-io/litter v1.5.5/go.mod h1:9gzJgR2i4ZpjZHsKvUXIRQVk7P+yM3e+jAF7bU2UI5U=
github.com/schollz/closestmatch v2.1.0+incompatible h1:Uel2GXEpJqOWBrlyI+oY9LTiyyjYS17cCYRqP13/SHk=
github.com/schollz/closestmatch v2.1.0+incompatible/go.mod h1:RtP1ddjLong6gTkbtmuhtR2uUrrJOpYzYRvbcPAid+g=
github.com/sergi/go-diff v1.0.0 h1:Kpca3qRNrduNnOQeazBd0ysaKrUJiIuISHxogkT9RPQ=
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
github.com/smarty/assertions v1.15.0 h1:cR//PqUBUiQRakZWqBiFFQ9wb8emQGDb0HeGdqGByCY=
github.com/smarty/assertions v1.15.0/go.mod h1:yABtdzeQs6l1brC900WlRNwj6ZR55d7B+E8C6HtKdec=
github.com/smartystreets/goconvey v1.8.1 h1:qGjIddxOk4grTu9JPOU31tVfq3cNdBlNa5sSznIX1xY=
github.com/smartystreets/goconvey v1.8.1/go.mod h1:+/u4qLyY6x1jReYOp7GOM2FSt8aP9CzCZL03bI28W60=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/tdewolff/minify/v2 v2.20.37 h1:Q97cx4STXCh1dlWDlNHZniE8BJ2EBL0+2b0n92BJQhw=
github.com/tdewolff/minify/v2 v2.20.37/go.mod h1:L1VYef/jwKw6Wwyk5A+T0mBjjn3mMPgmjjA688RNsxU=
github.com/tdewolff/parse/v2 v2.7.15 h1:hysDXtdGZIRF5UZXwpfn3ZWRbm+ru4l53/ajBRGpCTw=
github.com/tdewolff/parse/v2 v2.7.15/go.mod h1:3FbJWZp3XT9OWVN3Hmfp0p/a08v4h8J9W1aghka0soA=
github.com/tdewolff/test v1.0.11-0.20231101010635-f1265d231d52/go.mod h1:6DAvZliBAAnD7rhVgwaM7DE5/d9NMOAJ09SqYqeK4QE=
github.com/tdewolff/test v1.0.11-0.20240106005702-7de5f7df4739 h1:IkjBCtQOOjIn03u/dMQK9g+Iw9ewps4mCl1nB8Sscbo=
github.com/tdewolff/test v1.0.11-0.20240106005702-7de5f7df4739/go.mod h1:XPuWBzvdUzhCuxWO1ojpXsyzsA5bFoS3tO/Q3kFuTG8=
github.com/therootcompany/xz v1.0.1 h1:CmOtsn1CbtmyYiusbfmhmkpAAETj0wBIH6kCYaX+xzw=
github.com/therootcompany/xz v1.0.1/go.mod h1:3K3UH1yCKgBneZYhuQUvJ9HPD19UEXEI0BWbMn8qNMY=
github.com/tidwall/gjson v1.17.1 h1:wlYEnwqAHgzmhNUFfw7Xalt2JzQvsMx2Se4PcoFCT/U=
github.com/tidwall/gjson v1.17.1/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA=
github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
github.com/tidwall/pretty v1.2.0 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs=
github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
github.com/urfave/cli/v2 v2.16.3 h1:gHoFIwpPjoyIMbJp/VFd+/vuD0dAgFK4B6DpEMFJfQk=
github.com/urfave/cli/v2 v2.16.3/go.mod h1:1CNUng3PtjQMtRzJO4FMXBQvkGtuYRxxiR9xMa7jMwI=
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 h1:bAn7/zixMGCfxrRTfdpNzjtPYqr8smhKouy9mxVdGPU=
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673/go.mod h1:N3UwUGtsrSj3ccvlPHLoLsHnpR27oXr4ZE984MbSER8=
github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4=
github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
github.com/ulikunitz/xz v0.5.6/go.mod h1:2bypXElzHzzJZwzH67Y6wb67pO62Rzfn7BSiF4ABRW8=
github.com/ulikunitz/xz v0.5.10 h1:t92gobL9l3HE202wg3rlk19F6X+JOxl9BBrCCMYEYd8=
github.com/ulikunitz/xz v0.5.10/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
github.com/urfave/cli/v2 v2.27.2 h1:6e0H+AkS+zDckwPCUrZkKX38mRaau4nL2uipkJpbkcI=
github.com/urfave/cli/v2 v2.27.2/go.mod h1:g0+79LmHHATl7DAcHO99smiR/T7uGLw84w8Y42x+4eM=
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
github.com/vmihailenco/msgpack/v5 v5.4.1 h1:cQriyiUvjTwOHg8QZaPihLWeRAAVoCpE00IUPn0Bjt8=
github.com/vmihailenco/msgpack/v5 v5.4.1/go.mod h1:GaZTsDaehaPpQVyxrf5mtQlH+pc21PIudVV/E3rRQok=
github.com/vmihailenco/tagparser/v2 v2.0.0 h1:y09buUbR+b5aycVFQs/g70pqKVZNBmxwAhO7/IwNM9g=
github.com/vmihailenco/tagparser/v2 v2.0.0/go.mod h1:Wri+At7QHww0WTrCBeu4J6bNtoV6mEfg5OIWRZA9qds=
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f h1:J9EGpcZtP0E/raorCMxlFGSTBrsSlaDGf3jU/qvAE2c=
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHovont7NscjpAxXsDA8S8BMYve8Y5+7cuRE7R0=
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ=
github.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17UxZ74=
github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y=
github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 h1:gEOO8jv9F4OT7lGCjxCBTO/36wtF6j2nSip77qHd4x4=
github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1/go.mod h1:Ohn+xnUBiLI6FVj/9LpzZWtj1/D6lUovWYBkxHVV3aM=
github.com/yalp/jsonpath v0.0.0-20180802001716-5cc68e5049a0 h1:6fRhSjgLCkTD3JnJxvaJ4Sj+TYblw757bqYgZaOq5ZY=
github.com/yalp/jsonpath v0.0.0-20180802001716-5cc68e5049a0/go.mod h1:/LWChgwKmvncFJFHJ7Gvn9wZArjbV5/FppcK2fKk/tI=
github.com/yosssi/ace v0.0.5 h1:tUkIP/BLdKqrlrPwcmH0shwEEhTRHoGnc1wFIWmaBUA=
github.com/yosssi/ace v0.0.5/go.mod h1:ALfIzm2vT7t5ZE7uoIZqF3TQ7SAOyupFZnkrF5id+K0=
github.com/yudai/gojsondiff v1.0.0 h1:27cbfqXLVEJ1o8I6v3y9lg8Ydm53EKqHXAOMxEGlCOA=
github.com/yudai/gojsondiff v1.0.0/go.mod h1:AY32+k2cwILAkW1fbgxQ5mUmMiZFgLIV+FBNExI05xg=
github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82 h1:BHyfKlQyqbsFN5p3IfnEUduWvb9is428/nNb5L3U01M=
github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82/go.mod h1:lgjkn3NuSvDfVJdfcVVdX+jpBxNmX4rDAzaS45IcYoM=
github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go4.org v0.0.0-20200411211856-f5505b9728dd h1:BNJlw5kRTzdmyfh5U8F93HA2OwkP7ZGwA51eJ/0wKOU=
go4.org v0.0.0-20200411211856-f5505b9728dd/go.mod h1:CIiUVy99QCPfoE13bO4EZaz5GZMZXMSBGhxRdsvzbkg=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.25.0 h1:ypSNr+bnYL2YhwoMt2zPxHFmbAN1KZs/njMG3hxUp30=
golang.org/x/crypto v0.25.0/go.mod h1:T+wALwcMOSE0kXgUAnPAHqTLW+XHgcELELW8VaDgm/M=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek=
golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY=
golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
golang.org/x/exp v0.0.0-20240707233637-46b078467d37 h1:uLDX+AfeFCct3a2C7uIWBKMJIR3CJMhcgfrUAqjRK6w=
golang.org/x/exp v0.0.0-20240707233637-46b078467d37/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY=
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs=
golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.5.1/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190327091125-710a502c58a2/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.27.0 h1:5K3Njcw06/l2y9vpGCSdcxWOYHOUk3dVNGDXN+FvAys=
golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M=
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10 h1:WIoqL4EROvwiPdUtaip4VcDdpZ4kha7wBWZrbVKCIZg=
golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI=
golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.22.0 h1:BbsgPEJULsl2fV/AT3v15Mjva5yXKQDyKf+TbDz7QJk=
golang.org/x/term v0.22.0/go.mod h1:F3qCibpT5AMpCRfhfT53vVJwhLtIVHhB9XDjfFvnMI4=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4=
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4=
golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.1.9/go.mod h1:nABZi5QlRsZVlzPpHl034qft6wpY4eDcsTt5AaioBiU=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8=
google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg=
google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b h1:QRR6H1YWRnHb4Y/HeNFCTJLFVxaq6wH4YuVdsUOr75U=
gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
moul.io/http2curl/v2 v2.3.0 h1:9r3JfDzWPcbIklMOs2TnIFzDYvfAZvjeavG6EzP7jYs=
moul.io/http2curl/v2 v2.3.0/go.mod h1:RW4hyBjTWSYDOxapodpNEtX0g5Eb16sxklBqmd2RHcE=
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=

215
helpers.go Normal file
View File

@ -0,0 +1,215 @@
package main
import (
"bufio"
"fmt"
"io"
"net"
"net/http"
"os"
"path/filepath"
"strings"
"github.com/evanw/esbuild/pkg/api"
"github.com/otiai10/copy"
"github.com/trading-peter/gowebbuild/fsutils"
)
func purge(opts options) {
if opts.ESBuild.PurgeBeforeBuild {
if opts.ESBuild.Outdir != "" {
fmt.Printf("Purging output folder %s\n", opts.ESBuild.Outdir)
os.RemoveAll(opts.ESBuild.Outdir)
}
if opts.ESBuild.Outfile != "" {
fmt.Printf("Purging output file %s\n", opts.ESBuild.Outfile)
os.Remove(opts.ESBuild.Outfile)
}
}
}
func download(opts options) {
if len(opts.Download) == 0 {
return
}
for _, dl := range opts.Download {
if !fsutils.IsDir(filepath.Dir(dl.Dest)) {
fmt.Printf("Failed to find destination folder for downloading from %s\n", dl.Url)
continue
}
file, err := os.Create(dl.Dest)
if err != nil {
fmt.Printf("Failed to create file for downloading from %s: %v\n", dl.Url, err)
continue
}
defer file.Close()
client := http.Client{
CheckRedirect: func(r *http.Request, via []*http.Request) error {
r.URL.Opaque = r.URL.Path
return nil
},
}
fmt.Printf("Downloading %s to %s\n", dl.Url, dl.Dest)
resp, err := client.Get(dl.Url)
if err != nil {
fmt.Printf("Failed to download file from %s: %v\n", dl.Url, err)
continue
}
defer resp.Body.Close()
_, err = io.Copy(file, resp.Body)
if err != nil {
fmt.Printf("Failed to write file downloaded from %s: %v\n", dl.Url, err)
continue
}
}
}
func cp(opts options) {
if len(opts.Copy) == 0 {
fmt.Println("Nothing to copy")
return
}
for _, op := range opts.Copy {
paths, err := filepath.Glob(op.Src)
if err != nil {
fmt.Printf("Invalid glob pattern: %s\n", op.Src)
continue
}
destIsDir := fsutils.IsDir(op.Dest)
for _, p := range paths {
d := op.Dest
if destIsDir && fsutils.IsFile(p) {
d = filepath.Join(d, filepath.Base(p))
}
err := copy.Copy(p, d)
fmt.Printf("Copying %s to %s\n", p, d)
if err != nil {
fmt.Printf("Failed to copy %s: %v\n", p, err)
continue
}
}
}
}
func replace(opts options) {
if len(opts.Replace) == 0 {
fmt.Println("Nothing to replace")
return
}
for _, op := range opts.Replace {
paths, err := filepath.Glob(op.Pattern)
if err != nil {
fmt.Printf("Invalid glob pattern: %s\n", op.Pattern)
continue
}
for _, p := range paths {
if !fsutils.IsFile(p) {
continue
}
read, err := os.ReadFile(p)
if err != nil {
fmt.Printf("%+v\n", err)
os.Exit(1)
}
r := op.Replace
if strings.HasPrefix(op.Replace, "$") {
r = os.ExpandEnv(op.Replace)
}
count := strings.Count(string(read), op.Search)
if count > 0 {
fmt.Printf("Replacing %d occurrences of '%s' with '%s' in %s\n", count, op.Search, r, p)
newContents := strings.ReplaceAll(string(read), op.Search, r)
err = os.WriteFile(p, []byte(newContents), 0)
if err != nil {
fmt.Printf("%+v\n", err)
os.Exit(1)
}
}
}
}
}
func build(opts options) {
result := api.Build(cfgToESBuildCfg(opts))
if len(result.Errors) == 0 {
triggerReload <- struct{}{}
}
}
func getGoModuleName(root string) (string, error) {
modFile := filepath.Join(root, "go.mod")
if !fsutils.IsFile(modFile) {
return "", fmt.Errorf("go.mod file not found")
}
// Open the go.mod file
file, err := os.Open(modFile)
if err != nil {
return "", fmt.Errorf("error opening go.mod: %v", err)
}
defer file.Close()
// Create a scanner to read the file line by line
scanner := bufio.NewScanner(file)
// Iterate through the lines
for scanner.Scan() {
line := scanner.Text()
// Check if the line starts with "module "
if strings.HasPrefix(line, "module ") {
// Extract the module name
moduleName := strings.TrimSpace(strings.TrimPrefix(line, "module "))
return moduleName, nil
}
}
// Check for errors in scanning
if err := scanner.Err(); err != nil {
return "", fmt.Errorf("error scanning go.mod: %v", err)
}
return "", nil
}
func findFreePort(from, to int) int {
for port := from; port <= to; port++ {
if isFreePort(port) {
return port
}
port++
}
return -1
}
func isFreePort(port int) bool {
addr, err := net.ResolveTCPAddr("tcp", fmt.Sprintf("localhost:%d", port))
if err != nil {
return false
}
l, err := net.ListenTCP("tcp", addr)
if err != nil {
return false
}
defer l.Close()
return true
}

View File

@ -11,6 +11,7 @@ import (
"github.com/otiai10/copy"
"github.com/radovskyb/watcher"
"github.com/tidwall/gjson"
"github.com/trading-peter/gowebbuild/fsutils"
)
func link(from, to string) chan struct{} {
@ -25,7 +26,7 @@ func link(from, to string) chan struct{} {
}
packages := map[string]string{}
packageFiles := findFiles(from, "package.json")
packageFiles := fsutils.FindFiles(from, "package.json")
for i := range packageFiles {
content := readFileContent(packageFiles[i])
@ -45,6 +46,7 @@ func link(from, to string) chan struct{} {
w := watcher.New()
w.SetMaxEvents(1)
w.FilterOps(watcher.Write, watcher.Rename, watcher.Move, watcher.Create, watcher.Remove)
w.IgnoreHiddenFiles(true)
if err := w.AddRecursive(from); err != nil {
fmt.Println(err.Error())
@ -59,12 +61,12 @@ func link(from, to string) chan struct{} {
for k, v := range packages {
if strings.HasPrefix(event.Path, v) {
src := filepath.Dir(event.Path)
dest := filepath.Join(to, "node_modules", k)
subPath, _ := filepath.Rel(v, src)
dest := filepath.Join(to, "node_modules", k, subPath)
fmt.Printf("Copying %s to %s\n", src, dest)
err := copy.Copy(src, dest, copy.Options{
Skip: func(src string) (bool, error) {
ok, _ := filepath.Match("*.js", filepath.Base(src))
if ok && !strings.Contains(src, "node_modules") {
Skip: func(stat fs.FileInfo, src, dest string) (bool, error) {
if !isExcludedPath(src, "node_modules", ".git") && (stat.IsDir() || isIncludedExt(filepath.Base(src), "*.js", "*.ts")) {
return false, nil
}
@ -98,22 +100,23 @@ func link(from, to string) chan struct{} {
return requestBuildCh
}
func findFiles(root, name string) []string {
paths := []string{}
filepath.WalkDir(root, func(path string, d fs.DirEntry, err error) error {
if err != nil {
return nil
func isExcludedPath(srcPath string, exPaths ...string) bool {
for _, exP := range exPaths {
if strings.Contains(srcPath, exP) {
return true
}
}
return false
}
if !d.IsDir() && filepath.Base(path) == name && !strings.Contains(path, "node_modules") {
paths = append(paths, path)
func isIncludedExt(srcPath string, extensions ...string) bool {
for _, ext := range extensions {
if ok, _ := filepath.Match(ext, srcPath); ok {
return true
}
}
return nil
})
return paths
return false
}
func readFileContent(path string) string {

323
main.go
View File

@ -1,88 +1,32 @@
package main
import (
"encoding/json"
"errors"
"context"
"fmt"
"io/ioutil"
"os"
"os/signal"
"path/filepath"
"strings"
"syscall"
"time"
"github.com/evanw/esbuild/pkg/api"
"github.com/jaschaephraim/lrserver"
"github.com/otiai10/copy"
"github.com/radovskyb/watcher"
"github.com/urfave/cli/v2"
)
var triggerReload = make(chan struct{})
type ESBuildExtended struct {
api.BuildOptions
PurgeBeforeBuild bool
}
type options struct {
ESBuild ESBuildExtended
Watch struct {
Path string
Exclude []string
}
Serve struct {
Path string
Port int
}
Copy []struct {
Src string
Dest string
}
Replace []struct {
Pattern string
Search string
Replace string
}
Link struct {
From string
To string
}
}
func readCfg(cfgPath string) []options {
cfgContent, err := os.ReadFile(cfgPath)
if err != nil {
fmt.Printf("%+v\n", err)
os.Exit(1)
}
optsSetups := []options{}
err = json.Unmarshal(cfgContent, &optsSetups)
if err != nil {
opt := options{}
err = json.Unmarshal(cfgContent, &opt)
if err != nil {
fmt.Printf("%+v\n", err)
os.Exit(1)
}
optsSetups = append(optsSetups, opt)
}
return optsSetups
}
func main() {
cfgParam := &cli.StringFlag{
Name: "c",
Value: "./.gowebbuild.json",
Value: "./.gowebbuild.yaml",
Usage: "path to config file config file.",
}
app := &cli.App{
Name: "gowebbuild",
Usage: "All in one tool to build web frontend projects.",
Version: "4.1.0",
Version: "6.1.3",
Authors: []*cli.Author{{
Name: "trading-peter (https://github.com/trading-peter)",
}},
@ -94,7 +38,7 @@ Watch project and rebuild if a files changes:
$ gowebbuild
Use a different name or path for the config file (working directory is always the location of the config file):
$ gowebbuild -c watch
$ gowebbuild -c /path/to/gowebbuild.yaml watch
Production build:
$ gowebbuild build -p
@ -103,6 +47,24 @@ Manually replace a string within some files (not limited to project directory):
$ gowebbuild replace *.go foo bar
`,
Commands: []*cli.Command{
{
Name: "template",
Usage: "execute a template",
Flags: []cli.Flag{
cfgParam,
},
Action: tplAction,
},
{
Name: "npm-proxy",
Usage: "proxy npm packages",
Flags: []cli.Flag{
cfgParam,
},
Action: proxyAction,
},
{
Name: "build",
Usage: "build web sources one time and exit",
@ -131,6 +93,106 @@ $ gowebbuild replace *.go foo bar
Action: watchAction,
},
{
Name: "serve",
Usage: "serve a directory with a simply http server",
Flags: []cli.Flag{
&cli.StringFlag{
Name: "root",
Value: "./",
Usage: "folder to serve",
},
&cli.UintFlag{
Name: "port",
Value: uint(8080),
Usage: "serve directory this on port",
},
&cli.UintFlag{
Name: "lr-port",
Value: uint(lrserver.DefaultPort),
Usage: "port for the live reload server",
},
},
Action: func(ctx *cli.Context) error {
port := ctx.Uint("port")
root := ctx.String("root")
lrPort := ctx.Uint("lr-port")
if lrPort != 0 {
go func() {
w := watcher.New()
w.SetMaxEvents(1)
w.FilterOps(watcher.Write, watcher.Rename, watcher.Move, watcher.Create, watcher.Remove)
if err := w.AddRecursive(root); err != nil {
fmt.Println(err.Error())
os.Exit(1)
}
go func() {
for {
select {
case event := <-w.Event:
fmt.Printf("File %s changed\n", event.Name())
triggerReload <- struct{}{}
case err := <-w.Error:
fmt.Println(err.Error())
case <-w.Closed:
return
}
}
}()
if err := w.Start(time.Millisecond * 100); err != nil {
fmt.Println(err.Error())
}
}()
go func() {
lr := lrserver.New(lrserver.DefaultName, uint16(lrPort))
go func() {
for {
<-triggerReload
lr.Reload("")
}
}()
lr.SetStatusLog(nil)
err := lr.ListenAndServe()
if err != nil {
panic(err)
}
}()
}
return Serve(root, port)
},
},
{
Name: "download",
Usage: "execute downloads as configured",
Flags: []cli.Flag{
cfgParam,
},
Action: func(ctx *cli.Context) error {
cfgPath, err := filepath.Abs(ctx.String("c"))
if err != nil {
return err
}
os.Chdir(filepath.Dir(cfgPath))
opts := readCfg(cfgPath)
for i := range opts {
download(opts[i])
}
return nil
},
},
{
Name: "replace",
ArgsUsage: "[glob file pattern] [search] [replace]",
@ -150,9 +212,9 @@ $ gowebbuild replace *.go foo bar
replace(options{
Replace: []struct {
Pattern string
Search string
Replace string
Pattern string `yaml:"pattern"`
Search string `yaml:"search"`
Replace string `yaml:"replace"`
}{
{
Pattern: files,
@ -168,122 +230,17 @@ $ gowebbuild replace *.go foo bar
DefaultCommand: "watch",
}
if err := app.Run(os.Args); err != nil {
sigChan := make(chan os.Signal, 1)
signal.Notify(sigChan, os.Interrupt, syscall.SIGTERM)
appCtx, cancel := context.WithCancel(context.Background())
go func() {
<-sigChan
cancel()
fmt.Println("Received interrupt, shutting down...")
}()
if err := app.RunContext(appCtx, os.Args); err != nil {
fmt.Println(err)
}
}
func purge(opts options) {
if opts.ESBuild.PurgeBeforeBuild {
if opts.ESBuild.Outdir != "" {
os.RemoveAll(opts.ESBuild.Outdir)
}
if opts.ESBuild.Outfile != "" {
os.Remove(opts.ESBuild.Outfile)
}
}
}
func cp(opts options) {
if len(opts.Copy) == 0 {
fmt.Println("Nothing to copy")
return
}
for _, op := range opts.Copy {
paths, err := filepath.Glob(op.Src)
if err != nil {
fmt.Printf("Invalid glob pattern: %s\n", op.Src)
continue
}
destIsDir := isDir(op.Dest)
for _, p := range paths {
d := op.Dest
if destIsDir && isFile(p) {
d = filepath.Join(d, filepath.Base(p))
}
err := copy.Copy(p, d)
fmt.Printf("Copying %s to %s\n", p, d)
if err != nil {
fmt.Printf("Failed to copy %s: %v\n", p, err)
continue
}
}
}
}
func replace(opts options) {
if len(opts.Replace) == 0 {
fmt.Println("Nothing to replace")
return
}
for _, op := range opts.Replace {
paths, err := filepath.Glob(op.Pattern)
if err != nil {
fmt.Printf("Invalid glob pattern: %s\n", op.Pattern)
continue
}
for _, p := range paths {
if !isFile(p) {
continue
}
read, err := ioutil.ReadFile(p)
if err != nil {
fmt.Printf("%+v\n", err)
os.Exit(1)
}
r := op.Replace
if strings.HasPrefix(op.Replace, "$") {
r = os.ExpandEnv(op.Replace)
}
count := strings.Count(string(read), op.Search)
if count > 0 {
fmt.Printf("Replacing %d occurrences of '%s' with '%s' in %s\n", count, op.Search, r, p)
newContents := strings.ReplaceAll(string(read), op.Search, r)
err = ioutil.WriteFile(p, []byte(newContents), 0)
if err != nil {
fmt.Printf("%+v\n", err)
os.Exit(1)
}
}
}
}
}
func isFile(path string) bool {
stat, err := os.Stat(path)
if errors.Is(err, os.ErrNotExist) {
return false
}
return !stat.IsDir()
}
func isDir(path string) bool {
stat, err := os.Stat(path)
if errors.Is(err, os.ErrNotExist) {
os.MkdirAll(path, 0755)
return true
}
return err == nil && stat.IsDir()
}
func build(opts options) {
result := api.Build(opts.ESBuild.BuildOptions)
if len(result.Errors) == 0 {
triggerReload <- struct{}{}
}
}

87
npmproxy.go Normal file
View File

@ -0,0 +1,87 @@
package main
import (
"context"
"fmt"
"os"
"path/filepath"
"strings"
"github.com/trading-peter/gowebbuild/npmproxy"
"github.com/urfave/cli/v2"
)
func proxyAction(ctx *cli.Context) error {
cfgPath, err := filepath.Abs(ctx.String("c"))
if err != nil {
return err
}
projectDir := filepath.Dir(cfgPath)
os.Chdir(projectDir)
opts := readCfg(cfgPath)
return runProxy(ctx.Context, projectDir, opts)
}
func runProxy(ctx context.Context, projectDir string, opts []options) error {
overrides := []NpmProxyOverride{}
for _, o := range opts {
overrides = append(overrides, o.NpmProxy.Overrides...)
}
if len(overrides) == 0 {
return nil
}
fmt.Printf("Found %d npm overrides. Starting proxy server.\n", len(overrides))
// if fs.IsFile(filepath.Join(projectDir, ".npmrc")) {
// return fmt.Errorf(".npmrc file already exists in project root.")
// }
freePort := findFreePort(10000, 20000)
freePortInternal := findFreePort(20001, 30000)
if freePort == -1 || freePortInternal == -1 {
return fmt.Errorf("Failed to find free ports for proxy setup.")
}
list := []npmproxy.Override{}
npmrcRules := []string{
";CREATED BY GOWEBBUILD. DO NOT EDIT.",
";This file is used by the npm proxy server.",
";It is used to override the default registry for specific package namespaces.",
";This file will be removed after the proxy server is stopped.",
}
for _, o := range overrides {
list = append(list, npmproxy.Override{
Namespace: o.Namespace,
Upstream: o.Upstream,
PackageRoot: o.PackageRoot,
})
npmrcRules = append(npmrcRules, fmt.Sprintf("%s:registry=http://localhost:%d", o.Namespace, freePort))
}
err := os.WriteFile(filepath.Join(projectDir, ".npmrc"), []byte(strings.Join(npmrcRules, "\n")), 0644)
if err != nil {
return err
}
defer os.Remove(filepath.Join(projectDir, ".npmrc"))
proxy := npmproxy.New(
list,
projectDir,
npmproxy.WithPort(freePort),
npmproxy.WithInternalPort(freePortInternal),
)
proxy.Start(ctx)
fmt.Println("Stopped npm proxy server")
return nil
}

92
npmproxy/externalProxy.go Normal file
View File

@ -0,0 +1,92 @@
package npmproxy
import (
"bytes"
"context"
"fmt"
"net/http"
"net/http/httputil"
"net/url"
"strings"
"time"
"github.com/kataras/golog"
)
func (p *Proxy) externalHTTPServer(ctx context.Context) {
mux := http.NewServeMux()
srv := &http.Server{
Addr: p.externalProxyHost,
Handler: mux,
}
go func() {
<-ctx.Done()
shutdownCtx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
if err := srv.Shutdown(shutdownCtx); err != nil {
fmt.Printf("Failed to shutdown proxy server: %v\n", err)
}
}()
mux.HandleFunc("/", p.incomingNpmRequestHandler)
if err := srv.ListenAndServe(); err != nil {
if err != http.ErrServerClosed {
panic(err)
}
}
}
// Receives incoming requests from the npm cli and decides based on override rules what server it should forwarded to.
func (p *Proxy) incomingNpmRequestHandler(res http.ResponseWriter, req *http.Request) {
golog.Infof("Incoming NPM request for %s", req.URL.Path)
pkgPath := strings.TrimLeft(req.URL.Path, "/")
// If no matching override is found, we forward the request to the default registry.
_, ok := p.matchingOverride(pkgPath)
if !ok {
serveReverseProxy(p.DefaultRegistry, res, req)
return
}
// Process the override by forwarding the request to the internal proxy.
serveReverseProxy(p.internalProxyUrl, res, req)
// golog.Infof("Received request for url: %v", proxyUrl)
}
type ResponseWriterWrapper struct {
http.ResponseWriter
Body *bytes.Buffer
}
func (rw *ResponseWriterWrapper) Write(b []byte) (int, error) {
rw.Body.Write(b) // Capture the response body
return rw.ResponseWriter.Write(b) // Send the response to the original writer
}
func serveReverseProxy(target string, res http.ResponseWriter, req *http.Request) {
// parse the OriginalUrl
OriginalUrl, _ := url.Parse(target)
// create the reverse proxy
proxy := httputil.NewSingleHostReverseProxy(OriginalUrl)
// Update the headers to allow for SSL redirection
req.URL.Host = OriginalUrl.Host
req.URL.Scheme = OriginalUrl.Scheme
req.Header.Set("X-Forwarded-Host", req.Header.Get("Host"))
req.Host = OriginalUrl.Host
wrappedRes := &ResponseWriterWrapper{ResponseWriter: res, Body: new(bytes.Buffer)}
// Note that ServeHttp is non blocking and uses a go routine under the hood
proxy.ServeHTTP(wrappedRes, req)
// Print the captured response body
fmt.Println("Response body:", wrappedRes.Body.String())
}

67
npmproxy/internalProxy.go Normal file
View File

@ -0,0 +1,67 @@
package npmproxy
import (
"context"
"encoding/json"
"fmt"
"net/http"
"path/filepath"
"time"
"github.com/trading-peter/gowebbuild/fsutils"
)
func (p *Proxy) internalHTTPServer(ctx context.Context) {
mux := http.NewServeMux()
srv := &http.Server{
Addr: p.internalProxyHost,
Handler: mux,
}
go func() {
<-ctx.Done()
shutdownCtx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
if err := srv.Shutdown(shutdownCtx); err != nil {
fmt.Printf("Failed to shutdown internal server for npm proxy: %v\n", err)
}
}()
mux.HandleFunc("GET /{pkg}", func(w http.ResponseWriter, r *http.Request) {
pkgName := r.PathValue("pkg")
override, ok := p.matchingOverride(pkgName)
if !ok {
http.NotFound(w, r)
return
}
pkg, err := p.findPackageSource(override, pkgName)
if err != nil {
serveReverseProxy(override.Upstream, w, r)
return
}
json.NewEncoder(w).Encode(pkg)
})
mux.HandleFunc("GET /files/{file}", func(w http.ResponseWriter, r *http.Request) {
fileName := r.PathValue("file")
filePath := filepath.Join(p.pkgCachePath, fileName)
if !fsutils.IsFile(filePath) {
http.NotFound(w, r)
return
}
http.ServeFile(w, r, filePath)
})
if err := srv.ListenAndServe(); err != nil {
if err != http.ErrServerClosed {
panic(err)
}
}
}

95
npmproxy/proxy.go Normal file
View File

@ -0,0 +1,95 @@
package npmproxy
import (
"context"
"fmt"
"os"
"path/filepath"
"strings"
)
type Override struct {
Namespace string
Upstream string
PackageRoot string
}
type Proxy struct {
ProjectRoot string
Port int
InternalPort int
DefaultRegistry string
Overrides []Override
pkgCachePath string
externalProxyHost string
internalProxyHost string
internalProxyUrl string
}
type ProxyOption func(*Proxy)
func WithPort(port int) ProxyOption {
return func(p *Proxy) {
p.Port = port
}
}
func WithInternalPort(port int) ProxyOption {
return func(p *Proxy) {
p.InternalPort = port
}
}
func WithPkgCachePath(path string) ProxyOption {
return func(p *Proxy) {
p.pkgCachePath = path
}
}
func WithDefaultRegistry(registry string) ProxyOption {
return func(p *Proxy) {
p.DefaultRegistry = strings.TrimSuffix(registry, "/")
}
}
func New(overrides []Override, projectRoot string, options ...ProxyOption) *Proxy {
p := &Proxy{
ProjectRoot: projectRoot,
Port: 1234,
InternalPort: 1235,
DefaultRegistry: "https://registry.npmjs.org",
Overrides: overrides,
}
for _, option := range options {
option(p)
}
if p.pkgCachePath == "" {
homeDir, err := os.UserHomeDir()
if err != nil {
homeDir = "."
}
p.pkgCachePath = filepath.Join(homeDir, ".gowebbuild", "proxy", "cache")
}
p.externalProxyHost = fmt.Sprintf("127.0.0.1:%d", p.Port)
p.internalProxyHost = fmt.Sprintf("127.0.0.1:%d", p.InternalPort)
p.internalProxyUrl = fmt.Sprintf("http://%s", p.internalProxyHost)
return p
}
func (p *Proxy) Start(ctx context.Context) {
go p.internalHTTPServer(ctx)
p.externalHTTPServer(ctx)
}
func (p *Proxy) matchingOverride(path string) (*Override, bool) {
for _, o := range p.Overrides {
if strings.HasPrefix(path, o.Namespace) {
return &o, true
}
}
return nil, false
}

188
npmproxy/source.go Normal file
View File

@ -0,0 +1,188 @@
package npmproxy
import (
"context"
"crypto/sha512"
"encoding/base64"
"encoding/json"
"fmt"
"io"
"os"
"path/filepath"
"regexp"
"strings"
"github.com/Masterminds/semver/v3"
"github.com/kataras/golog"
"github.com/mholt/archiver/v4"
"github.com/trading-peter/gowebbuild/fsutils"
)
func (p *Proxy) readPackageJson(pkgPath string) (PackageJson, error) {
pkgFile := filepath.Join(pkgPath, "package.json")
if !fsutils.IsFile(pkgFile) {
return PackageJson{}, fmt.Errorf("package.json not found in %s", pkgPath)
}
pkgData, err := os.ReadFile(pkgFile)
if err != nil {
return PackageJson{}, err
}
pkg := PackageJson{}
err = json.Unmarshal(pkgData, &pkg)
if err != nil {
return PackageJson{}, err
}
return pkg, nil
}
func (p *Proxy) findDependencyVersionConstraint(projectPkg PackageJson, pkgName string) (*semver.Constraints, error) {
if verStr, ok := projectPkg.Dependencies[pkgName]; ok {
return semver.NewConstraint(verStr)
}
return nil, fmt.Errorf("package %s not found in project dependencies", pkgName)
}
func (p *Proxy) findPackageSource(override *Override, pkgName string) (*Package, error) {
pkgNameParts := strings.Split(pkgName, "/")
pkgPath := filepath.Join(override.PackageRoot, pkgNameParts[len(pkgNameParts)-1])
// Read the projects package.json and figure out the requested semver version, which probably will be a contraint to assert against (like "^1.2.3").
projectPkg, err := p.readPackageJson(p.ProjectRoot)
if err != nil {
return nil, err
}
reqVersion, err := p.findDependencyVersionConstraint(projectPkg, pkgName)
pkg, err := p.readPackageJson(pkgPath)
if err != nil {
return nil, err
}
pkgVersion, err := semver.NewVersion(pkg.Version)
if err != nil {
return nil, err
}
if !reqVersion.Check(pkgVersion) {
golog.Infof("Version %s in package sources for %s is not meeting the version constrains (%s) of the project. Forwarding request to upstream registry.", pkgVersion, pkgName, reqVersion)
return nil, nil
}
pkgArchive, err := p.createPackage(pkgPath, pkg)
if err != nil {
return nil, err
}
integrity, shasum, err := p.createHashes(pkgArchive)
if err != nil {
return nil, err
}
return &Package{
ID: pkg.Name,
Name: pkg.Name,
DistTags: DistTags{
Latest: pkg.Version,
},
Versions: map[string]Version{
pkg.Version: {
ID: pkg.Name,
Name: pkg.Name,
Version: pkg.Version,
Dependencies: pkg.Dependencies,
Dist: Dist{
Integrity: integrity,
Shasum: shasum,
Tarball: fmt.Sprintf("%s/files/%s", p.internalProxyUrl, filepath.Base(pkgArchive)),
},
},
},
}, nil
}
func (p *Proxy) createPackage(pkgPath string, pkg PackageJson) (string, error) {
err := os.MkdirAll(p.pkgCachePath, 0755)
if err != nil {
return "", err
}
pkgArchive := filepath.Join(p.pkgCachePath, sanitizePkgName(pkg.Name, pkg.Version)+".tar")
files, err := archiver.FilesFromDisk(nil, map[string]string{
pkgPath: ".",
})
if err != nil {
return "", err
}
filesFiltered := []archiver.File{}
filterRegex := regexp.MustCompile(`^node_modules|.git`)
for _, file := range files {
if filterRegex.MatchString(file.NameInArchive) {
continue
}
filesFiltered = append(filesFiltered, file)
}
out, err := os.Create(pkgArchive)
if err != nil {
return "", err
}
defer out.Close()
format := archiver.CompressedArchive{
Archival: archiver.Tar{},
}
err = format.Archive(context.Background(), out, filesFiltered)
if err != nil {
return "", err
}
return pkgArchive, nil
}
func (p *Proxy) createHashes(pkgArchive string) (string, string, error) {
// Open the file
file, err := os.Open(pkgArchive)
if err != nil {
return "", "", err
}
defer file.Close()
// Create a new SHA256 hash
hash := sha512.New()
// Copy the file data into the hash
if _, err := io.Copy(hash, file); err != nil {
return "", "", err
}
// Get the hash sum
hashSum := hash.Sum(nil)
// Generate the integrity string (SHA-256 base64-encoded)
integrity := "sha512-" + base64.StdEncoding.EncodeToString(hashSum)
// Generate the shasum (hexadecimal representation)
shasum := fmt.Sprintf("%x", hashSum)
return integrity, shasum, nil
}
// Replace all characters of the pages name that are not allowed in a URL with a hyphen.
func sanitizePkgName(pkgName string, version string) string {
pkgName = strings.ReplaceAll(pkgName, "@", "")
pkgName = strings.ReplaceAll(pkgName, "/", "_")
version = strings.ReplaceAll(version, ".", "_")
return fmt.Sprintf("%s_%s", pkgName, version)
}

51
npmproxy/types.go Normal file
View File

@ -0,0 +1,51 @@
package npmproxy
type PackageJson struct {
Name string `json:"name"`
Version string `json:"version"`
Dependencies map[string]string `json:"dependencies"`
}
type Package struct {
ID string `json:"_id"`
Name string `json:"name"`
Description string `json:"description"`
DistTags DistTags `json:"dist-tags"`
Versions map[string]Version `json:"versions"`
Readme string `json:"readme"`
Repository Repository `json:"repository"`
Author Author `json:"author"`
License string `json:"license"`
}
type DistTags struct {
Latest string `json:"latest"`
}
type Author struct {
Name string `json:"name"`
}
type Repository struct {
Type string `json:"type"`
URL string `json:"url"`
}
type Dist struct {
Integrity string `json:"integrity"`
Shasum string `json:"shasum"`
Tarball string `json:"tarball"`
}
type Version struct {
ID string `json:"_id"`
Name string `json:"name"`
Version string `json:"version"`
Description string `json:"description"`
Author Author `json:"author"`
License string `json:"license"`
Repository Repository `json:"repository"`
Dependencies map[string]string `json:"dependencies"`
Readme string `json:"readme"`
Dist Dist `json:"dist"`
}

View File

@ -1,6 +1,6 @@
{
"Watch": {
"Path": "./frontend/src",
"Paths": [ "./frontend/src" ],
"Exclude": [ "./dist" ]
},
"Copy": [
@ -45,5 +45,8 @@
"Bundle": true,
"Write": true,
"LogLevel": 3
},
"ProductionBuildOptions": {
"CmdPostBuild": "my-build-script.sh"
}
}

22
serve.go Normal file
View File

@ -0,0 +1,22 @@
package main
import (
"fmt"
"github.com/kataras/iris/v12"
)
func Serve(root string, port uint) error {
app := iris.New()
app.HandleDir("/", iris.Dir(root), iris.DirOptions{
IndexName: "/index.html",
Compress: false,
ShowList: true,
ShowHidden: true,
Cache: iris.DirCacheOptions{
Enable: false,
},
})
return app.Listen(fmt.Sprintf(":%d", port))
}

113
templates.go Normal file
View File

@ -0,0 +1,113 @@
package main
import (
_ "embed"
"fmt"
"os"
"path/filepath"
"runtime"
"text/template"
"github.com/Iilun/survey/v2"
"github.com/kataras/golog"
"github.com/trading-peter/gowebbuild/fsutils"
"github.com/urfave/cli/v2"
)
//go:embed templates/tpl.gowebbuild.yaml
var sampleConfig string
//go:embed templates/docker_image.sh
var dockerImage string
//go:embed templates/Dockerfile
var dockerFile string
//go:embed templates/.air.toml
var airToml string
//go:embed templates/.air.win.toml
var airWinToml string
var qs = []*survey.Question{
{
Name: "tpl",
Prompt: &survey.Select{
Message: "Choose a template:",
Options: []string{".air.toml", ".gowebbuild.yaml", "docker_image.sh", "Dockerfile"},
Default: "docker_image.sh",
},
},
}
func tplAction(ctx *cli.Context) error {
cfgPath, err := filepath.Abs(ctx.String("c"))
if err != nil {
return err
}
os.Chdir(filepath.Dir(cfgPath))
answers := struct {
Template string `survey:"tpl"`
}{}
err = survey.Ask(qs, &answers)
if err != nil {
return err
}
var tpl string
var fileName string
switch answers.Template {
case ".gowebbuild.yaml":
tpl = sampleConfig
fileName = ".gowebbuild.yaml"
case "docker_image.sh":
tpl = dockerImage
fileName = "docker_image.sh"
case "Dockerfile":
tpl = dockerFile
fileName = "Dockerfile"
case ".air.toml":
tpl = airToml
if runtime.GOOS == "windows" {
tpl = airWinToml
}
fileName = ".air.toml"
default:
golog.Fatal("Invalid template")
}
if fsutils.IsFile(fileName) {
fmt.Printf("File \"%s\" already exists.\n", fileName)
return nil
}
outFile, err := os.Create(fileName)
if err != nil {
return err
}
defer outFile.Close()
context := map[string]string{
"ProjectFolderName": filepath.Base(filepath.Dir(cfgPath)),
}
if moduleName, err := getGoModuleName(filepath.Dir(cfgPath)); err == nil {
context["GoModuleName"] = moduleName
}
err = template.Must(template.New("tpl").Parse(tpl)).Execute(outFile, context)
if err != nil {
return err
}
fmt.Printf("Created \"%s\" in project root.\n", fileName)
return nil
}

47
templates/.air.toml Normal file
View File

@ -0,0 +1,47 @@
# Config file for [Air](https://github.com/cosmtrek/air) in TOML format
# Working directory
# . or absolute path, please note that the directories following must be under root.
root = "."
tmp_dir = "tmp"
[build]
# Just plain old shell command. You could use `make` as well.
cmd = "go build -o ./tmp/main ./main.go"
# Binary file yields from `cmd`.
bin = "tmp/main"
# Customize binary.
full_bin = "./tmp/main run sample.json"
# Watch these filename extensions.
include_ext = ["go", "tpl", "tmpl", "html"]
# Ignore these filename extensions or directories.
exclude_dir = ["frontend-dist", "_mongo", "_db", "dist", "frontend", "web"]
# Watch these directories if you specified.
include_dir = []
# Exclude files.
exclude_file = []
# This log file places in your tmp_dir.
log = "air.log"
# It's not necessary to trigger build each time file changes if it's too frequent.
delay = 1000 # ms
# Stop running old binary when build errors occur.
stop_on_error = true
# Send Interrupt signal before killing process (windows does not support this feature)
send_interrupt = false
# Delay after sending Interrupt signal
kill_delay = 500 # ms
[log]
# Show log time
time = false
[color]
# Customize each part's color. If no color found, use the raw app log.
main = "magenta"
watcher = "cyan"
build = "yellow"
runner = "green"
[misc]
# Delete tmp directory on exit
clean_on_exit = true

45
templates/.air.win.toml Executable file
View File

@ -0,0 +1,45 @@
# Config file for [Air](https://github.com/cosmtrek/air) in TOML format
# Working directory
# . or absolute path, please note that the directories following must be under root.
root = "."
tmp_dir = "tmp"
[build]
# Just plain old shell command. You could use `make` as well.
cmd = "go build -o ./tmp/main.exe ./main.go"
# Binary file yields from `cmd`.
bin = "tmp/main.exe"
# Watch these filename extensions.
include_ext = ["go", "tpl", "tmpl", "html"]
# Ignore these filename extensions or directories.
exclude_dir = ["frontend-dist", "_mongo", "_db", "dist", "frontend", "web"]
# Watch these directories if you specified.
include_dir = ["backend"]
# Exclude files.
exclude_file = []
# This log file places in your tmp_dir.
log = "air.log"
# It's not necessary to trigger build each time file changes if it's too frequent.
delay = 1000 # ms
# Stop running old binary when build errors occur.
stop_on_error = true
# Send Interrupt signal before killing process (windows does not support this feature)
send_interrupt = false
# Delay after sending Interrupt signal
kill_delay = 500 # ms
[log]
# Show log time
time = false
[color]
# Customize each part's color. If no color found, use the raw app log.
main = "magenta"
watcher = "cyan"
build = "yellow"
runner = "green"
[misc]
# Delete tmp directory on exit
clean_on_exit = true

22
templates/Dockerfile Normal file
View File

@ -0,0 +1,22 @@
FROM alpine
RUN apk update --no-cache && apk add --no-cache ca-certificates tzdata
# Set timezone if necessary
#ENV TZ UTC
ENV USER=gouser
ENV UID=10001
RUN adduser \
--disabled-password \
--gecos "" \
--shell "/sbin/nologin" \
--no-create-home \
--uid "${UID}" \
"${USER}"
ADD {{.ProjectFolderName}} /app/{{.ProjectFolderName}}
WORKDIR /app
USER gouser:gouser
ENTRYPOINT ["./{{.ProjectFolderName}}"]

19
templates/docker_image.sh Executable file
View File

@ -0,0 +1,19 @@
#!/bin/bash
mkdir -p _build
cd _build
mkdir -p docker_out
rm -rf sources
git clone $(git remote get-url origin) sources
cd sources
git fetch --tags
ver=$(git describe --tags `git rev-list --tags --max-count=1`)
git checkout $ver
CGO_ENABLED=0 go build -ldflags="-s -w" -o ../{{.ProjectFolderName}} .
# A second run is needed to build the final image.
cd ..
docker build -f sources/Dockerfile --no-cache -t {{.GoModuleName}}:${ver} -t {{.GoModuleName}}:latest .
docker push {{.GoModuleName}}:${ver}
docker push {{.GoModuleName}}:latest
rm -rf sources {{.ProjectFolderName}}

View File

@ -0,0 +1,34 @@
- esbuild:
entryPoints:
- frontend/the-app.js
outdir: ./frontend-dist
sourcemap: 1
format: 3
splitting: true
platform: 0
bundle: true
write: true
logLevel: 3
purgeBeforeBuild: false
watch:
paths:
- ./frontend/src
exclude: []
# serve: # Uncomment and set a path to enable
# path: ""
# port: 8080
copy:
- src: ./frontend/index.html
dest: ./frontend-dist
# download:
# - url: https://example.com/some-file-or-asset.js
# dest: ./frontend/src/vendor/some-file-or-asset.js
# replace:
# - pattern: "*.go|*.js|*.html"
# search: "Something"
# replace: "This"
# link:
# from: ../../web/tp-elements
# to: ./frontend
# productionBuildOptions:
# cmdPostBuild: ""

View File

@ -2,11 +2,8 @@ package main
import (
"fmt"
"net/http"
"os"
"os/signal"
"path/filepath"
"syscall"
"time"
"github.com/jaschaephraim/lrserver"
@ -24,9 +21,6 @@ func watchAction(ctx *cli.Context) error {
os.Chdir(filepath.Dir(cfgPath))
optsSetups := readCfg(cfgPath)
c := make(chan os.Signal, 1)
signal.Notify(c, os.Interrupt, syscall.SIGTERM)
for i := range optsSetups {
opts := optsSetups[i]
@ -39,10 +33,16 @@ func watchAction(ctx *cli.Context) error {
w.Ignore(opts.Watch.Exclude...)
}
if err := w.AddRecursive(opts.Watch.Path); err != nil {
if opts.ESBuild.Outdir != "" {
w.Ignore(opts.ESBuild.Outdir)
}
for _, p := range opts.Watch.Paths {
if err := w.AddRecursive(p); err != nil {
fmt.Println(err.Error())
os.Exit(1)
}
}
go func() {
for {
@ -61,8 +61,9 @@ func watchAction(ctx *cli.Context) error {
}
}()
fmt.Printf("Watching %d elements in %s\n", len(w.WatchedFiles()), opts.Watch.Path)
fmt.Printf("Watching %d elements in %s\n", len(w.WatchedFiles()), opts.Watch.Paths)
purge(opts)
cp(opts)
build(opts)
replace(opts)
@ -74,15 +75,12 @@ func watchAction(ctx *cli.Context) error {
if opts.Serve.Path != "" {
go func() {
port := 8888
port := 8080
if opts.Serve.Port != 0 {
port = opts.Serve.Port
}
http.Handle("/", http.FileServer(http.Dir(opts.Serve.Path)))
fmt.Printf("Serving contents of %s at :%d\n", opts.Serve.Path, port)
err := http.ListenAndServe(fmt.Sprintf(":%d", port), nil)
err := Serve(opts.Serve.Path, uint(port))
if err != nil {
fmt.Printf("%+v\n", err.Error())
@ -105,7 +103,7 @@ func watchAction(ctx *cli.Context) error {
}
go func() {
fmt.Println("Starting live reload server")
fmt.Println("Starting live reload server.")
port := ctx.Uint("lr-port")
lr := lrserver.New(lrserver.DefaultName, uint16(port))
@ -123,8 +121,9 @@ func watchAction(ctx *cli.Context) error {
}
}()
<-c
fmt.Println("\nStopped watching")
runProxy(ctx.Context, filepath.Dir(cfgPath), optsSetups)
<-ctx.Done()
fmt.Println("Stopped watching.")
return nil
}