detached c14ab46

Add basic bubbletea example

commit
c14ab46837ee5c2b01e94331d9cbe3fec254ad68
author
bnema · · 2023-10-21 14:34 UTC
parent
15f17ac
signature
unsigned
11 files · +597 -431
M cmd/cli/main.go +4 -2
@@ -1,8 +1,10 @@
11 package main
22
3-import "fmt"
3+import (
4+ "github.com/bnema/gordon/internal/cli"
5+)
46
57 func main() {
6- fmt.Println("Hello World")
8+ cli.InitCli()
79
810 }
@@ -1,8 +1,10 @@
1 package main1 package main
2 2
3-import "fmt"3+import (
4+ "github.com/bnema/gordon/internal/cli"
5+)
4 6
5 func main() {7 func main() {
6- fmt.Println("Hello World")8+ cli.InitCli()
7 9
8 }10 }
M go.mod +24 -8
@@ -4,6 +4,7 @@ go 1.21.3
44
55 require (
66 github.com/PuerkitoBio/goquery v1.8.1
7+ github.com/charmbracelet/bubbletea v0.24.2
78 github.com/docker/docker v24.0.6+incompatible
89 github.com/docker/go-connections v0.4.0
910 github.com/gorilla/sessions v1.2.1
@@ -15,6 +16,23 @@ require (
1516 gopkg.in/yaml.v3 v3.0.1
1617 )
1718
19+require (
20+ github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
21+ github.com/containerd/console v1.0.4-0.20230313162750-1ae8d489ac81 // indirect
22+ github.com/kr/text v0.2.0 // indirect
23+ github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
24+ github.com/mattn/go-localereader v0.0.1 // indirect
25+ github.com/mattn/go-runewidth v0.0.14 // indirect
26+ github.com/muesli/ansi v0.0.0-20211018074035-2e021307bc4b // indirect
27+ github.com/muesli/cancelreader v0.2.2 // indirect
28+ github.com/muesli/reflow v0.3.0 // indirect
29+ github.com/muesli/termenv v0.15.1 // indirect
30+ github.com/rivo/uniseg v0.2.0 // indirect
31+ github.com/stretchr/testify v1.8.2 // indirect
32+ golang.org/x/sync v0.4.0 // indirect
33+ golang.org/x/term v0.13.0 // indirect
34+)
35+
1836 require (
1937 github.com/Microsoft/go-winio v0.6.1 // indirect
2038 github.com/andybalholm/cascadia v1.3.1 // indirect
@@ -27,7 +45,6 @@ require (
2745 github.com/gorilla/context v1.1.1 // indirect
2846 github.com/gorilla/css v1.0.0 // indirect
2947 github.com/gorilla/securecookie v1.1.1 // indirect
30- github.com/kr/text v0.1.0 // indirect
3148 github.com/labstack/gommon v0.4.0 // indirect
3249 github.com/mattn/go-colorable v0.1.13 // indirect
3350 github.com/mattn/go-isatty v0.0.19 // indirect
@@ -37,16 +54,15 @@ require (
3754 github.com/opencontainers/image-spec v1.1.0-rc2.0.20221005185240-3a7f492d3f1b // indirect
3855 github.com/pkg/errors v0.9.1 // indirect
3956 github.com/rogpeppe/go-internal v1.11.0 // indirect
40- github.com/stretchr/testify v1.8.2 // indirect
4157 github.com/valyala/bytebufferpool v1.0.0 // indirect
4258 github.com/valyala/fasttemplate v1.2.2 // indirect
43- golang.org/x/crypto v0.11.0 // indirect
44- golang.org/x/mod v0.9.0 // indirect
45- golang.org/x/net v0.12.0 // indirect
46- golang.org/x/sys v0.10.0 // indirect
47- golang.org/x/text v0.11.0 // indirect
59+ golang.org/x/crypto v0.14.0 // indirect
60+ golang.org/x/mod v0.13.0 // indirect
61+ golang.org/x/net v0.17.0 // indirect
62+ golang.org/x/sys v0.13.0 // indirect
63+ golang.org/x/text v0.13.0 // indirect
4864 golang.org/x/time v0.3.0 // indirect
49- golang.org/x/tools v0.7.0 // indirect
65+ golang.org/x/tools v0.14.0 // indirect
5066 gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 // indirect
5167 gotest.tools/v3 v3.5.0 // indirect
5268 )
@@ -4,6 +4,7 @@ go 1.21.3
4 4
5 require (5 require (
6 github.com/PuerkitoBio/goquery v1.8.16 github.com/PuerkitoBio/goquery v1.8.1
7+ github.com/charmbracelet/bubbletea v0.24.2
7 github.com/docker/docker v24.0.6+incompatible8 github.com/docker/docker v24.0.6+incompatible
8 github.com/docker/go-connections v0.4.09 github.com/docker/go-connections v0.4.0
9 github.com/gorilla/sessions v1.2.110 github.com/gorilla/sessions v1.2.1
@@ -15,6 +16,23 @@ require (
15 gopkg.in/yaml.v3 v3.0.116 gopkg.in/yaml.v3 v3.0.1
16 )17 )
17 18
19+require (
20+ github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
21+ github.com/containerd/console v1.0.4-0.20230313162750-1ae8d489ac81 // indirect
22+ github.com/kr/text v0.2.0 // indirect
23+ github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
24+ github.com/mattn/go-localereader v0.0.1 // indirect
25+ github.com/mattn/go-runewidth v0.0.14 // indirect
26+ github.com/muesli/ansi v0.0.0-20211018074035-2e021307bc4b // indirect
27+ github.com/muesli/cancelreader v0.2.2 // indirect
28+ github.com/muesli/reflow v0.3.0 // indirect
29+ github.com/muesli/termenv v0.15.1 // indirect
30+ github.com/rivo/uniseg v0.2.0 // indirect
31+ github.com/stretchr/testify v1.8.2 // indirect
32+ golang.org/x/sync v0.4.0 // indirect
33+ golang.org/x/term v0.13.0 // indirect
34+)
35+
18 require (36 require (
19 github.com/Microsoft/go-winio v0.6.1 // indirect37 github.com/Microsoft/go-winio v0.6.1 // indirect
20 github.com/andybalholm/cascadia v1.3.1 // indirect38 github.com/andybalholm/cascadia v1.3.1 // indirect
@@ -27,7 +45,6 @@ require (
27 github.com/gorilla/context v1.1.1 // indirect45 github.com/gorilla/context v1.1.1 // indirect
28 github.com/gorilla/css v1.0.0 // indirect46 github.com/gorilla/css v1.0.0 // indirect
29 github.com/gorilla/securecookie v1.1.1 // indirect47 github.com/gorilla/securecookie v1.1.1 // indirect
30- github.com/kr/text v0.1.0 // indirect
31 github.com/labstack/gommon v0.4.0 // indirect48 github.com/labstack/gommon v0.4.0 // indirect
32 github.com/mattn/go-colorable v0.1.13 // indirect49 github.com/mattn/go-colorable v0.1.13 // indirect
33 github.com/mattn/go-isatty v0.0.19 // indirect50 github.com/mattn/go-isatty v0.0.19 // indirect
@@ -37,16 +54,15 @@ require (
37 github.com/opencontainers/image-spec v1.1.0-rc2.0.20221005185240-3a7f492d3f1b // indirect54 github.com/opencontainers/image-spec v1.1.0-rc2.0.20221005185240-3a7f492d3f1b // indirect
38 github.com/pkg/errors v0.9.1 // indirect55 github.com/pkg/errors v0.9.1 // indirect
39 github.com/rogpeppe/go-internal v1.11.0 // indirect56 github.com/rogpeppe/go-internal v1.11.0 // indirect
40- github.com/stretchr/testify v1.8.2 // indirect
41 github.com/valyala/bytebufferpool v1.0.0 // indirect57 github.com/valyala/bytebufferpool v1.0.0 // indirect
42 github.com/valyala/fasttemplate v1.2.2 // indirect58 github.com/valyala/fasttemplate v1.2.2 // indirect
43- golang.org/x/crypto v0.11.0 // indirect59+ golang.org/x/crypto v0.14.0 // indirect
44- golang.org/x/mod v0.9.0 // indirect60+ golang.org/x/mod v0.13.0 // indirect
45- golang.org/x/net v0.12.0 // indirect61+ golang.org/x/net v0.17.0 // indirect
46- golang.org/x/sys v0.10.0 // indirect62+ golang.org/x/sys v0.13.0 // indirect
47- golang.org/x/text v0.11.0 // indirect63+ golang.org/x/text v0.13.0 // indirect
48 golang.org/x/time v0.3.0 // indirect64 golang.org/x/time v0.3.0 // indirect
49- golang.org/x/tools v0.7.0 // indirect65+ golang.org/x/tools v0.14.0 // indirect
50 gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 // indirect66 gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 // indirect
51 gotest.tools/v3 v3.5.0 // indirect67 gotest.tools/v3 v3.5.0 // indirect
52 )68 )
M go.sum +44 -17
@@ -6,8 +6,15 @@ github.com/PuerkitoBio/goquery v1.8.1 h1:uQxhNlArOIdbrH1tr0UXwdVFgDcZDrZVdcpygAc
66 github.com/PuerkitoBio/goquery v1.8.1/go.mod h1:Q8ICL1kNUJ2sXGoAhPGUdYDJvgQgHzJsnnd3H7Ho5jQ=
77 github.com/andybalholm/cascadia v1.3.1 h1:nhxRkql1kdYCc8Snf7D5/D3spOX+dBgjA6u8x004T2c=
88 github.com/andybalholm/cascadia v1.3.1/go.mod h1:R4bJ1UQfqADjvDa4P6HZHLh/3OxWWEqc0Sk8XGwHqvA=
9+github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k=
10+github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8=
911 github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk=
1012 github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4=
13+github.com/charmbracelet/bubbletea v0.24.2 h1:uaQIKx9Ai6Gdh5zpTbGiWpytMU+CfsPp06RaW2cx/SY=
14+github.com/charmbracelet/bubbletea v0.24.2/go.mod h1:XdrNrV4J8GiyshTtx3DNuYkR1FDaJmO3l2nejekbsgg=
15+github.com/containerd/console v1.0.4-0.20230313162750-1ae8d489ac81 h1:q2hJAaP1k2wIvVRd/hEHD7lacgqrCPS+k8g1MndzfWY=
16+github.com/containerd/console v1.0.4-0.20230313162750-1ae8d489ac81/go.mod h1:YynlIjWYF8myEu6sdkwKIvGQq+cOckRm6So2avqoYAk=
17+github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
1118 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
1219 github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
1320 github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
@@ -41,15 +48,16 @@ github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI
4148 github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
4249 github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
4350 github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
44-github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
45-github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
46-github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
51+github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
52+github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
4753 github.com/labstack/echo-contrib v0.15.0 h1:9K+oRU265y4Mu9zpRDv3X+DGTqUALY6oRHCSZZKCRVU=
4854 github.com/labstack/echo-contrib v0.15.0/go.mod h1:lei+qt5CLB4oa7VHTE0yEfQSEB9XTJI1LUqko9UWvo4=
4955 github.com/labstack/echo/v4 v4.11.1 h1:dEpLU2FLg4UVmvCGPuk/APjlH6GDpbEPti61srUUUs4=
5056 github.com/labstack/echo/v4 v4.11.1/go.mod h1:YuYRTSM3CHs2ybfrL8Px48bO6BAnYIN4l8wSTMP6BDQ=
5157 github.com/labstack/gommon v0.4.0 h1:y7cvthEAEbU0yHOf4axH8ZG2NH8knB9iNSoTO8dyIk8=
5258 github.com/labstack/gommon v0.4.0/go.mod h1:uW6kP17uPlLJsD3ijUYn3/M5bAxtlZhMI6m3MFxTMTM=
59+github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY=
60+github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
5361 github.com/mattn/go-colorable v0.1.11/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=
5462 github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
5563 github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
@@ -57,6 +65,11 @@ github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27k
5765 github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
5866 github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA=
5967 github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
68+github.com/mattn/go-localereader v0.0.1 h1:ygSAOl7ZXTx4RdPYinUpg6W99U8jWvWi9Ye2JC/oIi4=
69+github.com/mattn/go-localereader v0.0.1/go.mod h1:8fBrzywKY7BI3czFoHkuzRoWE9C+EiG4R1k4Cjx5p88=
70+github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk=
71+github.com/mattn/go-runewidth v0.0.14 h1:+xnbZSEeDbOIg5/mE6JF0w6n9duR1l3/WmbinWVwUuU=
72+github.com/mattn/go-runewidth v0.0.14/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
6073 github.com/mattn/go-sqlite3 v1.14.17 h1:mCRHCLDUBXgpKAqIKsaAaAsrAlbkeomtRFKXh2L6YIM=
6174 github.com/mattn/go-sqlite3 v1.14.17/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
6275 github.com/microcosm-cc/bluemonday v1.0.25 h1:4NEwSfiJ+Wva0VxN5B8OwMicaJvD8r9tlJWm9rtloEg=
@@ -65,6 +78,14 @@ github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0=
6578 github.com/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y=
6679 github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A=
6780 github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc=
81+github.com/muesli/ansi v0.0.0-20211018074035-2e021307bc4b h1:1XF24mVaiu7u+CFywTdcDo2ie1pzzhwjt6RHqzpMU34=
82+github.com/muesli/ansi v0.0.0-20211018074035-2e021307bc4b/go.mod h1:fQuZ0gauxyBcmsdE3ZT4NasjaRdxmbCS0jRHsrWu3Ho=
83+github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA=
84+github.com/muesli/cancelreader v0.2.2/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo=
85+github.com/muesli/reflow v0.3.0 h1:IFsN6K9NfGtjeggFP+68I4chLZV2yIKsXJFNZ+eWh6s=
86+github.com/muesli/reflow v0.3.0/go.mod h1:pbwTDkVPibjO2kyvBQRBxTWEEGDGq0FlB1BIKtnHY/8=
87+github.com/muesli/termenv v0.15.1 h1:UzuTb/+hhlBugQz28rpzey4ZuKcZ03MeKsoG7IJZIxs=
88+github.com/muesli/termenv v0.15.1/go.mod h1:HeAQPTzpfs016yGtA4g00CsdYnVLJvxsS4ANqrZs2sQ=
6889 github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
6990 github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
7091 github.com/opencontainers/image-spec v1.1.0-rc2.0.20221005185240-3a7f492d3f1b h1:YWuSjZCQAPM8UUBLkYUk1e+rZcvWHJmFb6i6rM44Xs8=
@@ -73,6 +94,9 @@ github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
7394 github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
7495 github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
7596 github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
97+github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
98+github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
99+github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
76100 github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M=
77101 github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA=
78102 github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
@@ -95,13 +119,13 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk
95119 golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
96120 golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
97121 golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
98-golang.org/x/crypto v0.11.0 h1:6Ewdq3tDic1mg5xRO4milcWCfMVQhI4NkqWWvqejpuA=
99-golang.org/x/crypto v0.11.0/go.mod h1:xgJhtzW8F9jGdVFWZESrid1U1bjeNy4zgy5cRr/CIio=
122+golang.org/x/crypto v0.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc=
123+golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4=
100124 golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
101125 golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
102126 golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
103-golang.org/x/mod v0.9.0 h1:KENHtAZL2y3NLMYZeHY9DW8HW8V+kQyJsY/V9JlKvCs=
104-golang.org/x/mod v0.9.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
127+golang.org/x/mod v0.13.0 h1:I/DsJXRlw/8l/0c24sM9yb0T4z9liZTduXvdAWYiysY=
128+golang.org/x/mod v0.13.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
105129 golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
106130 golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
107131 golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
@@ -110,14 +134,14 @@ golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v
110134 golang.org/x/net v0.0.0-20210916014120-12bc252f5db8/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
111135 golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
112136 golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
113-golang.org/x/net v0.12.0 h1:cfawfvKITfUsFCeJIHJrbSxpeu/E81khclypR0GVT50=
114-golang.org/x/net v0.12.0/go.mod h1:zEVYFnQC7m/vmpQFELhcD1EWkZlX69l4oqgmer6hfKA=
137+golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM=
138+golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
115139 golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
116140 golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
117141 golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
118142 golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
119-golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o=
120-golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
143+golang.org/x/sync v0.4.0 h1:zxkM55ReGkDlKSM+Fu41A+zmbZuaPVbGMzvvdUPznYQ=
144+golang.org/x/sync v0.4.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
121145 golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
122146 golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
123147 golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -130,20 +154,23 @@ golang.org/x/sys v0.0.0-20211103235746-7861aae1554b/go.mod h1:oPkhp1MJrh7nUepCBc
130154 golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
131155 golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
132156 golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
157+golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
133158 golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
134159 golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
135-golang.org/x/sys v0.10.0 h1:SqMFp9UcQJZa+pmYuAKjd9xq1f0j5rLcDIk0mj4qAsA=
136-golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
160+golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE=
161+golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
137162 golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
138163 golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
139164 golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
165+golang.org/x/term v0.13.0 h1:bb+I9cTfFazGW51MZqBVmZy7+JEJMouUHTUSKVQLBek=
166+golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U=
140167 golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
141168 golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
142169 golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
143170 golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
144171 golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
145-golang.org/x/text v0.11.0 h1:LAntKIrcmeSKERyiOh0XMV39LXS8IE9UL2yP7+f5ij4=
146-golang.org/x/text v0.11.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
172+golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k=
173+golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
147174 golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4=
148175 golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
149176 golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
@@ -151,8 +178,8 @@ golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtn
151178 golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
152179 golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
153180 golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
154-golang.org/x/tools v0.7.0 h1:W4OVu8VVOaIO0yzWMNdepAulS7YfoS3Zabrm8DOXXU4=
155-golang.org/x/tools v0.7.0/go.mod h1:4pg6aUX35JBAogB10C9AtvVL+qowtN4pT3CGSQex14s=
181+golang.org/x/tools v0.14.0 h1:jvNa2pY0M4r62jkRQ6RwEZZyPcymeL9XZMLBbV7U2nc=
182+golang.org/x/tools v0.14.0/go.mod h1:uYBEerGOWcJyEORxN+Ek8+TT266gXkNlHdJBwexUsBg=
156183 golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
157184 golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
158185 golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
@@ -6,8 +6,15 @@ github.com/PuerkitoBio/goquery v1.8.1 h1:uQxhNlArOIdbrH1tr0UXwdVFgDcZDrZVdcpygAc
6 github.com/PuerkitoBio/goquery v1.8.1/go.mod h1:Q8ICL1kNUJ2sXGoAhPGUdYDJvgQgHzJsnnd3H7Ho5jQ=6 github.com/PuerkitoBio/goquery v1.8.1/go.mod h1:Q8ICL1kNUJ2sXGoAhPGUdYDJvgQgHzJsnnd3H7Ho5jQ=
7 github.com/andybalholm/cascadia v1.3.1 h1:nhxRkql1kdYCc8Snf7D5/D3spOX+dBgjA6u8x004T2c=7 github.com/andybalholm/cascadia v1.3.1 h1:nhxRkql1kdYCc8Snf7D5/D3spOX+dBgjA6u8x004T2c=
8 github.com/andybalholm/cascadia v1.3.1/go.mod h1:R4bJ1UQfqADjvDa4P6HZHLh/3OxWWEqc0Sk8XGwHqvA=8 github.com/andybalholm/cascadia v1.3.1/go.mod h1:R4bJ1UQfqADjvDa4P6HZHLh/3OxWWEqc0Sk8XGwHqvA=
9+github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k=
10+github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8=
9 github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk=11 github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk=
10 github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4=12 github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4=
13+github.com/charmbracelet/bubbletea v0.24.2 h1:uaQIKx9Ai6Gdh5zpTbGiWpytMU+CfsPp06RaW2cx/SY=
14+github.com/charmbracelet/bubbletea v0.24.2/go.mod h1:XdrNrV4J8GiyshTtx3DNuYkR1FDaJmO3l2nejekbsgg=
15+github.com/containerd/console v1.0.4-0.20230313162750-1ae8d489ac81 h1:q2hJAaP1k2wIvVRd/hEHD7lacgqrCPS+k8g1MndzfWY=
16+github.com/containerd/console v1.0.4-0.20230313162750-1ae8d489ac81/go.mod h1:YynlIjWYF8myEu6sdkwKIvGQq+cOckRm6So2avqoYAk=
17+github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
11 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=18 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
12 github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=19 github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
13 github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=20 github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
@@ -41,15 +48,16 @@ github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI
41 github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=48 github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
42 github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=49 github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
43 github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=50 github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
44-github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=51+github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
45-github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=52+github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
46-github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
47 github.com/labstack/echo-contrib v0.15.0 h1:9K+oRU265y4Mu9zpRDv3X+DGTqUALY6oRHCSZZKCRVU=53 github.com/labstack/echo-contrib v0.15.0 h1:9K+oRU265y4Mu9zpRDv3X+DGTqUALY6oRHCSZZKCRVU=
48 github.com/labstack/echo-contrib v0.15.0/go.mod h1:lei+qt5CLB4oa7VHTE0yEfQSEB9XTJI1LUqko9UWvo4=54 github.com/labstack/echo-contrib v0.15.0/go.mod h1:lei+qt5CLB4oa7VHTE0yEfQSEB9XTJI1LUqko9UWvo4=
49 github.com/labstack/echo/v4 v4.11.1 h1:dEpLU2FLg4UVmvCGPuk/APjlH6GDpbEPti61srUUUs4=55 github.com/labstack/echo/v4 v4.11.1 h1:dEpLU2FLg4UVmvCGPuk/APjlH6GDpbEPti61srUUUs4=
50 github.com/labstack/echo/v4 v4.11.1/go.mod h1:YuYRTSM3CHs2ybfrL8Px48bO6BAnYIN4l8wSTMP6BDQ=56 github.com/labstack/echo/v4 v4.11.1/go.mod h1:YuYRTSM3CHs2ybfrL8Px48bO6BAnYIN4l8wSTMP6BDQ=
51 github.com/labstack/gommon v0.4.0 h1:y7cvthEAEbU0yHOf4axH8ZG2NH8knB9iNSoTO8dyIk8=57 github.com/labstack/gommon v0.4.0 h1:y7cvthEAEbU0yHOf4axH8ZG2NH8knB9iNSoTO8dyIk8=
52 github.com/labstack/gommon v0.4.0/go.mod h1:uW6kP17uPlLJsD3ijUYn3/M5bAxtlZhMI6m3MFxTMTM=58 github.com/labstack/gommon v0.4.0/go.mod h1:uW6kP17uPlLJsD3ijUYn3/M5bAxtlZhMI6m3MFxTMTM=
59+github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY=
60+github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
53 github.com/mattn/go-colorable v0.1.11/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=61 github.com/mattn/go-colorable v0.1.11/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=
54 github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=62 github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
55 github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=63 github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
@@ -57,6 +65,11 @@ github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27k
57 github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=65 github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
58 github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA=66 github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA=
59 github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=67 github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
68+github.com/mattn/go-localereader v0.0.1 h1:ygSAOl7ZXTx4RdPYinUpg6W99U8jWvWi9Ye2JC/oIi4=
69+github.com/mattn/go-localereader v0.0.1/go.mod h1:8fBrzywKY7BI3czFoHkuzRoWE9C+EiG4R1k4Cjx5p88=
70+github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk=
71+github.com/mattn/go-runewidth v0.0.14 h1:+xnbZSEeDbOIg5/mE6JF0w6n9duR1l3/WmbinWVwUuU=
72+github.com/mattn/go-runewidth v0.0.14/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
60 github.com/mattn/go-sqlite3 v1.14.17 h1:mCRHCLDUBXgpKAqIKsaAaAsrAlbkeomtRFKXh2L6YIM=73 github.com/mattn/go-sqlite3 v1.14.17 h1:mCRHCLDUBXgpKAqIKsaAaAsrAlbkeomtRFKXh2L6YIM=
61 github.com/mattn/go-sqlite3 v1.14.17/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=74 github.com/mattn/go-sqlite3 v1.14.17/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
62 github.com/microcosm-cc/bluemonday v1.0.25 h1:4NEwSfiJ+Wva0VxN5B8OwMicaJvD8r9tlJWm9rtloEg=75 github.com/microcosm-cc/bluemonday v1.0.25 h1:4NEwSfiJ+Wva0VxN5B8OwMicaJvD8r9tlJWm9rtloEg=
@@ -65,6 +78,14 @@ github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0=
65 github.com/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y=78 github.com/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y=
66 github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A=79 github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A=
67 github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc=80 github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc=
81+github.com/muesli/ansi v0.0.0-20211018074035-2e021307bc4b h1:1XF24mVaiu7u+CFywTdcDo2ie1pzzhwjt6RHqzpMU34=
82+github.com/muesli/ansi v0.0.0-20211018074035-2e021307bc4b/go.mod h1:fQuZ0gauxyBcmsdE3ZT4NasjaRdxmbCS0jRHsrWu3Ho=
83+github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA=
84+github.com/muesli/cancelreader v0.2.2/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo=
85+github.com/muesli/reflow v0.3.0 h1:IFsN6K9NfGtjeggFP+68I4chLZV2yIKsXJFNZ+eWh6s=
86+github.com/muesli/reflow v0.3.0/go.mod h1:pbwTDkVPibjO2kyvBQRBxTWEEGDGq0FlB1BIKtnHY/8=
87+github.com/muesli/termenv v0.15.1 h1:UzuTb/+hhlBugQz28rpzey4ZuKcZ03MeKsoG7IJZIxs=
88+github.com/muesli/termenv v0.15.1/go.mod h1:HeAQPTzpfs016yGtA4g00CsdYnVLJvxsS4ANqrZs2sQ=
68 github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=89 github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
69 github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=90 github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
70 github.com/opencontainers/image-spec v1.1.0-rc2.0.20221005185240-3a7f492d3f1b h1:YWuSjZCQAPM8UUBLkYUk1e+rZcvWHJmFb6i6rM44Xs8=91 github.com/opencontainers/image-spec v1.1.0-rc2.0.20221005185240-3a7f492d3f1b h1:YWuSjZCQAPM8UUBLkYUk1e+rZcvWHJmFb6i6rM44Xs8=
@@ -73,6 +94,9 @@ github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
73 github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=94 github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
74 github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=95 github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
75 github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=96 github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
97+github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
98+github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
99+github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
76 github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M=100 github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M=
77 github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA=101 github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA=
78 github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=102 github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
@@ -95,13 +119,13 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk
95 golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=119 golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
96 golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=120 golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
97 golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=121 golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
98-golang.org/x/crypto v0.11.0 h1:6Ewdq3tDic1mg5xRO4milcWCfMVQhI4NkqWWvqejpuA=122+golang.org/x/crypto v0.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc=
99-golang.org/x/crypto v0.11.0/go.mod h1:xgJhtzW8F9jGdVFWZESrid1U1bjeNy4zgy5cRr/CIio=123+golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4=
100 golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=124 golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
101 golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=125 golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
102 golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=126 golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
103-golang.org/x/mod v0.9.0 h1:KENHtAZL2y3NLMYZeHY9DW8HW8V+kQyJsY/V9JlKvCs=127+golang.org/x/mod v0.13.0 h1:I/DsJXRlw/8l/0c24sM9yb0T4z9liZTduXvdAWYiysY=
104-golang.org/x/mod v0.9.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=128+golang.org/x/mod v0.13.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
105 golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=129 golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
106 golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=130 golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
107 golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=131 golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
@@ -110,14 +134,14 @@ golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v
110 golang.org/x/net v0.0.0-20210916014120-12bc252f5db8/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=134 golang.org/x/net v0.0.0-20210916014120-12bc252f5db8/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
111 golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=135 golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
112 golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=136 golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
113-golang.org/x/net v0.12.0 h1:cfawfvKITfUsFCeJIHJrbSxpeu/E81khclypR0GVT50=137+golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM=
114-golang.org/x/net v0.12.0/go.mod h1:zEVYFnQC7m/vmpQFELhcD1EWkZlX69l4oqgmer6hfKA=138+golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
115 golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=139 golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
116 golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=140 golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
117 golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=141 golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
118 golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=142 golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
119-golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o=143+golang.org/x/sync v0.4.0 h1:zxkM55ReGkDlKSM+Fu41A+zmbZuaPVbGMzvvdUPznYQ=
120-golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=144+golang.org/x/sync v0.4.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
121 golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=145 golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
122 golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=146 golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
123 golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=147 golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -130,20 +154,23 @@ golang.org/x/sys v0.0.0-20211103235746-7861aae1554b/go.mod h1:oPkhp1MJrh7nUepCBc
130 golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=154 golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
131 golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=155 golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
132 golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=156 golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
157+golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
133 golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=158 golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
134 golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=159 golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
135-golang.org/x/sys v0.10.0 h1:SqMFp9UcQJZa+pmYuAKjd9xq1f0j5rLcDIk0mj4qAsA=160+golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE=
136-golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=161+golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
137 golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=162 golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
138 golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=163 golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
139 golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=164 golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
165+golang.org/x/term v0.13.0 h1:bb+I9cTfFazGW51MZqBVmZy7+JEJMouUHTUSKVQLBek=
166+golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U=
140 golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=167 golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
141 golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=168 golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
142 golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=169 golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
143 golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=170 golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
144 golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=171 golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
145-golang.org/x/text v0.11.0 h1:LAntKIrcmeSKERyiOh0XMV39LXS8IE9UL2yP7+f5ij4=172+golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k=
146-golang.org/x/text v0.11.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=173+golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
147 golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4=174 golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4=
148 golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=175 golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
149 golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=176 golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
@@ -151,8 +178,8 @@ golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtn
151 golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=178 golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
152 golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=179 golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
153 golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=180 golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
154-golang.org/x/tools v0.7.0 h1:W4OVu8VVOaIO0yzWMNdepAulS7YfoS3Zabrm8DOXXU4=181+golang.org/x/tools v0.14.0 h1:jvNa2pY0M4r62jkRQ6RwEZZyPcymeL9XZMLBbV7U2nc=
155-golang.org/x/tools v0.7.0/go.mod h1:4pg6aUX35JBAogB10C9AtvVL+qowtN4pT3CGSQex14s=182+golang.org/x/tools v0.14.0/go.mod h1:uYBEerGOWcJyEORxN+Ek8+TT266gXkNlHdJBwexUsBg=
156 golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=183 golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
157 golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=184 golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
158 golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=185 golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
A internal/cli/cli.go +18 -0
@@ -0,0 +1,18 @@
1+package cli
2+
3+import (
4+ "log"
5+
6+ "github.com/bnema/gordon/internal/cli/cmd"
7+ "github.com/bnema/gordon/internal/cli/mvu"
8+ tea "github.com/charmbracelet/bubbletea"
9+)
10+
11+
12+
13+func InitCli(a *app.App, m *mvu.Model)) {
14+ p := tea.NewProgram(Init, mvu.Init, mvu.Update, mvu.View)
15+ if _, err := p.Run(); err != nil {
16+ log.Fatal(err)
17+ }
18+}
@@ -0,0 +1,18 @@
1+package cli
2+
3+import (
4+ "log"
5+
6+ "github.com/bnema/gordon/internal/cli/cmd"
7+ "github.com/bnema/gordon/internal/cli/mvu"
8+ tea "github.com/charmbracelet/bubbletea"
9+)
10+
11+
12+
13+func InitCli(a *app.App, m *mvu.Model)) {
14+ p := tea.NewProgram(Init, mvu.Init, mvu.Update, mvu.View)
15+ if _, err := p.Run(); err != nil {
16+ log.Fatal(err)
17+ }
18+}
A internal/cli/cmd/checkserver.go +28 -0
@@ -0,0 +1,28 @@
1+package cmd
2+
3+import (
4+ "fmt"
5+ "net/http"
6+ "time"
7+
8+ "github.com/bnema/gordon/internal/app"
9+ tea "github.com/charmbracelet/bubbletea"
10+)
11+
12+type statusMsg int
13+type timeout time.Duration
14+
15+func CheckServer(a *app.App) tea.Msg {
16+ url := a.Config.Admin.Path
17+ c := &http.Client{
18+ Timeout: 10 * time.Second,
19+ }
20+ res, err := c.Get(url)
21+ if err != nil {
22+ fmt.Println(err)
23+ return mvu.ErrMsg{Err: err}
24+ }
25+ defer res.Body.Close() // nolint:errcheck
26+
27+ return statusMsg(res.StatusCode)
28+}
@@ -0,0 +1,28 @@
1+package cmd
2+
3+import (
4+ "fmt"
5+ "net/http"
6+ "time"
7+
8+ "github.com/bnema/gordon/internal/app"
9+ tea "github.com/charmbracelet/bubbletea"
10+)
11+
12+type statusMsg int
13+type timeout time.Duration
14+
15+func CheckServer(a *app.App) tea.Msg {
16+ url := a.Config.Admin.Path
17+ c := &http.Client{
18+ Timeout: 10 * time.Second,
19+ }
20+ res, err := c.Get(url)
21+ if err != nil {
22+ fmt.Println(err)
23+ return mvu.ErrMsg{Err: err}
24+ }
25+ defer res.Body.Close() // nolint:errcheck
26+
27+ return statusMsg(res.StatusCode)
28+}
A internal/cli/cmd/push.go +3 -0
@@ -0,0 +1,3 @@
1+package cmd
2+
3+// the push command is used to push a container image to gordon's endpoint
@@ -0,0 +1,3 @@
1+package cmd
2+
3+// the push command is used to push a container image to gordon's endpoint
A internal/cli/cmd/root.go +8 -0
@@ -0,0 +1,8 @@
1+// the root command is the entrypoint for the gordon cli (default: client)
2+package cmd
3+
4+// if no command selected print all commands
5+// available commands for now are:
6+// - push
7+// - serve
8+// - version
@@ -0,0 +1,8 @@
1+// the root command is the entrypoint for the gordon cli (default: client)
2+package cmd
3+
4+// if no command selected print all commands
5+// available commands for now are:
6+// - push
7+// - serve
8+// - version
A internal/cli/cmd/serve.go +3 -0
@@ -0,0 +1,3 @@
1+// the serve command is used to start the gordon server
2+// Path: internal/cli/cmd/serve.go
3+package cmd
@@ -0,0 +1,3 @@
1+// the serve command is used to start the gordon server
2+// Path: internal/cli/cmd/serve.go
3+package cmd
A internal/cli/cmd/update.go +406 -0
@@ -0,0 +1,406 @@
1+// the update command is used to update gordon's executable to the latest version
2+
3+// Heavily inspired by Pocketbase's update command
4+// See: https://github.com/pocketbase/pocketbase/blob/master/plugins/ghupdate/ghupdate.go
5+package cmd
6+
7+// import (
8+// "context"
9+// "encoding/json"
10+// "errors"
11+// "fmt"
12+// "io"
13+// "log"
14+// "net/http"
15+// "os"
16+// "path/filepath"
17+// "runtime"
18+// "strconv"
19+// "strings"
20+
21+// "github.com/AlecAivazis/survey/v2"
22+// "github.com/fatih/color"
23+// "github.com/pocketbase/pocketbase/core"
24+// "github.com/pocketbase/pocketbase/tools/archive"
25+// "github.com/spf13/cobra"
26+// )
27+
28+// // HttpClient is a base HTTP client interface (usually used for test purposes).
29+// type HttpClient interface {
30+// Do(req *http.Request) (*http.Response, error)
31+// }
32+
33+// // Config defines the config options of the ghupdate plugin.
34+// //
35+// // NB! This plugin is considered experimental and its config options may change in the future.
36+// type Config struct {
37+// // Owner specifies the account owner of the repository (default to "pocketbase").
38+// Owner string
39+
40+// // Repo specifies the name of the repository (default to "pocketbase").
41+// Repo string
42+
43+// // ArchiveExecutable specifies the name of the executable file in the release archive
44+// // (default to "pocketbase"; an additional ".exe" check is also performed as a fallback).
45+// ArchiveExecutable string
46+
47+// // Optional context to use when fetching and downloading the latest release.
48+// Context context.Context
49+
50+// // The HTTP client to use when fetching and downloading the latest release.
51+// // Defaults to `http.DefaultClient`.
52+// HttpClient HttpClient
53+// }
54+
55+// // MustRegister registers the ghupdate plugin to the provided app instance
56+// // and panic if it fails.
57+// func MustRegister(app core.App, rootCmd *cobra.Command, config Config) {
58+// if err := Register(app, rootCmd, config); err != nil {
59+// panic(err)
60+// }
61+// }
62+
63+// // Register registers the ghupdate plugin to the provided app instance.
64+// func Register(app core.App, rootCmd *cobra.Command, config Config) error {
65+// p := &plugin{
66+// app: app,
67+// currentVersion: rootCmd.Version,
68+// config: config,
69+// }
70+
71+// if p.config.Owner == "" {
72+// p.config.Owner = "pocketbase"
73+// }
74+
75+// if p.config.Repo == "" {
76+// p.config.Repo = "pocketbase"
77+// }
78+
79+// if p.config.ArchiveExecutable == "" {
80+// p.config.ArchiveExecutable = "pocketbase"
81+// }
82+
83+// if p.config.HttpClient == nil {
84+// p.config.HttpClient = http.DefaultClient
85+// }
86+
87+// if p.config.Context == nil {
88+// p.config.Context = context.Background()
89+// }
90+
91+// rootCmd.AddCommand(p.updateCmd())
92+
93+// return nil
94+// }
95+
96+// type plugin struct {
97+// app core.App
98+// currentVersion string
99+// config Config
100+// }
101+
102+// func (p *plugin) updateCmd() *cobra.Command {
103+// var withBackup bool
104+
105+// command := &cobra.Command{
106+// Use: "update",
107+// Short: "Automatically updates the current PocketBase executable with the latest available version",
108+// // @todo remove after logs generalization
109+// // prevents printing the error log twice
110+// SilenceErrors: true,
111+// SilenceUsage: true,
112+// RunE: func(command *cobra.Command, args []string) error {
113+// var needConfirm bool
114+// if isMaybeRunningInDocker() {
115+// needConfirm = true
116+// color.Yellow("NB! It seems that you are in a Docker container.")
117+// color.Yellow("The update command may not work as expected in this context because usually the version of the app is managed by the container image itself.")
118+// } else if isMaybeRunningInNixOS() {
119+// needConfirm = true
120+// color.Yellow("NB! It seems that you are in a NixOS.")
121+// color.Yellow("Due to the non-standard filesystem implementation of the environment, the update command may not work as expected.")
122+// }
123+
124+// if needConfirm {
125+// confirm := false
126+// prompt := &survey.Confirm{
127+// Message: "Do you want to proceed with the update?",
128+// }
129+// survey.AskOne(prompt, &confirm)
130+// if !confirm {
131+// fmt.Println("The command has been cancelled.")
132+// return nil
133+// }
134+// }
135+
136+// return p.update(withBackup)
137+// },
138+// }
139+
140+// command.PersistentFlags().BoolVar(
141+// &withBackup,
142+// "backup",
143+// true,
144+// "Creates a pb_data backup at the end of the update process",
145+// )
146+
147+// return command
148+// }
149+
150+// func (p *plugin) update(withBackup bool) error {
151+// color.Yellow("Fetching release information...")
152+
153+// latest, err := fetchLatestRelease(
154+// p.config.Context,
155+// p.config.HttpClient,
156+// p.config.Owner,
157+// p.config.Repo,
158+// )
159+// if err != nil {
160+// return err
161+// }
162+
163+// if compareVersions(strings.TrimPrefix(p.currentVersion, "v"), strings.TrimPrefix(latest.Tag, "v")) <= 0 {
164+// color.Green("You already have the latest PocketBase %s.", p.currentVersion)
165+// return nil
166+// }
167+
168+// suffix := archiveSuffix(runtime.GOOS, runtime.GOARCH)
169+// if suffix == "" {
170+// return errors.New("unsupported platform")
171+// }
172+
173+// asset, err := latest.findAssetBySuffix(suffix)
174+// if err != nil {
175+// return err
176+// }
177+
178+// releaseDir := filepath.Join(p.app.DataDir(), core.LocalTempDirName)
179+// defer os.RemoveAll(releaseDir)
180+
181+// color.Yellow("Downloading %s...", asset.Name)
182+
183+// // download the release asset
184+// assetZip := filepath.Join(releaseDir, asset.Name)
185+// if err := downloadFile(p.config.Context, p.config.HttpClient, asset.DownloadUrl, assetZip); err != nil {
186+// return err
187+// }
188+
189+// color.Yellow("Extracting %s...", asset.Name)
190+
191+// extractDir := filepath.Join(releaseDir, "extracted_"+asset.Name)
192+// defer os.RemoveAll(extractDir)
193+
194+// if err := archive.Extract(assetZip, extractDir); err != nil {
195+// return err
196+// }
197+
198+// color.Yellow("Replacing the executable...")
199+
200+// oldExec, err := os.Executable()
201+// if err != nil {
202+// return err
203+// }
204+// renamedOldExec := oldExec + ".old"
205+// defer os.Remove(renamedOldExec)
206+
207+// newExec := filepath.Join(extractDir, p.config.ArchiveExecutable)
208+// if _, err := os.Stat(newExec); err != nil {
209+// // try again with an .exe extension
210+// newExec = newExec + ".exe"
211+// if _, fallbackErr := os.Stat(newExec); fallbackErr != nil {
212+// return fmt.Errorf("The executable in the extracted path is missing or it is inaccessible: %v, %v", err, fallbackErr)
213+// }
214+// }
215+
216+// // rename the current executable
217+// if err := os.Rename(oldExec, renamedOldExec); err != nil {
218+// return fmt.Errorf("Failed to rename the current executable: %w", err)
219+// }
220+
221+// tryToRevertExecChanges := func() {
222+// if revertErr := os.Rename(renamedOldExec, oldExec); revertErr != nil && p.app.IsDebug() {
223+// log.Println(revertErr)
224+// }
225+// }
226+
227+// // replace with the extracted binary
228+// if err := os.Rename(newExec, oldExec); err != nil {
229+// tryToRevertExecChanges()
230+// return fmt.Errorf("Failed replacing the executable: %w", err)
231+// }
232+
233+// if withBackup {
234+// color.Yellow("Creating pb_data backup...")
235+
236+// backupName := fmt.Sprintf("@update_%s.zip", latest.Tag)
237+// if err := p.app.CreateBackup(p.config.Context, backupName); err != nil {
238+// tryToRevertExecChanges()
239+// return err
240+// }
241+// }
242+
243+// color.HiBlack("---")
244+// color.Green("Update completed successfully! You can start the executable as usual.")
245+
246+// return nil
247+// }
248+
249+// func fetchLatestRelease(
250+// ctx context.Context,
251+// client HttpClient,
252+// owner string,
253+// repo string,
254+// ) (*release, error) {
255+// url := fmt.Sprintf("https://api.github.com/repos/%s/%s/releases/latest", owner, repo)
256+
257+// req, err := http.NewRequestWithContext(ctx, "GET", url, nil)
258+// if err != nil {
259+// return nil, err
260+// }
261+
262+// res, err := client.Do(req)
263+// if err != nil {
264+// return nil, err
265+// }
266+// defer res.Body.Close()
267+
268+// rawBody, err := io.ReadAll(res.Body)
269+// if err != nil {
270+// return nil, err
271+// }
272+
273+// // http.Client doesn't treat non 2xx responses as error
274+// if res.StatusCode >= 400 {
275+// return nil, fmt.Errorf(
276+// "(%d) failed to fetch latest releases:\n%s",
277+// res.StatusCode,
278+// string(rawBody),
279+// )
280+// }
281+
282+// result := &release{}
283+// if err := json.Unmarshal(rawBody, result); err != nil {
284+// return nil, err
285+// }
286+
287+// return result, nil
288+// }
289+
290+// func downloadFile(
291+// ctx context.Context,
292+// client HttpClient,
293+// url string,
294+// destPath string,
295+// ) error {
296+// req, err := http.NewRequestWithContext(ctx, "GET", url, nil)
297+// if err != nil {
298+// return err
299+// }
300+
301+// res, err := client.Do(req)
302+// if err != nil {
303+// return err
304+// }
305+// defer res.Body.Close()
306+
307+// // http.Client doesn't treat non 2xx responses as error
308+// if res.StatusCode >= 400 {
309+// return fmt.Errorf("(%d) failed to send download file request", res.StatusCode)
310+// }
311+
312+// // ensure that the dest parent dir(s) exist
313+// if err := os.MkdirAll(filepath.Dir(destPath), os.ModePerm); err != nil {
314+// return err
315+// }
316+
317+// dest, err := os.Create(destPath)
318+// if err != nil {
319+// return err
320+// }
321+// defer dest.Close()
322+
323+// if _, err := io.Copy(dest, res.Body); err != nil {
324+// return err
325+// }
326+
327+// return nil
328+// }
329+
330+// func archiveSuffix(goos, goarch string) string {
331+// switch goos {
332+// case "linux":
333+// switch goarch {
334+// case "amd64":
335+// return "_linux_amd64.zip"
336+// case "arm64":
337+// return "_linux_arm64.zip"
338+// case "arm":
339+// return "_linux_armv7.zip"
340+// }
341+// case "darwin":
342+// switch goarch {
343+// case "amd64":
344+// return "_darwin_amd64.zip"
345+// case "arm64":
346+// return "_darwin_arm64.zip"
347+// }
348+// case "windows":
349+// switch goarch {
350+// case "amd64":
351+// return "_windows_amd64.zip"
352+// case "arm64":
353+// return "_windows_arm64.zip"
354+// }
355+// }
356+
357+// return ""
358+// }
359+
360+// func compareVersions(a, b string) int {
361+// aSplit := strings.Split(a, ".")
362+// aTotal := len(aSplit)
363+
364+// bSplit := strings.Split(b, ".")
365+// bTotal := len(bSplit)
366+
367+// limit := aTotal
368+// if bTotal > aTotal {
369+// limit = bTotal
370+// }
371+
372+// for i := 0; i < limit; i++ {
373+// var x, y int
374+
375+// if i < aTotal {
376+// x, _ = strconv.Atoi(aSplit[i])
377+// }
378+
379+// if i < bTotal {
380+// y, _ = strconv.Atoi(bSplit[i])
381+// }
382+
383+// if x < y {
384+// return 1 // b is newer
385+// }
386+
387+// if x > y {
388+// return -1 // a is newer
389+// }
390+// }
391+
392+// return 0 // equal
393+// }
394+
395+// // note: not completely reliable as it may not work on all platforms
396+// // but should at least provide a warning for the most common use cases
397+// func isMaybeRunningInDocker() bool {
398+// _, err := os.Stat("/.dockerenv")
399+// return err == nil
400+// }
401+
402+// // note: untested
403+// func isMaybeRunningInNixOS() bool {
404+// _, err := os.Stat("/etc/NIXOS")
405+// return err == nil
406+// }
@@ -0,0 +1,406 @@
1+// the update command is used to update gordon's executable to the latest version
2+
3+// Heavily inspired by Pocketbase's update command
4+// See: https://github.com/pocketbase/pocketbase/blob/master/plugins/ghupdate/ghupdate.go
5+package cmd
6+
7+// import (
8+// "context"
9+// "encoding/json"
10+// "errors"
11+// "fmt"
12+// "io"
13+// "log"
14+// "net/http"
15+// "os"
16+// "path/filepath"
17+// "runtime"
18+// "strconv"
19+// "strings"
20+
21+// "github.com/AlecAivazis/survey/v2"
22+// "github.com/fatih/color"
23+// "github.com/pocketbase/pocketbase/core"
24+// "github.com/pocketbase/pocketbase/tools/archive"
25+// "github.com/spf13/cobra"
26+// )
27+
28+// // HttpClient is a base HTTP client interface (usually used for test purposes).
29+// type HttpClient interface {
30+// Do(req *http.Request) (*http.Response, error)
31+// }
32+
33+// // Config defines the config options of the ghupdate plugin.
34+// //
35+// // NB! This plugin is considered experimental and its config options may change in the future.
36+// type Config struct {
37+// // Owner specifies the account owner of the repository (default to "pocketbase").
38+// Owner string
39+
40+// // Repo specifies the name of the repository (default to "pocketbase").
41+// Repo string
42+
43+// // ArchiveExecutable specifies the name of the executable file in the release archive
44+// // (default to "pocketbase"; an additional ".exe" check is also performed as a fallback).
45+// ArchiveExecutable string
46+
47+// // Optional context to use when fetching and downloading the latest release.
48+// Context context.Context
49+
50+// // The HTTP client to use when fetching and downloading the latest release.
51+// // Defaults to `http.DefaultClient`.
52+// HttpClient HttpClient
53+// }
54+
55+// // MustRegister registers the ghupdate plugin to the provided app instance
56+// // and panic if it fails.
57+// func MustRegister(app core.App, rootCmd *cobra.Command, config Config) {
58+// if err := Register(app, rootCmd, config); err != nil {
59+// panic(err)
60+// }
61+// }
62+
63+// // Register registers the ghupdate plugin to the provided app instance.
64+// func Register(app core.App, rootCmd *cobra.Command, config Config) error {
65+// p := &plugin{
66+// app: app,
67+// currentVersion: rootCmd.Version,
68+// config: config,
69+// }
70+
71+// if p.config.Owner == "" {
72+// p.config.Owner = "pocketbase"
73+// }
74+
75+// if p.config.Repo == "" {
76+// p.config.Repo = "pocketbase"
77+// }
78+
79+// if p.config.ArchiveExecutable == "" {
80+// p.config.ArchiveExecutable = "pocketbase"
81+// }
82+
83+// if p.config.HttpClient == nil {
84+// p.config.HttpClient = http.DefaultClient
85+// }
86+
87+// if p.config.Context == nil {
88+// p.config.Context = context.Background()
89+// }
90+
91+// rootCmd.AddCommand(p.updateCmd())
92+
93+// return nil
94+// }
95+
96+// type plugin struct {
97+// app core.App
98+// currentVersion string
99+// config Config
100+// }
101+
102+// func (p *plugin) updateCmd() *cobra.Command {
103+// var withBackup bool
104+
105+// command := &cobra.Command{
106+// Use: "update",
107+// Short: "Automatically updates the current PocketBase executable with the latest available version",
108+// // @todo remove after logs generalization
109+// // prevents printing the error log twice
110+// SilenceErrors: true,
111+// SilenceUsage: true,
112+// RunE: func(command *cobra.Command, args []string) error {
113+// var needConfirm bool
114+// if isMaybeRunningInDocker() {
115+// needConfirm = true
116+// color.Yellow("NB! It seems that you are in a Docker container.")
117+// color.Yellow("The update command may not work as expected in this context because usually the version of the app is managed by the container image itself.")
118+// } else if isMaybeRunningInNixOS() {
119+// needConfirm = true
120+// color.Yellow("NB! It seems that you are in a NixOS.")
121+// color.Yellow("Due to the non-standard filesystem implementation of the environment, the update command may not work as expected.")
122+// }
123+
124+// if needConfirm {
125+// confirm := false
126+// prompt := &survey.Confirm{
127+// Message: "Do you want to proceed with the update?",
128+// }
129+// survey.AskOne(prompt, &confirm)
130+// if !confirm {
131+// fmt.Println("The command has been cancelled.")
132+// return nil
133+// }
134+// }
135+
136+// return p.update(withBackup)
137+// },
138+// }
139+
140+// command.PersistentFlags().BoolVar(
141+// &withBackup,
142+// "backup",
143+// true,
144+// "Creates a pb_data backup at the end of the update process",
145+// )
146+
147+// return command
148+// }
149+
150+// func (p *plugin) update(withBackup bool) error {
151+// color.Yellow("Fetching release information...")
152+
153+// latest, err := fetchLatestRelease(
154+// p.config.Context,
155+// p.config.HttpClient,
156+// p.config.Owner,
157+// p.config.Repo,
158+// )
159+// if err != nil {
160+// return err
161+// }
162+
163+// if compareVersions(strings.TrimPrefix(p.currentVersion, "v"), strings.TrimPrefix(latest.Tag, "v")) <= 0 {
164+// color.Green("You already have the latest PocketBase %s.", p.currentVersion)
165+// return nil
166+// }
167+
168+// suffix := archiveSuffix(runtime.GOOS, runtime.GOARCH)
169+// if suffix == "" {
170+// return errors.New("unsupported platform")
171+// }
172+
173+// asset, err := latest.findAssetBySuffix(suffix)
174+// if err != nil {
175+// return err
176+// }
177+
178+// releaseDir := filepath.Join(p.app.DataDir(), core.LocalTempDirName)
179+// defer os.RemoveAll(releaseDir)
180+
181+// color.Yellow("Downloading %s...", asset.Name)
182+
183+// // download the release asset
184+// assetZip := filepath.Join(releaseDir, asset.Name)
185+// if err := downloadFile(p.config.Context, p.config.HttpClient, asset.DownloadUrl, assetZip); err != nil {
186+// return err
187+// }
188+
189+// color.Yellow("Extracting %s...", asset.Name)
190+
191+// extractDir := filepath.Join(releaseDir, "extracted_"+asset.Name)
192+// defer os.RemoveAll(extractDir)
193+
194+// if err := archive.Extract(assetZip, extractDir); err != nil {
195+// return err
196+// }
197+
198+// color.Yellow("Replacing the executable...")
199+
200+// oldExec, err := os.Executable()
201+// if err != nil {
202+// return err
203+// }
204+// renamedOldExec := oldExec + ".old"
205+// defer os.Remove(renamedOldExec)
206+
207+// newExec := filepath.Join(extractDir, p.config.ArchiveExecutable)
208+// if _, err := os.Stat(newExec); err != nil {
209+// // try again with an .exe extension
210+// newExec = newExec + ".exe"
211+// if _, fallbackErr := os.Stat(newExec); fallbackErr != nil {
212+// return fmt.Errorf("The executable in the extracted path is missing or it is inaccessible: %v, %v", err, fallbackErr)
213+// }
214+// }
215+
216+// // rename the current executable
217+// if err := os.Rename(oldExec, renamedOldExec); err != nil {
218+// return fmt.Errorf("Failed to rename the current executable: %w", err)
219+// }
220+
221+// tryToRevertExecChanges := func() {
222+// if revertErr := os.Rename(renamedOldExec, oldExec); revertErr != nil && p.app.IsDebug() {
223+// log.Println(revertErr)
224+// }
225+// }
226+
227+// // replace with the extracted binary
228+// if err := os.Rename(newExec, oldExec); err != nil {
229+// tryToRevertExecChanges()
230+// return fmt.Errorf("Failed replacing the executable: %w", err)
231+// }
232+
233+// if withBackup {
234+// color.Yellow("Creating pb_data backup...")
235+
236+// backupName := fmt.Sprintf("@update_%s.zip", latest.Tag)
237+// if err := p.app.CreateBackup(p.config.Context, backupName); err != nil {
238+// tryToRevertExecChanges()
239+// return err
240+// }
241+// }
242+
243+// color.HiBlack("---")
244+// color.Green("Update completed successfully! You can start the executable as usual.")
245+
246+// return nil
247+// }
248+
249+// func fetchLatestRelease(
250+// ctx context.Context,
251+// client HttpClient,
252+// owner string,
253+// repo string,
254+// ) (*release, error) {
255+// url := fmt.Sprintf("https://api.github.com/repos/%s/%s/releases/latest", owner, repo)
256+
257+// req, err := http.NewRequestWithContext(ctx, "GET", url, nil)
258+// if err != nil {
259+// return nil, err
260+// }
261+
262+// res, err := client.Do(req)
263+// if err != nil {
264+// return nil, err
265+// }
266+// defer res.Body.Close()
267+
268+// rawBody, err := io.ReadAll(res.Body)
269+// if err != nil {
270+// return nil, err
271+// }
272+
273+// // http.Client doesn't treat non 2xx responses as error
274+// if res.StatusCode >= 400 {
275+// return nil, fmt.Errorf(
276+// "(%d) failed to fetch latest releases:\n%s",
277+// res.StatusCode,
278+// string(rawBody),
279+// )
280+// }
281+
282+// result := &release{}
283+// if err := json.Unmarshal(rawBody, result); err != nil {
284+// return nil, err
285+// }
286+
287+// return result, nil
288+// }
289+
290+// func downloadFile(
291+// ctx context.Context,
292+// client HttpClient,
293+// url string,
294+// destPath string,
295+// ) error {
296+// req, err := http.NewRequestWithContext(ctx, "GET", url, nil)
297+// if err != nil {
298+// return err
299+// }
300+
301+// res, err := client.Do(req)
302+// if err != nil {
303+// return err
304+// }
305+// defer res.Body.Close()
306+
307+// // http.Client doesn't treat non 2xx responses as error
308+// if res.StatusCode >= 400 {
309+// return fmt.Errorf("(%d) failed to send download file request", res.StatusCode)
310+// }
311+
312+// // ensure that the dest parent dir(s) exist
313+// if err := os.MkdirAll(filepath.Dir(destPath), os.ModePerm); err != nil {
314+// return err
315+// }
316+
317+// dest, err := os.Create(destPath)
318+// if err != nil {
319+// return err
320+// }
321+// defer dest.Close()
322+
323+// if _, err := io.Copy(dest, res.Body); err != nil {
324+// return err
325+// }
326+
327+// return nil
328+// }
329+
330+// func archiveSuffix(goos, goarch string) string {
331+// switch goos {
332+// case "linux":
333+// switch goarch {
334+// case "amd64":
335+// return "_linux_amd64.zip"
336+// case "arm64":
337+// return "_linux_arm64.zip"
338+// case "arm":
339+// return "_linux_armv7.zip"
340+// }
341+// case "darwin":
342+// switch goarch {
343+// case "amd64":
344+// return "_darwin_amd64.zip"
345+// case "arm64":
346+// return "_darwin_arm64.zip"
347+// }
348+// case "windows":
349+// switch goarch {
350+// case "amd64":
351+// return "_windows_amd64.zip"
352+// case "arm64":
353+// return "_windows_arm64.zip"
354+// }
355+// }
356+
357+// return ""
358+// }
359+
360+// func compareVersions(a, b string) int {
361+// aSplit := strings.Split(a, ".")
362+// aTotal := len(aSplit)
363+
364+// bSplit := strings.Split(b, ".")
365+// bTotal := len(bSplit)
366+
367+// limit := aTotal
368+// if bTotal > aTotal {
369+// limit = bTotal
370+// }
371+
372+// for i := 0; i < limit; i++ {
373+// var x, y int
374+
375+// if i < aTotal {
376+// x, _ = strconv.Atoi(aSplit[i])
377+// }
378+
379+// if i < bTotal {
380+// y, _ = strconv.Atoi(bSplit[i])
381+// }
382+
383+// if x < y {
384+// return 1 // b is newer
385+// }
386+
387+// if x > y {
388+// return -1 // a is newer
389+// }
390+// }
391+
392+// return 0 // equal
393+// }
394+
395+// // note: not completely reliable as it may not work on all platforms
396+// // but should at least provide a warning for the most common use cases
397+// func isMaybeRunningInDocker() bool {
398+// _, err := os.Stat("/.dockerenv")
399+// return err == nil
400+// }
401+
402+// // note: untested
403+// func isMaybeRunningInNixOS() bool {
404+// _, err := os.Stat("/etc/NIXOS")
405+// return err == nil
406+// }
A internal/cli/mvu/mvu.go +59 -0
@@ -0,0 +1,59 @@
1+package mvu
2+
3+import (
4+ "fmt"
5+ "net/http"
6+
7+ tea "github.com/charmbracelet/bubbletea"
8+)
9+
10+type Model struct {
11+ status int
12+ err error
13+ url string
14+}
15+
16+type statusMsg int
17+
18+type ErrMsg struct {
19+ Err error
20+}
21+
22+func (m Model) Init() tea.Cmd {
23+ return nil
24+}
25+
26+func Error(e ErrMsg) string { return e.Err.Error() }
27+
28+func Update(msg tea.Msg, m Model) (tea.Model, tea.Cmd) {
29+ switch msg := msg.(type) {
30+ case tea.KeyMsg:
31+ switch msg.String() {
32+ case "q", "ctrl+c", "esc":
33+ return m, tea.Quit
34+ default:
35+ return nil
36+ }
37+
38+ case statusMsg:
39+ m.status = int(msg)
40+ return m, tea.Quit
41+
42+ case ErrMsg:
43+ m.err = msg
44+ return nil
45+
46+ default:
47+ return nil
48+ }
49+}
50+
51+func (m Model) View() string {
52+ s := fmt.Sprintf("Checking %s...", m.url)
53+ if m.err != nil {
54+ s += fmt.Sprintf("something went wrong: %s", m.err)
55+ } else if m.status != 0 {
56+ s += fmt.Sprintf("%d %s", m.status, http.StatusText(m.status))
57+ }
58+ return s + "\n"
59+}
@@ -0,0 +1,59 @@
1+package mvu
2+
3+import (
4+ "fmt"
5+ "net/http"
6+
7+ tea "github.com/charmbracelet/bubbletea"
8+)
9+
10+type Model struct {
11+ status int
12+ err error
13+ url string
14+}
15+
16+type statusMsg int
17+
18+type ErrMsg struct {
19+ Err error
20+}
21+
22+func (m Model) Init() tea.Cmd {
23+ return nil
24+}
25+
26+func Error(e ErrMsg) string { return e.Err.Error() }
27+
28+func Update(msg tea.Msg, m Model) (tea.Model, tea.Cmd) {
29+ switch msg := msg.(type) {
30+ case tea.KeyMsg:
31+ switch msg.String() {
32+ case "q", "ctrl+c", "esc":
33+ return m, tea.Quit
34+ default:
35+ return nil
36+ }
37+
38+ case statusMsg:
39+ m.status = int(msg)
40+ return m, tea.Quit
41+
42+ case ErrMsg:
43+ m.err = msg
44+ return nil
45+
46+ default:
47+ return nil
48+ }
49+}
50+
51+func (m Model) View() string {
52+ s := fmt.Sprintf("Checking %s...", m.url)
53+ if m.err != nil {
54+ s += fmt.Sprintf("something went wrong: %s", m.err)
55+ } else if m.status != 0 {
56+ s += fmt.Sprintf("%d %s", m.status, http.StatusText(m.status))
57+ }
58+ return s + "\n"
59+}
D internal/commands/update.go +0 -404
@@ -1,404 +0,0 @@
1-// Heavily inspired by Pocketbase's update command
2-// See: https://github.com/pocketbase/pocketbase/blob/master/plugins/ghupdate/ghupdate.go
3-package commands
4-
5-import (
6- "context"
7- "encoding/json"
8- "errors"
9- "fmt"
10- "io"
11- "log"
12- "net/http"
13- "os"
14- "path/filepath"
15- "runtime"
16- "strconv"
17- "strings"
18-
19- "github.com/AlecAivazis/survey/v2"
20- "github.com/fatih/color"
21- "github.com/pocketbase/pocketbase/core"
22- "github.com/pocketbase/pocketbase/tools/archive"
23- "github.com/spf13/cobra"
24-)
25-
26-// HttpClient is a base HTTP client interface (usually used for test purposes).
27-type HttpClient interface {
28- Do(req *http.Request) (*http.Response, error)
29-}
30-
31-// Config defines the config options of the ghupdate plugin.
32-//
33-// NB! This plugin is considered experimental and its config options may change in the future.
34-type Config struct {
35- // Owner specifies the account owner of the repository (default to "pocketbase").
36- Owner string
37-
38- // Repo specifies the name of the repository (default to "pocketbase").
39- Repo string
40-
41- // ArchiveExecutable specifies the name of the executable file in the release archive
42- // (default to "pocketbase"; an additional ".exe" check is also performed as a fallback).
43- ArchiveExecutable string
44-
45- // Optional context to use when fetching and downloading the latest release.
46- Context context.Context
47-
48- // The HTTP client to use when fetching and downloading the latest release.
49- // Defaults to `http.DefaultClient`.
50- HttpClient HttpClient
51-}
52-
53-// MustRegister registers the ghupdate plugin to the provided app instance
54-// and panic if it fails.
55-func MustRegister(app core.App, rootCmd *cobra.Command, config Config) {
56- if err := Register(app, rootCmd, config); err != nil {
57- panic(err)
58- }
59-}
60-
61-// Register registers the ghupdate plugin to the provided app instance.
62-func Register(app core.App, rootCmd *cobra.Command, config Config) error {
63- p := &plugin{
64- app: app,
65- currentVersion: rootCmd.Version,
66- config: config,
67- }
68-
69- if p.config.Owner == "" {
70- p.config.Owner = "pocketbase"
71- }
72-
73- if p.config.Repo == "" {
74- p.config.Repo = "pocketbase"
75- }
76-
77- if p.config.ArchiveExecutable == "" {
78- p.config.ArchiveExecutable = "pocketbase"
79- }
80-
81- if p.config.HttpClient == nil {
82- p.config.HttpClient = http.DefaultClient
83- }
84-
85- if p.config.Context == nil {
86- p.config.Context = context.Background()
87- }
88-
89- rootCmd.AddCommand(p.updateCmd())
90-
91- return nil
92-}
93-
94-type plugin struct {
95- app core.App
96- currentVersion string
97- config Config
98-}
99-
100-func (p *plugin) updateCmd() *cobra.Command {
101- var withBackup bool
102-
103- command := &cobra.Command{
104- Use: "update",
105- Short: "Automatically updates the current PocketBase executable with the latest available version",
106- // @todo remove after logs generalization
107- // prevents printing the error log twice
108- SilenceErrors: true,
109- SilenceUsage: true,
110- RunE: func(command *cobra.Command, args []string) error {
111- var needConfirm bool
112- if isMaybeRunningInDocker() {
113- needConfirm = true
114- color.Yellow("NB! It seems that you are in a Docker container.")
115- color.Yellow("The update command may not work as expected in this context because usually the version of the app is managed by the container image itself.")
116- } else if isMaybeRunningInNixOS() {
117- needConfirm = true
118- color.Yellow("NB! It seems that you are in a NixOS.")
119- color.Yellow("Due to the non-standard filesystem implementation of the environment, the update command may not work as expected.")
120- }
121-
122- if needConfirm {
123- confirm := false
124- prompt := &survey.Confirm{
125- Message: "Do you want to proceed with the update?",
126- }
127- survey.AskOne(prompt, &confirm)
128- if !confirm {
129- fmt.Println("The command has been cancelled.")
130- return nil
131- }
132- }
133-
134- return p.update(withBackup)
135- },
136- }
137-
138- command.PersistentFlags().BoolVar(
139- &withBackup,
140- "backup",
141- true,
142- "Creates a pb_data backup at the end of the update process",
143- )
144-
145- return command
146-}
147-
148-func (p *plugin) update(withBackup bool) error {
149- color.Yellow("Fetching release information...")
150-
151- latest, err := fetchLatestRelease(
152- p.config.Context,
153- p.config.HttpClient,
154- p.config.Owner,
155- p.config.Repo,
156- )
157- if err != nil {
158- return err
159- }
160-
161- if compareVersions(strings.TrimPrefix(p.currentVersion, "v"), strings.TrimPrefix(latest.Tag, "v")) <= 0 {
162- color.Green("You already have the latest PocketBase %s.", p.currentVersion)
163- return nil
164- }
165-
166- suffix := archiveSuffix(runtime.GOOS, runtime.GOARCH)
167- if suffix == "" {
168- return errors.New("unsupported platform")
169- }
170-
171- asset, err := latest.findAssetBySuffix(suffix)
172- if err != nil {
173- return err
174- }
175-
176- releaseDir := filepath.Join(p.app.DataDir(), core.LocalTempDirName)
177- defer os.RemoveAll(releaseDir)
178-
179- color.Yellow("Downloading %s...", asset.Name)
180-
181- // download the release asset
182- assetZip := filepath.Join(releaseDir, asset.Name)
183- if err := downloadFile(p.config.Context, p.config.HttpClient, asset.DownloadUrl, assetZip); err != nil {
184- return err
185- }
186-
187- color.Yellow("Extracting %s...", asset.Name)
188-
189- extractDir := filepath.Join(releaseDir, "extracted_"+asset.Name)
190- defer os.RemoveAll(extractDir)
191-
192- if err := archive.Extract(assetZip, extractDir); err != nil {
193- return err
194- }
195-
196- color.Yellow("Replacing the executable...")
197-
198- oldExec, err := os.Executable()
199- if err != nil {
200- return err
201- }
202- renamedOldExec := oldExec + ".old"
203- defer os.Remove(renamedOldExec)
204-
205- newExec := filepath.Join(extractDir, p.config.ArchiveExecutable)
206- if _, err := os.Stat(newExec); err != nil {
207- // try again with an .exe extension
208- newExec = newExec + ".exe"
209- if _, fallbackErr := os.Stat(newExec); fallbackErr != nil {
210- return fmt.Errorf("The executable in the extracted path is missing or it is inaccessible: %v, %v", err, fallbackErr)
211- }
212- }
213-
214- // rename the current executable
215- if err := os.Rename(oldExec, renamedOldExec); err != nil {
216- return fmt.Errorf("Failed to rename the current executable: %w", err)
217- }
218-
219- tryToRevertExecChanges := func() {
220- if revertErr := os.Rename(renamedOldExec, oldExec); revertErr != nil && p.app.IsDebug() {
221- log.Println(revertErr)
222- }
223- }
224-
225- // replace with the extracted binary
226- if err := os.Rename(newExec, oldExec); err != nil {
227- tryToRevertExecChanges()
228- return fmt.Errorf("Failed replacing the executable: %w", err)
229- }
230-
231- if withBackup {
232- color.Yellow("Creating pb_data backup...")
233-
234- backupName := fmt.Sprintf("@update_%s.zip", latest.Tag)
235- if err := p.app.CreateBackup(p.config.Context, backupName); err != nil {
236- tryToRevertExecChanges()
237- return err
238- }
239- }
240-
241- color.HiBlack("---")
242- color.Green("Update completed successfully! You can start the executable as usual.")
243-
244- return nil
245-}
246-
247-func fetchLatestRelease(
248- ctx context.Context,
249- client HttpClient,
250- owner string,
251- repo string,
252-) (*release, error) {
253- url := fmt.Sprintf("https://api.github.com/repos/%s/%s/releases/latest", owner, repo)
254-
255- req, err := http.NewRequestWithContext(ctx, "GET", url, nil)
256- if err != nil {
257- return nil, err
258- }
259-
260- res, err := client.Do(req)
261- if err != nil {
262- return nil, err
263- }
264- defer res.Body.Close()
265-
266- rawBody, err := io.ReadAll(res.Body)
267- if err != nil {
268- return nil, err
269- }
270-
271- // http.Client doesn't treat non 2xx responses as error
272- if res.StatusCode >= 400 {
273- return nil, fmt.Errorf(
274- "(%d) failed to fetch latest releases:\n%s",
275- res.StatusCode,
276- string(rawBody),
277- )
278- }
279-
280- result := &release{}
281- if err := json.Unmarshal(rawBody, result); err != nil {
282- return nil, err
283- }
284-
285- return result, nil
286-}
287-
288-func downloadFile(
289- ctx context.Context,
290- client HttpClient,
291- url string,
292- destPath string,
293-) error {
294- req, err := http.NewRequestWithContext(ctx, "GET", url, nil)
295- if err != nil {
296- return err
297- }
298-
299- res, err := client.Do(req)
300- if err != nil {
301- return err
302- }
303- defer res.Body.Close()
304-
305- // http.Client doesn't treat non 2xx responses as error
306- if res.StatusCode >= 400 {
307- return fmt.Errorf("(%d) failed to send download file request", res.StatusCode)
308- }
309-
310- // ensure that the dest parent dir(s) exist
311- if err := os.MkdirAll(filepath.Dir(destPath), os.ModePerm); err != nil {
312- return err
313- }
314-
315- dest, err := os.Create(destPath)
316- if err != nil {
317- return err
318- }
319- defer dest.Close()
320-
321- if _, err := io.Copy(dest, res.Body); err != nil {
322- return err
323- }
324-
325- return nil
326-}
327-
328-func archiveSuffix(goos, goarch string) string {
329- switch goos {
330- case "linux":
331- switch goarch {
332- case "amd64":
333- return "_linux_amd64.zip"
334- case "arm64":
335- return "_linux_arm64.zip"
336- case "arm":
337- return "_linux_armv7.zip"
338- }
339- case "darwin":
340- switch goarch {
341- case "amd64":
342- return "_darwin_amd64.zip"
343- case "arm64":
344- return "_darwin_arm64.zip"
345- }
346- case "windows":
347- switch goarch {
348- case "amd64":
349- return "_windows_amd64.zip"
350- case "arm64":
351- return "_windows_arm64.zip"
352- }
353- }
354-
355- return ""
356-}
357-
358-func compareVersions(a, b string) int {
359- aSplit := strings.Split(a, ".")
360- aTotal := len(aSplit)
361-
362- bSplit := strings.Split(b, ".")
363- bTotal := len(bSplit)
364-
365- limit := aTotal
366- if bTotal > aTotal {
367- limit = bTotal
368- }
369-
370- for i := 0; i < limit; i++ {
371- var x, y int
372-
373- if i < aTotal {
374- x, _ = strconv.Atoi(aSplit[i])
375- }
376-
377- if i < bTotal {
378- y, _ = strconv.Atoi(bSplit[i])
379- }
380-
381- if x < y {
382- return 1 // b is newer
383- }
384-
385- if x > y {
386- return -1 // a is newer
387- }
388- }
389-
390- return 0 // equal
391-}
392-
393-// note: not completely reliable as it may not work on all platforms
394-// but should at least provide a warning for the most common use cases
395-func isMaybeRunningInDocker() bool {
396- _, err := os.Stat("/.dockerenv")
397- return err == nil
398-}
399-
400-// note: untested
401-func isMaybeRunningInNixOS() bool {
402- _, err := os.Stat("/etc/NIXOS")
403- return err == nil
404-}
@@ -1,404 +0,0 @@
1-// Heavily inspired by Pocketbase's update command
2-// See: https://github.com/pocketbase/pocketbase/blob/master/plugins/ghupdate/ghupdate.go
3-package commands
4-
5-import (
6- "context"
7- "encoding/json"
8- "errors"
9- "fmt"
10- "io"
11- "log"
12- "net/http"
13- "os"
14- "path/filepath"
15- "runtime"
16- "strconv"
17- "strings"
18-
19- "github.com/AlecAivazis/survey/v2"
20- "github.com/fatih/color"
21- "github.com/pocketbase/pocketbase/core"
22- "github.com/pocketbase/pocketbase/tools/archive"
23- "github.com/spf13/cobra"
24-)
25-
26-// HttpClient is a base HTTP client interface (usually used for test purposes).
27-type HttpClient interface {
28- Do(req *http.Request) (*http.Response, error)
29-}
30-
31-// Config defines the config options of the ghupdate plugin.
32-//
33-// NB! This plugin is considered experimental and its config options may change in the future.
34-type Config struct {
35- // Owner specifies the account owner of the repository (default to "pocketbase").
36- Owner string
37-
38- // Repo specifies the name of the repository (default to "pocketbase").
39- Repo string
40-
41- // ArchiveExecutable specifies the name of the executable file in the release archive
42- // (default to "pocketbase"; an additional ".exe" check is also performed as a fallback).
43- ArchiveExecutable string
44-
45- // Optional context to use when fetching and downloading the latest release.
46- Context context.Context
47-
48- // The HTTP client to use when fetching and downloading the latest release.
49- // Defaults to `http.DefaultClient`.
50- HttpClient HttpClient
51-}
52-
53-// MustRegister registers the ghupdate plugin to the provided app instance
54-// and panic if it fails.
55-func MustRegister(app core.App, rootCmd *cobra.Command, config Config) {
56- if err := Register(app, rootCmd, config); err != nil {
57- panic(err)
58- }
59-}
60-
61-// Register registers the ghupdate plugin to the provided app instance.
62-func Register(app core.App, rootCmd *cobra.Command, config Config) error {
63- p := &plugin{
64- app: app,
65- currentVersion: rootCmd.Version,
66- config: config,
67- }
68-
69- if p.config.Owner == "" {
70- p.config.Owner = "pocketbase"
71- }
72-
73- if p.config.Repo == "" {
74- p.config.Repo = "pocketbase"
75- }
76-
77- if p.config.ArchiveExecutable == "" {
78- p.config.ArchiveExecutable = "pocketbase"
79- }
80-
81- if p.config.HttpClient == nil {
82- p.config.HttpClient = http.DefaultClient
83- }
84-
85- if p.config.Context == nil {
86- p.config.Context = context.Background()
87- }
88-
89- rootCmd.AddCommand(p.updateCmd())
90-
91- return nil
92-}
93-
94-type plugin struct {
95- app core.App
96- currentVersion string
97- config Config
98-}
99-
100-func (p *plugin) updateCmd() *cobra.Command {
101- var withBackup bool
102-
103- command := &cobra.Command{
104- Use: "update",
105- Short: "Automatically updates the current PocketBase executable with the latest available version",
106- // @todo remove after logs generalization
107- // prevents printing the error log twice
108- SilenceErrors: true,
109- SilenceUsage: true,
110- RunE: func(command *cobra.Command, args []string) error {
111- var needConfirm bool
112- if isMaybeRunningInDocker() {
113- needConfirm = true
114- color.Yellow("NB! It seems that you are in a Docker container.")
115- color.Yellow("The update command may not work as expected in this context because usually the version of the app is managed by the container image itself.")
116- } else if isMaybeRunningInNixOS() {
117- needConfirm = true
118- color.Yellow("NB! It seems that you are in a NixOS.")
119- color.Yellow("Due to the non-standard filesystem implementation of the environment, the update command may not work as expected.")
120- }
121-
122- if needConfirm {
123- confirm := false
124- prompt := &survey.Confirm{
125- Message: "Do you want to proceed with the update?",
126- }
127- survey.AskOne(prompt, &confirm)
128- if !confirm {
129- fmt.Println("The command has been cancelled.")
130- return nil
131- }
132- }
133-
134- return p.update(withBackup)
135- },
136- }
137-
138- command.PersistentFlags().BoolVar(
139- &withBackup,
140- "backup",
141- true,
142- "Creates a pb_data backup at the end of the update process",
143- )
144-
145- return command
146-}
147-
148-func (p *plugin) update(withBackup bool) error {
149- color.Yellow("Fetching release information...")
150-
151- latest, err := fetchLatestRelease(
152- p.config.Context,
153- p.config.HttpClient,
154- p.config.Owner,
155- p.config.Repo,
156- )
157- if err != nil {
158- return err
159- }
160-
161- if compareVersions(strings.TrimPrefix(p.currentVersion, "v"), strings.TrimPrefix(latest.Tag, "v")) <= 0 {
162- color.Green("You already have the latest PocketBase %s.", p.currentVersion)
163- return nil
164- }
165-
166- suffix := archiveSuffix(runtime.GOOS, runtime.GOARCH)
167- if suffix == "" {
168- return errors.New("unsupported platform")
169- }
170-
171- asset, err := latest.findAssetBySuffix(suffix)
172- if err != nil {
173- return err
174- }
175-
176- releaseDir := filepath.Join(p.app.DataDir(), core.LocalTempDirName)
177- defer os.RemoveAll(releaseDir)
178-
179- color.Yellow("Downloading %s...", asset.Name)
180-
181- // download the release asset
182- assetZip := filepath.Join(releaseDir, asset.Name)
183- if err := downloadFile(p.config.Context, p.config.HttpClient, asset.DownloadUrl, assetZip); err != nil {
184- return err
185- }
186-
187- color.Yellow("Extracting %s...", asset.Name)
188-
189- extractDir := filepath.Join(releaseDir, "extracted_"+asset.Name)
190- defer os.RemoveAll(extractDir)
191-
192- if err := archive.Extract(assetZip, extractDir); err != nil {
193- return err
194- }
195-
196- color.Yellow("Replacing the executable...")
197-
198- oldExec, err := os.Executable()
199- if err != nil {
200- return err
201- }
202- renamedOldExec := oldExec + ".old"
203- defer os.Remove(renamedOldExec)
204-
205- newExec := filepath.Join(extractDir, p.config.ArchiveExecutable)
206- if _, err := os.Stat(newExec); err != nil {
207- // try again with an .exe extension
208- newExec = newExec + ".exe"
209- if _, fallbackErr := os.Stat(newExec); fallbackErr != nil {
210- return fmt.Errorf("The executable in the extracted path is missing or it is inaccessible: %v, %v", err, fallbackErr)
211- }
212- }
213-
214- // rename the current executable
215- if err := os.Rename(oldExec, renamedOldExec); err != nil {
216- return fmt.Errorf("Failed to rename the current executable: %w", err)
217- }
218-
219- tryToRevertExecChanges := func() {
220- if revertErr := os.Rename(renamedOldExec, oldExec); revertErr != nil && p.app.IsDebug() {
221- log.Println(revertErr)
222- }
223- }
224-
225- // replace with the extracted binary
226- if err := os.Rename(newExec, oldExec); err != nil {
227- tryToRevertExecChanges()
228- return fmt.Errorf("Failed replacing the executable: %w", err)
229- }
230-
231- if withBackup {
232- color.Yellow("Creating pb_data backup...")
233-
234- backupName := fmt.Sprintf("@update_%s.zip", latest.Tag)
235- if err := p.app.CreateBackup(p.config.Context, backupName); err != nil {
236- tryToRevertExecChanges()
237- return err
238- }
239- }
240-
241- color.HiBlack("---")
242- color.Green("Update completed successfully! You can start the executable as usual.")
243-
244- return nil
245-}
246-
247-func fetchLatestRelease(
248- ctx context.Context,
249- client HttpClient,
250- owner string,
251- repo string,
252-) (*release, error) {
253- url := fmt.Sprintf("https://api.github.com/repos/%s/%s/releases/latest", owner, repo)
254-
255- req, err := http.NewRequestWithContext(ctx, "GET", url, nil)
256- if err != nil {
257- return nil, err
258- }
259-
260- res, err := client.Do(req)
261- if err != nil {
262- return nil, err
263- }
264- defer res.Body.Close()
265-
266- rawBody, err := io.ReadAll(res.Body)
267- if err != nil {
268- return nil, err
269- }
270-
271- // http.Client doesn't treat non 2xx responses as error
272- if res.StatusCode >= 400 {
273- return nil, fmt.Errorf(
274- "(%d) failed to fetch latest releases:\n%s",
275- res.StatusCode,
276- string(rawBody),
277- )
278- }
279-
280- result := &release{}
281- if err := json.Unmarshal(rawBody, result); err != nil {
282- return nil, err
283- }
284-
285- return result, nil
286-}
287-
288-func downloadFile(
289- ctx context.Context,
290- client HttpClient,
291- url string,
292- destPath string,
293-) error {
294- req, err := http.NewRequestWithContext(ctx, "GET", url, nil)
295- if err != nil {
296- return err
297- }
298-
299- res, err := client.Do(req)
300- if err != nil {
301- return err
302- }
303- defer res.Body.Close()
304-
305- // http.Client doesn't treat non 2xx responses as error
306- if res.StatusCode >= 400 {
307- return fmt.Errorf("(%d) failed to send download file request", res.StatusCode)
308- }
309-
310- // ensure that the dest parent dir(s) exist
311- if err := os.MkdirAll(filepath.Dir(destPath), os.ModePerm); err != nil {
312- return err
313- }
314-
315- dest, err := os.Create(destPath)
316- if err != nil {
317- return err
318- }
319- defer dest.Close()
320-
321- if _, err := io.Copy(dest, res.Body); err != nil {
322- return err
323- }
324-
325- return nil
326-}
327-
328-func archiveSuffix(goos, goarch string) string {
329- switch goos {
330- case "linux":
331- switch goarch {
332- case "amd64":
333- return "_linux_amd64.zip"
334- case "arm64":
335- return "_linux_arm64.zip"
336- case "arm":
337- return "_linux_armv7.zip"
338- }
339- case "darwin":
340- switch goarch {
341- case "amd64":
342- return "_darwin_amd64.zip"
343- case "arm64":
344- return "_darwin_arm64.zip"
345- }
346- case "windows":
347- switch goarch {
348- case "amd64":
349- return "_windows_amd64.zip"
350- case "arm64":
351- return "_windows_arm64.zip"
352- }
353- }
354-
355- return ""
356-}
357-
358-func compareVersions(a, b string) int {
359- aSplit := strings.Split(a, ".")
360- aTotal := len(aSplit)
361-
362- bSplit := strings.Split(b, ".")
363- bTotal := len(bSplit)
364-
365- limit := aTotal
366- if bTotal > aTotal {
367- limit = bTotal
368- }
369-
370- for i := 0; i < limit; i++ {
371- var x, y int
372-
373- if i < aTotal {
374- x, _ = strconv.Atoi(aSplit[i])
375- }
376-
377- if i < bTotal {
378- y, _ = strconv.Atoi(bSplit[i])
379- }
380-
381- if x < y {
382- return 1 // b is newer
383- }
384-
385- if x > y {
386- return -1 // a is newer
387- }
388- }
389-
390- return 0 // equal
391-}
392-
393-// note: not completely reliable as it may not work on all platforms
394-// but should at least provide a warning for the most common use cases
395-func isMaybeRunningInDocker() bool {
396- _, err := os.Stat("/.dockerenv")
397- return err == nil
398-}
399-
400-// note: untested
401-func isMaybeRunningInNixOS() bool {
402- _, err := os.Stat("/etc/NIXOS")
403- return err == nil
404-}

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 7

Tags 106

↑↓ moveenter openesc close