diff --git a/build.go b/build.go new file mode 100644 index 0000000..b44d59c --- /dev/null +++ b/build.go @@ -0,0 +1,31 @@ +package main + +import ( + "os" + "path/filepath" + + "github.com/evanw/esbuild/pkg/api" + "github.com/urfave/cli/v2" +) + +func buildAction(ctx *cli.Context) error { + cfgPath := ctx.String("c") + os.Chdir(filepath.Dir(cfgPath)) + opts := readCfg(cfgPath) + + for _, o := range opts { + cp(o) + + if ctx.Bool("p") { + o.ESBuild.MinifyIdentifiers = true + o.ESBuild.MinifySyntax = true + o.ESBuild.MinifyWhitespace = true + o.ESBuild.Sourcemap = api.SourceMapNone + } + + api.Build(o.ESBuild) + replace(o) + } + + return nil +} diff --git a/go.mod b/go.mod index 850191d..d811731 100644 --- a/go.mod +++ b/go.mod @@ -12,10 +12,14 @@ require ( ) require ( + github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect github.com/gorilla/websocket v1.5.0 // indirect + github.com/russross/blackfriday/v2 v2.1.0 // indirect github.com/smartystreets/goconvey v1.7.2 // 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 ) diff --git a/go.sum b/go.sum index 837310f..6186fa0 100644 --- a/go.sum +++ b/go.sum @@ -1,3 +1,5 @@ +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= @@ -19,6 +21,8 @@ github.com/otiai10/mint v1.3.3 h1:7JgpsBaN0uMkyju4tbYHu0mnM55hNKVYLsXmwr15NQI= github.com/otiai10/mint v1.3.3/go.mod h1:/yxELlJQ0ufhjUwhshSj+wFjZ78CnZ48/1wtmBH1OTc= github.com/radovskyb/watcher v1.0.7 h1:AYePLih6dpmS32vlHfhCeli8127LzkIgwJGcwwe8tUE= github.com/radovskyb/watcher v1.0.7/go.mod h1:78okwvY5wPdzcb1UYnip1pvrZNIVEIh/Cm+ZuvsUYIg= +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= @@ -29,6 +33,10 @@ 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= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= diff --git a/linker.go b/linker.go index 128ddee..03e74e2 100644 --- a/linker.go +++ b/linker.go @@ -32,10 +32,15 @@ func link(from, to string) chan struct{} { name := gjson.Get(content, "name").String() if deps[name] { - packages[name] = filepath.Dir(packageFiles[i]) + pp, err := filepath.Abs(filepath.Dir(packageFiles[i])) + if err == nil { + packages[name] = pp + } } } + fmt.Printf("Found %d npm packages to monitor for changes.\n", len(packages)) + go func() { w := watcher.New() w.SetMaxEvents(1) diff --git a/main.go b/main.go index 0c2a925..bf7d3c0 100644 --- a/main.go +++ b/main.go @@ -5,19 +5,13 @@ import ( "errors" "fmt" "io/ioutil" - "net/http" "os" - "os/signal" "path/filepath" "strings" - "syscall" - "time" "github.com/evanw/esbuild/pkg/api" - "github.com/goyek/goyek" - "github.com/jaschaephraim/lrserver" "github.com/otiai10/copy" - "github.com/radovskyb/watcher" + "github.com/urfave/cli/v2" ) var triggerReload = make(chan struct{}) @@ -73,160 +67,76 @@ func readCfg(cfgPath string) []options { } func main() { - flow := &goyek.Flow{} + cfgParam := &cli.StringFlag{ + Name: "c", + Value: "./.gowebbuild.json", + Usage: "path to config file config file.", + } - cfgPathParam := flow.RegisterStringParam(goyek.StringParam{ - Name: "c", - Usage: "Path to config file config file.", - Default: "./.gowebbuild.json", - }) + app := &cli.App{ + Commands: []*cli.Command{ + { + Name: "build", + Usage: "build web sources one time and exit", + Flags: []cli.Flag{ + cfgParam, + &cli.BoolFlag{ + Name: "p", + Value: false, + Usage: "use production ready build settings", + }, + }, + Action: buildAction, + }, - prodParam := flow.RegisterBoolParam(goyek.BoolParam{ - Name: "p", - Usage: "Use production ready build settings", - Default: false, - }) + { + Name: "watch", + Usage: "watch for changes and trigger the build", + Flags: []cli.Flag{ + cfgParam, + }, + Action: watchAction, + }, - buildOnly := goyek.Task{ - Name: "build", - Usage: "", - Params: goyek.Params{cfgPathParam, prodParam}, - Action: func(tf *goyek.TF) { - cfgPath := cfgPathParam.Get(tf) - os.Chdir(filepath.Dir(cfgPath)) - opts := readCfg(cfgPath) + { + Name: "replace", + ArgsUsage: "[files] [search] [replace]", + Usage: "replace text in files", + Action: func(ctx *cli.Context) error { + files := ctx.Args().Get(0) + searchStr := ctx.Args().Get(1) + replaceStr := ctx.Args().Get(2) - for _, o := range opts { - cp(o) + if files == "" { + return fmt.Errorf("invalid file pattern") + } - if prodParam.Get(tf) { - o.ESBuild.MinifyIdentifiers = true - o.ESBuild.MinifySyntax = true - o.ESBuild.MinifyWhitespace = true - o.ESBuild.Sourcemap = api.SourceMapNone - } + if searchStr == "" { + return fmt.Errorf("invalid search string") + } - api.Build(o.ESBuild) - replace(o) - } + replace(options{ + Replace: []struct { + Pattern string + Search string + Replace string + }{ + { + Pattern: files, + Search: searchStr, + Replace: replaceStr, + }, + }, + }) + return nil + }, + }, }, } - watch := goyek.Task{ - Name: "watch", - Usage: "", - Params: goyek.Params{cfgPathParam}, - Action: func(tf *goyek.TF) { - cfgPath := cfgPathParam.Get(tf) - 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] - - go func(opts options) { - w := watcher.New() - w.SetMaxEvents(1) - w.FilterOps(watcher.Write, watcher.Rename, watcher.Move, watcher.Create, watcher.Remove) - - if len(opts.Watch.Exclude) > 0 { - w.Ignore(opts.Watch.Exclude...) - } - - if err := w.AddRecursive(opts.Watch.Path); 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()) - cp(opts) - build(opts) - replace(opts) - case err := <-w.Error: - fmt.Println(err.Error()) - case <-w.Closed: - return - } - } - }() - - fmt.Printf("Watching %d elements in %s\n", len(w.WatchedFiles()), opts.Watch.Path) - - cp(opts) - build(opts) - replace(opts) - - if err := w.Start(time.Millisecond * 100); err != nil { - fmt.Println(err.Error()) - } - }(opts) - - if opts.Serve.Path != "" { - go func() { - port := 8888 - 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) - - if err != nil { - fmt.Printf("%+v\n", err.Error()) - os.Exit(1) - } - }() - } - - if opts.Link.From != "" { - reqBuildCh := link(opts.Link.From, opts.Link.To) - - go func() { - for range reqBuildCh { - cp(opts) - build(opts) - replace(opts) - } - }() - } - } - - go func() { - fmt.Println("Starting live reload server") - lr := lrserver.New(lrserver.DefaultName, lrserver.DefaultPort) - - go func() { - for { - <-triggerReload - lr.Reload("") - } - }() - - lr.SetStatusLog(nil) - err := lr.ListenAndServe() - if err != nil { - panic(err) - } - }() - - <-c - fmt.Println("\nExit") - os.Exit(0) - }, + if err := app.Run(os.Args); err != nil { + fmt.Println(err) } - - flow.DefaultTask = flow.Register(watch) - flow.Register(buildOnly) - flow.Main() } func cp(opts options) { diff --git a/watch.go b/watch.go new file mode 100644 index 0000000..2e2320a --- /dev/null +++ b/watch.go @@ -0,0 +1,123 @@ +package main + +import ( + "fmt" + "net/http" + "os" + "os/signal" + "path/filepath" + "syscall" + "time" + + "github.com/jaschaephraim/lrserver" + "github.com/radovskyb/watcher" + "github.com/urfave/cli/v2" +) + +func watchAction(ctx *cli.Context) error { + cfgPath := ctx.String("c") + 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] + + go func(opts options) { + w := watcher.New() + w.SetMaxEvents(1) + w.FilterOps(watcher.Write, watcher.Rename, watcher.Move, watcher.Create, watcher.Remove) + + if len(opts.Watch.Exclude) > 0 { + w.Ignore(opts.Watch.Exclude...) + } + + if err := w.AddRecursive(opts.Watch.Path); 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()) + cp(opts) + build(opts) + replace(opts) + case err := <-w.Error: + fmt.Println(err.Error()) + case <-w.Closed: + return + } + } + }() + + fmt.Printf("Watching %d elements in %s\n", len(w.WatchedFiles()), opts.Watch.Path) + + cp(opts) + build(opts) + replace(opts) + + if err := w.Start(time.Millisecond * 100); err != nil { + fmt.Println(err.Error()) + } + }(opts) + + if opts.Serve.Path != "" { + go func() { + port := 8888 + 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) + + if err != nil { + fmt.Printf("%+v\n", err.Error()) + os.Exit(1) + } + }() + } + + if opts.Link.From != "" { + reqBuildCh := link(opts.Link.From, opts.Link.To) + + go func() { + for range reqBuildCh { + cp(opts) + build(opts) + replace(opts) + } + }() + } + } + + go func() { + fmt.Println("Starting live reload server") + lr := lrserver.New(lrserver.DefaultName, lrserver.DefaultPort) + + go func() { + for { + <-triggerReload + lr.Reload("") + } + }() + + lr.SetStatusLog(nil) + err := lr.ListenAndServe() + if err != nil { + panic(err) + } + }() + + <-c + fmt.Println("\nStopped watching") + + return nil +}