diff --git a/go.mod b/go.mod index 85606124c..0739022ca 100644 --- a/go.mod +++ b/go.mod @@ -7,6 +7,7 @@ require ( github.com/MakeNowJust/heredoc v1.0.0 github.com/briandowns/spinner v1.11.1 github.com/charmbracelet/glamour v0.2.1-0.20200724174618-1246d13c1684 + github.com/cli/browser v1.1.0 github.com/cli/oauth v0.8.0 github.com/cli/safeexec v1.0.0 github.com/cpuguy83/go-md2man/v2 v2.0.0 diff --git a/go.sum b/go.sum index b7808129f..6bc761e13 100644 --- a/go.sum +++ b/go.sum @@ -4,22 +4,31 @@ cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSR cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= +cloud.google.com/go v0.46.3 h1:AVXDdKsrtX33oR9fbCMu/+c1o8Ofjq6Ku/MInaLVg5Y= cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= +cloud.google.com/go/bigquery v1.0.1 h1:hL+ycaJpVE9M7nLoiXb/Pn10ENE2u+oddxbD8uu0ZVU= cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= +cloud.google.com/go/datastore v1.0.0 h1:Kt+gOPPp2LEPWp8CSfxhsM8ik9CcyE/gYu+0r+RnZvM= cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= +cloud.google.com/go/firestore v1.1.0 h1:9x7Bx0A9R5/M9jibeJeZWqjeVEIxYW9fZYqB9a70/bY= cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk= +cloud.google.com/go/pubsub v1.0.1 h1:W9tAK3E57P75u0XLLR82LZyw8VpAnhmyTOxW9qzmyj8= cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= +cloud.google.com/go/storage v1.0.0 h1:VV2nUM3wwLLGh9lSABFgZMjInyUbJeaRSE64WuAIQ+4= cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= +dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9 h1:VpgP7xuJadIUuKccphEpTJnWhS2jkQyMt6Y7pJCD7fY= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= github.com/AlecAivazis/survey/v2 v2.2.7 h1:5NbxkF4RSKmpywYdcRgUmos1o+roJY8duCLZXbVjoig= github.com/AlecAivazis/survey/v2 v2.2.7/go.mod h1:9DYvHgXtiXm6nCn+jXnOXLKbH+Yo9u8fAS/SduGdoPk= github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802 h1:1BDTz0u9nC3//pOCMdNH+CiXJVYJh5UQNCOBG7jbELc= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/MakeNowJust/heredoc v1.0.0 h1:cXCdzVdstXyiTqTvfqk9SDHpKNjxuom+DOlyEeQ4pzQ= github.com/MakeNowJust/heredoc v1.0.0/go.mod h1:mG5amYoWBHf8vpLOuehzbGGw0EHxpZZ6lCpQ4fNJ8LE= github.com/Netflix/go-expect v0.0.0-20180615182759-c93bf25de8e8 h1:xzYJEypr/85nBpB11F9br+3HUrpgb+fcm5iADzXXYEw= github.com/Netflix/go-expect v0.0.0-20180615182759-c93bf25de8e8/go.mod h1:oX5x61PbNXchhh0oikYAH+4Pcfw5LKv21+Jnpr6r6Pc= +github.com/OneOfOne/xxhash v1.2.2 h1:KMrpdQIwFcEqXDklaen+P1axHaj9BSKzvpUUfnHldSE= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= github.com/alecthomas/assert v0.0.0-20170929043011-405dbfeb8e38 h1:smF2tmSOzy2Mm+0dGI2AIUHY+w0BUc+4tn40djz7+6U= github.com/alecthomas/assert v0.0.0-20170929043011-405dbfeb8e38/go.mod h1:r7bzyVFMNntcxPZXK3/+KdruV1H5KSlyVY0gc+NgInI= @@ -27,36 +36,53 @@ github.com/alecthomas/chroma v0.7.3 h1:NfdAERMy+esYQs8OXk0I868/qDxxCEo7FMz1WIqMA github.com/alecthomas/chroma v0.7.3/go.mod h1:sko8vR34/90zvl5QdcUdvzL3J8NKjAUx9va9jPuFNoM= github.com/alecthomas/colour v0.0.0-20160524082231-60882d9e2721 h1:JHZL0hZKJ1VENNfmXvHbgYlbUOvpzYzvy2aZU5gXVeo= github.com/alecthomas/colour v0.0.0-20160524082231-60882d9e2721/go.mod h1:QO9JBoKquHd+jz9nshCh40fOfO+JzsoXy8qTHF68zU0= +github.com/alecthomas/kong v0.2.4 h1:Y0ZBCHAvHhTHw7FFJ2FzCAAG4pkbTgA45nc7BpMhDNk= github.com/alecthomas/kong v0.2.4/go.mod h1:kQOmtJgV+Lb4aj+I2LEn40cbtawdWJ9Y8QLq+lElKxE= github.com/alecthomas/repr v0.0.0-20180818092828-117648cd9897 h1:p9Sln00KOTlrYkxI1zYWl1QLnEqAqEARBEYa8FQnQcY= github.com/alecthomas/repr v0.0.0-20180818092828-117648cd9897/go.mod h1:xTS7Pm1pD1mvyM075QCDSRqH6qRLXylzS24ZTpRiSzQ= +github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc h1:cAKDfWh5VpdgMhJosfJnn5/FoN2SRZ4p7fJNX58YPaU= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= +github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf h1:qet1QNfXsQxTZqLG4oE62mJzwPIB8+Tee4RNCL9ulrY= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e h1:QEF07wC0T1rKkctt1RINW/+RMTVmiwxETico2l3gxJA= github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= +github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da h1:8GUt8eRujhVEGZFFEjBj46YV4rDjvGrNxb0KMWYkL2I= github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= +github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310 h1:BUAU3CGlLvorLI26FmByPp2eC2qla6E1Tw+scpcg/to= github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= +github.com/beorn7/perks v1.0.0 h1:HWo1m869IqiPhD389kmkxeTalrjNbbJTC8LXupb+sl0= github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= +github.com/bgentry/speakeasy v0.1.0 h1:ByYyxL9InA1OWqxJqqp2A5pYHUrCiAL6K3J+LKSsQkY= github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= +github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c h1:+0HFd5KSZ/mm3JmhmrDukiId5iR6w4+BdFtfSy4yWIc= github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84= github.com/briandowns/spinner v1.11.1 h1:OixPqDEcX3juo5AjQZAnFPbeUA0jvkp2qzB5gOZJ/L0= github.com/briandowns/spinner v1.11.1/go.mod h1:QOuQk7x+EaDASo80FEXwlwiA+j/PPIcX3FScO+3/ZPQ= +github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/charmbracelet/glamour v0.2.1-0.20200724174618-1246d13c1684 h1:YMyvXRstOQc7n6eneHfudVMbARSCmZ7EZGjtTkkeB3A= github.com/charmbracelet/glamour v0.2.1-0.20200724174618-1246d13c1684/go.mod h1:UA27Kwj3QHialP74iU6C+Gpc8Y7IOAKupeKMLLBURWM= -github.com/cli/browser v1.0.0 h1:RIleZgXrhdiCVgFBSjtWwkLPUCWyhhhN5k5HGSBt1js= github.com/cli/browser v1.0.0/go.mod h1:IEWkHYbLjkhtjwwWlwTHW2lGxeS5gezEQBMLTwDHf5Q= +github.com/cli/browser v1.1.0 h1:xOZBfkfY9L9vMBgqb1YwRirGu6QFaQ5dP/vXt5ENSOY= +github.com/cli/browser v1.1.0/go.mod h1:HKMQAt9t12kov91Mn7RfZxyJQQgWgyS/3SZswlZ5iTI= github.com/cli/oauth v0.8.0 h1:YTFgPXSTvvDUFti3tR4o6q7Oll2SnQ9ztLwCAn4/IOA= github.com/cli/oauth v0.8.0/go.mod h1:qd/FX8ZBD6n1sVNQO3aIdRxeu5LGw9WhKnYhIIoC2A4= github.com/cli/safeexec v1.0.0 h1:0VngyaIyqACHdcMNWfo6+KdUYnqEr2Sg+bSP1pdF+dI= github.com/cli/safeexec v1.0.0/go.mod h1:Z/D4tTN8Vs5gXYHDCbaM1S/anmEDnJb1iW0+EJ5zx3Q= github.com/cli/shurcooL-graphql v0.0.0-20200707151639-0f7232a2bf7e h1:aq/1jlmtZoS6nlSp3yLOTZQ50G+dzHdeRNENgE/iBew= github.com/cli/shurcooL-graphql v0.0.0-20200707151639-0f7232a2bf7e/go.mod h1:it23pLwxmz6OyM6I5O0ATIXQS1S190Nas26L5Kahp4U= +github.com/client9/misspell v0.3.4 h1:ta993UF76GwbvJcIo3Y68y/M3WxlpEHPWIGDkJYwzJI= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/coreos/bbolt v1.3.2 h1:wZwiHHUieZCquLkDL0B8UhzreNWsPHooDAG3q34zk0s= github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= +github.com/coreos/etcd v3.3.13+incompatible h1:8F3hqu9fGYLBifCmRCJsicFqDx/D68Rt3q1JMazcgBQ= github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= +github.com/coreos/go-semver v0.3.0 h1:wkHLiw0WNATZnSG7epLsujiMCgPAc9xhjJ4tgnAxmfM= github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= +github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e h1:Wf6HqHfScWJN9/ZjdUKyjop4mf3Qdd+1TvvltAvM3m8= github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= +github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f h1:lBNOc5arjvs8E5mO2tbpBpLoyyu8B6e44T7hJy6potg= github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= github.com/cpuguy83/go-md2man/v2 v2.0.0 h1:EoUDS0afbrsXAZ9YQ9jdu/mZ2sXgT1/2yyNng4PGlyM= github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= @@ -65,7 +91,9 @@ github.com/danwakefield/fnmatch v0.0.0-20160403171240-cbb64ac3d964/go.mod h1:Xd9 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM= github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= +github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954 h1:RMLoZVzv4GliuWafOuPuQDKSm1SJph7uCRnnS61JAn4= github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= github.com/dlclark/regexp2 v1.2.0 h1:8sAhBGEM0dRWogWqWyQeIJnxjWO6oIjl8FKqREDsGfk= github.com/dlclark/regexp2 v1.2.0/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc= @@ -73,21 +101,31 @@ github.com/enescakir/emoji v1.0.0 h1:W+HsNql8swfCQFtioDGDHCHri8nudlK1n5p2rHCJoog github.com/enescakir/emoji v1.0.0/go.mod h1:Bt1EKuLnKDTYpLALApstIkAjdDrS/8IAgTkKp+WKFD0= github.com/fatih/color v1.7.0 h1:DkWD4oS2D8LGGgTQ6IvwJJXSL5Vp2ffcQg58nFV38Ys= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= +github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/gabriel-vasile/mimetype v1.1.2 h1:gaPnPcNor5aZSVCJVSGipcpbgMWiAAj9z182ocSGbHU= github.com/gabriel-vasile/mimetype v1.1.2/go.mod h1:6CDPel/o/3/s4+bp6kIbsWATq8pmgOisOPG40CJa6To= +github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1 h1:QbL/5oDUmRBzO9/Z7Seo6zf912W/a6Sr4Eu0G/3Jho0= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= +github.com/go-kit/kit v0.8.0 h1:Wz+5lgoB0kkuqLEc6NVmwRknTKP6dTGbSqvhZtBI/j0= github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= +github.com/go-logfmt/logfmt v0.4.0 h1:MP4Eh7ZCb31lleYCFuwm0oe4/YGak+5l1vA2NOE80nA= github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= +github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= +github.com/gogo/protobuf v1.2.1 h1:/s5zKNz0uPFCZ5hddgPdo2TK2TVrUNMn0OOX8/aZMTE= github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef h1:veQD95Isof8w9/WXiA+pa3tz3fJXkt5B7QaRBrM62gk= github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.3.1 h1:qGJ6qTW+x6xX/my+8YUVl4WNpX9B7+/l2tRsHGZ7f2s= github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1 h1:YF8+flBXS5eO826T4nzqPrxfhQThhXl0YzfuUPu4SBg= @@ -95,6 +133,7 @@ github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5y github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/btree v1.0.0 h1:0udJVsspx3VBr5FwtLhQQtuAsVc79tTq0ocGIPAU6qo= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/go-cmp v0.2.0 h1:+dTQ8DZQJz0Mb/HjFlkptS1FeQ4cWSnN941F8aEG4SQ= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= @@ -103,41 +142,69 @@ github.com/google/go-cmp v0.5.2 h1:X2ev0eStA3AbceY54o37/0PQ/UWqKEiiO2dKL5OPaFM= github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/goterm v0.0.0-20190703233501-fc88cf888a3f h1:5CjVwnuUcp5adK4gmY6i72gpVFVnZDP2h5TmPScB6u4= github.com/google/goterm v0.0.0-20190703233501-fc88cf888a3f/go.mod h1:nOFQdrUlIlx6M6ODdSpBj1NVA+VgLC6kmw60mkw34H4= +github.com/google/martian v2.1.0+incompatible h1:/CP5g8u/VJHijgedC/Legn3BAbAaWPgecwXBIDzw5no= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20190515194954-54271f7e092f h1:Jnx61latede7zDD3DiiP4gmNz33uK0U5HDUaF0a/HVQ= github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/renameio v0.1.0 h1:GOZbcHa3HfsPKPlmyPyN2KEohoMXOhdMbHrvbpl2QaA= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4= github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= +github.com/googleapis/gax-go/v2 v2.0.5 h1:sjZBwGj9Jlw33ImPtvFviGYvseOtDM7hkSKB7+Tv3SM= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= +github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= +github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc= github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/graph-gophers/graphql-go v0.0.0-20200622220639-c1d9693c95a6 h1:s0NiTDKy3CsD/GX4MoCaEgDFTxVV4dqlOHn/5pSrNIk= github.com/graph-gophers/graphql-go v0.0.0-20200622220639-c1d9693c95a6/go.mod h1:9CQHMSxwO4MprSdzoIEobiHpoLtHm77vfxsvsIN5Vuc= +github.com/grpc-ecosystem/go-grpc-middleware v1.0.0 h1:Iju5GlWwrvL6UBg4zJJt3btmonfrMlCDdsejg4CZE7c= github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= +github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 h1:Ovs26xHkKqVztRpIrF/92BcuyuQ/YW4NSIpoGtfXNho= github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= +github.com/grpc-ecosystem/grpc-gateway v1.9.0 h1:bM6ZAFZmc/wPFaRDi0d5L7hGEZEx/2u+Tmr2evNHDiI= github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= +github.com/hashicorp/consul/api v1.1.0 h1:BNQPM9ytxj6jbjjdRPioQ94T6YXriSopn0i8COv6SRA= github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q= +github.com/hashicorp/consul/sdk v0.1.1 h1:LnuDWGNsoajlhGyHJvuWW6FVqRl8JOTPqS6CPTsYjhY= github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= +github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/go-cleanhttp v0.5.1 h1:dH3aiDG9Jvb5r5+bYHsikaOUIpcM0xvgMXVoDkXMzJM= github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= +github.com/hashicorp/go-immutable-radix v1.0.0 h1:AKDB1HM5PWEA7i4nhcpwOrO2byshxBjXVn/J/3+z5/0= github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= +github.com/hashicorp/go-msgpack v0.5.3 h1:zKjpN5BK/P5lMYrLmBHdBULWbJ0XpYR+7NGzqkZzoD4= github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= +github.com/hashicorp/go-multierror v1.0.0 h1:iVjPR7a6H0tWELX5NxNe7bYopibicUzc7uPribsnS6o= github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= +github.com/hashicorp/go-rootcerts v1.0.0 h1:Rqb66Oo1X/eSV1x66xbDccZjhJigjg0+e82kpwzSwCI= github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU= +github.com/hashicorp/go-sockaddr v1.0.0 h1:GeH6tui99pF4NJgfnhp+L6+FfobzVW3Ah46sLo0ICXs= github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU= +github.com/hashicorp/go-syslog v1.0.0 h1:KaodqZuhUoZereWVIYmpUgZysurB1kBLX2j0MwMrUAE= github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go-uuid v1.0.1 h1:fv1ep09latC32wFoVwnqcnKJGnMSdBanPczbHAYm1BE= github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/go-version v1.2.1 h1:zEfKbn2+PDgroKdiOzqiE8rsmLqU2uwi5PB5pBJ3TkI= github.com/hashicorp/go-version v1.2.1/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= +github.com/hashicorp/go.net v0.0.1 h1:sNCoNyDEvN1xa+X0baata4RdcpKwcMS6DH+xwfqPgjw= github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/golang-lru v0.5.1 h1:0hERBMJE1eitiLkihrMvRVBYAkpHzc/J3QdDN+dAcgU= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= +github.com/hashicorp/logutils v1.0.0 h1:dLEQVugN8vlakKOUE3ihGLTZJRB4j+M2cdTm/ORI65Y= github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= +github.com/hashicorp/mdns v1.0.0 h1:WhIgCr5a7AaVH6jPUwjtRuuE7/RDufnUvzIr48smyxs= github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ= +github.com/hashicorp/memberlist v0.1.3 h1:EmmoJme1matNzb+hMpDuR/0sbJSUisxyqBGG676r31M= github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= +github.com/hashicorp/serf v0.8.2 h1:YZ7UKsJv+hKjqGVUUbtE3HNj79Eln2oQ75tniF6iPt0= github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= github.com/henvic/httpretty v0.0.6 h1:JdzGzKZBajBfnvlMALXXMVQWxWMF/ofTy8C3/OSUTxs= github.com/henvic/httpretty v0.0.6/go.mod h1:X38wLjWXHkXT7r2+uK8LjCMne9rsuNaBLJ+5cU2/Pmo= @@ -147,21 +214,31 @@ github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NH github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/itchyny/astgen-go v0.0.0-20210113000433-0da0671862a3 h1:l7vogWrq+zj8v5t/G69/eT13nAGs2H7cq+CI2nlnKdk= github.com/itchyny/astgen-go v0.0.0-20210113000433-0da0671862a3/go.mod h1:296z3W7Xsrp2mlIY88ruDKscuvrkL6zXCNRtaYVshzw= +github.com/itchyny/go-flags v1.5.0 h1:Z5q2ist2sfDjDlExVPBrMqlsEDxDR2h4zuOElB0OEYI= github.com/itchyny/go-flags v1.5.0/go.mod h1:lenkYuCobuxLBAd/HGFE4LRoW8D3B6iXRQfWYJ+MNbA= github.com/itchyny/gojq v0.12.1 h1:pQJrG8LXgEbZe9hvpfjKg7UlBfieQQydIw3YQq+7WIA= github.com/itchyny/gojq v0.12.1/go.mod h1:Y5Lz0qoT54ii+ucY/K3yNDy19qzxZvWNBMBpKUDQR/4= github.com/itchyny/timefmt-go v0.1.1 h1:rLpnm9xxb39PEEVzO0n4IRp0q6/RmBc7Dy/rE4HrA0U= github.com/itchyny/timefmt-go v0.1.1/go.mod h1:0osSSCQSASBJMsIZnhAaF1C2fCBTJZXrnj37mG8/c+A= +github.com/jonboulle/clockwork v0.1.0 h1:VKV+ZcuP6l3yW9doeqz6ziZGgcynBVQO+obU0+0hcPo= github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= +github.com/json-iterator/go v1.1.6 h1:MrUvLMLTMxbqFJ9kzlvat/rYZqZnW3u4wkLzWTaFwKs= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= +github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024 h1:rBMNdlhTLzJjJSDIjNEXX1Pz3Hmwmz91v+zycvx9PJc= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= +github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= +github.com/julienschmidt/httprouter v1.2.0 h1:TDTW5Yz1mjftljbcKqRcrYhd4XeOoI98t+9HbQbYf7g= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs= github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8= +github.com/kisielk/errcheck v1.1.0 h1:ZqfnKyx9KGpRcW04j5nnPDgRgoXUeLh2YFBeFzphcA0= github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= +github.com/kisielk/gotool v1.0.0 h1:AV2c/EiW3KqPNT9ZKl07ehoAGi4C5/01Cfbblndcapg= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/konsorten/go-windows-terminal-sequences v1.0.1 h1:mweAR1A6xJ3oS2pRaGiHgQ4OO8tzTaLawm8vnODuwDk= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515 h1:T+h1c/A9Gawja4Y9mFVWj2vyii2bbUNDw3kt9VxK2EY= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= @@ -172,6 +249,7 @@ github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/lucasb-eyer/go-colorful v1.0.3 h1:QIbQXiugsb+q10B+MI+7DI1oQLdmnep86tWFlaaUAac= github.com/lucasb-eyer/go-colorful v1.0.3/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= +github.com/magiconair/properties v1.8.1 h1:ZC2Vc7/ZFkGmsVC9KvOjumD+G5lXy2RtTKyzRKO2BQ4= github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= github.com/mattn/go-colorable v0.1.2 h1:/bC9yWikZXAL9uJdulbSfyVNIR3n3trXl+v8+1sx8mU= @@ -188,6 +266,7 @@ github.com/mattn/go-runewidth v0.0.7 h1:Ei8KR0497xHyKJPAv59M1dkC+rOZCMBJ+t3fZ+tw github.com/mattn/go-runewidth v0.0.7/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0= github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= +github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b h1:j7+1HpAFS1zy5+Q4qx1fWh90gTKwiN4QCGoY9TWyyO4= github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE= @@ -195,17 +274,25 @@ github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d h1:5PJl274Y63IEHC+7izoQ github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE= github.com/microcosm-cc/bluemonday v1.0.2 h1:5lPfLTTAvAbtS0VqT+94yOtFnGfUWYyx0+iToC3Os3s= github.com/microcosm-cc/bluemonday v1.0.2/go.mod h1:iVP4YcDBq+n/5fb23BhYFvIMq/leAFZyRl6bYmGDlGc= +github.com/miekg/dns v1.0.14 h1:9jZdLNd/P4+SfEJ0TNyxYpsK8N4GtfylBLqtbYN1sbA= github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= +github.com/mitchellh/cli v1.0.0 h1:iGBIsUe3+HZ/AD/Vd7DErOt5sU9fa8Uj7A2s1aggv1Y= github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/go-testing-interface v1.0.0 h1:fzU/JVNcaqHQEcVFAKeR41fkiLdIPrefOvVG1VZ96U0= github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= +github.com/mitchellh/gox v0.4.0 h1:lfGJxY7ToLJQjHHwi0EX6uYBdK78egf954SQl13PQJc= github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg= +github.com/mitchellh/iochan v1.0.0 h1:C+X3KsSTLFVBr/tK1eYN/vs4rJcvsiLU338UhYPJWeY= github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY= github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI= github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/muesli/reflow v0.1.0 h1:oQdpLfO56lr5pgLvqD0TcjW85rDjSYSBVdiG1Ch1ddM= github.com/muesli/reflow v0.1.0/go.mod h1:I9bWAt7QTg/que/qmUCJBGlj7wEq8OAFBjPNjc6xK4I= @@ -213,36 +300,52 @@ github.com/muesli/termenv v0.6.0 h1:zxvzTBmo4ZcxhNGGWeMz+Tttm51eF5bmPjfy4MCRYlk= github.com/muesli/termenv v0.6.0/go.mod h1:SohX91w6swWA4AYU+QmPx+aSgXhWO0juiyID9UZmbpA= github.com/muesli/termenv v0.7.4 h1:/pBqvU5CpkY53tU0vVn+xgs2ZTX63aH5nY+SSps5Xa8= github.com/muesli/termenv v0.7.4/go.mod h1:pZ7qY9l3F7e5xsAOS0zCew2tME+p7bWeBkotCEcIIcc= +github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223 h1:F9x/1yl3T2AeKLr2AMdilSD8+f9bvMnNN8VS5iDtovc= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= +github.com/oklog/ulid v1.3.1 h1:EGfNDEx6MqHz8B3uNV6QAib1UR2Lm97sHi3ocA6ESJ4= github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= github.com/olekukonko/tablewriter v0.0.4 h1:vHD/YYe1Wolo78koG299f7V/VAS08c6IpCLn+Ejf/w8= github.com/olekukonko/tablewriter v0.0.4/go.mod h1:zq6QwlOf5SlnkVbMSr5EoBv3636FWnp+qbPhuoO21uA= +github.com/opentracing/opentracing-go v1.1.0 h1:pWlfV3Bxv7k65HYwkikxat0+s3pV4bsqf19k25Ur8rU= github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= +github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c h1:Lgl0gzECD8GnQ5QCWA8o6BtfL6mDH5rQgM4/fX3avOs= github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= +github.com/pelletier/go-toml v1.2.0 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181zc= github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/posener/complete v1.1.1 h1:ccV59UEOTzVDnDUEFdT95ZzHVZ+5+158q8+SJb2QV5w= github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= +github.com/prometheus/client_golang v0.9.3 h1:9iH4JKXLzFbOAdtqv/a+j8aewx2Y8lAjAydhbaScPF8= github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= +github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90 h1:S/YWwWx/RA8rT8tKFRuGUZhuA90OyIBpPCXkcbwU8DE= github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= +github.com/prometheus/common v0.4.0 h1:7etb9YClo3a6HjLzfl6rIQaU+FDfi0VSX39io3aQ+DM= github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084 h1:sofwID9zm4tzrgykg80hfFph1mryUeLRsUfoocVVmRY= github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= +github.com/prometheus/tsdb v0.7.1 h1:YZcsG11NqnK4czYLrWd9mpEuAJIHVQLwdrleYfszMAA= github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= github.com/rivo/uniseg v0.1.0 h1:+2KBaVoUmb9XzDsrx/Ct0W/EYOSFf/nWTauy++DprtY= github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= +github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af h1:gu+uRPtBe88sKxUCEXRoeCvVG90TJmwhiqRpvdhQFng= github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= +github.com/rogpeppe/go-internal v1.3.0 h1:RR9dF3JtopPvtkroDZuVD7qquD0bnHlKSqaQhgwt8yk= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/russross/blackfriday/v2 v2.0.1 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0RK8m9o+Q= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f h1:UFr9zpz4xgTnIE5yIMtWAMngCdZ9p/+q6lTbgelo80M= github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= +github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529 h1:nn5Wsu0esKSJiIVhscUtVbo7ada43DJhG55ua/hjS5I= github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= github.com/sergi/go-diff v1.0.0 h1:Kpca3qRNrduNnOQeazBd0ysaKrUJiIuISHxogkT9RPQ= github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= @@ -250,20 +353,29 @@ github.com/shurcooL/githubv4 v0.0.0-20200928013246-d292edc3691b h1:0/ecDXh/HTHRt github.com/shurcooL/githubv4 v0.0.0-20200928013246-d292edc3691b/go.mod h1:hAF0iLZy4td2EX+/8Tw+4nodhlMrwN3HupfaXj3zkGo= github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= +github.com/sirupsen/logrus v1.2.0 h1:juTguoYk5qI21pwyTXY3B3Y5cOTH3ZUyZCg1v/mihuo= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= +github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= +github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s= github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= +github.com/soheilhy/cmux v0.1.4 h1:0HKaf1o97UwFjHH9o5XsHUOF+tqmdA7KEzXLpiyaw0E= github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= +github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72 h1:qLC7fQah7D6K1B0ujays3HV9gkFtllcxhzImRR7ArPQ= github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= +github.com/spf13/afero v1.1.2 h1:m8/z1t7/fwjysjQRYbP0RD+bUIF/8tJwPdEZsI83ACI= github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= +github.com/spf13/cast v1.3.0 h1:oget//CVOEoFewqQxwr0Ej5yjygnqGkvggSE/gB35Q8= github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= github.com/spf13/cobra v1.1.1 h1:KfztREH0tPxJJ+geloSLaAkaPkr4ki2Er5quFV1TDo4= github.com/spf13/cobra v1.1.1/go.mod h1:WnodtKOvamDL/PwE2M4iKs8aMDBZ5Q5klgD3qfVJQMI= +github.com/spf13/jwalterweatherman v1.0.0 h1:XHEdyB+EcvlqZamSM4ZOMGlc93t6AcsBEu9Gc1vn7yk= github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg= github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/viper v1.7.0 h1:xVKxvI7ouOI5I+U9s2eeiUfMaWBVoXA3AWskkrqK0VM= github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1 h1:2vfRuCMp5sSVIDSqO8oNnWJq7mPa6KVP3iPIwFBuy8A= @@ -274,16 +386,24 @@ github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0 github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s= github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= +github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5 h1:LnC5Kc/wtumK+WB441p7ynQJzVuNRJiqddSIE3IlSEQ= github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= +github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2 h1:eY9dn8+vbi4tKz5Qo6v2eYzo7kUS51QINcR5jNpbZS8= github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= github.com/yuin/goldmark v1.2.0 h1:WOOcyaJPlzb8fZ8TloxFe8QZkhOOJx87leDa9MIT9dc= github.com/yuin/goldmark v1.2.0/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +go.etcd.io/bbolt v1.3.2 h1:Z/90sZLPOeCy2PwprqkFa25PdkusRzaj9P8zm/KNyvk= go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= +go.opencensus.io v0.22.0 h1:C9hSCOW830chIVkdja34wa6Ky+IzWllkUinR+BtRZd4= go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= +go.uber.org/atomic v1.4.0 h1:cxzIVoETapQEqDhQu3QfnvXAV4AlzcvUCxkVUFw3+EU= go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= +go.uber.org/multierr v1.1.0 h1:HoEmRHQPVSqub6w2z2d2EOVs2fjyFRGyofhKuyDq0QI= go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= +go.uber.org/zap v1.10.0 h1:ORx85nbTijNz8ljznvCMR1ZBIPKFn3jQrag10X2AsuM= go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= @@ -300,8 +420,10 @@ golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= +golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136 h1:A1gGSx58LAGVHUUsOf7IiR0u8Xb6W51gRwfDBhkdcaw= golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= +golang.org/x/image v0.0.0-20190802002840-cff245a6509b h1:+qEpEAPhDZ1o0x3tHzZTQDArnOixOzGD9HUJfcg0mb4= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= @@ -309,10 +431,13 @@ golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTk golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190930215403-16217165b5de h1:5hukYrvBGR8/eNkX5mdUezrA6JiaEZDtJb9Ei+1LlBs= golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= +golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028 h1:4+4C/Iv2U4fMZBiMCc98MG1In4gJY5YRhtpDNeDeHWs= golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= +golang.org/x/mod v0.1.0 h1:sfUMP1Gu8qASkorDVjnMuvgJzwFbTZSeXFiGBYAVdl4= golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -367,6 +492,8 @@ golang.org/x/sys v0.0.0-20200413165638-669c56c373c4 h1:opSr2sbRXk5X5/givKrrKj9HX golang.org/x/sys v0.0.0-20200413165638-669c56c373c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210113181707-4bcb84eeeb78 h1:nVuTkr9L6Bq62qpUqKo/RnZCFfzDBL0bYo6w9OJUqZY= golang.org/x/sys v0.0.0-20210113181707-4bcb84eeeb78/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210319071255-635bc2c9138d h1:jbzgAvDZn8aEnytae+4ou0J0GwFZoHR0hOrTg4qH8GA= +golang.org/x/sys v0.0.0-20210319071255-635bc2c9138d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -374,6 +501,7 @@ golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.4 h1:0YWbFKbhXG/wIiuHDSKpS0Iy7FSA+u45VtBMfQcFTTc= golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20190308202827-9d24e82272b4 h1:SvFZT6jyqRaOeXpc5h/JSfZenJ2O330aBsf7JfSUXmQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -391,6 +519,7 @@ golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgw golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc h1:NCy3Ohtk6Iny5V/reW2Ktypo4zIpWBdRJ1uFMjBxdg8= golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= @@ -399,6 +528,7 @@ google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEt google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.13.0 h1:Q3Ui3V3/CVinFWFiW39Iw0kMuVrRzYX0wN6OPFp0lTA= google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= google.golang.org/appengine v1.1.0 h1:igQkv0AAhEIvTEpD5LIpAfav2eeVO9HBTjvKHVJPRSs= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= @@ -414,17 +544,23 @@ google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRn google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= +google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a h1:Ob5/580gVHBJZgXnff1cZDbG+xLtMVE5mDRTe+nIsX4= google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= +google.golang.org/grpc v1.21.1 h1:j6XxA85m/6txkUCHvzlV5f+HBNl/1r5cZ2A/3IEFOO8= google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= +gopkg.in/alecthomas/kingpin.v2 v2.2.6 h1:jMFz6MfLP0/4fUyZle81rXUoxOBFi19VUFKVDOQfozc= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/errgo.v2 v2.1.0 h1:0vLT13EuvQ0hNvakwLuFZ/jYrLp5F3kcWHXdRggjCE8= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/ini.v1 v1.51.0 h1:AQvPpx3LzTDM0AjnIRlVFwFFGC+npRopjZxLJj6gdno= gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/resty.v1 v1.12.0 h1:CuXP0Pjfw9rOuY6EP+UvtNvt5DSqHpIxILZKT/quCZI= gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= @@ -437,5 +573,7 @@ gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.1-2019.2.3 h1:3JgtbtFHMiCmsznwGVTUWbgGov+pVqnlf1dEJTNAXeM= honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= +rsc.io/binaryregexp v0.2.0 h1:HfqmD5MEmC0zvwBuF187nq9mdnXjXsSivRiXN7SmRkE= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= diff --git a/internal/authflow/flow.go b/internal/authflow/flow.go index 41e06bc21..aa878bd32 100644 --- a/internal/authflow/flow.go +++ b/internal/authflow/flow.go @@ -10,7 +10,7 @@ import ( "github.com/cli/cli/api" "github.com/cli/cli/internal/ghinstance" - "github.com/cli/cli/pkg/browser" + "github.com/cli/cli/pkg/cmdutil" "github.com/cli/cli/pkg/iostreams" "github.com/cli/oauth" ) @@ -93,11 +93,9 @@ func authFlow(oauthHost string, IO *iostreams.IOStreams, notice string, addition fmt.Fprintf(w, "- %s to open %s in your browser... ", cs.Bold("Press Enter"), oauthHost) _ = waitForEnter(IO.In) - browseCmd, err := browser.Command(url) - if err != nil { - return err - } - if err := browseCmd.Run(); err != nil { + // FIXME: read the browser from cmd Factory rather than recreating it + browser := cmdutil.NewBrowser(os.Getenv("BROWSER"), IO.Out, IO.ErrOut) + if err := browser.Browse(url); err != nil { fmt.Fprintf(w, "%s Failed opening a web browser at %s\n", cs.Red("!"), url) fmt.Fprintf(w, " %s\n", err) fmt.Fprint(w, " Please try entering the URL in your browser manually\n") diff --git a/pkg/browser/browser.go b/pkg/browser/browser.go deleted file mode 100644 index ec4c59e6c..000000000 --- a/pkg/browser/browser.go +++ /dev/null @@ -1,80 +0,0 @@ -package browser - -import ( - "os" - "os/exec" - "runtime" - "strings" - - "github.com/cli/safeexec" - "github.com/google/shlex" -) - -// BrowserEnv simply returns the $BROWSER environment variable -func FromEnv() string { - return os.Getenv("BROWSER") -} - -// Command produces an exec.Cmd respecting runtime.GOOS and $BROWSER environment variable -func Command(url string) (*exec.Cmd, error) { - launcher := FromEnv() - if launcher != "" { - return FromLauncher(launcher, url) - } - return ForOS(runtime.GOOS, url), nil -} - -// ForOS produces an exec.Cmd to open the web browser for different OS -func ForOS(goos, url string) *exec.Cmd { - exe := "open" - var args []string - switch goos { - case "darwin": - args = append(args, url) - case "windows": - exe, _ = lookPath("cmd") - r := strings.NewReplacer("&", "^&") - args = append(args, "/c", "start", r.Replace(url)) - default: - exe = linuxExe() - args = append(args, url) - } - - cmd := exec.Command(exe, args...) - cmd.Stderr = os.Stderr - return cmd -} - -// FromLauncher parses the launcher string based on shell splitting rules -func FromLauncher(launcher, url string) (*exec.Cmd, error) { - args, err := shlex.Split(launcher) - if err != nil { - return nil, err - } - - exe, err := lookPath(args[0]) - if err != nil { - return nil, err - } - - args = append(args, url) - cmd := exec.Command(exe, args[1:]...) - cmd.Stderr = os.Stderr - return cmd, nil -} - -func linuxExe() string { - exe := "xdg-open" - - _, err := lookPath(exe) - if err != nil { - _, err := lookPath("wslview") - if err == nil { - exe = "wslview" - } - } - - return exe -} - -var lookPath = safeexec.LookPath diff --git a/pkg/browser/browser_test.go b/pkg/browser/browser_test.go deleted file mode 100644 index 6a24327da..000000000 --- a/pkg/browser/browser_test.go +++ /dev/null @@ -1,75 +0,0 @@ -package browser - -import ( - "errors" - "reflect" - "testing" -) - -func TestForOS(t *testing.T) { - type args struct { - goos string - url string - } - tests := []struct { - name string - args args - exe string - want []string - }{ - { - name: "macOS", - args: args{ - goos: "darwin", - url: "https://example.com/path?a=1&b=2", - }, - want: []string{"open", "https://example.com/path?a=1&b=2"}, - }, - { - name: "Linux", - args: args{ - goos: "linux", - url: "https://example.com/path?a=1&b=2", - }, - exe: "xdg-open", - want: []string{"xdg-open", "https://example.com/path?a=1&b=2"}, - }, - { - name: "WSL", - args: args{ - goos: "linux", - url: "https://example.com/path?a=1&b=2", - }, - exe: "wslview", - want: []string{"wslview", "https://example.com/path?a=1&b=2"}, - }, - { - name: "Windows", - args: args{ - goos: "windows", - url: "https://example.com/path?a=1&b=2&c=3", - }, - exe: "cmd", - want: []string{"cmd", "/c", "start", "https://example.com/path?a=1^&b=2^&c=3"}, - }, - } - for _, tt := range tests { - origLookPath := lookPath - lookPath = func(file string) (string, error) { - if file == tt.exe { - return file, nil - } else { - return "", errors.New("not found") - } - } - defer func() { - lookPath = origLookPath - }() - - t.Run(tt.name, func(t *testing.T) { - if cmd := ForOS(tt.args.goos, tt.args.url); !reflect.DeepEqual(cmd.Args, tt.want) { - t.Errorf("ForOS() = %v, want %v", cmd.Args, tt.want) - } - }) - } -} diff --git a/pkg/cmd/factory/default.go b/pkg/cmd/factory/default.go index 04b9104d7..e9023c852 100644 --- a/pkg/cmd/factory/default.go +++ b/pkg/cmd/factory/default.go @@ -69,5 +69,6 @@ func New(appVersion string) *cmdutil.Factory { return currentBranch, nil }, Executable: ghExecutable, + Browser: cmdutil.NewBrowser(os.Getenv("BROWSER"), io.Out, io.ErrOut), } } diff --git a/pkg/cmd/gist/create/create.go b/pkg/cmd/gist/create/create.go index dcee4e4e4..645638bca 100644 --- a/pkg/cmd/gist/create/create.go +++ b/pkg/cmd/gist/create/create.go @@ -23,6 +23,10 @@ import ( "github.com/spf13/cobra" ) +type browser interface { + Browse(string) error +} + type CreateOptions struct { IO *iostreams.IOStreams @@ -34,6 +38,7 @@ type CreateOptions struct { Config func() (config.Config, error) HttpClient func() (*http.Client, error) + Browser browser } func NewCmdCreate(f *cmdutil.Factory, runF func(*CreateOptions) error) *cobra.Command { @@ -41,6 +46,7 @@ func NewCmdCreate(f *cmdutil.Factory, runF func(*CreateOptions) error) *cobra.Co IO: f.IOStreams, Config: f.Config, HttpClient: f.HttpClient, + Browser: f.Browser, } cmd := &cobra.Command{ @@ -156,7 +162,7 @@ func createRun(opts *CreateOptions) error { if opts.WebMode { fmt.Fprintf(opts.IO.Out, "Opening %s in your browser.\n", utils.DisplayURL(gist.HTMLURL)) - return utils.OpenInBrowser(gist.HTMLURL) + return opts.Browser.Browse(gist.HTMLURL) } fmt.Fprintln(opts.IO.Out, gist.HTMLURL) diff --git a/pkg/cmd/gist/create/create_test.go b/pkg/cmd/gist/create/create_test.go index 2b4d73588..0c2d0f39f 100644 --- a/pkg/cmd/gist/create/create_test.go +++ b/pkg/cmd/gist/create/create_test.go @@ -172,6 +172,7 @@ func Test_createRun(t *testing.T) { wantStderr string wantParams map[string]interface{} wantErr bool + wantBrowse string }{ { name: "public", @@ -265,6 +266,7 @@ func Test_createRun(t *testing.T) { wantOut: "Opening gist.github.com/aa5a315d61ae9438b18d in your browser.\n", wantStderr: "- Creating gist fixture.txt\n✓ Created gist fixture.txt\n", wantErr: false, + wantBrowse: "https://gist.github.com/aa5a315d61ae9438b18d", wantParams: map[string]interface{}{ "description": "", "updated_at": "0001-01-01T00:00:00Z", @@ -296,11 +298,11 @@ func Test_createRun(t *testing.T) { io, stdin, stdout, stderr := iostreams.Test() tt.opts.IO = io - cs, teardown := run.Stub() + browser := &cmdutil.TestBrowser{} + tt.opts.Browser = browser + + _, teardown := run.Stub() defer teardown(t) - if tt.opts.WebMode { - cs.Register(`https://gist\.github\.com/aa5a315d61ae9438b18d$`, 0, "") - } t.Run(tt.name, func(t *testing.T) { stdin.WriteString(tt.stdin) @@ -318,6 +320,7 @@ func Test_createRun(t *testing.T) { assert.Equal(t, tt.wantStderr, stderr.String()) assert.Equal(t, tt.wantParams, reqBody) reg.Verify(t) + browser.Verify(t, tt.wantBrowse) }) } } diff --git a/pkg/cmd/gist/view/view.go b/pkg/cmd/gist/view/view.go index 2b49c642d..131fa6e82 100644 --- a/pkg/cmd/gist/view/view.go +++ b/pkg/cmd/gist/view/view.go @@ -20,10 +20,15 @@ import ( "github.com/spf13/cobra" ) +type browser interface { + Browse(string) error +} + type ViewOptions struct { IO *iostreams.IOStreams Config func() (config.Config, error) HttpClient func() (*http.Client, error) + Browser browser Selector string Filename string @@ -37,6 +42,7 @@ func NewCmdView(f *cmdutil.Factory, runF func(*ViewOptions) error) *cobra.Comman IO: f.IOStreams, Config: f.Config, HttpClient: f.HttpClient, + Browser: f.Browser, } cmd := &cobra.Command{ @@ -106,7 +112,7 @@ func viewRun(opts *ViewOptions) error { if opts.IO.IsStderrTTY() { fmt.Fprintf(opts.IO.ErrOut, "Opening %s in your browser.\n", utils.DisplayURL(gistURL)) } - return utils.OpenInBrowser(gistURL) + return opts.Browser.Browse(gistURL) } if strings.Contains(gistID, "/") { diff --git a/pkg/cmd/issue/comment/comment.go b/pkg/cmd/issue/comment/comment.go index 94545cb6b..c25b9ccf5 100644 --- a/pkg/cmd/issue/comment/comment.go +++ b/pkg/cmd/issue/comment/comment.go @@ -9,7 +9,6 @@ import ( issueShared "github.com/cli/cli/pkg/cmd/issue/shared" prShared "github.com/cli/cli/pkg/cmd/pr/shared" "github.com/cli/cli/pkg/cmdutil" - "github.com/cli/cli/utils" "github.com/spf13/cobra" ) @@ -20,7 +19,7 @@ func NewCmdComment(f *cmdutil.Factory, runF func(*prShared.CommentableOptions) e EditSurvey: prShared.CommentableEditSurvey(f.Config, f.IOStreams), InteractiveEditSurvey: prShared.CommentableInteractiveEditSurvey(f.Config, f.IOStreams), ConfirmSubmitSurvey: prShared.CommentableConfirmSubmitSurvey, - OpenInBrowser: utils.OpenInBrowser, + OpenInBrowser: f.Browser.Browse, } var bodyFile string diff --git a/pkg/cmd/issue/comment/comment_test.go b/pkg/cmd/issue/comment/comment_test.go index 5274e7ad0..8e1fa9ad6 100644 --- a/pkg/cmd/issue/comment/comment_test.go +++ b/pkg/cmd/issue/comment/comment_test.go @@ -152,6 +152,7 @@ func TestNewCmdComment(t *testing.T) { f := &cmdutil.Factory{ IOStreams: io, + Browser: &cmdutil.TestBrowser{}, } argv, err := shlex.Split(tt.input) diff --git a/pkg/cmd/issue/create/create.go b/pkg/cmd/issue/create/create.go index 44ae3eb98..4b3ef910d 100644 --- a/pkg/cmd/issue/create/create.go +++ b/pkg/cmd/issue/create/create.go @@ -17,11 +17,16 @@ import ( "github.com/spf13/cobra" ) +type browser interface { + Browse(string) error +} + type CreateOptions struct { HttpClient func() (*http.Client, error) Config func() (config.Config, error) IO *iostreams.IOStreams BaseRepo func() (ghrepo.Interface, error) + Browser browser RootDirOverride string @@ -44,6 +49,7 @@ func NewCmdCreate(f *cmdutil.Factory, runF func(*CreateOptions) error) *cobra.Co IO: f.IOStreams, HttpClient: f.HttpClient, Config: f.Config, + Browser: f.Browser, } var bodyFile string @@ -171,7 +177,7 @@ func createRun(opts *CreateOptions) (err error) { if isTerminal { fmt.Fprintf(opts.IO.ErrOut, "Opening %s in your browser.\n", utils.DisplayURL(openURL)) } - return utils.OpenInBrowser(openURL) + return opts.Browser.Browse(openURL) } if isTerminal { @@ -286,7 +292,7 @@ func createRun(opts *CreateOptions) (err error) { if isTerminal { fmt.Fprintf(opts.IO.ErrOut, "Opening %s in your browser.\n", utils.DisplayURL(openURL)) } - return utils.OpenInBrowser(openURL) + return opts.Browser.Browse(openURL) } else if action == prShared.SubmitAction { params := map[string]interface{}{ "title": tb.Title, diff --git a/pkg/cmd/issue/create/create_test.go b/pkg/cmd/issue/create/create_test.go index 13fd3b98c..5ecde4599 100644 --- a/pkg/cmd/issue/create/create_test.go +++ b/pkg/cmd/issue/create/create_test.go @@ -8,7 +8,6 @@ import ( "net/http" "os" "path/filepath" - "strings" "testing" "github.com/MakeNowJust/heredoc" @@ -145,6 +144,7 @@ func runCommandWithRootDirOverridden(rt http.RoundTripper, isTTY bool, cli strin io.SetStdinTTY(isTTY) io.SetStderrTTY(isTTY) + browser := &cmdutil.TestBrowser{} factory := &cmdutil.Factory{ IOStreams: io, HttpClient: func() (*http.Client, error) { @@ -156,6 +156,7 @@ func runCommandWithRootDirOverridden(rt http.RoundTripper, isTTY bool, cli strin BaseRepo: func() (ghrepo.Interface, error) { return ghrepo.New("OWNER", "REPO"), nil }, + Browser: browser, } cmd := NewCmdCreate(factory, func(opts *CreateOptions) error { @@ -175,8 +176,9 @@ func runCommandWithRootDirOverridden(rt http.RoundTripper, isTTY bool, cli strin _, err = cmd.ExecuteC() return &test.CmdOut{ - OutBuf: stdout, - ErrBuf: stderr, + OutBuf: stdout, + ErrBuf: stderr, + BrowsedURL: browser.BrowsedURL(), }, err } @@ -355,6 +357,7 @@ func TestIssueCreate_nonLegacyTemplate(t *testing.T) { } assert.Equal(t, "https://github.com/OWNER/REPO/issues/12\n", output.String()) + assert.Equal(t, "", output.BrowsedURL) } func TestIssueCreate_continueInBrowser(t *testing.T) { @@ -388,14 +391,9 @@ func TestIssueCreate_continueInBrowser(t *testing.T) { }, }) - cs, cmdTeardown := run.Stub() + _, cmdTeardown := run.Stub() defer cmdTeardown(t) - cs.Register(`https://github\.com`, 0, "", func(args []string) { - url := strings.ReplaceAll(args[len(args)-1], "^", "") - assert.Equal(t, "https://github.com/OWNER/REPO/issues/new?body=body&title=hello", url) - }) - output, err := runCommand(http, true, `-b body`) if err != nil { t.Errorf("error running command `issue create`: %v", err) @@ -408,6 +406,7 @@ func TestIssueCreate_continueInBrowser(t *testing.T) { Opening github.com/OWNER/REPO/issues/new in your browser. `), output.Stderr()) + assert.Equal(t, "https://github.com/OWNER/REPO/issues/new?body=body&title=hello", output.BrowsedURL) } func TestIssueCreate_metadata(t *testing.T) { @@ -520,14 +519,9 @@ func TestIssueCreate_web(t *testing.T) { `), ) - cs, cmdTeardown := run.Stub() + _, cmdTeardown := run.Stub() defer cmdTeardown(t) - cs.Register(`https://github\.com`, 0, "", func(args []string) { - url := strings.ReplaceAll(args[len(args)-1], "^", "") - assert.Equal(t, "https://github.com/OWNER/REPO/issues/new?assignees=MonaLisa", url) - }) - output, err := runCommand(http, true, `--web -a @me`) if err != nil { t.Errorf("error running command `issue create`: %v", err) @@ -535,20 +529,16 @@ func TestIssueCreate_web(t *testing.T) { assert.Equal(t, "", output.String()) assert.Equal(t, "Opening github.com/OWNER/REPO/issues/new in your browser.\n", output.Stderr()) + assert.Equal(t, "https://github.com/OWNER/REPO/issues/new?assignees=MonaLisa", output.BrowsedURL) } func TestIssueCreate_webTitleBody(t *testing.T) { http := &httpmock.Registry{} defer http.Verify(t) - cs, cmdTeardown := run.Stub() + _, cmdTeardown := run.Stub() defer cmdTeardown(t) - cs.Register(`https://github\.com`, 0, "", func(args []string) { - url := strings.ReplaceAll(args[len(args)-1], "^", "") - assert.Equal(t, "https://github.com/OWNER/REPO/issues/new?body=mybody&title=mytitle", url) - }) - output, err := runCommand(http, true, `-w -t mytitle -b mybody`) if err != nil { t.Errorf("error running command `issue create`: %v", err) @@ -556,6 +546,7 @@ func TestIssueCreate_webTitleBody(t *testing.T) { assert.Equal(t, "", output.String()) assert.Equal(t, "Opening github.com/OWNER/REPO/issues/new in your browser.\n", output.Stderr()) + assert.Equal(t, "https://github.com/OWNER/REPO/issues/new?body=mybody&title=mytitle", output.BrowsedURL) } func TestIssueCreate_webTitleBodyAtMeAssignee(t *testing.T) { @@ -571,14 +562,9 @@ func TestIssueCreate_webTitleBodyAtMeAssignee(t *testing.T) { `), ) - cs, cmdTeardown := run.Stub() + _, cmdTeardown := run.Stub() defer cmdTeardown(t) - cs.Register(`https://github\.com`, 0, "", func(args []string) { - url := strings.ReplaceAll(args[len(args)-1], "^", "") - assert.Equal(t, "https://github.com/OWNER/REPO/issues/new?assignees=MonaLisa&body=mybody&title=mytitle", url) - }) - output, err := runCommand(http, true, `-w -t mytitle -b mybody -a @me`) if err != nil { t.Errorf("error running command `issue create`: %v", err) @@ -586,6 +572,7 @@ func TestIssueCreate_webTitleBodyAtMeAssignee(t *testing.T) { assert.Equal(t, "", output.String()) assert.Equal(t, "Opening github.com/OWNER/REPO/issues/new in your browser.\n", output.Stderr()) + assert.Equal(t, "https://github.com/OWNER/REPO/issues/new?assignees=MonaLisa&body=mybody&title=mytitle", output.BrowsedURL) } func TestIssueCreate_AtMeAssignee(t *testing.T) { @@ -666,14 +653,9 @@ func TestIssueCreate_webProject(t *testing.T) { } } } } `)) - cs, cmdTeardown := run.Stub() + _, cmdTeardown := run.Stub() defer cmdTeardown(t) - cs.Register(`https://github\.com`, 0, "", func(args []string) { - url := strings.ReplaceAll(args[len(args)-1], "^", "") - assert.Equal(t, "https://github.com/OWNER/REPO/issues/new?projects=OWNER%2FREPO%2F1&title=Title", url) - }) - output, err := runCommand(http, true, `-w -t Title -p Cleanup`) if err != nil { t.Errorf("error running command `issue create`: %v", err) @@ -681,4 +663,5 @@ func TestIssueCreate_webProject(t *testing.T) { assert.Equal(t, "", output.String()) assert.Equal(t, "Opening github.com/OWNER/REPO/issues/new in your browser.\n", output.Stderr()) + assert.Equal(t, "https://github.com/OWNER/REPO/issues/new?projects=OWNER%2FREPO%2F1&title=Title", output.BrowsedURL) } diff --git a/pkg/cmd/issue/list/list.go b/pkg/cmd/issue/list/list.go index 5a76a5390..663b44ecd 100644 --- a/pkg/cmd/issue/list/list.go +++ b/pkg/cmd/issue/list/list.go @@ -19,11 +19,16 @@ import ( "github.com/spf13/cobra" ) +type browser interface { + Browse(string) error +} + type ListOptions struct { HttpClient func() (*http.Client, error) Config func() (config.Config, error) IO *iostreams.IOStreams BaseRepo func() (ghrepo.Interface, error) + Browser browser WebMode bool @@ -42,6 +47,7 @@ func NewCmdList(f *cmdutil.Factory, runF func(*ListOptions) error) *cobra.Comman IO: f.IOStreams, HttpClient: f.HttpClient, Config: f.Config, + Browser: f.Browser, } cmd := &cobra.Command{ @@ -118,7 +124,7 @@ func listRun(opts *ListOptions) error { if isTerminal { fmt.Fprintf(opts.IO.ErrOut, "Opening %s in your browser.\n", utils.DisplayURL(openURL)) } - return utils.OpenInBrowser(openURL) + return opts.Browser.Browse(openURL) } listResult, err := issueList(httpClient, baseRepo, filterOptions, opts.LimitResults) diff --git a/pkg/cmd/issue/list/list_test.go b/pkg/cmd/issue/list/list_test.go index d374f34ba..1e5e597c8 100644 --- a/pkg/cmd/issue/list/list_test.go +++ b/pkg/cmd/issue/list/list_test.go @@ -5,7 +5,6 @@ import ( "io/ioutil" "net/http" "regexp" - "strings" "testing" "github.com/MakeNowJust/heredoc" @@ -177,24 +176,42 @@ func TestIssueList_disabledIssues(t *testing.T) { } func TestIssueList_web(t *testing.T) { - http := &httpmock.Registry{} - defer http.Verify(t) + io, _, stdout, stderr := iostreams.Test() + io.SetStdoutTTY(true) + io.SetStderrTTY(true) + browser := &cmdutil.TestBrowser{} - cs, cmdTeardown := run.Stub() + reg := &httpmock.Registry{} + defer reg.Verify(t) + + _, cmdTeardown := run.Stub() defer cmdTeardown(t) - cs.Register(`https://github\.com`, 0, "", func(args []string) { - url := strings.ReplaceAll(args[len(args)-1], "^", "") - assert.Equal(t, "https://github.com/OWNER/REPO/issues?q=is%3Aissue+assignee%3Apeter+label%3Abug+label%3Adocs+author%3Ajohn+mentions%3Afrank+milestone%3Av1.1", url) + err := listRun(&ListOptions{ + IO: io, + Browser: browser, + HttpClient: func() (*http.Client, error) { + return &http.Client{Transport: reg}, nil + }, + BaseRepo: func() (ghrepo.Interface, error) { + return ghrepo.New("OWNER", "REPO"), nil + }, + WebMode: true, + State: "all", + Assignee: "peter", + Author: "john", + Labels: []string{"bug", "docs"}, + Mention: "frank", + Milestone: "v1.1", + LimitResults: 10, }) - - output, err := runCommand(http, true, "--web -a peter -A john -l bug -l docs -L 10 -s all --mention frank --milestone v1.1") if err != nil { t.Errorf("error running command `issue list` with `--web` flag: %v", err) } - assert.Equal(t, "", output.String()) - assert.Equal(t, "Opening github.com/OWNER/REPO/issues in your browser.\n", output.Stderr()) + assert.Equal(t, "", stdout.String()) + assert.Equal(t, "Opening github.com/OWNER/REPO/issues in your browser.\n", stderr.String()) + browser.Verify(t, "https://github.com/OWNER/REPO/issues?q=is%3Aissue+assignee%3Apeter+label%3Abug+label%3Adocs+author%3Ajohn+mentions%3Afrank+milestone%3Av1.1") } func TestIssueList_Search_web(t *testing.T) { diff --git a/pkg/cmd/issue/shared/lookup_test.go b/pkg/cmd/issue/shared/lookup_test.go new file mode 100644 index 000000000..a34c1fbb5 --- /dev/null +++ b/pkg/cmd/issue/shared/lookup_test.go @@ -0,0 +1,102 @@ +package shared + +import ( + "net/http" + "testing" + + "github.com/cli/cli/api" + "github.com/cli/cli/internal/ghrepo" + "github.com/cli/cli/pkg/httpmock" +) + +func TestIssueFromArg(t *testing.T) { + type args struct { + baseRepoFn func() (ghrepo.Interface, error) + selector string + } + tests := []struct { + name string + args args + httpStub func(*httpmock.Registry) + wantIssue int + wantRepo string + wantErr bool + }{ + { + name: "number argument", + args: args{ + selector: "13", + baseRepoFn: func() (ghrepo.Interface, error) { + return ghrepo.FromFullName("OWNER/REPO") + }, + }, + httpStub: func(r *httpmock.Registry) { + r.Register( + httpmock.GraphQL(`query IssueByNumber\b`), + httpmock.StringResponse(`{"data":{"repository":{ + "hasIssuesEnabled": true, + "issue":{"number":13} + }}}`)) + }, + wantIssue: 13, + wantRepo: "https://github.com/OWNER/REPO", + }, + { + name: "number with hash argument", + args: args{ + selector: "#13", + baseRepoFn: func() (ghrepo.Interface, error) { + return ghrepo.FromFullName("OWNER/REPO") + }, + }, + httpStub: func(r *httpmock.Registry) { + r.Register( + httpmock.GraphQL(`query IssueByNumber\b`), + httpmock.StringResponse(`{"data":{"repository":{ + "hasIssuesEnabled": true, + "issue":{"number":13} + }}}`)) + }, + wantIssue: 13, + wantRepo: "https://github.com/OWNER/REPO", + }, + { + name: "URL argument", + args: args{ + selector: "https://example.org/OWNER/REPO/issues/13#comment-123", + baseRepoFn: nil, + }, + httpStub: func(r *httpmock.Registry) { + r.Register( + httpmock.GraphQL(`query IssueByNumber\b`), + httpmock.StringResponse(`{"data":{"repository":{ + "hasIssuesEnabled": true, + "issue":{"number":13} + }}}`)) + }, + wantIssue: 13, + wantRepo: "https://example.org/OWNER/REPO", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + reg := &httpmock.Registry{} + if tt.httpStub != nil { + tt.httpStub(reg) + } + httpClient := &http.Client{Transport: reg} + issue, repo, err := IssueFromArg(api.NewClientFromHTTP(httpClient), tt.args.baseRepoFn, tt.args.selector) + if (err != nil) != tt.wantErr { + t.Errorf("IssueFromArg() error = %v, wantErr %v", err, tt.wantErr) + return + } + if issue.Number != tt.wantIssue { + t.Errorf("want issue #%d, got #%d", tt.wantIssue, issue.Number) + } + repoURL := ghrepo.GenerateRepoURL(repo, "") + if repoURL != tt.wantRepo { + t.Errorf("want repo %s, got %s", tt.wantRepo, repoURL) + } + }) + } +} diff --git a/pkg/cmd/issue/view/view.go b/pkg/cmd/issue/view/view.go index aaf415266..39e354133 100644 --- a/pkg/cmd/issue/view/view.go +++ b/pkg/cmd/issue/view/view.go @@ -20,10 +20,15 @@ import ( "github.com/spf13/cobra" ) +type browser interface { + Browse(string) error +} + type ViewOptions struct { HttpClient func() (*http.Client, error) IO *iostreams.IOStreams BaseRepo func() (ghrepo.Interface, error) + Browser browser SelectorArg string WebMode bool @@ -36,6 +41,7 @@ func NewCmdView(f *cmdutil.Factory, runF func(*ViewOptions) error) *cobra.Comman opts := &ViewOptions{ IO: f.IOStreams, HttpClient: f.HttpClient, + Browser: f.Browser, Now: time.Now, } @@ -86,7 +92,7 @@ func viewRun(opts *ViewOptions) error { if opts.IO.IsStdoutTTY() { fmt.Fprintf(opts.IO.ErrOut, "Opening %s in your browser.\n", utils.DisplayURL(openURL)) } - return utils.OpenInBrowser(openURL) + return opts.Browser.Browse(openURL) } if opts.Comments { diff --git a/pkg/cmd/issue/view/view_test.go b/pkg/cmd/issue/view/view_test.go index 8eaf4469b..dc5c5dfb5 100644 --- a/pkg/cmd/issue/view/view_test.go +++ b/pkg/cmd/issue/view/view_test.go @@ -5,7 +5,6 @@ import ( "fmt" "io/ioutil" "net/http" - "strings" "testing" "time" @@ -59,65 +58,45 @@ func runCommand(rt http.RoundTripper, isTTY bool, cli string) (*test.CmdOut, err } func TestIssueView_web(t *testing.T) { - http := &httpmock.Registry{} - defer http.Verify(t) + io, _, stdout, stderr := iostreams.Test() + io.SetStdoutTTY(true) + io.SetStderrTTY(true) + browser := &cmdutil.TestBrowser{} - http.Register( + reg := &httpmock.Registry{} + defer reg.Verify(t) + + reg.Register( httpmock.GraphQL(`query IssueByNumber\b`), httpmock.StringResponse(` { "data": { "repository": { "hasIssuesEnabled": true, "issue": { "number": 123, "url": "https://github.com/OWNER/REPO/issues/123" } } } } - `), - ) + `)) - cs, cmdTeardown := run.Stub() + _, cmdTeardown := run.Stub() defer cmdTeardown(t) - cs.Register(`https://github\.com`, 0, "", func(args []string) { - url := strings.ReplaceAll(args[len(args)-1], "^", "") - assert.Equal(t, "https://github.com/OWNER/REPO/issues/123", url) + err := viewRun(&ViewOptions{ + IO: io, + Browser: browser, + HttpClient: func() (*http.Client, error) { + return &http.Client{Transport: reg}, nil + }, + BaseRepo: func() (ghrepo.Interface, error) { + return ghrepo.New("OWNER", "REPO"), nil + }, + WebMode: true, + SelectorArg: "123", }) - - output, err := runCommand(http, true, "-w 123") if err != nil { t.Errorf("error running command `issue view`: %v", err) } - assert.Equal(t, "", output.String()) - assert.Equal(t, "Opening github.com/OWNER/REPO/issues/123 in your browser.\n", output.Stderr()) -} - -func TestIssueView_web_numberArgWithHash(t *testing.T) { - http := &httpmock.Registry{} - defer http.Verify(t) - - http.Register( - httpmock.GraphQL(`query IssueByNumber\b`), - httpmock.StringResponse(` - { "data": { "repository": { "hasIssuesEnabled": true, "issue": { - "number": 123, - "url": "https://github.com/OWNER/REPO/issues/123" - } } } } - `), - ) - - cs, cmdTeardown := run.Stub() - defer cmdTeardown(t) - - cs.Register(`https://github\.com`, 0, "", func(args []string) { - url := strings.ReplaceAll(args[len(args)-1], "^", "") - assert.Equal(t, "https://github.com/OWNER/REPO/issues/123", url) - }) - - output, err := runCommand(http, true, "-w \"#123\"") - if err != nil { - t.Errorf("error running command `issue view`: %v", err) - } - - assert.Equal(t, "", output.String()) - assert.Equal(t, "Opening github.com/OWNER/REPO/issues/123 in your browser.\n", output.Stderr()) + assert.Equal(t, "", stdout.String()) + assert.Equal(t, "Opening github.com/OWNER/REPO/issues/123 in your browser.\n", stderr.String()) + browser.Verify(t, "https://github.com/OWNER/REPO/issues/123") } func TestIssueView_nontty_Preview(t *testing.T) { @@ -318,36 +297,6 @@ func TestIssueView_disabledIssues(t *testing.T) { } } -func TestIssueView_web_urlArg(t *testing.T) { - http := &httpmock.Registry{} - defer http.Verify(t) - - http.Register( - httpmock.GraphQL(`query IssueByNumber\b`), - httpmock.StringResponse(` - { "data": { "repository": { "hasIssuesEnabled": true, "issue": { - "number": 123, - "url": "https://github.com/OWNER/REPO/issues/123" - } } } } - `), - ) - - cs, cmdTeardown := run.Stub() - defer cmdTeardown(t) - - cs.Register(`https://github\.com`, 0, "", func(args []string) { - url := strings.ReplaceAll(args[len(args)-1], "^", "") - assert.Equal(t, "https://github.com/OWNER/REPO/issues/123", url) - }) - - output, err := runCommand(http, true, "-w https://github.com/OWNER/REPO/issues/123") - if err != nil { - t.Errorf("error running command `issue view`: %v", err) - } - - assert.Equal(t, "", output.String()) -} - func TestIssueView_tty_Comments(t *testing.T) { tests := map[string]struct { cli string diff --git a/pkg/cmd/pr/checks/checks.go b/pkg/cmd/pr/checks/checks.go index 89eb8e5d3..83f4ce567 100644 --- a/pkg/cmd/pr/checks/checks.go +++ b/pkg/cmd/pr/checks/checks.go @@ -17,9 +17,14 @@ import ( "github.com/spf13/cobra" ) +type browser interface { + Browse(string) error +} + type ChecksOptions struct { HttpClient func() (*http.Client, error) IO *iostreams.IOStreams + Browser browser BaseRepo func() (ghrepo.Interface, error) Branch func() (string, error) Remotes func() (context.Remotes, error) @@ -36,6 +41,7 @@ func NewCmdChecks(f *cmdutil.Factory, runF func(*ChecksOptions) error) *cobra.Co Branch: f.Branch, Remotes: f.Remotes, BaseRepo: f.BaseRepo, + Browser: f.Browser, } cmd := &cobra.Command{ @@ -95,7 +101,7 @@ func checksRun(opts *ChecksOptions) error { if isTerminal { fmt.Fprintf(opts.IO.ErrOut, "Opening %s in your browser.\n", utils.DisplayURL(openURL)) } - return utils.OpenInBrowser(openURL) + return opts.Browser.Browse(openURL) } passing := 0 diff --git a/pkg/cmd/pr/checks/checks_test.go b/pkg/cmd/pr/checks/checks_test.go index aa1a9baba..fdcce3f9e 100644 --- a/pkg/cmd/pr/checks/checks_test.go +++ b/pkg/cmd/pr/checks/checks_test.go @@ -212,36 +212,28 @@ func TestChecksRun_web(t *testing.T) { isTTY bool wantStderr string wantStdout string + wantBrowse string }{ { name: "tty", isTTY: true, wantStderr: "Opening github.com/OWNER/REPO/pull/123/checks in your browser.\n", wantStdout: "", + wantBrowse: "https://github.com/OWNER/REPO/pull/123/checks", }, { name: "nontty", isTTY: false, wantStderr: "", wantStdout: "", + wantBrowse: "https://github.com/OWNER/REPO/pull/123/checks", }, } - - reg := &httpmock.Registry{} - - opts := &ChecksOptions{ - WebMode: true, - HttpClient: func() (*http.Client, error) { - return &http.Client{Transport: reg}, nil - }, - BaseRepo: func() (ghrepo.Interface, error) { - return ghrepo.New("OWNER", "REPO"), nil - }, - SelectorArg: "123", - } - for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { + browser := &cmdutil.TestBrowser{} + reg := &httpmock.Registry{} + reg.Register( httpmock.GraphQL(`query PullRequestByNumber\b`), httpmock.FileResponse("./fixtures/allPassing.json")) @@ -250,17 +242,26 @@ func TestChecksRun_web(t *testing.T) { io.SetStdinTTY(tc.isTTY) io.SetStderrTTY(tc.isTTY) - opts.IO = io - - cs, teardown := run.Stub() + _, teardown := run.Stub() defer teardown(t) - cs.Register(`https://github\.com/OWNER/REPO/pull/123/checks$`, 0, "") - err := checksRun(opts) + err := checksRun(&ChecksOptions{ + IO: io, + Browser: browser, + WebMode: true, + HttpClient: func() (*http.Client, error) { + return &http.Client{Transport: reg}, nil + }, + BaseRepo: func() (ghrepo.Interface, error) { + return ghrepo.New("OWNER", "REPO"), nil + }, + SelectorArg: "123", + }) assert.NoError(t, err) assert.Equal(t, tc.wantStdout, stdout.String()) assert.Equal(t, tc.wantStderr, stderr.String()) reg.Verify(t) + browser.Verify(t, tc.wantBrowse) }) } } diff --git a/pkg/cmd/pr/comment/comment.go b/pkg/cmd/pr/comment/comment.go index dc9341ebe..b993fd407 100644 --- a/pkg/cmd/pr/comment/comment.go +++ b/pkg/cmd/pr/comment/comment.go @@ -10,7 +10,6 @@ import ( "github.com/cli/cli/internal/ghrepo" "github.com/cli/cli/pkg/cmd/pr/shared" "github.com/cli/cli/pkg/cmdutil" - "github.com/cli/cli/utils" "github.com/spf13/cobra" ) @@ -21,7 +20,7 @@ func NewCmdComment(f *cmdutil.Factory, runF func(*shared.CommentableOptions) err EditSurvey: shared.CommentableEditSurvey(f.Config, f.IOStreams), InteractiveEditSurvey: shared.CommentableInteractiveEditSurvey(f.Config, f.IOStreams), ConfirmSubmitSurvey: shared.CommentableConfirmSubmitSurvey, - OpenInBrowser: utils.OpenInBrowser, + OpenInBrowser: f.Browser.Browse, } var bodyFile string diff --git a/pkg/cmd/pr/comment/comment_test.go b/pkg/cmd/pr/comment/comment_test.go index 56277874e..429af7cda 100644 --- a/pkg/cmd/pr/comment/comment_test.go +++ b/pkg/cmd/pr/comment/comment_test.go @@ -173,6 +173,7 @@ func TestNewCmdComment(t *testing.T) { f := &cmdutil.Factory{ IOStreams: io, + Browser: &cmdutil.TestBrowser{}, } argv, err := shlex.Split(tt.input) diff --git a/pkg/cmd/pr/create/create.go b/pkg/cmd/pr/create/create.go index 0de13bd49..22ca05de4 100644 --- a/pkg/cmd/pr/create/create.go +++ b/pkg/cmd/pr/create/create.go @@ -24,6 +24,10 @@ import ( "github.com/spf13/cobra" ) +type browser interface { + Browse(string) error +} + type CreateOptions struct { // This struct stores user input and factory functions HttpClient func() (*http.Client, error) @@ -31,6 +35,7 @@ type CreateOptions struct { IO *iostreams.IOStreams Remotes func() (context.Remotes, error) Branch func() (string, error) + Browser browser TitleProvided bool BodyProvided bool @@ -79,6 +84,7 @@ func NewCmdCreate(f *cmdutil.Factory, runF func(*CreateOptions) error) *cobra.Co Config: f.Config, Remotes: f.Remotes, Branch: f.Branch, + Browser: f.Browser, } var bodyFile string @@ -650,7 +656,7 @@ func previewPR(opts CreateOptions, ctx CreateContext, state shared.IssueMetadata if opts.IO.IsStdinTTY() && opts.IO.IsStdoutTTY() { fmt.Fprintf(opts.IO.ErrOut, "Opening %s in your browser.\n", utils.DisplayURL(openURL)) } - return utils.OpenInBrowser(openURL) + return opts.Browser.Browse(openURL) } diff --git a/pkg/cmd/pr/create/create_test.go b/pkg/cmd/pr/create/create_test.go index 7e94cf084..9d338bcad 100644 --- a/pkg/cmd/pr/create/create_test.go +++ b/pkg/cmd/pr/create/create_test.go @@ -8,7 +8,6 @@ import ( "net/http" "os" "path/filepath" - "strings" "testing" "github.com/MakeNowJust/heredoc" @@ -166,8 +165,10 @@ func runCommandWithRootDirOverridden(rt http.RoundTripper, remotes context.Remot io.SetStdinTTY(isTTY) io.SetStderrTTY(isTTY) + browser := &cmdutil.TestBrowser{} factory := &cmdutil.Factory{ IOStreams: io, + Browser: browser, HttpClient: func() (*http.Client, error) { return &http.Client{Transport: rt}, nil }, @@ -211,8 +212,9 @@ func runCommandWithRootDirOverridden(rt http.RoundTripper, remotes context.Remot _, err = cmd.ExecuteC() return &test.CmdOut{ - OutBuf: stdout, - ErrBuf: stderr, + OutBuf: stdout, + ErrBuf: stderr, + BrowsedURL: browser.BrowsedURL(), }, err } @@ -231,16 +233,13 @@ func TestPRCreate_nontty_web(t *testing.T) { cs.Register(`git status --porcelain`, 0, "") cs.Register(`git( .+)? log( .+)? origin/master\.\.\.feature`, 0, "") - cs.Register(`https://github\.com`, 0, "", func(args []string) { - url := strings.ReplaceAll(args[len(args)-1], "^", "") - assert.Equal(t, "https://github.com/OWNER/REPO/compare/master...feature?expand=1", url) - }) output, err := runCommand(http, nil, "feature", false, `--web --head=feature`) require.NoError(t, err) assert.Equal(t, "", output.String()) assert.Equal(t, "", output.Stderr()) + assert.Equal(t, "https://github.com/OWNER/REPO/compare/master...feature?expand=1", output.BrowsedURL) } func TestPRCreate_recover(t *testing.T) { @@ -823,10 +822,6 @@ func TestPRCreate_web(t *testing.T) { cs.Register(`git show-ref --verify -- HEAD refs/remotes/origin/feature`, 0, "") cs.Register(`git( .+)? log( .+)? origin/master\.\.\.feature`, 0, "") cs.Register(`git push --set-upstream origin HEAD:feature`, 0, "") - cs.Register(`https://github\.com`, 0, "", func(args []string) { - url := strings.ReplaceAll(args[len(args)-1], "^", "") - assert.Equal(t, "https://github.com/OWNER/REPO/compare/master...feature?expand=1", url) - }) ask, cleanupAsk := prompt.InitAskStubber() defer cleanupAsk() @@ -837,6 +832,7 @@ func TestPRCreate_web(t *testing.T) { assert.Equal(t, "", output.String()) assert.Equal(t, "Opening github.com/OWNER/REPO/compare/master...feature in your browser.\n", output.Stderr()) + assert.Equal(t, "https://github.com/OWNER/REPO/compare/master...feature?expand=1", output.BrowsedURL) } func TestPRCreate_webProject(t *testing.T) { @@ -877,10 +873,6 @@ func TestPRCreate_webProject(t *testing.T) { cs.Register(`git show-ref --verify -- HEAD refs/remotes/origin/feature`, 0, "") cs.Register(`git( .+)? log( .+)? origin/master\.\.\.feature`, 0, "") cs.Register(`git push --set-upstream origin HEAD:feature`, 0, "") - cs.Register(`https://github\.com`, 0, "", func(args []string) { - url := strings.ReplaceAll(args[len(args)-1], "^", "") - assert.Equal(t, "https://github.com/OWNER/REPO/compare/master...feature?expand=1&projects=ORG%2F1", url) - }) ask, cleanupAsk := prompt.InitAskStubber() defer cleanupAsk() @@ -891,6 +883,7 @@ func TestPRCreate_webProject(t *testing.T) { assert.Equal(t, "", output.String()) assert.Equal(t, "Opening github.com/OWNER/REPO/compare/master...feature in your browser.\n", output.Stderr()) + assert.Equal(t, "https://github.com/OWNER/REPO/compare/master...feature?expand=1&projects=ORG%2F1", output.BrowsedURL) } func Test_determineTrackingBranch_empty(t *testing.T) { diff --git a/pkg/cmd/pr/list/list.go b/pkg/cmd/pr/list/list.go index 97c35cd9c..292d39f37 100644 --- a/pkg/cmd/pr/list/list.go +++ b/pkg/cmd/pr/list/list.go @@ -17,10 +17,15 @@ import ( "github.com/spf13/cobra" ) +type browser interface { + Browse(string) error +} + type ListOptions struct { HttpClient func() (*http.Client, error) IO *iostreams.IOStreams BaseRepo func() (ghrepo.Interface, error) + Browser browser WebMode bool LimitResults int @@ -36,6 +41,7 @@ func NewCmdList(f *cmdutil.Factory, runF func(*ListOptions) error) *cobra.Comman opts := &ListOptions{ IO: f.IOStreams, HttpClient: f.HttpClient, + Browser: f.Browser, } cmd := &cobra.Command{ @@ -105,7 +111,7 @@ func listRun(opts *ListOptions) error { if opts.IO.IsStdoutTTY() { fmt.Fprintf(opts.IO.ErrOut, "Opening %s in your browser.\n", utils.DisplayURL(openURL)) } - return utils.OpenInBrowser(openURL) + return opts.Browser.Browse(openURL) } listResult, err := listPullRequests(httpClient, baseRepo, filters, opts.LimitResults) diff --git a/pkg/cmd/pr/list/list_test.go b/pkg/cmd/pr/list/list_test.go index 59856abae..56c5c8a26 100644 --- a/pkg/cmd/pr/list/list_test.go +++ b/pkg/cmd/pr/list/list_test.go @@ -24,8 +24,10 @@ func runCommand(rt http.RoundTripper, isTTY bool, cli string) (*test.CmdOut, err io.SetStdinTTY(isTTY) io.SetStderrTTY(isTTY) + browser := &cmdutil.TestBrowser{} factory := &cmdutil.Factory{ IOStreams: io, + Browser: browser, HttpClient: func() (*http.Client, error) { return &http.Client{Transport: rt}, nil }, @@ -48,8 +50,9 @@ func runCommand(rt http.RoundTripper, isTTY bool, cli string) (*test.CmdOut, err _, err = cmd.ExecuteC() return &test.CmdOut{ - OutBuf: stdout, - ErrBuf: stderr, + OutBuf: stdout, + ErrBuf: stderr, + BrowsedURL: browser.BrowsedURL(), }, err } @@ -197,14 +200,9 @@ func TestPRList_web(t *testing.T) { http := initFakeHTTP() defer http.Verify(t) - cs, cmdTeardown := run.Stub() + _, cmdTeardown := run.Stub() defer cmdTeardown(t) - cs.Register(`https://github\.com`, 0, "", func(args []string) { - url := strings.ReplaceAll(args[len(args)-1], "^", "") - assert.Equal(t, "https://github.com/OWNER/REPO/pulls?q=is%3Apr+is%3Amerged+assignee%3Apeter+label%3Abug+label%3Adocs+base%3Atrunk", url) - }) - output, err := runCommand(http, true, "--web -a peter -l bug -l docs -L 10 -s merged -B trunk") if err != nil { t.Errorf("error running command `pr list` with `--web` flag: %v", err) @@ -212,4 +210,5 @@ func TestPRList_web(t *testing.T) { assert.Equal(t, "", output.String()) assert.Equal(t, "Opening github.com/OWNER/REPO/pulls in your browser.\n", output.Stderr()) + assert.Equal(t, "https://github.com/OWNER/REPO/pulls?q=is%3Apr+is%3Amerged+assignee%3Apeter+label%3Abug+label%3Adocs+base%3Atrunk", output.BrowsedURL) } diff --git a/pkg/cmd/pr/shared/lookup_test.go b/pkg/cmd/pr/shared/lookup_test.go new file mode 100644 index 000000000..4d843d7ae --- /dev/null +++ b/pkg/cmd/pr/shared/lookup_test.go @@ -0,0 +1,108 @@ +package shared + +import ( + "net/http" + "testing" + + "github.com/cli/cli/api" + "github.com/cli/cli/context" + "github.com/cli/cli/internal/ghrepo" + "github.com/cli/cli/pkg/httpmock" +) + +func TestPRFromArgs(t *testing.T) { + type args struct { + baseRepoFn func() (ghrepo.Interface, error) + branchFn func() (string, error) + remotesFn func() (context.Remotes, error) + selector string + } + tests := []struct { + name string + args args + httpStub func(*httpmock.Registry) + wantPR int + wantRepo string + wantErr bool + }{ + { + name: "number argument", + args: args{ + selector: "13", + baseRepoFn: func() (ghrepo.Interface, error) { + return ghrepo.FromFullName("OWNER/REPO") + }, + }, + httpStub: func(r *httpmock.Registry) { + r.Register( + httpmock.GraphQL(`query PullRequestByNumber\b`), + httpmock.StringResponse(`{"data":{"repository":{ + "pullRequest":{"number":13} + }}}`)) + }, + wantPR: 13, + wantRepo: "https://github.com/OWNER/REPO", + }, + { + name: "number with hash argument", + args: args{ + selector: "#13", + baseRepoFn: func() (ghrepo.Interface, error) { + return ghrepo.FromFullName("OWNER/REPO") + }, + }, + httpStub: func(r *httpmock.Registry) { + r.Register( + httpmock.GraphQL(`query PullRequestByNumber\b`), + httpmock.StringResponse(`{"data":{"repository":{ + "pullRequest":{"number":13} + }}}`)) + }, + wantPR: 13, + wantRepo: "https://github.com/OWNER/REPO", + }, + { + name: "URL argument", + args: args{ + selector: "https://example.org/OWNER/REPO/pull/13/files", + baseRepoFn: nil, + }, + httpStub: func(r *httpmock.Registry) { + r.Register( + httpmock.GraphQL(`query PullRequest_fields\b`), + httpmock.StringResponse(`{"data":{}}`)) + r.Register( + httpmock.GraphQL(`query PullRequest_fields2\b`), + httpmock.StringResponse(`{"data":{}}`)) + r.Register( + httpmock.GraphQL(`query PullRequestByNumber\b`), + httpmock.StringResponse(`{"data":{"repository":{ + "pullRequest":{"number":13} + }}}`)) + }, + wantPR: 13, + wantRepo: "https://example.org/OWNER/REPO", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + reg := &httpmock.Registry{} + if tt.httpStub != nil { + tt.httpStub(reg) + } + httpClient := &http.Client{Transport: reg} + pr, repo, err := PRFromArgs(api.NewClientFromHTTP(httpClient), tt.args.baseRepoFn, tt.args.branchFn, tt.args.remotesFn, tt.args.selector) + if (err != nil) != tt.wantErr { + t.Errorf("IssueFromArg() error = %v, wantErr %v", err, tt.wantErr) + return + } + if pr.Number != tt.wantPR { + t.Errorf("want issue #%d, got #%d", tt.wantPR, pr.Number) + } + repoURL := ghrepo.GenerateRepoURL(repo, "") + if repoURL != tt.wantRepo { + t.Errorf("want repo %s, got %s", tt.wantRepo, repoURL) + } + }) + } +} diff --git a/pkg/cmd/pr/view/view.go b/pkg/cmd/pr/view/view.go index e38b0fb23..57cab8e5a 100644 --- a/pkg/cmd/pr/view/view.go +++ b/pkg/cmd/pr/view/view.go @@ -22,10 +22,15 @@ import ( "github.com/spf13/cobra" ) +type browser interface { + Browse(string) error +} + type ViewOptions struct { HttpClient func() (*http.Client, error) Config func() (config.Config, error) IO *iostreams.IOStreams + Browser browser BaseRepo func() (ghrepo.Interface, error) Remotes func() (context.Remotes, error) Branch func() (string, error) @@ -42,6 +47,7 @@ func NewCmdView(f *cmdutil.Factory, runF func(*ViewOptions) error) *cobra.Comman Config: f.Config, Remotes: f.Remotes, Branch: f.Branch, + Browser: f.Browser, } cmd := &cobra.Command{ @@ -96,7 +102,7 @@ func viewRun(opts *ViewOptions) error { if connectedToTerminal { fmt.Fprintf(opts.IO.ErrOut, "Opening %s in your browser.\n", utils.DisplayURL(openURL)) } - return utils.OpenInBrowser(openURL) + return opts.Browser.Browse(openURL) } opts.IO.DetectTerminalTheme() diff --git a/pkg/cmd/pr/view/view_test.go b/pkg/cmd/pr/view/view_test.go index 509d1cf68..cda0e266c 100644 --- a/pkg/cmd/pr/view/view_test.go +++ b/pkg/cmd/pr/view/view_test.go @@ -5,7 +5,6 @@ import ( "fmt" "io/ioutil" "net/http" - "strings" "testing" "github.com/cli/cli/context" @@ -118,8 +117,10 @@ func runCommand(rt http.RoundTripper, branch string, isTTY bool, cli string) (*t io.SetStdinTTY(isTTY) io.SetStderrTTY(isTTY) + browser := &cmdutil.TestBrowser{} factory := &cmdutil.Factory{ IOStreams: io, + Browser: browser, HttpClient: func() (*http.Client, error) { return &http.Client{Transport: rt}, nil }, @@ -156,8 +157,9 @@ func runCommand(rt http.RoundTripper, branch string, isTTY bool, cli string) (*t _, err = cmd.ExecuteC() return &test.CmdOut{ - OutBuf: stdout, - ErrBuf: stderr, + OutBuf: stdout, + ErrBuf: stderr, + BrowsedURL: browser.BrowsedURL(), }, err } @@ -585,10 +587,6 @@ func TestPRView_web_currentBranch(t *testing.T) { defer cmdTeardown(t) cs.Register(`git config --get-regexp.+branch\\\.blueberries\\\.`, 0, "") - cs.Register(`https://github\.com`, 0, "", func(args []string) { - url := strings.ReplaceAll(args[len(args)-1], "^", "") - assert.Equal(t, "https://github.com/OWNER/REPO/pull/10", url) - }) output, err := runCommand(http, "blueberries", true, "-w") if err != nil { @@ -597,6 +595,7 @@ func TestPRView_web_currentBranch(t *testing.T) { assert.Equal(t, "", output.String()) assert.Equal(t, "Opening github.com/OWNER/REPO/pull/10 in your browser.\n", output.Stderr()) + assert.Equal(t, "https://github.com/OWNER/REPO/pull/10", output.BrowsedURL) } func TestPRView_web_noResultsForBranch(t *testing.T) { @@ -627,137 +626,16 @@ func TestPRView_web_numberArg(t *testing.T) { } } } }`), ) - cs, cmdTeardown := run.Stub() + _, cmdTeardown := run.Stub() defer cmdTeardown(t) - cs.Register(`https://github\.com`, 0, "", func(args []string) { - url := strings.ReplaceAll(args[len(args)-1], "^", "") - assert.Equal(t, "https://github.com/OWNER/REPO/pull/23", url) - }) - output, err := runCommand(http, "master", true, "-w 23") if err != nil { t.Errorf("error running command `pr view`: %v", err) } assert.Equal(t, "", output.String()) -} - -func TestPRView_web_numberArgWithHash(t *testing.T) { - http := &httpmock.Registry{} - defer http.Verify(t) - - http.Register( - httpmock.GraphQL(`query PullRequestByNumber\b`), - httpmock.StringResponse(` - { "data": { "repository": { "pullRequest": { - "url": "https://github.com/OWNER/REPO/pull/23" - } } } }`), - ) - - cs, cmdTeardown := run.Stub() - defer cmdTeardown(t) - - cs.Register(`https://github\.com`, 0, "", func(args []string) { - url := strings.ReplaceAll(args[len(args)-1], "^", "") - assert.Equal(t, "https://github.com/OWNER/REPO/pull/23", url) - }) - - output, err := runCommand(http, "master", true, `-w "#23"`) - if err != nil { - t.Errorf("error running command `pr view`: %v", err) - } - - assert.Equal(t, "", output.String()) -} - -func TestPRView_web_urlArg(t *testing.T) { - http := &httpmock.Registry{} - defer http.Verify(t) - - http.Register( - httpmock.GraphQL(`query PullRequestByNumber\b`), - httpmock.StringResponse(` - { "data": { "repository": { "pullRequest": { - "url": "https://github.com/OWNER/REPO/pull/23" - } } } }`), - ) - - cs, cmdTeardown := run.Stub() - defer cmdTeardown(t) - - cs.Register(`https://github\.com`, 0, "", func(args []string) { - url := strings.ReplaceAll(args[len(args)-1], "^", "") - assert.Equal(t, "https://github.com/OWNER/REPO/pull/23", url) - }) - - output, err := runCommand(http, "master", true, "-w https://github.com/OWNER/REPO/pull/23/files") - if err != nil { - t.Errorf("error running command `pr view`: %v", err) - } - - assert.Equal(t, "", output.String()) -} - -func TestPRView_web_branchArg(t *testing.T) { - http := &httpmock.Registry{} - defer http.Verify(t) - - http.Register( - httpmock.GraphQL(`query PullRequestForBranch\b`), - httpmock.StringResponse(` - { "data": { "repository": { "pullRequests": { "nodes": [ - { "headRefName": "blueberries", - "isCrossRepository": false, - "url": "https://github.com/OWNER/REPO/pull/23" } - ] } } } }`), - ) - - cs, cmdTeardown := run.Stub() - defer cmdTeardown(t) - - cs.Register(`https://github\.com`, 0, "", func(args []string) { - url := strings.ReplaceAll(args[len(args)-1], "^", "") - assert.Equal(t, "https://github.com/OWNER/REPO/pull/23", url) - }) - - output, err := runCommand(http, "master", true, "-w blueberries") - if err != nil { - t.Errorf("error running command `pr view`: %v", err) - } - - assert.Equal(t, "", output.String()) -} - -func TestPRView_web_branchWithOwnerArg(t *testing.T) { - http := &httpmock.Registry{} - defer http.Verify(t) - - http.Register( - httpmock.GraphQL(`query PullRequestForBranch\b`), - httpmock.StringResponse(` - { "data": { "repository": { "pullRequests": { "nodes": [ - { "headRefName": "blueberries", - "isCrossRepository": true, - "headRepositoryOwner": { "login": "hubot" }, - "url": "https://github.com/hubot/REPO/pull/23" } - ] } } } }`), - ) - - cs, cmdTeardown := run.Stub() - defer cmdTeardown(t) - - cs.Register(`https://github\.com`, 0, "", func(args []string) { - url := strings.ReplaceAll(args[len(args)-1], "^", "") - assert.Equal(t, "https://github.com/hubot/REPO/pull/23", url) - }) - - output, err := runCommand(http, "master", true, "-w hubot:blueberries") - if err != nil { - t.Errorf("error running command `pr view`: %v", err) - } - - assert.Equal(t, "", output.String()) + assert.Equal(t, "https://github.com/OWNER/REPO/pull/23", output.BrowsedURL) } func TestPRView_tty_Comments(t *testing.T) { diff --git a/pkg/cmd/release/view/view.go b/pkg/cmd/release/view/view.go index 4dcfdeee2..9bcccb675 100644 --- a/pkg/cmd/release/view/view.go +++ b/pkg/cmd/release/view/view.go @@ -17,10 +17,15 @@ import ( "github.com/spf13/cobra" ) +type browser interface { + Browse(string) error +} + type ViewOptions struct { HttpClient func() (*http.Client, error) IO *iostreams.IOStreams BaseRepo func() (ghrepo.Interface, error) + Browser browser TagName string WebMode bool @@ -30,6 +35,7 @@ func NewCmdView(f *cmdutil.Factory, runF func(*ViewOptions) error) *cobra.Comman opts := &ViewOptions{ IO: f.IOStreams, HttpClient: f.HttpClient, + Browser: f.Browser, } cmd := &cobra.Command{ @@ -91,7 +97,7 @@ func viewRun(opts *ViewOptions) error { if opts.IO.IsStdoutTTY() { fmt.Fprintf(opts.IO.ErrOut, "Opening %s in your browser.\n", utils.DisplayURL(release.HTMLURL)) } - return utils.OpenInBrowser(release.HTMLURL) + return opts.Browser.Browse(release.HTMLURL) } if opts.IO.IsStdoutTTY() { diff --git a/pkg/cmd/repo/view/view.go b/pkg/cmd/repo/view/view.go index 4a1def8b8..3fa70268e 100644 --- a/pkg/cmd/repo/view/view.go +++ b/pkg/cmd/repo/view/view.go @@ -21,10 +21,15 @@ import ( "github.com/spf13/cobra" ) +type browser interface { + Browse(string) error +} + type ViewOptions struct { HttpClient func() (*http.Client, error) IO *iostreams.IOStreams BaseRepo func() (ghrepo.Interface, error) + Browser browser RepoArg string Web bool @@ -36,6 +41,7 @@ func NewCmdView(f *cmdutil.Factory, runF func(*ViewOptions) error) *cobra.Comman IO: f.IOStreams, HttpClient: f.HttpClient, BaseRepo: f.BaseRepo, + Browser: f.Browser, } cmd := &cobra.Command{ @@ -106,7 +112,7 @@ func viewRun(opts *ViewOptions) error { if opts.IO.IsStdoutTTY() { fmt.Fprintf(opts.IO.ErrOut, "Opening %s in your browser.\n", utils.DisplayURL(openURL)) } - return utils.OpenInBrowser(openURL) + return opts.Browser.Browse(openURL) } fullName := ghrepo.FullName(toView) diff --git a/pkg/cmd/repo/view/view_test.go b/pkg/cmd/repo/view/view_test.go index 005ec8515..fd91dcd70 100644 --- a/pkg/cmd/repo/view/view_test.go +++ b/pkg/cmd/repo/view/view_test.go @@ -99,15 +99,19 @@ func Test_RepoView_Web(t *testing.T) { name string stdoutTTY bool wantStderr string + wantBrowse string }{ { name: "tty", stdoutTTY: true, wantStderr: "Opening github.com/OWNER/REPO in your browser.\n", + wantBrowse: "https://github.com/OWNER/REPO", }, { name: "nontty", + stdoutTTY: false, wantStderr: "", + wantBrowse: "https://github.com/OWNER/REPO", }, } @@ -115,6 +119,7 @@ func Test_RepoView_Web(t *testing.T) { reg := &httpmock.Registry{} reg.StubRepoInfoResponse("OWNER", "REPO", "main") + browser := &cmdutil.TestBrowser{} opts := &ViewOptions{ Web: true, HttpClient: func() (*http.Client, error) { @@ -123,6 +128,7 @@ func Test_RepoView_Web(t *testing.T) { BaseRepo: func() (ghrepo.Interface, error) { return ghrepo.New("OWNER", "REPO"), nil }, + Browser: browser, } io, _, stdout, stderr := iostreams.Test() @@ -132,9 +138,8 @@ func Test_RepoView_Web(t *testing.T) { t.Run(tt.name, func(t *testing.T) { io.SetStdoutTTY(tt.stdoutTTY) - cs, teardown := run.Stub() + _, teardown := run.Stub() defer teardown(t) - cs.Register(`https://github\.com/OWNER/REPO$`, 0, "") if err := viewRun(opts); err != nil { t.Errorf("viewRun() error = %v", err) @@ -142,6 +147,7 @@ func Test_RepoView_Web(t *testing.T) { assert.Equal(t, "", stdout.String()) assert.Equal(t, tt.wantStderr, stderr.String()) reg.Verify(t) + browser.Verify(t, tt.wantBrowse) }) } } diff --git a/pkg/cmdutil/factory.go b/pkg/cmdutil/factory.go index eb9546b52..96b6ec0b8 100644 --- a/pkg/cmdutil/factory.go +++ b/pkg/cmdutil/factory.go @@ -9,8 +9,14 @@ import ( "github.com/cli/cli/pkg/iostreams" ) +type Browser interface { + Browse(string) error +} + type Factory struct { - IOStreams *iostreams.IOStreams + IOStreams *iostreams.IOStreams + Browser Browser + HttpClient func() (*http.Client, error) BaseRepo func() (ghrepo.Interface, error) Remotes func() (context.Remotes, error) diff --git a/pkg/cmdutil/web_browser.go b/pkg/cmdutil/web_browser.go new file mode 100644 index 000000000..aa33373cb --- /dev/null +++ b/pkg/cmdutil/web_browser.go @@ -0,0 +1,83 @@ +package cmdutil + +import ( + "io" + "os/exec" + + "github.com/cli/browser" + "github.com/cli/safeexec" + "github.com/google/shlex" +) + +func NewBrowser(launcher string, stdout, stderr io.Writer) Browser { + return &webBrowser{ + launcher: launcher, + stdout: stdout, + stderr: stderr, + } +} + +type webBrowser struct { + launcher string + stdout io.Writer + stderr io.Writer +} + +func (b *webBrowser) Browse(url string) error { + if b.launcher != "" { + launcherArgs, err := shlex.Split(b.launcher) + if err != nil { + return err + } + launcherExe, err := safeexec.LookPath(launcherArgs[0]) + if err != nil { + return err + } + args := append(launcherArgs[1:], url) + cmd := exec.Command(launcherExe, args...) + cmd.Stdout = b.stdout + cmd.Stderr = b.stderr + return cmd.Run() + } + + return browser.OpenURL(url) +} + +type TestBrowser struct { + urls []string +} + +func (b *TestBrowser) Browse(url string) error { + b.urls = append(b.urls, url) + return nil +} + +func (b *TestBrowser) BrowsedURL() string { + if len(b.urls) > 0 { + return b.urls[0] + } + return "" +} + +type _testing interface { + Errorf(string, ...interface{}) + Helper() +} + +func (b *TestBrowser) Verify(t _testing, url string) { + t.Helper() + if url != "" { + switch len(b.urls) { + case 0: + t.Errorf("expected browser to open URL %q, but it was never invoked", url) + case 1: + if url != b.urls[0] { + t.Errorf("expected browser to open URL %q, got %q", url, b.urls[0]) + } + default: + t.Errorf("expected browser to open one URL, but was invoked %d times", len(b.urls)) + } + } else if len(b.urls) > 0 { + t.Errorf("expected no browser to open, but was invoked %d times: %v", len(b.urls), b.urls) + } +} diff --git a/test/helpers.go b/test/helpers.go index 0cef94802..5ca900f59 100644 --- a/test/helpers.go +++ b/test/helpers.go @@ -7,7 +7,9 @@ import ( // TODO copypasta from command package type CmdOut struct { - OutBuf, ErrBuf *bytes.Buffer + OutBuf *bytes.Buffer + ErrBuf *bytes.Buffer + BrowsedURL string } func (c CmdOut) String() string { diff --git a/utils/utils.go b/utils/utils.go index 57b39b5ba..e614d3dd0 100644 --- a/utils/utils.go +++ b/utils/utils.go @@ -5,27 +5,8 @@ import ( "net/url" "strings" "time" - - "github.com/cli/cli/internal/run" - "github.com/cli/cli/pkg/browser" ) -// OpenInBrowser opens the url in a web browser based on OS and $BROWSER environment variable -func OpenInBrowser(url string) error { - browseCmd, err := browser.Command(url) - if err != nil { - return err - } - err = run.PrepareCmd(browseCmd).Run() - if err != nil { - browserEnv := browser.FromEnv() - if browserEnv != "" { - return fmt.Errorf("%w\nNote: check your BROWSER environment variable", err) - } - } - return err -} - func Pluralize(num int, thing string) string { if num == 1 { return fmt.Sprintf("%d %s", num, thing)