Merge remote-tracking branch 'origin' into bugfix/release/create/no-tag-when-generated-available

This commit is contained in:
Mislav Marohnić 2022-01-17 13:27:47 +01:00
commit e54b29e9cf
56 changed files with 782 additions and 696 deletions

View file

@ -17,6 +17,15 @@ jobs:
- name: Check out code
uses: actions/checkout@v2
- name: Cache Go modules
uses: actions/cache@v2
with:
path: ~/go
key: ${{ runner.os }}-build-${{ hashFiles('go.mod') }}
restore-keys: |
${{ runner.os }}-build-
${{ runner.os }}-
- name: Download dependencies
run: go mod download

5
go.mod
View file

@ -6,7 +6,7 @@ require (
github.com/AlecAivazis/survey/v2 v2.3.2
github.com/MakeNowJust/heredoc v1.0.0
github.com/briandowns/spinner v1.18.0
github.com/charmbracelet/glamour v0.3.0
github.com/charmbracelet/glamour v0.4.0
github.com/cli/browser v1.1.0
github.com/cli/oauth v0.9.0
github.com/cli/safeexec v1.0.0
@ -26,8 +26,7 @@ require (
github.com/mattn/go-colorable v0.1.12
github.com/mattn/go-isatty v0.0.14
github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d
github.com/microcosm-cc/bluemonday v1.0.16 // indirect
github.com/muesli/reflow v0.2.1-0.20210502190812-c80126ec2ad5
github.com/muesli/reflow v0.3.0
github.com/muesli/termenv v0.9.0
github.com/muhammadmuzzammil1998/jsonc v0.0.0-20201229145248-615b0916ca38
github.com/opentracing/opentracing-go v1.1.0

43
go.sum
View file

@ -56,15 +56,8 @@ github.com/MakeNowJust/heredoc v1.0.0/go.mod h1:mG5amYoWBHf8vpLOuehzbGGw0EHxpZZ6
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/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=
github.com/alecthomas/chroma v0.8.2 h1:x3zkuE2lUk/RIekyAJ3XRqSCP4zwWDfcw/YJCuCAACg=
github.com/alecthomas/chroma v0.8.2/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/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/chroma v0.10.0 h1:7XDcGkCQopCNKjZHfYrNLraA+M7e0fMiJ/Mfikbfjek=
github.com/alecthomas/chroma v0.10.0/go.mod h1:jtJATyUxlIORhUOFNA9NZDWGAQ8wpxQQqNSB4rjA/1s=
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
@ -88,8 +81,8 @@ github.com/census-instrumentation/opencensus-proto v0.3.0/go.mod h1:f6KPmirojxKA
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/charmbracelet/glamour v0.3.0 h1:3H+ZrKlSg8s+WU6V7eF2eRVYt8lCueffbi7r2+ffGkc=
github.com/charmbracelet/glamour v0.3.0/go.mod h1:TzF0koPZhqq0YVBNL100cPHznAAjVj7fksX2RInwjGw=
github.com/charmbracelet/glamour v0.4.0 h1:scR+smyB7WdmrlIaff6IVlm48P48JaNM7JypM/VGl4k=
github.com/charmbracelet/glamour v0.4.0/go.mod h1:9ZRtG19AUIzcTm7FGLGbq3D5WKQ5UyZBbQsMQN0XIqc=
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
@ -123,13 +116,11 @@ github.com/cpuguy83/go-md2man/v2 v2.0.1 h1:r/myEWzV9lfsM1tFLgDyu0atFtJ1fXn261LKY
github.com/cpuguy83/go-md2man/v2 v2.0.1/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/creack/pty v1.1.17 h1:QeVUsEDNrLBW4tMgZHvxy18sKtr6VI492kBhUfhDJNI=
github.com/creack/pty v1.1.17/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4=
github.com/danwakefield/fnmatch v0.0.0-20160403171240-cbb64ac3d964 h1:y5HC9v93H5EPKqaS1UYVg1uYah5Xf51mBfIoWehClUQ=
github.com/danwakefield/fnmatch v0.0.0-20160403171240-cbb64ac3d964/go.mod h1:Xd9hchkHSWYkEqJwUGisez3G1QY8Ryz0sdWrLPMGjLk=
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/dlclark/regexp2 v1.2.0 h1:8sAhBGEM0dRWogWqWyQeIJnxjWO6oIjl8FKqREDsGfk=
github.com/dlclark/regexp2 v1.2.0/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc=
github.com/dlclark/regexp2 v1.4.0 h1:F1rxgk7p4uKjwIQxBs9oAXe5CqrXlCduYEJvrF4u93E=
github.com/dlclark/regexp2 v1.4.0/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc=
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
@ -333,16 +324,15 @@ github.com/mattn/go-isatty v0.0.13/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Ky
github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y=
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
github.com/mattn/go-runewidth v0.0.10/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk=
github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk=
github.com/mattn/go-runewidth v0.0.13 h1:lTGmDsbAYt5DmK6OnoV7EuIF1wEIFAcxld6ypU4OSgU=
github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE=
github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d h1:5PJl274Y63IEHC+7izoQE9x6ikvDFZS2mDVS3drnohI=
github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE=
github.com/microcosm-cc/bluemonday v1.0.6/go.mod h1:HOT/6NaBlR0f9XlxD3zolN6Z3N8Lp4pvhp+jLS5ihnI=
github.com/microcosm-cc/bluemonday v1.0.16 h1:kHmAq2t7WPWLjiGvzKa5o3HzSfahUKiOq7fAPUiMNIc=
github.com/microcosm-cc/bluemonday v1.0.16/go.mod h1:Z0r70sCuXHig8YpBzCc5eGHAap2K7e/u082ZUpDRRqM=
github.com/microcosm-cc/bluemonday v1.0.17 h1:Z1a//hgsQ4yjC+8zEkV8IWySkXnsxmdSY642CTFQb5Y=
github.com/microcosm-cc/bluemonday v1.0.17/go.mod h1:Z0r70sCuXHig8YpBzCc5eGHAap2K7e/u082ZUpDRRqM=
github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
github.com/miekg/dns v1.1.26/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso=
github.com/miekg/dns v1.1.41/go.mod h1:p6aan82bvRIyn+zDIv9xYNUpwa73JcSh9BKwknJysuI=
@ -357,10 +347,8 @@ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJ
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/muesli/reflow v0.2.0/go.mod h1:qT22vjVmM9MIUeLgsVYe/Ye7eZlbv9dZjL3dVhUqLX8=
github.com/muesli/reflow v0.2.1-0.20210502190812-c80126ec2ad5 h1:T+Fc6qGlSfM+z0JPlp+n5rijvlg6C6JYFSNaqnCifDU=
github.com/muesli/reflow v0.2.1-0.20210502190812-c80126ec2ad5/go.mod h1:Xk+z4oIWdQqJzsxyjgl3P22oYZnHdZ8FFTHAQQt5BMQ=
github.com/muesli/termenv v0.8.1/go.mod h1:kzt/D/4a88RoheZmwfqorY3A+tnsSMA9HJC/fQSFKo0=
github.com/muesli/reflow v0.3.0 h1:IFsN6K9NfGtjeggFP+68I4chLZV2yIKsXJFNZ+eWh6s=
github.com/muesli/reflow v0.3.0/go.mod h1:pbwTDkVPibjO2kyvBQRBxTWEEGDGq0FlB1BIKtnHY/8=
github.com/muesli/termenv v0.9.0 h1:wnbOaGz+LUR3jNT0zOzinPnyDaCZUQRZj9GxK8eRVl8=
github.com/muesli/termenv v0.9.0/go.mod h1:R/LzAKf+suGs4IsO95y7+7DpFHO0KABgnZqtlyx2mBw=
github.com/muhammadmuzzammil1998/jsonc v0.0.0-20201229145248-615b0916ca38 h1:0FrBxrkJ0hVembTb/e4EU5Ml6vLcOusAqymmYISg5Uo=
@ -375,7 +363,6 @@ github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144T
github.com/pelletier/go-toml v1.9.4/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
@ -403,8 +390,6 @@ github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQD
github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
github.com/sagikazarmark/crypt v0.3.0/go.mod h1:uD/D+6UF4SrIR1uGEv7bBNkNqLGqUr43MRiaGWX1Nig=
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=
github.com/shurcooL/githubv4 v0.0.0-20200928013246-d292edc3691b h1:0/ecDXh/HTHRtSDSFnD2/Ta1yQ5J76ZspVY4u0/jGFk=
github.com/shurcooL/githubv4 v0.0.0-20200928013246-d292edc3691b/go.mod h1:hAF0iLZy4td2EX+/8Tw+4nodhlMrwN3HupfaXj3zkGo=
github.com/shurcooL/graphql v0.0.0-20200928012149-18c5c3165e3a h1:KikTa6HtAK8cS1qjvUvvq4QO21QnwC+EfvB+OAuZ/ZU=
@ -440,9 +425,9 @@ github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9de
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.3.3/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
github.com/yuin/goldmark v1.3.5 h1:dPmz1Snjq0kmkz159iL7S6WzdahUTHnHB5M56WFVifs=
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
github.com/yuin/goldmark v1.4.4 h1:zNWRjYUW32G9KirMXYHQHVNFkXvMI7LpgNW2AgYAoIs=
github.com/yuin/goldmark v1.4.4/go.mod h1:rmuwmfZ0+bvzB24eSC//bk1R1Zp3hM0OXYv/G2LIilg=
github.com/yuin/goldmark-emoji v1.0.1 h1:ctuWEyzGBwiucEqxzwe0SOYDXPAucOrE9NQC18Wa1os=
github.com/yuin/goldmark-emoji v1.0.1/go.mod h1:2w1E6FEWLcDQkoTE+7HU6QF1F6SLlNGjRIBbIZQFqkQ=
go.etcd.io/etcd/api/v3 v3.5.1/go.mod h1:cbVKeC6lCfl7j/8jBhAK6aIYO9XOjdptoxU/nLQcPvs=
@ -531,7 +516,6 @@ golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v
golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc=
golang.org/x/net v0.0.0-20210331212208-0fccb6fa2b5c/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
golang.org/x/net v0.0.0-20210410081132-afb366fc7cd1/go.mod h1:9tjilg8BloeKEkVJvy7fQ90B1CfIiPueXVOjqfkSzI8=
golang.org/x/net v0.0.0-20210503060351-7fd8e65b6420/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
@ -601,7 +585,6 @@ golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200413165638-669c56c373c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=

View file

@ -27,13 +27,11 @@ type iconfig interface {
Write() error
}
func AuthFlowWithConfig(cfg iconfig, IO *iostreams.IOStreams, hostname, notice string, additionalScopes []string) (string, error) {
func AuthFlowWithConfig(cfg iconfig, IO *iostreams.IOStreams, hostname, notice string, additionalScopes []string, isInteractive bool) (string, error) {
// TODO this probably shouldn't live in this package. It should probably be in a new package that
// depends on both iostreams and config.
stderr := IO.ErrOut
cs := IO.ColorScheme()
token, userLogin, err := authFlow(hostname, IO, notice, additionalScopes)
token, userLogin, err := authFlow(hostname, IO, notice, additionalScopes, isInteractive)
if err != nil {
return "", err
}
@ -47,19 +45,10 @@ func AuthFlowWithConfig(cfg iconfig, IO *iostreams.IOStreams, hostname, notice s
return "", err
}
err = cfg.Write()
if err != nil {
return "", err
}
fmt.Fprintf(stderr, "%s Authentication complete. %s to continue...\n",
cs.SuccessIcon(), cs.Bold("Press Enter"))
_ = waitForEnter(IO.In)
return token, nil
return token, cfg.Write()
}
func authFlow(oauthHost string, IO *iostreams.IOStreams, notice string, additionalScopes []string) (string, string, error) {
func authFlow(oauthHost string, IO *iostreams.IOStreams, notice string, additionalScopes []string, isInteractive bool) (string, string, error) {
w := IO.ErrOut
cs := IO.ColorScheme()
@ -90,7 +79,12 @@ func authFlow(oauthHost string, IO *iostreams.IOStreams, notice string, addition
return nil
},
BrowseURL: func(url string) error {
fmt.Fprintf(w, "- %s to open %s in your browser... ", cs.Bold("Press Enter"), oauthHost)
if !isInteractive {
fmt.Fprintf(w, "%s to continue in your web browser: %s\n", cs.Bold("Open this URL"), url)
return nil
}
fmt.Fprintf(w, "%s to open %s in your browser... ", cs.Bold("Press Enter"), oauthHost)
_ = waitForEnter(IO.In)
// FIXME: read the browser from cmd Factory rather than recreating it
@ -103,7 +97,7 @@ func authFlow(oauthHost string, IO *iostreams.IOStreams, notice string, addition
return nil
},
WriteSuccessHTML: func(w io.Writer) {
fmt.Fprintln(w, oauthSuccessPage)
fmt.Fprint(w, oauthSuccessPage)
},
HTTPClient: httpClient,
Stdin: IO.In,

View file

@ -72,13 +72,23 @@ func RESTPrefix(hostname string) string {
}
func GistPrefix(hostname string) string {
prefix := "https://"
if strings.EqualFold(hostname, localhost) {
prefix = "http://"
}
return prefix + GistHost(hostname)
}
func GistHost(hostname string) string {
if IsEnterprise(hostname) {
return fmt.Sprintf("https://%s/gist/", hostname)
return fmt.Sprintf("%s/gist/", hostname)
}
if strings.EqualFold(hostname, localhost) {
return fmt.Sprintf("http://%s/gist/", hostname)
return fmt.Sprintf("%s/gist/", hostname)
}
return fmt.Sprintf("https://gist.%s/", hostname)
return fmt.Sprintf("gist.%s/", hostname)
}
func HostPrefix(hostname string) string {

View file

@ -100,12 +100,18 @@ func helperRun(opts *CredentialOptions) error {
return err
}
lookupHost := wants["host"]
var gotUser string
gotToken, source, _ := cfg.GetWithSource(wants["host"], "oauth_token")
gotToken, source, _ := cfg.GetWithSource(lookupHost, "oauth_token")
if gotToken == "" && strings.HasPrefix(lookupHost, "gist.") {
lookupHost = strings.TrimPrefix(lookupHost, "gist.")
gotToken, source, _ = cfg.GetWithSource(lookupHost, "oauth_token")
}
if strings.HasSuffix(source, "_TOKEN") {
gotUser = tokenUser
} else {
gotUser, _, _ = cfg.GetWithSource(wants["host"], "user")
gotUser, _, _ = cfg.GetWithSource(lookupHost, "user")
}
if gotUser == "" || gotToken == "" {

View file

@ -74,6 +74,32 @@ func Test_helperRun(t *testing.T) {
`),
wantStderr: "",
},
{
name: "gist host",
opts: CredentialOptions{
Operation: "get",
Config: func() (config, error) {
return tinyConfig{
"_source": "/Users/monalisa/.config/gh/hosts.yml",
"github.com:user": "monalisa",
"github.com:oauth_token": "OTOKEN",
}, nil
},
},
input: heredoc.Doc(`
protocol=https
host=gist.github.com
username=monalisa
`),
wantErr: false,
wantStdout: heredoc.Doc(`
protocol=https
host=gist.github.com
username=monalisa
password=OTOKEN
`),
wantStderr: "",
},
{
name: "url input",
opts: CredentialOptions{

View file

@ -68,37 +68,34 @@ func NewCmdLogin(f *cmdutil.Factory, runF func(*LoginOptions) error) *cobra.Comm
$ gh auth login --hostname enterprise.internal
`),
RunE: func(cmd *cobra.Command, args []string) error {
if !opts.IO.CanPrompt() && !(tokenStdin || opts.Web) {
return cmdutil.FlagErrorf("--web or --with-token required when not running interactively")
}
if tokenStdin && opts.Web {
return cmdutil.FlagErrorf("specify only one of --web or --with-token")
return cmdutil.FlagErrorf("specify only one of `--web` or `--with-token`")
}
if tokenStdin && len(opts.Scopes) > 0 {
return cmdutil.FlagErrorf("specify only one of `--scopes` or `--with-token`")
}
if tokenStdin {
defer opts.IO.In.Close()
token, err := ioutil.ReadAll(opts.IO.In)
if err != nil {
return fmt.Errorf("failed to read token from STDIN: %w", err)
return fmt.Errorf("failed to read token from standard input: %w", err)
}
opts.Token = strings.TrimSpace(string(token))
}
if opts.IO.CanPrompt() && opts.Token == "" && !opts.Web {
if opts.IO.CanPrompt() && opts.Token == "" {
opts.Interactive = true
}
if cmd.Flags().Changed("hostname") {
if err := ghinstance.HostnameValidator(opts.Hostname); err != nil {
return cmdutil.FlagErrorf("error parsing --hostname: %w", err)
return cmdutil.FlagErrorf("error parsing hostname: %w", err)
}
}
if !opts.Interactive {
if opts.Hostname == "" {
opts.Hostname = ghinstance.Default()
}
if opts.Hostname == "" && (!opts.Interactive || opts.Web) {
opts.Hostname = ghinstance.Default()
}
opts.MainExecutable = f.Executable()
@ -125,15 +122,11 @@ func loginRun(opts *LoginOptions) error {
}
hostname := opts.Hostname
if hostname == "" {
if opts.Interactive {
var err error
hostname, err = promptForHostname()
if err != nil {
return err
}
} else {
return errors.New("must specify --hostname")
if opts.Interactive && hostname == "" {
var err error
hostname, err = promptForHostname()
if err != nil {
return err
}
}

View file

@ -5,6 +5,7 @@ import (
"net/http"
"os"
"regexp"
"runtime"
"testing"
"github.com/MakeNowJust/heredoc"
@ -18,6 +19,21 @@ import (
"github.com/stretchr/testify/assert"
)
func stubHomeDir(t *testing.T, dir string) {
homeEnv := "HOME"
switch runtime.GOOS {
case "windows":
homeEnv = "USERPROFILE"
case "plan9":
homeEnv = "home"
}
oldHomeDir := os.Getenv(homeEnv)
os.Setenv(homeEnv, dir)
t.Cleanup(func() {
os.Setenv(homeEnv, oldHomeDir)
})
}
func Test_NewCmdLogin(t *testing.T) {
tests := []struct {
name string
@ -50,13 +66,19 @@ func Test_NewCmdLogin(t *testing.T) {
name: "nontty, hostname",
stdinTTY: false,
cli: "--hostname claire.redfield",
wantsErr: true,
wants: LoginOptions{
Hostname: "claire.redfield",
Token: "",
},
},
{
name: "nontty",
stdinTTY: false,
cli: "",
wantsErr: true,
wants: LoginOptions{
Hostname: "github.com",
Token: "",
},
},
{
name: "nontty, with-token, hostname",
@ -102,8 +124,9 @@ func Test_NewCmdLogin(t *testing.T) {
stdinTTY: true,
cli: "--web",
wants: LoginOptions{
Hostname: "github.com",
Web: true,
Hostname: "github.com",
Web: true,
Interactive: true,
},
},
{
@ -345,6 +368,8 @@ func Test_loginRun_nontty(t *testing.T) {
}
func Test_loginRun_Survey(t *testing.T) {
stubHomeDir(t, t.TempDir())
tests := []struct {
name string
opts *LoginOptions
@ -370,8 +395,8 @@ func Test_loginRun_Survey(t *testing.T) {
// httpmock.StringResponse(`{"data":{"viewer":{"login":"jillv"}}}`))
},
askStubs: func(as *prompt.AskStubber) {
as.StubOne(0) // host type github.com
as.StubOne(false) // do not continue
as.StubPrompt("What account do you want to log into?").AnswerWith("GitHub.com")
as.StubPrompt("You're already logged into github.com. Do you want to re-authenticate?").AnswerWith(false)
},
wantHosts: "", // nothing should have been written to hosts
wantErrOut: nil,
@ -389,10 +414,10 @@ func Test_loginRun_Survey(t *testing.T) {
git_protocol: https
`),
askStubs: func(as *prompt.AskStubber) {
as.StubOne("HTTPS") // git_protocol
as.StubOne(false) // cache credentials
as.StubOne(1) // auth mode: token
as.StubOne("def456") // auth token
as.StubPrompt("What is your preferred protocol for Git operations?").AnswerWith("HTTPS")
as.StubPrompt("Authenticate Git with your GitHub credentials?").AnswerWith(false)
as.StubPrompt("How would you like to authenticate GitHub CLI?").AnswerWith("Paste an authentication token")
as.StubPrompt("Paste your authentication token:").AnswerWith("def456")
},
runStubs: func(rs *run.CommandStubber) {
rs.Register(`git config credential\.https:/`, 1, "")
@ -418,12 +443,12 @@ func Test_loginRun_Survey(t *testing.T) {
Interactive: true,
},
askStubs: func(as *prompt.AskStubber) {
as.StubOne(1) // host type enterprise
as.StubOne("brad.vickers") // hostname
as.StubOne("HTTPS") // git_protocol
as.StubOne(false) // cache credentials
as.StubOne(1) // auth mode: token
as.StubOne("def456") // auth token
as.StubPrompt("What account do you want to log into?").AnswerWith("GitHub Enterprise Server")
as.StubPrompt("GHE hostname:").AnswerWith("brad.vickers")
as.StubPrompt("What is your preferred protocol for Git operations?").AnswerWith("HTTPS")
as.StubPrompt("Authenticate Git with your GitHub credentials?").AnswerWith(false)
as.StubPrompt("How would you like to authenticate GitHub CLI?").AnswerWith("Paste an authentication token")
as.StubPrompt("Paste your authentication token:").AnswerWith("def456")
},
runStubs: func(rs *run.CommandStubber) {
rs.Register(`git config credential\.https:/`, 1, "")
@ -449,11 +474,11 @@ func Test_loginRun_Survey(t *testing.T) {
Interactive: true,
},
askStubs: func(as *prompt.AskStubber) {
as.StubOne(0) // host type github.com
as.StubOne("HTTPS") // git_protocol
as.StubOne(false) // cache credentials
as.StubOne(1) // auth mode: token
as.StubOne("def456") // auth token
as.StubPrompt("What account do you want to log into?").AnswerWith("GitHub.com")
as.StubPrompt("What is your preferred protocol for Git operations?").AnswerWith("HTTPS")
as.StubPrompt("Authenticate Git with your GitHub credentials?").AnswerWith(false)
as.StubPrompt("How would you like to authenticate GitHub CLI?").AnswerWith("Paste an authentication token")
as.StubPrompt("Paste your authentication token:").AnswerWith("def456")
},
runStubs: func(rs *run.CommandStubber) {
rs.Register(`git config credential\.https:/`, 1, "")
@ -473,11 +498,11 @@ func Test_loginRun_Survey(t *testing.T) {
Interactive: true,
},
askStubs: func(as *prompt.AskStubber) {
as.StubOne(0) // host type github.com
as.StubOne("SSH") // git_protocol
as.StubOne(10) // TODO: SSH key selection
as.StubOne(1) // auth mode: token
as.StubOne("def456") // auth token
as.StubPrompt("What account do you want to log into?").AnswerWith("GitHub.com")
as.StubPrompt("What is your preferred protocol for Git operations?").AnswerWith("SSH")
as.StubPrompt("Generate a new SSH key to add to your GitHub account?").AnswerWith(false)
as.StubPrompt("How would you like to authenticate GitHub CLI?").AnswerWith("Paste an authentication token")
as.StubPrompt("Paste your authentication token:").AnswerWith("def456")
},
wantErrOut: regexp.MustCompile("Tip: you can generate a Personal Access Token here https://github.com/settings/tokens"),
},
@ -523,8 +548,7 @@ func Test_loginRun_Survey(t *testing.T) {
hostsBuf := bytes.Buffer{}
defer config.StubWriteConfig(&mainBuf, &hostsBuf)()
as, teardown := prompt.InitAskStubber()
defer teardown()
as := prompt.NewAskStubber(t)
if tt.askStubs != nil {
tt.askStubs(as)
}

View file

@ -106,8 +106,8 @@ func Test_logoutRun_tty(t *testing.T) {
cfgHosts: []string{"cheryl.mason", "github.com"},
wantHosts: "cheryl.mason:\n oauth_token: abc123\n",
askStubs: func(as *prompt.AskStubber) {
as.StubOne("github.com")
as.StubOne(true)
as.StubPrompt("What account do you want to log out of?").AnswerWith("github.com")
as.StubPrompt("Are you sure you want to log out of github.com account 'cybilb'?").AnswerWith(true)
},
wantErrOut: regexp.MustCompile(`Logged out of github.com account 'cybilb'`),
},
@ -116,7 +116,7 @@ func Test_logoutRun_tty(t *testing.T) {
opts: &LogoutOptions{},
cfgHosts: []string{"github.com"},
askStubs: func(as *prompt.AskStubber) {
as.StubOne(true)
as.StubPrompt("Are you sure you want to log out of github.com account 'cybilb'?").AnswerWith(true)
},
wantErrOut: regexp.MustCompile(`Logged out of github.com account 'cybilb'`),
},
@ -133,7 +133,7 @@ func Test_logoutRun_tty(t *testing.T) {
cfgHosts: []string{"cheryl.mason", "github.com"},
wantHosts: "github.com:\n oauth_token: abc123\n",
askStubs: func(as *prompt.AskStubber) {
as.StubOne(true)
as.StubPrompt("Are you sure you want to log out of cheryl.mason account 'cybilb'?").AnswerWith(true)
},
wantErrOut: regexp.MustCompile(`Logged out of cheryl.mason account 'cybilb'`),
},
@ -169,8 +169,7 @@ func Test_logoutRun_tty(t *testing.T) {
hostsBuf := bytes.Buffer{}
defer config.StubWriteConfig(&mainBuf, &hostsBuf)()
as, teardown := prompt.InitAskStubber()
defer teardown()
as := prompt.NewAskStubber(t)
if tt.askStubs != nil {
tt.askStubs(as)
}

View file

@ -26,7 +26,7 @@ type RefreshOptions struct {
Hostname string
Scopes []string
AuthFlow func(config.Config, *iostreams.IOStreams, string, []string) error
AuthFlow func(config.Config, *iostreams.IOStreams, string, []string, bool) error
Interactive bool
}
@ -35,8 +35,8 @@ func NewCmdRefresh(f *cmdutil.Factory, runF func(*RefreshOptions) error) *cobra.
opts := &RefreshOptions{
IO: f.IOStreams,
Config: f.Config,
AuthFlow: func(cfg config.Config, io *iostreams.IOStreams, hostname string, scopes []string) error {
_, err := authflow.AuthFlowWithConfig(cfg, io, hostname, "", scopes)
AuthFlow: func(cfg config.Config, io *iostreams.IOStreams, hostname string, scopes []string, interactive bool) error {
_, err := authflow.AuthFlowWithConfig(cfg, io, hostname, "", scopes, interactive)
return err
},
httpClient: http.DefaultClient,
@ -154,10 +154,13 @@ func refreshRun(opts *RefreshOptions) error {
additionalScopes = append(additionalScopes, credentialFlow.Scopes()...)
}
if err := opts.AuthFlow(cfg, opts.IO, hostname, append(opts.Scopes, additionalScopes...)); err != nil {
if err := opts.AuthFlow(cfg, opts.IO, hostname, append(opts.Scopes, additionalScopes...), opts.Interactive); err != nil {
return err
}
cs := opts.IO.ColorScheme()
fmt.Fprintf(opts.IO.ErrOut, "%s Authentication complete.\n", cs.SuccessIcon())
if credentialFlow.ShouldSetup() {
username, _ := cfg.Get(hostname, "user")
password, _ := cfg.Get(hostname, "oauth_token")

View file

@ -194,7 +194,7 @@ func Test_refreshRun(t *testing.T) {
Hostname: "",
},
askStubs: func(as *prompt.AskStubber) {
as.StubOne("github.com")
as.StubPrompt("What account do you want to refresh auth for?").AnswerWith("github.com")
},
wantAuthArgs: authArgs{
hostname: "github.com",
@ -232,7 +232,7 @@ func Test_refreshRun(t *testing.T) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
aa := authArgs{}
tt.opts.AuthFlow = func(_ config.Config, _ *iostreams.IOStreams, hostname string, scopes []string) error {
tt.opts.AuthFlow = func(_ config.Config, _ *iostreams.IOStreams, hostname string, scopes []string, interactive bool) error {
aa.hostname = hostname
aa.scopes = scopes
return nil
@ -276,8 +276,7 @@ func Test_refreshRun(t *testing.T) {
hostsBuf := bytes.Buffer{}
defer config.StubWriteConfig(&mainBuf, &hostsBuf)()
as, teardown := prompt.InitAskStubber()
defer teardown()
as := prompt.NewAskStubber(t)
if tt.askStubs != nil {
tt.askStubs(as)
}

View file

@ -63,25 +63,46 @@ func (flow *GitCredentialFlow) Setup(hostname, username, authToken string) error
func (flow *GitCredentialFlow) gitCredentialSetup(hostname, username, password string) error {
if flow.helper == "" {
// first use a blank value to indicate to git we want to sever the chain of credential helpers
preConfigureCmd, err := git.GitCommand("config", "--global", "--replace-all", gitCredentialHelperKey(hostname), "")
if err != nil {
return err
}
if err = run.PrepareCmd(preConfigureCmd).Run(); err != nil {
return err
credHelperKeys := []string{
gitCredentialHelperKey(hostname),
}
// use GitHub CLI as a credential helper (for this host only)
configureCmd, err := git.GitCommand(
"config", "--global", "--add",
gitCredentialHelperKey(hostname),
fmt.Sprintf("!%s auth git-credential", shellQuote(flow.Executable)),
)
if err != nil {
return err
gistHost := strings.TrimSuffix(ghinstance.GistHost(hostname), "/")
if strings.HasPrefix(gistHost, "gist.") {
credHelperKeys = append(credHelperKeys, gitCredentialHelperKey(gistHost))
}
return run.PrepareCmd(configureCmd).Run()
var configErr error
for _, credHelperKey := range credHelperKeys {
if configErr != nil {
break
}
// first use a blank value to indicate to git we want to sever the chain of credential helpers
preConfigureCmd, err := git.GitCommand("config", "--global", "--replace-all", credHelperKey, "")
if err != nil {
configErr = err
break
}
if err = run.PrepareCmd(preConfigureCmd).Run(); err != nil {
configErr = err
break
}
// second configure the actual helper for this host
configureCmd, err := git.GitCommand(
"config", "--global", "--add",
credHelperKey,
fmt.Sprintf("!%s auth git-credential", shellQuote(flow.Executable)),
)
if err != nil {
configErr = err
} else {
configErr = run.PrepareCmd(configureCmd).Run()
}
}
return configErr
}
// clear previous cached credentials
@ -121,7 +142,8 @@ func (flow *GitCredentialFlow) gitCredentialSetup(hostname, username, password s
}
func gitCredentialHelperKey(hostname string) string {
return fmt.Sprintf("credential.%s.helper", strings.TrimSuffix(ghinstance.HostPrefix(hostname), "/"))
host := strings.TrimSuffix(ghinstance.HostPrefix(hostname), "/")
return fmt.Sprintf("credential.%s.helper", host)
}
func gitCredentialHelper(hostname string) (helper string, err error) {

View file

@ -22,7 +22,54 @@ func TestGitCredentialSetup_configureExisting(t *testing.T) {
}
}
func TestGitCredentialSetup_setOurs(t *testing.T) {
func TestGitCredentialsSetup_setOurs_GH(t *testing.T) {
cs, restoreRun := run.Stub()
defer restoreRun(t)
cs.Register(`git config --global --replace-all credential\.`, 0, "", func(args []string) {
if key := args[len(args)-2]; key != "credential.https://github.com.helper" {
t.Errorf("git config key was %q", key)
}
if val := args[len(args)-1]; val != "" {
t.Errorf("global credential helper configured to %q", val)
}
})
cs.Register(`git config --global --add credential\.`, 0, "", func(args []string) {
if key := args[len(args)-2]; key != "credential.https://github.com.helper" {
t.Errorf("git config key was %q", key)
}
if val := args[len(args)-1]; val != "!/path/to/gh auth git-credential" {
t.Errorf("global credential helper configured to %q", val)
}
})
cs.Register(`git config --global --replace-all credential\.`, 0, "", func(args []string) {
if key := args[len(args)-2]; key != "credential.https://gist.github.com.helper" {
t.Errorf("git config key was %q", key)
}
if val := args[len(args)-1]; val != "" {
t.Errorf("global credential helper configured to %q", val)
}
})
cs.Register(`git config --global --add credential\.`, 0, "", func(args []string) {
if key := args[len(args)-2]; key != "credential.https://gist.github.com.helper" {
t.Errorf("git config key was %q", key)
}
if val := args[len(args)-1]; val != "!/path/to/gh auth git-credential" {
t.Errorf("global credential helper configured to %q", val)
}
})
f := GitCredentialFlow{
Executable: "/path/to/gh",
helper: "",
}
if err := f.gitCredentialSetup("github.com", "monalisa", "PASSWD"); err != nil {
t.Errorf("GitCredentialSetup() error = %v", err)
}
}
func TestGitCredentialSetup_setOurs_nonGH(t *testing.T) {
cs, restoreRun := run.Stub()
defer restoreRun(t)
cs.Register(`git config --global --replace-all credential\.`, 0, "", func(args []string) {

View file

@ -99,7 +99,7 @@ func Login(opts *LoginOptions) error {
var authMode int
if opts.Web {
authMode = 0
} else {
} else if opts.Interactive {
err := prompt.SurveyAskOne(&survey.Select{
Message: "How would you like to authenticate GitHub CLI?",
Options: []string{
@ -117,10 +117,11 @@ func Login(opts *LoginOptions) error {
if authMode == 0 {
var err error
authToken, err = authflow.AuthFlowWithConfig(cfg, opts.IO, hostname, "", append(opts.Scopes, additionalScopes...))
authToken, err = authflow.AuthFlowWithConfig(cfg, opts.IO, hostname, "", append(opts.Scopes, additionalScopes...), opts.Interactive)
if err != nil {
return fmt.Errorf("failed to authenticate via web browser: %w", err)
}
fmt.Fprintf(opts.IO.ErrOut, "%s Authentication complete.\n", cs.SuccessIcon())
userValidated = true
} else {
minimumScopes := append([]string{"repo", "read:org"}, additionalScopes...)

View file

@ -47,14 +47,13 @@ func TestLogin_ssh(t *testing.T) {
httpmock.REST("POST", "api/v3/user/keys"),
httpmock.StringResponse(`{}`))
ask, askRestore := prompt.InitAskStubber()
defer askRestore()
ask := prompt.NewAskStubber(t)
ask.StubOne("SSH") // preferred protocol
ask.StubOne(true) // generate a new key
ask.StubOne("monkey") // enter a passphrase
ask.StubOne(1) // paste a token
ask.StubOne("ATOKEN") // token
ask.StubPrompt("What is your preferred protocol for Git operations?").AnswerWith("SSH")
ask.StubPrompt("Generate a new SSH key to add to your GitHub account?").AnswerWith(true)
ask.StubPrompt("Enter a passphrase for your new SSH key (Optional)").AnswerWith("monkey")
ask.StubPrompt("How would you like to authenticate GitHub CLI?").AnswerWith("Paste an authentication token")
ask.StubPrompt("Paste your authentication token:").AnswerWith("ATOKEN")
rs, runRestore := run.Stub()
defer runRestore(t)

View file

@ -316,8 +316,10 @@ func TestNewCmdExtension(t *testing.T) {
},
isTTY: true,
askStubs: func(as *prompt.AskStubber) {
as.StubOne("test")
as.StubOne(0)
as.StubPrompt("Extension name:").AnswerWith("test")
as.StubPrompt("What kind of extension?").
AssertOptions([]string{"Script (Bash, Ruby, Python, etc)", "Go", "Other Precompiled (C++, Rust, etc)"}).
AnswerDefault()
},
wantStdout: heredoc.Doc(`
Created directory gh-test
@ -456,8 +458,7 @@ func TestNewCmdExtension(t *testing.T) {
assertFunc = tt.managerStubs(em)
}
as, teardown := prompt.InitAskStubber()
defer teardown()
as := prompt.NewAskStubber(t)
if tt.askStubs != nil {
tt.askStubs(as)
}

View file

@ -10,7 +10,6 @@ import (
"github.com/cli/cli/v2/pkg/cmdutil"
"github.com/cli/cli/v2/pkg/httpmock"
"github.com/cli/cli/v2/pkg/iostreams"
"github.com/cli/cli/v2/pkg/prompt"
"github.com/google/shlex"
"github.com/stretchr/testify/assert"
)
@ -61,7 +60,6 @@ func Test_deleteRun(t *testing.T) {
opts *DeleteOptions
gist *shared.Gist
httpStubs func(*httpmock.Registry)
askStubs func(*prompt.AskStubber)
nontty bool
wantErr bool
wantStderr string
@ -122,12 +120,6 @@ func Test_deleteRun(t *testing.T) {
tt.httpStubs(reg)
}
as, teardown := prompt.InitAskStubber()
defer teardown()
if tt.askStubs != nil {
tt.askStubs(as)
}
if tt.opts == nil {
tt.opts = &DeleteOptions{}
}

View file

@ -146,8 +146,8 @@ func Test_editRun(t *testing.T) {
{
name: "multiple files, submit",
askStubs: func(as *prompt.AskStubber) {
as.StubOne("unix.md")
as.StubOne("Submit")
as.StubPrompt("Edit which file?").AnswerWith("unix.md")
as.StubPrompt("What next?").AnswerWith("Submit")
},
gist: &shared.Gist{
ID: "1234",
@ -191,8 +191,8 @@ func Test_editRun(t *testing.T) {
{
name: "multiple files, cancel",
askStubs: func(as *prompt.AskStubber) {
as.StubOne("unix.md")
as.StubOne("Cancel")
as.StubPrompt("Edit which file?").AnswerWith("unix.md")
as.StubPrompt("What next?").AnswerWith("Cancel")
},
wantErr: "CancelError",
gist: &shared.Gist{
@ -280,12 +280,6 @@ func Test_editRun(t *testing.T) {
tt.httpStubs(reg)
}
as, teardown := prompt.InitAskStubber()
defer teardown()
if tt.askStubs != nil {
tt.askStubs(as)
}
if tt.opts == nil {
tt.opts = &EditOptions{}
}
@ -308,6 +302,11 @@ func Test_editRun(t *testing.T) {
}
t.Run(tt.name, func(t *testing.T) {
as := prompt.NewAskStubber(t)
if tt.askStubs != nil {
tt.askStubs(as)
}
err := editRun(tt.opts)
reg.Verify(t)
if tt.wantErr != "" {

View file

@ -128,8 +128,7 @@ func viewRun(opts *ViewOptions) error {
return err
}
theme := opts.IO.DetectTerminalTheme()
markdownStyle := markdown.GetStyle(theme)
opts.IO.DetectTerminalTheme()
if err := opts.IO.StartPager(); err != nil {
fmt.Fprintf(opts.IO.ErrOut, "starting pager failed: %v\n", err)
}
@ -145,7 +144,7 @@ func viewRun(opts *ViewOptions) error {
}
if strings.Contains(gf.Type, "markdown") && !opts.Raw {
rendered, err := markdown.Render(gf.Content, markdownStyle)
rendered, err := markdown.Render(gf.Content, markdown.WithIO(opts.IO))
if err != nil {
return err
}

View file

@ -355,9 +355,8 @@ func Test_viewRun(t *testing.T) {
)),
)
as, surveyteardown := prompt.InitAskStubber()
defer surveyteardown()
as.StubOne(0)
as := prompt.NewAskStubber(t)
as.StubPrompt("Select a gist").AnswerDefault()
}
if tt.opts == nil {
@ -392,16 +391,18 @@ func Test_viewRun(t *testing.T) {
func Test_promptGists(t *testing.T) {
tests := []struct {
name string
gistIndex int
response string
wantOut string
gist *shared.Gist
wantErr bool
name string
askStubs func(as *prompt.AskStubber)
response string
wantOut string
gist *shared.Gist
wantErr bool
}{
{
name: "multiple files, select first gist",
gistIndex: 0,
name: "multiple files, select first gist",
askStubs: func(as *prompt.AskStubber) {
as.StubPrompt("Select a gist").AnswerWith("cool.txt about 6 hours ago")
},
response: `{ "data": { "viewer": { "gists": { "nodes": [
{
"name": "gistid1",
@ -421,8 +422,10 @@ func Test_promptGists(t *testing.T) {
wantOut: "gistid1",
},
{
name: "multiple files, select second gist",
gistIndex: 1,
name: "multiple files, select second gist",
askStubs: func(as *prompt.AskStubber) {
as.StubPrompt("Select a gist").AnswerWith("gistfile0.txt about 6 hours ago")
},
response: `{ "data": { "viewer": { "gists": { "nodes": [
{
"name": "gistid1",
@ -465,11 +468,12 @@ func Test_promptGists(t *testing.T) {
)
client := &http.Client{Transport: reg}
as, surveyteardown := prompt.InitAskStubber()
defer surveyteardown()
as.StubOne(tt.gistIndex)
t.Run(tt.name, func(t *testing.T) {
as := prompt.NewAskStubber(t)
if tt.askStubs != nil {
tt.askStubs(as)
}
gistID, err := promptGists(client, "github.com", io.ColorScheme())
assert.NoError(t, err)
assert.Equal(t, tt.wantOut, gistID)

View file

@ -401,27 +401,11 @@ func TestIssueCreate_recover(t *testing.T) {
assert.Equal(t, []interface{}{"BUGID", "TODOID"}, inputs["labelIds"])
}))
as, teardown := prompt.InitAskStubber()
defer teardown()
as := prompt.NewAskStubber(t)
as.Stub([]*prompt.QuestionStub{
{
Name: "Title",
Default: true,
},
})
as.Stub([]*prompt.QuestionStub{
{
Name: "Body",
Default: true,
},
})
as.Stub([]*prompt.QuestionStub{
{
Name: "confirmation",
Value: 0,
},
})
as.StubPrompt("Title").AnswerDefault()
as.StubPrompt("Body").AnswerDefault()
as.StubPrompt("What's next?").AnswerWith("Submit")
tmpfile, err := ioutil.TempFile(t.TempDir(), "testrecover*")
assert.NoError(t, err)
@ -484,25 +468,11 @@ func TestIssueCreate_nonLegacyTemplate(t *testing.T) {
}),
)
as, teardown := prompt.InitAskStubber()
defer teardown()
as := prompt.NewAskStubber(t)
// template
as.StubOne(1)
// body
as.Stub([]*prompt.QuestionStub{
{
Name: "Body",
Default: true,
},
}) // body
// confirm
as.Stub([]*prompt.QuestionStub{
{
Name: "confirmation",
Value: 0,
},
})
as.StubPrompt("Choose a template").AnswerWith("Submit a request")
as.StubPrompt("Body").AnswerDefault()
as.StubPrompt("What's next?").AnswerWith("Submit")
output, err := runCommandWithRootDirOverridden(http, true, `-t hello`, "./fixtures/repoWithNonLegacyIssueTemplates")
if err != nil {
@ -526,23 +496,10 @@ func TestIssueCreate_continueInBrowser(t *testing.T) {
} } }`),
)
as, teardown := prompt.InitAskStubber()
defer teardown()
as := prompt.NewAskStubber(t)
// title
as.Stub([]*prompt.QuestionStub{
{
Name: "Title",
Value: "hello",
},
})
// confirm
as.Stub([]*prompt.QuestionStub{
{
Name: "confirmation",
Value: 1,
},
})
as.StubPrompt("Title").AnswerWith("hello")
as.StubPrompt("What's next?").AnswerWith("Continue in browser")
_, cmdTeardown := run.Stub()
defer cmdTeardown(t)

View file

@ -75,9 +75,9 @@ func TestIssueDelete(t *testing.T) {
assert.Equal(t, inputs["issueId"], "THE-ID")
}),
)
as, teardown := prompt.InitAskStubber()
defer teardown()
as.StubOne("13")
as := prompt.NewAskStubber(t)
as.StubPrompt("You're going to delete issue #13. This action cannot be reversed. To confirm, type the issue number:").AnswerWith("13")
output, err := runCommand(httpRegistry, true, "13")
if err != nil {
@ -103,9 +103,9 @@ func TestIssueDelete_cancel(t *testing.T) {
"issue": { "id": "THE-ID", "number": 13, "title": "The title of the issue"}
} } }`),
)
as, teardown := prompt.InitAskStubber()
defer teardown()
as.StubOne("14")
as := prompt.NewAskStubber(t)
as.StubPrompt("You're going to delete issue #13. This action cannot be reversed. To confirm, type the issue number:").AnswerWith("14")
output, err := runCommand(httpRegistry, true, "13")
if err != nil {

View file

@ -231,8 +231,7 @@ func printHumanIssuePreview(opts *ViewOptions, issue *api.Issue) error {
if issue.Body == "" {
md = fmt.Sprintf("\n %s\n\n", cs.Gray("No description provided"))
} else {
style := markdown.GetStyle(opts.IO.TerminalTheme())
md, err = markdown.Render(issue.Body, style)
md, err = markdown.Render(issue.Body, markdown.WithIO(opts.IO))
if err != nil {
return err
}

View file

@ -283,26 +283,13 @@ func TestPRCreate_recover(t *testing.T) {
cs.Register(`git status --porcelain`, 0, "")
cs.Register(`git( .+)? log( .+)? origin/master\.\.\.feature`, 0, "")
//nolint:staticcheck // SA1019: prompt.InitAskStubber is deprecated: use NewAskStubber
as, teardown := prompt.InitAskStubber()
defer teardown()
as.Stub([]*prompt.QuestionStub{
{
Name: "Title",
Default: true,
},
})
as.Stub([]*prompt.QuestionStub{
{
Name: "Body",
Default: true,
},
})
as.Stub([]*prompt.QuestionStub{
{
Name: "confirmation",
Value: 0,
},
})
as.StubPrompt("Title").AnswerDefault()
as.StubPrompt("Body").AnswerDefault()
as.StubPrompt("What's next?").AnswerDefault()
tmpfile, err := ioutil.TempFile(t.TempDir(), "testrecover*")
assert.NoError(t, err)
@ -393,9 +380,11 @@ func TestPRCreate(t *testing.T) {
cs.Register(`git show-ref --verify -- HEAD refs/remotes/origin/feature`, 0, "")
cs.Register(`git push --set-upstream origin HEAD:feature`, 0, "")
//nolint:staticcheck // SA1019: prompt.InitAskStubber is deprecated: use NewAskStubber
ask, cleanupAsk := prompt.InitAskStubber()
defer cleanupAsk()
ask.StubOne(0)
ask.StubPrompt("Where should we push the 'feature' branch?").AnswerDefault()
output, err := runCommand(http, nil, "feature", true, `-t "my title" -b "my body"`)
require.NoError(t, err)
@ -438,9 +427,11 @@ func TestPRCreate_NoMaintainerModify(t *testing.T) {
cs.Register(`git show-ref --verify -- HEAD refs/remotes/origin/feature`, 0, "")
cs.Register(`git push --set-upstream origin HEAD:feature`, 0, "")
//nolint:staticcheck // SA1019: prompt.InitAskStubber is deprecated: use NewAskStubber
ask, cleanupAsk := prompt.InitAskStubber()
defer cleanupAsk()
ask.StubOne(0)
ask.StubPrompt("Where should we push the 'feature' branch?").AnswerDefault()
output, err := runCommand(http, nil, "feature", true, `-t "my title" -b "my body" --no-maintainer-edit`)
require.NoError(t, err)
@ -488,9 +479,13 @@ func TestPRCreate_createFork(t *testing.T) {
cs.Register(`git remote add -f fork https://github.com/monalisa/REPO.git`, 0, "")
cs.Register(`git push --set-upstream fork HEAD:feature`, 0, "")
//nolint:staticcheck // SA1019: prompt.InitAskStubber is deprecated: use NewAskStubber
ask, cleanupAsk := prompt.InitAskStubber()
defer cleanupAsk()
ask.StubOne(1)
ask.StubPrompt("Where should we push the 'feature' branch?").
AssertOptions([]string{"OWNER/REPO", "Create a fork of OWNER/REPO", "Skip pushing the branch", "Cancel"}).
AnswerWith("Create a fork of OWNER/REPO")
output, err := runCommand(http, nil, "feature", true, `-t title -b body`)
require.NoError(t, err)
@ -544,6 +539,7 @@ func TestPRCreate_pushedToNonBaseRepo(t *testing.T) {
deadbeef refs/remotes/origin/feature
`)) // determineTrackingBranch
//nolint:staticcheck // SA1019: prompt.InitAskStubber is deprecated: use NewAskStubber
_, cleanupAsk := prompt.InitAskStubber()
defer cleanupAsk()
@ -585,6 +581,7 @@ func TestPRCreate_pushedToDifferentBranchName(t *testing.T) {
deadbeef refs/remotes/origin/my-feat2
`)) // determineTrackingBranch
//nolint:staticcheck // SA1019: prompt.InitAskStubber is deprecated: use NewAskStubber
_, cleanupAsk := prompt.InitAskStubber()
defer cleanupAsk()
@ -618,21 +615,17 @@ func TestPRCreate_nonLegacyTemplate(t *testing.T) {
cs.Register(`git( .+)? log( .+)? origin/master\.\.\.feature`, 0, "1234567890,commit 0\n2345678901,commit 1")
cs.Register(`git status --porcelain`, 0, "")
//nolint:staticcheck // SA1019: prompt.InitAskStubber is deprecated: use NewAskStubber
as, teardown := prompt.InitAskStubber()
defer teardown()
as.StubOne(0) // template
as.Stub([]*prompt.QuestionStub{
{
Name: "Body",
Default: true,
},
}) // body
as.Stub([]*prompt.QuestionStub{
{
Name: "confirmation",
Value: 0,
},
}) // confirm
as.StubPrompt("Choose a template").
AssertOptions([]string{"Bug fix", "Open a blank pull request"}).
AnswerWith("Bug fix")
as.StubPrompt("Body").AnswerDefault()
as.StubPrompt("What's next?").
AssertOptions([]string{"Submit", "Continue in browser", "Add metadata", "Cancel"}).
AnswerDefault()
output, err := runCommandWithRootDirOverridden(http, nil, "feature", true, `-t "my title" -H feature`, "./fixtures/repoWithNonLegacyPRTemplates")
require.NoError(t, err)
@ -771,9 +764,13 @@ func TestPRCreate_web(t *testing.T) {
cs.Register(`git( .+)? log( .+)? origin/master\.\.\.feature`, 0, "")
cs.Register(`git push --set-upstream origin HEAD:feature`, 0, "")
//nolint:staticcheck // SA1019: prompt.InitAskStubber is deprecated: use NewAskStubber
ask, cleanupAsk := prompt.InitAskStubber()
defer cleanupAsk()
ask.StubOne(0)
ask.StubPrompt("Where should we push the 'feature' branch?").
AssertOptions([]string{"OWNER/REPO", "Skip pushing the branch", "Cancel"}).
AnswerDefault()
output, err := runCommand(http, nil, "feature", true, `--web`)
require.NoError(t, err)
@ -842,9 +839,11 @@ func TestPRCreate_webProject(t *testing.T) {
cs.Register(`git( .+)? log( .+)? origin/master\.\.\.feature`, 0, "")
cs.Register(`git push --set-upstream origin HEAD:feature`, 0, "")
//nolint:staticcheck // SA1019: prompt.InitAskStubber is deprecated: use NewAskStubber
ask, cleanupAsk := prompt.InitAskStubber()
defer cleanupAsk()
ask.StubOne(0)
ask.StubPrompt("Where should we push the 'feature' branch?").AnswerDefault()
output, err := runCommand(http, nil, "feature", true, `--web -p Triage`)
require.NoError(t, err)

View file

@ -769,8 +769,10 @@ func TestPrMerge_alreadyMerged(t *testing.T) {
cs.Register(`git branch -D blueberries`, 0, "")
cs.Register(`git pull --ff-only`, 0, "")
//nolint:staticcheck // SA1019: prompt.InitAskStubber is deprecated: use NewAskStubber
as, surveyTeardown := prompt.InitAskStubber()
defer surveyTeardown()
//nolint:staticcheck // SA1019: as.StubOne is deprecated: use StubPrompt
as.StubOne(true)
output, err := runCommand(http, "blueberries", true, "pr merge 4")
@ -846,11 +848,15 @@ func TestPRMerge_interactive(t *testing.T) {
cs.Register(`git rev-parse --verify refs/heads/blueberries`, 0, "")
//nolint:staticcheck // SA1019: prompt.InitAskStubber is deprecated: use NewAskStubber
as, surveyTeardown := prompt.InitAskStubber()
defer surveyTeardown()
as.StubOne(0) // Merge method survey
as.StubOne(false) // Delete branch survey
//nolint:staticcheck // SA1019: as.StubOne is deprecated: use StubPrompt
as.StubOne(0) // Merge method survey
//nolint:staticcheck // SA1019: as.StubOne is deprecated: use StubPrompt
as.StubOne(false) // Delete branch survey
//nolint:staticcheck // SA1019: as.StubOne is deprecated: use StubPrompt
as.StubOne("Submit") // Confirm submit survey
output, err := runCommand(http, "blueberries", true, "")
@ -905,10 +911,13 @@ func TestPRMerge_interactiveWithDeleteBranch(t *testing.T) {
cs.Register(`git branch -D blueberries`, 0, "")
cs.Register(`git pull --ff-only`, 0, "")
//nolint:staticcheck // SA1019: prompt.InitAskStubber is deprecated: use NewAskStubber
as, surveyTeardown := prompt.InitAskStubber()
defer surveyTeardown()
as.StubOne(0) // Merge method survey
//nolint:staticcheck // SA1019: as.StubOne is deprecated: use StubPrompt
as.StubOne(0) // Merge method survey
//nolint:staticcheck // SA1019: as.StubOne is deprecated: use StubPrompt
as.StubOne("Submit") // Confirm submit survey
output, err := runCommand(http, "blueberries", true, "-d")
@ -964,14 +973,20 @@ func TestPRMerge_interactiveSquashEditCommitMsgAndSubject(t *testing.T) {
_, cmdTeardown := run.Stub()
defer cmdTeardown(t)
//nolint:staticcheck // SA1019: prompt.InitAskStubber is deprecated: use NewAskStubber
as, surveyTeardown := prompt.InitAskStubber()
defer surveyTeardown()
as.StubOne(2) // Merge method survey
as.StubOne(false) // Delete branch survey
//nolint:staticcheck // SA1019: as.StubOne is deprecated: use StubPrompt
as.StubOne(2) // Merge method survey
//nolint:staticcheck // SA1019: as.StubOne is deprecated: use StubPrompt
as.StubOne(false) // Delete branch survey
//nolint:staticcheck // SA1019: as.StubOne is deprecated: use StubPrompt
as.StubOne("Edit commit subject") // Confirm submit survey
//nolint:staticcheck // SA1019: as.StubOne is deprecated: use StubPrompt
as.StubOne("Edit commit message") // Confirm submit survey
as.StubOne("Submit") // Confirm submit survey
//nolint:staticcheck // SA1019: as.StubOne is deprecated: use StubPrompt
as.StubOne("Submit") // Confirm submit survey
err := mergeRun(&MergeOptions{
IO: io,
@ -1017,11 +1032,15 @@ func TestPRMerge_interactiveCancelled(t *testing.T) {
cs.Register(`git rev-parse --verify refs/heads/`, 0, "")
//nolint:staticcheck // SA1019: prompt.InitAskStubber is deprecated: use NewAskStubber
as, surveyTeardown := prompt.InitAskStubber()
defer surveyTeardown()
as.StubOne(0) // Merge method survey
as.StubOne(true) // Delete branch survey
//nolint:staticcheck // SA1019: as.StubOne is deprecated: use StubPrompt
as.StubOne(0) // Merge method survey
//nolint:staticcheck // SA1019: as.StubOne is deprecated: use StubPrompt
as.StubOne(true) // Delete branch survey
//nolint:staticcheck // SA1019: as.StubOne is deprecated: use StubPrompt
as.StubOne("Cancel") // Confirm submit survey
output, err := runCommand(http, "blueberries", true, "")
@ -1038,8 +1057,10 @@ func Test_mergeMethodSurvey(t *testing.T) {
RebaseMergeAllowed: true,
SquashMergeAllowed: true,
}
//nolint:staticcheck // SA1019: prompt.InitAskStubber is deprecated: use NewAskStubber
as, surveyTeardown := prompt.InitAskStubber()
defer surveyTeardown()
//nolint:staticcheck // SA1019: as.StubOne is deprecated: use StubPrompt
as.StubOne(0) // Select first option which is rebase merge
method, err := mergeMethodSurvey(repo)
assert.Nil(t, err)

View file

@ -273,8 +273,7 @@ func reviewSurvey(io *iostreams.IOStreams, editorCommand string) (*api.PullReque
}
if len(bodyAnswers.Body) > 0 {
style := markdown.GetStyle(io.DetectTerminalTheme())
renderedBody, err := markdown.Render(bodyAnswers.Body, style)
renderedBody, err := markdown.Render(bodyAnswers.Body, markdown.WithIO(io))
if err != nil {
return nil, err
}

View file

@ -270,21 +270,25 @@ func TestPRReview_interactive(t *testing.T) {
}),
)
//nolint:staticcheck // SA1019: prompt.InitAskStubber is deprecated: use NewAskStubber
as, teardown := prompt.InitAskStubber()
defer teardown()
//nolint:staticcheck // SA1019: as.Stub is deprecated: use StubPrompt
as.Stub([]*prompt.QuestionStub{
{
Name: "reviewType",
Value: "Approve",
},
})
//nolint:staticcheck // SA1019: as.Stub is deprecated: use StubPrompt
as.Stub([]*prompt.QuestionStub{
{
Name: "body",
Value: "cool story",
},
})
//nolint:staticcheck // SA1019: as.Stub is deprecated: use StubPrompt
as.Stub([]*prompt.QuestionStub{
{
Name: "confirm",
@ -309,27 +313,10 @@ func TestPRReview_interactive_no_body(t *testing.T) {
shared.RunCommandFinder("", &api.PullRequest{ID: "THE-ID", Number: 123}, ghrepo.New("OWNER", "REPO"))
as, teardown := prompt.InitAskStubber()
defer teardown()
as := prompt.NewAskStubber(t)
as.Stub([]*prompt.QuestionStub{
{
Name: "reviewType",
Value: "Request changes",
},
})
as.Stub([]*prompt.QuestionStub{
{
Name: "body",
Default: true,
},
})
as.Stub([]*prompt.QuestionStub{
{
Name: "confirm",
Value: true,
},
})
as.StubPrompt("What kind of review do you want to give?").AnswerWith("Request changes")
as.StubPrompt("Review body").AnswerWith("")
_, err := runCommand(http, nil, true, "")
assert.EqualError(t, err, "this type of review cannot be blank")
@ -350,21 +337,25 @@ func TestPRReview_interactive_blank_approve(t *testing.T) {
}),
)
//nolint:staticcheck // SA1019: prompt.InitAskStubber is deprecated: use NewAskStubber
as, teardown := prompt.InitAskStubber()
defer teardown()
//nolint:staticcheck // SA1019: as.Stub is deprecated: use StubPrompt
as.Stub([]*prompt.QuestionStub{
{
Name: "reviewType",
Value: "Approve",
},
})
//nolint:staticcheck // SA1019: as.Stub is deprecated: use StubPrompt
as.Stub([]*prompt.QuestionStub{
{
Name: "body",
Default: true,
},
})
//nolint:staticcheck // SA1019: as.Stub is deprecated: use StubPrompt
as.Stub([]*prompt.QuestionStub{
{
Name: "confirm",

View file

@ -123,8 +123,7 @@ func formatComment(io *iostreams.IOStreams, comment Comment, newest bool) (strin
if comment.Content() == "" {
md = fmt.Sprintf("\n %s\n\n", cs.Gray("No body provided"))
} else {
style := markdown.GetStyle(io.TerminalTheme())
md, err = markdown.Render(comment.Content(), style)
md, err = markdown.Render(comment.Content(), markdown.WithIO(io))
if err != nil {
return "", err
}

View file

@ -43,15 +43,18 @@ func TestMetadataSurvey_selectAll(t *testing.T) {
},
}
//nolint:staticcheck // SA1019: prompt.InitAskStubber is deprecated: use NewAskStubber
as, restoreAsk := prompt.InitAskStubber()
defer restoreAsk()
//nolint:staticcheck // SA1019: as.Stub is deprecated: use StubPrompt
as.Stub([]*prompt.QuestionStub{
{
Name: "metadata",
Value: []string{"Labels", "Projects", "Assignees", "Reviewers", "Milestone"},
},
})
//nolint:staticcheck // SA1019: as.Stub is deprecated: use StubPrompt
as.Stub([]*prompt.QuestionStub{
{
Name: "reviewers",
@ -71,7 +74,7 @@ func TestMetadataSurvey_selectAll(t *testing.T) {
},
{
Name: "milestone",
Value: []string{"(none)"},
Value: "(none)",
},
})
@ -109,15 +112,18 @@ func TestMetadataSurvey_keepExisting(t *testing.T) {
},
}
//nolint:staticcheck // SA1019: prompt.InitAskStubber is deprecated: use NewAskStubber
as, restoreAsk := prompt.InitAskStubber()
defer restoreAsk()
//nolint:staticcheck // SA1019: as.Stub is deprecated: use StubPrompt
as.Stub([]*prompt.QuestionStub{
{
Name: "metadata",
Value: []string{"Labels", "Projects"},
},
})
//nolint:staticcheck // SA1019: as.Stub is deprecated: use StubPrompt
as.Stub([]*prompt.QuestionStub{
{
Name: "labels",

View file

@ -63,9 +63,11 @@ func TestTemplateManager_hasAPI(t *testing.T) {
assert.Equal(t, "LEGACY", string(m.LegacyBody()))
//nolint:staticcheck // SA1019: prompt.InitAskStubber is deprecated: use NewAskStubber
as, askRestore := prompt.InitAskStubber()
defer askRestore()
//nolint:staticcheck // SA1019: as.StubOne is deprecated: use StubPrompt
as.StubOne(1) // choose "Feature Request"
tpl, err := m.Choose()
assert.NoError(t, err)

View file

@ -215,8 +215,7 @@ func printHumanPrPreview(opts *ViewOptions, pr *api.PullRequest) error {
if pr.Body == "" {
md = fmt.Sprintf("\n %s\n\n", cs.Gray("No description provided"))
} else {
style := markdown.GetStyle(opts.IO.TerminalTheme())
md, err = markdown.Render(pr.Body, style)
md, err = markdown.Render(pr.Body, markdown.WithIO(opts.IO))
if err != nil {
return err
}

View file

@ -515,27 +515,16 @@ func Test_createRun_interactive(t *testing.T) {
name: "create a release from existing tag",
opts: &CreateOptions{},
askStubs: func(as *prompt.AskStubber) {
as.StubOne("v1.2.3") // Tag prompt
as.Stub([]*prompt.QuestionStub{
{
Name: "name",
Value: "title",
},
{
Name: "releaseNotesAction",
Value: "Leave blank",
},
})
as.Stub([]*prompt.QuestionStub{
{
Name: "prerelease",
Value: false,
},
{
Name: "submitAction",
Value: "Publish release",
},
})
as.StubPrompt("Choose a tag").
AssertOptions([]string{"v1.2.3", "v1.2.2", "v1.0.0", "v0.1.2", "Create a new tag"}).
AnswerWith("v1.2.3")
as.StubPrompt("Title (optional)").AnswerWith("")
as.StubPrompt("Release notes").
AssertOptions([]string{"Write my own", "Write using generated notes as template", "Leave blank"}).
AnswerWith("Leave blank")
as.StubPrompt("Is this a prerelease?").AnswerWith(false)
as.StubPrompt("Submit?").
AssertOptions([]string{"Publish release", "Save as draft", "Cancel"}).AnswerWith("Publish release")
},
httpStubs: func(reg *httpmock.Registry) {
reg.Register(httpmock.REST("GET", "repos/OWNER/REPO/tags"), httpmock.StatusStringResponse(200, `[
@ -558,28 +547,12 @@ func Test_createRun_interactive(t *testing.T) {
name: "create a release from new tag",
opts: &CreateOptions{},
askStubs: func(as *prompt.AskStubber) {
as.StubOne("Create a new tag") // Tag prompt
as.StubOne("v1.2.3") // New tag prompt
as.Stub([]*prompt.QuestionStub{
{
Name: "name",
Value: "title",
},
{
Name: "releaseNotesAction",
Value: "Leave blank",
},
})
as.Stub([]*prompt.QuestionStub{
{
Name: "prerelease",
Value: false,
},
{
Name: "submitAction",
Value: "Publish release",
},
})
as.StubPrompt("Choose a tag").AnswerWith("Create a new tag")
as.StubPrompt("Tag name").AnswerWith("v1.2.3")
as.StubPrompt("Title (optional)").AnswerWith("")
as.StubPrompt("Release notes").AnswerWith("Leave blank")
as.StubPrompt("Is this a prerelease?").AnswerWith(false)
as.StubPrompt("Submit?").AnswerWith("Publish release")
},
httpStubs: func(reg *httpmock.Registry) {
reg.Register(httpmock.REST("GET", "repos/OWNER/REPO/tags"), httpmock.StatusStringResponse(200, `[
@ -604,26 +577,10 @@ func Test_createRun_interactive(t *testing.T) {
TagName: "v1.2.3",
},
askStubs: func(as *prompt.AskStubber) {
as.Stub([]*prompt.QuestionStub{
{
Name: "name",
Value: "title",
},
{
Name: "releaseNotesAction",
Value: "Write using generated notes as template",
},
})
as.Stub([]*prompt.QuestionStub{
{
Name: "prerelease",
Value: false,
},
{
Name: "submitAction",
Value: "Publish release",
},
})
as.StubPrompt("Title (optional)").AnswerDefault()
as.StubPrompt("Release notes").AnswerWith("Write using generated notes as template")
as.StubPrompt("Is this a prerelease?").AnswerWith(false)
as.StubPrompt("Submit?").AnswerWith("Publish release")
},
httpStubs: func(reg *httpmock.Registry) {
reg.Register(httpmock.REST("POST", "repos/OWNER/REPO/releases/generate-notes"),
@ -641,7 +598,7 @@ func Test_createRun_interactive(t *testing.T) {
wantParams: map[string]interface{}{
"body": "generated body",
"draft": false,
"name": "title",
"name": "generated name",
"prerelease": false,
"tag_name": "v1.2.3",
},
@ -674,6 +631,7 @@ func Test_createRun_interactive(t *testing.T) {
return val, nil
}
//nolint:staticcheck // SA1019: prompt.InitAskStubber is deprecated: use NewAskStubber
as, teardown := prompt.InitAskStubber()
defer teardown()
if tt.askStubs != nil {

View file

@ -141,8 +141,7 @@ func renderReleaseTTY(io *iostreams.IOStreams, release *shared.Release) error {
fmt.Fprintf(w, "%s\n", iofmt.Gray(fmt.Sprintf("%s released this %s", release.Author.Login, utils.FuzzyAgo(time.Since(*release.PublishedAt)))))
}
style := markdown.GetStyle(io.TerminalTheme())
renderedDescription, err := markdown.Render(release.Body, style)
renderedDescription, err := markdown.Render(release.Body, markdown.WithIO(io))
if err != nil {
return err
}

View file

@ -80,6 +80,7 @@ func Test_ArchiveRun(t *testing.T) {
name: "unarchived repo tty",
wantStdout: "✓ Archived repository OWNER/REPO\n",
askStubs: func(q *prompt.AskStubber) {
//nolint:staticcheck // SA1019: q.StubOne is deprecated: use StubPrompt
q.StubOne(true)
},
isTTY: true,
@ -98,6 +99,7 @@ func Test_ArchiveRun(t *testing.T) {
wantStdout: "✓ Archived repository OWNER/REPO\n",
opts: ArchiveOptions{},
askStubs: func(q *prompt.AskStubber) {
//nolint:staticcheck // SA1019: q.StubOne is deprecated: use StubPrompt
q.StubOne(true)
},
isTTY: true,
@ -138,6 +140,7 @@ func Test_ArchiveRun(t *testing.T) {
io, _, stdout, stderr := iostreams.Test()
tt.opts.IO = io
//nolint:staticcheck // SA1019: prompt.InitAskStubber is deprecated: use NewAskStubber
q, teardown := prompt.InitAskStubber()
defer teardown()
if tt.askStubs != nil {

View file

@ -171,22 +171,30 @@ func Test_createRun(t *testing.T) {
tty: true,
wantStdout: "✓ Created repository OWNER/REPO on GitHub\n",
askStubs: func(as *prompt.AskStubber) {
//nolint:staticcheck // SA1019: as.StubOne is deprecated: use StubPrompt
as.StubOne("Create a new repository on GitHub from scratch")
//nolint:staticcheck // SA1019: as.Stub is deprecated: use StubPrompt
as.Stub([]*prompt.QuestionStub{
{Name: "repoName", Value: "REPO"},
{Name: "repoDescription", Value: "my new repo"},
{Name: "repoVisibility", Value: "PRIVATE"},
{Name: "repoVisibility", Value: "Private"},
})
//nolint:staticcheck // SA1019: as.Stub is deprecated: use StubPrompt
as.Stub([]*prompt.QuestionStub{
{Name: "addGitIgnore", Value: true}})
//nolint:staticcheck // SA1019: as.Stub is deprecated: use StubPrompt
as.Stub([]*prompt.QuestionStub{
{Name: "chooseGitIgnore", Value: "Go"}})
//nolint:staticcheck // SA1019: as.Stub is deprecated: use StubPrompt
as.Stub([]*prompt.QuestionStub{
{Name: "addLicense", Value: true}})
//nolint:staticcheck // SA1019: as.Stub is deprecated: use StubPrompt
as.Stub([]*prompt.QuestionStub{
{Name: "chooseLicense", Value: "GNU Lesser General Public License v3.0"}})
//nolint:staticcheck // SA1019: as.Stub is deprecated: use StubPrompt
as.Stub([]*prompt.QuestionStub{
{Name: "confirmSubmit", Value: true}})
//nolint:staticcheck // SA1019: as.StubOne is deprecated: use StubPrompt
as.StubOne(true) //clone locally?
},
httpStubs: func(reg *httpmock.Registry) {
@ -210,16 +218,21 @@ func Test_createRun(t *testing.T) {
opts: &CreateOptions{Interactive: true},
tty: true,
askStubs: func(as *prompt.AskStubber) {
//nolint:staticcheck // SA1019: as.StubOne is deprecated: use StubPrompt
as.StubOne("Create a new repository on GitHub from scratch")
//nolint:staticcheck // SA1019: as.Stub is deprecated: use StubPrompt
as.Stub([]*prompt.QuestionStub{
{Name: "repoName", Value: "REPO"},
{Name: "repoDescription", Value: "my new repo"},
{Name: "repoVisibility", Value: "PRIVATE"},
{Name: "repoVisibility", Value: "Private"},
})
//nolint:staticcheck // SA1019: as.Stub is deprecated: use StubPrompt
as.Stub([]*prompt.QuestionStub{
{Name: "addGitIgnore", Value: false}})
//nolint:staticcheck // SA1019: as.Stub is deprecated: use StubPrompt
as.Stub([]*prompt.QuestionStub{
{Name: "addLicense", Value: false}})
//nolint:staticcheck // SA1019: as.Stub is deprecated: use StubPrompt
as.Stub([]*prompt.QuestionStub{
{Name: "confirmSubmit", Value: false}})
},
@ -232,13 +245,17 @@ func Test_createRun(t *testing.T) {
opts: &CreateOptions{Interactive: true},
tty: true,
askStubs: func(as *prompt.AskStubber) {
//nolint:staticcheck // SA1019: as.StubOne is deprecated: use StubPrompt
as.StubOne("Push an existing local repository to GitHub")
//nolint:staticcheck // SA1019: as.StubOne is deprecated: use StubPrompt
as.StubOne(".")
//nolint:staticcheck // SA1019: as.Stub is deprecated: use StubPrompt
as.Stub([]*prompt.QuestionStub{
{Name: "repoName", Value: "REPO"},
{Name: "repoDescription", Value: "my new repo"},
{Name: "repoVisibility", Value: "PRIVATE"},
{Name: "repoVisibility", Value: "Private"},
})
//nolint:staticcheck // SA1019: as.StubOne is deprecated: use StubPrompt
as.StubOne(false)
},
httpStubs: func(reg *httpmock.Registry) {
@ -269,16 +286,22 @@ func Test_createRun(t *testing.T) {
opts: &CreateOptions{Interactive: true},
tty: true,
askStubs: func(as *prompt.AskStubber) {
//nolint:staticcheck // SA1019: as.StubOne is deprecated: use StubPrompt
as.StubOne("Push an existing local repository to GitHub")
//nolint:staticcheck // SA1019: as.StubOne is deprecated: use StubPrompt
as.StubOne(".")
//nolint:staticcheck // SA1019: as.Stub is deprecated: use StubPrompt
as.Stub([]*prompt.QuestionStub{
{Name: "repoName", Value: "REPO"},
{Name: "repoDescription", Value: "my new repo"},
{Name: "repoVisibility", Value: "PRIVATE"},
{Name: "repoVisibility", Value: "Private"},
})
as.StubOne(true) //ask for adding a remote
//nolint:staticcheck // SA1019: as.StubOne is deprecated: use StubPrompt
as.StubOne(true) //ask for adding a remote
//nolint:staticcheck // SA1019: as.StubOne is deprecated: use StubPrompt
as.StubOne("origin") //ask for remote name
as.StubOne(false) //ask to push to remote
//nolint:staticcheck // SA1019: as.StubOne is deprecated: use StubPrompt
as.StubOne(false) //ask to push to remote
},
httpStubs: func(reg *httpmock.Registry) {
reg.Register(
@ -309,16 +332,22 @@ func Test_createRun(t *testing.T) {
opts: &CreateOptions{Interactive: true},
tty: true,
askStubs: func(as *prompt.AskStubber) {
//nolint:staticcheck // SA1019: as.StubOne is deprecated: use StubPrompt
as.StubOne("Push an existing local repository to GitHub")
//nolint:staticcheck // SA1019: as.StubOne is deprecated: use StubPrompt
as.StubOne(".")
//nolint:staticcheck // SA1019: as.Stub is deprecated: use StubPrompt
as.Stub([]*prompt.QuestionStub{
{Name: "repoName", Value: "REPO"},
{Name: "repoDescription", Value: "my new repo"},
{Name: "repoVisibility", Value: "PRIVATE"},
{Name: "repoVisibility", Value: "Private"},
})
as.StubOne(true) //ask for adding a remote
//nolint:staticcheck // SA1019: as.StubOne is deprecated: use StubPrompt
as.StubOne(true) //ask for adding a remote
//nolint:staticcheck // SA1019: as.StubOne is deprecated: use StubPrompt
as.StubOne("origin") //ask for remote name
as.StubOne(true) //ask to push to remote
//nolint:staticcheck // SA1019: as.StubOne is deprecated: use StubPrompt
as.StubOne(true) //ask to push to remote
},
httpStubs: func(reg *httpmock.Registry) {
reg.Register(
@ -407,6 +436,7 @@ func Test_createRun(t *testing.T) {
},
}
for _, tt := range tests {
//nolint:staticcheck // SA1019: prompt.InitAskStubber is deprecated: use NewAskStubber
q, teardown := prompt.InitAskStubber()
defer teardown()
if tt.askStubs != nil {

View file

@ -94,6 +94,7 @@ func Test_deleteRun(t *testing.T) {
askStubs: func(q *prompt.AskStubber) {
// TODO: survey stubber doesn't have WithValidator support
// so this always passes regardless of prompt input
//nolint:staticcheck // SA1019: q.StubOne is deprecated: use StubPrompt
q.StubOne("OWNER/REPO")
},
httpStubs: func(reg *httpmock.Registry) {
@ -108,6 +109,7 @@ func Test_deleteRun(t *testing.T) {
opts: &DeleteOptions{},
wantStdout: "✓ Deleted repository OWNER/REPO\n",
askStubs: func(q *prompt.AskStubber) {
//nolint:staticcheck // SA1019: q.StubOne is deprecated: use StubPrompt
q.StubOne("OWNER/REPO")
},
httpStubs: func(reg *httpmock.Registry) {
@ -134,6 +136,7 @@ func Test_deleteRun(t *testing.T) {
wantStdout: "✓ Deleted repository OWNER/REPO\n",
tty: true,
askStubs: func(q *prompt.AskStubber) {
//nolint:staticcheck // SA1019: q.StubOne is deprecated: use StubPrompt
q.StubOne("OWNER/REPO")
},
httpStubs: func(reg *httpmock.Registry) {
@ -147,6 +150,7 @@ func Test_deleteRun(t *testing.T) {
},
}
for _, tt := range tests {
//nolint:staticcheck // SA1019: prompt.InitAskStubber is deprecated: use NewAskStubber
q, teardown := prompt.InitAskStubber()
defer teardown()
if tt.askStubs != nil {

View file

@ -236,6 +236,7 @@ func TestRepoFork(t *testing.T) {
},
httpStubs: forkPost,
askStubs: func(as *prompt.AskStubber) {
//nolint:staticcheck // SA1019: as.StubOne is deprecated: use StubPrompt
as.StubOne(false)
},
wantErrOut: "✓ Created fork someone/REPO\n",
@ -254,6 +255,7 @@ func TestRepoFork(t *testing.T) {
cs.Register(`git remote add -f origin https://github.com/someone/REPO.git`, 0, "")
},
askStubs: func(as *prompt.AskStubber) {
//nolint:staticcheck // SA1019: as.StubOne is deprecated: use StubPrompt
as.StubOne(true)
},
wantErrOut: "✓ Created fork someone/REPO\n✓ Added remote origin\n",
@ -442,6 +444,7 @@ func TestRepoFork(t *testing.T) {
},
httpStubs: forkPost,
askStubs: func(as *prompt.AskStubber) {
//nolint:staticcheck // SA1019: as.StubOne is deprecated: use StubPrompt
as.StubOne(false)
},
wantErrOut: "✓ Created fork someone/REPO\n",
@ -455,6 +458,7 @@ func TestRepoFork(t *testing.T) {
},
httpStubs: forkPost,
askStubs: func(as *prompt.AskStubber) {
//nolint:staticcheck // SA1019: as.StubOne is deprecated: use StubPrompt
as.StubOne(true)
},
execStubs: func(cs *run.CommandStubber) {
@ -475,6 +479,7 @@ func TestRepoFork(t *testing.T) {
},
httpStubs: forkPost,
askStubs: func(as *prompt.AskStubber) {
//nolint:staticcheck // SA1019: as.StubOne is deprecated: use StubPrompt
as.StubOne(true)
},
execStubs: func(cs *run.CommandStubber) {
@ -570,6 +575,7 @@ func TestRepoFork(t *testing.T) {
return tt.remotes, nil
}
//nolint:staticcheck // SA1019: prompt.InitAskStubber is deprecated: use NewAskStubber
as, teardown := prompt.InitAskStubber()
defer teardown()
if tt.askStubs != nil {

View file

@ -117,6 +117,7 @@ func TestRenameRun(t *testing.T) {
name: "none argument",
wantOut: "✓ Renamed repository OWNER/NEW_REPO\n✓ Updated the \"origin\" remote\n",
askStubs: func(q *prompt.AskStubber) {
//nolint:staticcheck // SA1019: q.StubOne is deprecated: use StubPrompt
q.StubOne("NEW_REPO")
},
httpStubs: func(reg *httpmock.Registry) {
@ -136,6 +137,7 @@ func TestRenameRun(t *testing.T) {
},
wantOut: "✓ Renamed repository OWNER/NEW_REPO\n",
askStubs: func(q *prompt.AskStubber) {
//nolint:staticcheck // SA1019: q.StubOne is deprecated: use StubPrompt
q.StubOne("NEW_REPO")
},
httpStubs: func(reg *httpmock.Registry) {
@ -184,6 +186,7 @@ func TestRenameRun(t *testing.T) {
},
wantOut: "✓ Renamed repository OWNER/NEW_REPO\n✓ Updated the \"origin\" remote\n",
askStubs: func(q *prompt.AskStubber) {
//nolint:staticcheck // SA1019: q.StubOne is deprecated: use StubPrompt
q.StubOne(true)
},
httpStubs: func(reg *httpmock.Registry) {
@ -204,6 +207,7 @@ func TestRenameRun(t *testing.T) {
DoConfirm: true,
},
askStubs: func(q *prompt.AskStubber) {
//nolint:staticcheck // SA1019: q.StubOne is deprecated: use StubPrompt
q.StubOne(false)
},
wantOut: "",
@ -211,6 +215,7 @@ func TestRenameRun(t *testing.T) {
}
for _, tt := range testCases {
//nolint:staticcheck // SA1019: prompt.InitAskStubber is deprecated: use NewAskStubber
q, teardown := prompt.InitAskStubber()
defer teardown()
if tt.askStubs != nil {

View file

@ -187,8 +187,7 @@ func viewRun(opts *ViewOptions) error {
readmeContent = cs.Gray("This repository does not have a README")
} else if isMarkdownFile(readme.Filename) {
var err error
style := markdown.GetStyle(opts.IO.TerminalTheme())
readmeContent, err = markdown.RenderWithBaseURL(readme.Content, style, readme.BaseURL)
readmeContent, err = markdown.Render(readme.Content, markdown.WithIO(opts.IO), markdown.WithBaseURL(readme.BaseURL))
if err != nil {
return fmt.Errorf("error rendering markdown: %w", err)
}

View file

@ -14,13 +14,12 @@ import (
func referenceHelpFn(io *iostreams.IOStreams) func(*cobra.Command, []string) {
return func(cmd *cobra.Command, args []string) {
wrapWidth := 0
style := "notty"
if io.IsStdoutTTY() {
io.DetectTerminalTheme()
wrapWidth = io.TerminalWidth()
style = markdown.GetStyle(io.DetectTerminalTheme())
}
md, err := markdown.RenderWithWrap(cmd.Long, style, wrapWidth)
md, err := markdown.Render(cmd.Long, markdown.WithIO(io), markdown.WithWrap(wrapWidth))
if err != nil {
fmt.Fprintln(io.ErrOut, err)
return

View file

@ -174,6 +174,7 @@ func TestRunCancel(t *testing.T) {
httpmock.StatusStringResponse(202, "{}"))
},
askStubs: func(as *prompt.AskStubber) {
//nolint:staticcheck // SA1019: as.StubOne is deprecated: use StubPrompt
as.StubOne(0)
},
wantOut: "✓ Request to cancel workflow submitted.\n",
@ -195,6 +196,7 @@ func TestRunCancel(t *testing.T) {
return ghrepo.FromFullName("OWNER/REPO")
}
//nolint:staticcheck // SA1019: prompt.InitAskStubber is deprecated: use NewAskStubber
as, teardown := prompt.InitAskStubber()
defer teardown()
if tt.askStubs != nil {

View file

@ -137,6 +137,7 @@ func TestRerun(t *testing.T) {
httpmock.StringResponse("{}"))
},
askStubs: func(as *prompt.AskStubber) {
//nolint:staticcheck // SA1019: as.StubOne is deprecated: use StubPrompt
as.StubOne(2)
},
wantOut: "✓ Requested rerun of run 1234\n",
@ -193,6 +194,7 @@ func TestRerun(t *testing.T) {
return ghrepo.FromFullName("OWNER/REPO")
}
//nolint:staticcheck // SA1019: prompt.InitAskStubber is deprecated: use NewAskStubber
as, teardown := prompt.InitAskStubber()
defer teardown()
if tt.askStubs != nil {

View file

@ -375,6 +375,7 @@ func TestViewRun(t *testing.T) {
httpmock.JSONResponse([]shared.Annotation{}))
},
askStubs: func(as *prompt.AskStubber) {
//nolint:staticcheck // SA1019: as.StubOne is deprecated: use StubPrompt
as.StubOne(2)
},
opts: &ViewOptions{
@ -411,7 +412,9 @@ func TestViewRun(t *testing.T) {
httpmock.FileResponse("./fixtures/run_log.zip"))
},
askStubs: func(as *prompt.AskStubber) {
//nolint:staticcheck // SA1019: as.StubOne is deprecated: use StubPrompt
as.StubOne(2)
//nolint:staticcheck // SA1019: as.StubOne is deprecated: use StubPrompt
as.StubOne(1)
},
wantOut: coolJobRunLogOutput,
@ -464,7 +467,9 @@ func TestViewRun(t *testing.T) {
httpmock.FileResponse("./fixtures/run_log.zip"))
},
askStubs: func(as *prompt.AskStubber) {
//nolint:staticcheck // SA1019: as.StubOne is deprecated: use StubPrompt
as.StubOne(2)
//nolint:staticcheck // SA1019: as.StubOne is deprecated: use StubPrompt
as.StubOne(0)
},
wantOut: expectedRunLogOutput,
@ -523,7 +528,9 @@ func TestViewRun(t *testing.T) {
httpmock.FileResponse("./fixtures/run_log.zip"))
},
askStubs: func(as *prompt.AskStubber) {
//nolint:staticcheck // SA1019: as.StubOne is deprecated: use StubPrompt
as.StubOne(4)
//nolint:staticcheck // SA1019: as.StubOne is deprecated: use StubPrompt
as.StubOne(2)
},
wantOut: quuxTheBarfLogOutput,
@ -576,7 +583,9 @@ func TestViewRun(t *testing.T) {
httpmock.FileResponse("./fixtures/run_log.zip"))
},
askStubs: func(as *prompt.AskStubber) {
//nolint:staticcheck // SA1019: as.StubOne is deprecated: use StubPrompt
as.StubOne(4)
//nolint:staticcheck // SA1019: as.StubOne is deprecated: use StubPrompt
as.StubOne(0)
},
wantOut: quuxTheBarfLogOutput,
@ -700,7 +709,9 @@ func TestViewRun(t *testing.T) {
httpmock.JSONResponse(shared.FailedJobAnnotations))
},
askStubs: func(as *prompt.AskStubber) {
//nolint:staticcheck // SA1019: as.StubOne is deprecated: use StubPrompt
as.StubOne(2)
//nolint:staticcheck // SA1019: as.StubOne is deprecated: use StubPrompt
as.StubOne(0)
},
wantOut: "\n✓ trunk successful · 3\nTriggered via push about 59 minutes ago\n\nJOBS\n✓ cool job in 4m34s (ID 10)\nX sad job in 4m34s (ID 20)\n ✓ barf the quux\n X quux the barf\n\nANNOTATIONS\nX the job is sad\nsad job: blaze.py#420\n\n\nFor more information about a job, try: gh run view --job=<job-id>\nView this run on GitHub: https://github.com/runs/3\n",
@ -733,7 +744,9 @@ func TestViewRun(t *testing.T) {
httpmock.JSONResponse([]shared.Annotation{}))
},
askStubs: func(as *prompt.AskStubber) {
//nolint:staticcheck // SA1019: as.StubOne is deprecated: use StubPrompt
as.StubOne(2)
//nolint:staticcheck // SA1019: as.StubOne is deprecated: use StubPrompt
as.StubOne(1)
},
wantOut: "\n✓ trunk successful · 3\nTriggered via push about 59 minutes ago\n\n✓ cool job in 4m34s (ID 10)\n ✓ fob the barz\n ✓ barz the fob\n\nTo see the full job log, try: gh run view --log --job=10\nView this run on GitHub: https://github.com/runs/3\n",
@ -830,6 +843,7 @@ func TestViewRun(t *testing.T) {
return ghrepo.FromFullName("OWNER/REPO")
}
//nolint:staticcheck // SA1019: prompt.InitAskStubber is deprecated: use NewAskStubber
as, teardown := prompt.InitAskStubber()
defer teardown()
if tt.askStubs != nil {

View file

@ -234,7 +234,9 @@ func TestWatchRun(t *testing.T) {
},
httpStubs: successfulRunStubs,
askStubs: func(as *prompt.AskStubber) {
as.StubOne(1)
as.StubPrompt("Select a workflow run").
AssertOptions([]string{"* cool commit, run (trunk) Feb 23, 2021", "* cool commit, more runs (trunk) Feb 23, 2021"}).
AnswerWith("* cool commit, more runs (trunk) Feb 23, 2021")
},
wantOut: "\x1b[2J\x1b[0;0H\x1b[JRefreshing run status every 0 seconds. Press Ctrl+C to quit.\n\n* trunk more runs · 2\nTriggered via push about 59 minutes ago\n\nJOBS\n✓ cool job in 4m34s (ID 10)\n ✓ fob the barz\n ✓ barz the fob\n\x1b[0;0H\x1b[JRefreshing run status every 0 seconds. Press Ctrl+C to quit.\n\n✓ trunk more runs · 2\nTriggered via push about 59 minutes ago\n\nJOBS\n✓ cool job in 4m34s (ID 10)\n ✓ fob the barz\n ✓ barz the fob\n\n✓ Run more runs (2) completed with 'success'\n",
},
@ -247,7 +249,9 @@ func TestWatchRun(t *testing.T) {
},
httpStubs: successfulRunStubs,
askStubs: func(as *prompt.AskStubber) {
as.StubOne(1)
as.StubPrompt("Select a workflow run").
AssertOptions([]string{"* cool commit, run (trunk) Feb 23, 2021", "* cool commit, more runs (trunk) Feb 23, 2021"}).
AnswerWith("* cool commit, more runs (trunk) Feb 23, 2021")
},
wantOut: "\x1b[2J\x1b[2JRefreshing run status every 0 seconds. Press Ctrl+C to quit.\n\n* trunk more runs · 2\nTriggered via push about 59 minutes ago\n\nJOBS\n✓ cool job in 4m34s (ID 10)\n ✓ fob the barz\n ✓ barz the fob\n\x1b[2JRefreshing run status every 0 seconds. Press Ctrl+C to quit.\n\n✓ trunk more runs · 2\nTriggered via push about 59 minutes ago\n\nJOBS\n✓ cool job in 4m34s (ID 10)\n ✓ fob the barz\n ✓ barz the fob\n",
},
@ -262,7 +266,9 @@ func TestWatchRun(t *testing.T) {
},
httpStubs: failedRunStubs,
askStubs: func(as *prompt.AskStubber) {
as.StubOne(1)
as.StubPrompt("Select a workflow run").
AssertOptions([]string{"* cool commit, run (trunk) Feb 23, 2021", "* cool commit, more runs (trunk) Feb 23, 2021"}).
AnswerWith("* cool commit, more runs (trunk) Feb 23, 2021")
},
wantOut: "\x1b[2J\x1b[0;0H\x1b[JRefreshing run status every 0 seconds. Press Ctrl+C to quit.\n\n* trunk more runs · 2\nTriggered via push about 59 minutes ago\n\n\x1b[0;0H\x1b[JRefreshing run status every 0 seconds. Press Ctrl+C to quit.\n\nX trunk more runs · 2\nTriggered via push about 59 minutes ago\n\nJOBS\nX sad job in 4m34s (ID 20)\n ✓ barf the quux\n X quux the barf\n\nANNOTATIONS\nX the job is sad\nsad job: blaze.py#420\n\n\nX Run more runs (2) completed with 'failure'\n",
wantErr: true,
@ -278,7 +284,9 @@ func TestWatchRun(t *testing.T) {
},
httpStubs: failedRunStubs,
askStubs: func(as *prompt.AskStubber) {
as.StubOne(1)
as.StubPrompt("Select a workflow run").
AssertOptions([]string{"* cool commit, run (trunk) Feb 23, 2021", "* cool commit, more runs (trunk) Feb 23, 2021"}).
AnswerWith("* cool commit, more runs (trunk) Feb 23, 2021")
},
wantOut: "\x1b[2J\x1b[2JRefreshing run status every 0 seconds. Press Ctrl+C to quit.\n\n* trunk more runs · 2\nTriggered via push about 59 minutes ago\n\n\x1b[2JRefreshing run status every 0 seconds. Press Ctrl+C to quit.\n\nX trunk more runs · 2\nTriggered via push about 59 minutes ago\n\nJOBS\nX sad job in 4m34s (ID 20)\n ✓ barf the quux\n X quux the barf\n\nANNOTATIONS\nX the job is sad\nsad job: blaze.py#420\n\n",
wantErr: true,
@ -313,13 +321,12 @@ func TestWatchRun(t *testing.T) {
return ghrepo.FromFullName("OWNER/REPO")
}
as, teardown := prompt.InitAskStubber()
defer teardown()
if tt.askStubs != nil {
tt.askStubs(as)
}
t.Run(tt.name, func(t *testing.T) {
as := prompt.NewAskStubber(t)
if tt.askStubs != nil {
tt.askStubs(as)
}
err := watchRun(tt.opts)
if tt.wantErr {
assert.EqualError(t, err, tt.errMsg)

View file

@ -487,10 +487,8 @@ func Test_getBodyPrompt(t *testing.T) {
io.SetStdinTTY(true)
io.SetStdoutTTY(true)
as, teardown := prompt.InitAskStubber()
defer teardown()
as.StubOne("cool secret")
as := prompt.NewAskStubber(t)
as.StubPrompt("Paste your secret").AnswerWith("cool secret")
body, err := getBody(&SetOptions{
IO: io,

View file

@ -121,7 +121,7 @@ func TestDisableRun(t *testing.T) {
httpmock.StatusStringResponse(204, "{}"))
},
askStubs: func(as *prompt.AskStubber) {
as.StubOne(1)
as.StubPrompt("Select a workflow").AnswerWith("another workflow (another.yml)")
},
wantOut: "✓ Disabled another workflow\n",
},
@ -176,7 +176,7 @@ func TestDisableRun(t *testing.T) {
httpmock.StatusStringResponse(204, "{}"))
},
askStubs: func(as *prompt.AskStubber) {
as.StubOne(1)
as.StubPrompt("Which workflow do you mean?").AnswerWith("another workflow (yetanother.yml)")
},
wantOut: "✓ Disabled another workflow\n",
},
@ -277,13 +277,12 @@ func TestDisableRun(t *testing.T) {
return ghrepo.FromFullName("OWNER/REPO")
}
as, teardown := prompt.InitAskStubber()
defer teardown()
if tt.askStubs != nil {
tt.askStubs(as)
}
t.Run(tt.name, func(t *testing.T) {
as := prompt.NewAskStubber(t)
if tt.askStubs != nil {
tt.askStubs(as)
}
err := runDisable(tt.opts)
if tt.wantErr {
assert.Error(t, err)

View file

@ -121,7 +121,7 @@ func TestEnableRun(t *testing.T) {
httpmock.StatusStringResponse(204, "{}"))
},
askStubs: func(as *prompt.AskStubber) {
as.StubOne(0)
as.StubPrompt("Select a workflow").AnswerWith("a disabled workflow (disabled.yml)")
},
wantOut: "✓ Enabled a disabled workflow\n",
},
@ -176,7 +176,7 @@ func TestEnableRun(t *testing.T) {
httpmock.StatusStringResponse(204, "{}"))
},
askStubs: func(as *prompt.AskStubber) {
as.StubOne(1)
as.StubPrompt("Which workflow do you mean?").AnswerWith("a disabled workflow (anotherDisabled.yml)")
},
wantOut: "✓ Enabled a disabled workflow\n",
},
@ -194,9 +194,6 @@ func TestEnableRun(t *testing.T) {
httpmock.REST("PUT", "repos/OWNER/REPO/actions/workflows/456/enable"),
httpmock.StatusStringResponse(204, "{}"))
},
askStubs: func(as *prompt.AskStubber) {
as.StubOne(0)
},
wantOut: "✓ Enabled a disabled workflow\n",
},
{
@ -279,13 +276,12 @@ func TestEnableRun(t *testing.T) {
return ghrepo.FromFullName("OWNER/REPO")
}
as, teardown := prompt.InitAskStubber()
defer teardown()
if tt.askStubs != nil {
tt.askStubs(as)
}
t.Run(tt.name, func(t *testing.T) {
as := prompt.NewAskStubber(t)
if tt.askStubs != nil {
tt.askStubs(as)
}
err := runEnable(tt.opts)
if tt.wantErr {
assert.Error(t, err)

View file

@ -557,7 +557,7 @@ jobs:
httpmock.StatusStringResponse(204, "cool"))
},
askStubs: func(as *prompt.AskStubber) {
as.StubOne(0)
as.StubPrompt("Select a workflow").AnswerDefault()
},
wantBody: map[string]interface{}{
"inputs": map[string]interface{}{},
@ -594,17 +594,9 @@ jobs:
httpmock.StatusStringResponse(204, "cool"))
},
askStubs: func(as *prompt.AskStubber) {
as.StubOne(0)
as.Stub([]*prompt.QuestionStub{
{
Name: "greeting",
Default: true,
},
{
Name: "name",
Value: "scully",
},
})
as.StubPrompt("Select a workflow").AnswerDefault()
as.StubPrompt("greeting").AnswerWith("hi")
as.StubPrompt("name").AnswerWith("scully")
},
wantBody: map[string]interface{}{
"inputs": map[string]interface{}{
@ -638,12 +630,12 @@ jobs:
}, "github.com"), nil
}
as, teardown := prompt.InitAskStubber()
defer teardown()
if tt.askStubs != nil {
tt.askStubs(as)
}
t.Run(tt.name, func(t *testing.T) {
as := prompt.NewAskStubber(t)
if tt.askStubs != nil {
tt.askStubs(as)
}
err := runRun(tt.opts)
if tt.wantErr {
assert.Error(t, err)

View file

@ -156,8 +156,7 @@ func viewWorkflowContent(opts *ViewOptions, client *api.Client, workflow *shared
yaml := string(yamlBytes)
theme := opts.IO.DetectTerminalTheme()
markdownStyle := markdown.GetStyle(theme)
opts.IO.DetectTerminalTheme()
if err := opts.IO.StartPager(); err != nil {
fmt.Fprintf(opts.IO.ErrOut, "starting pager failed: %v\n", err)
}
@ -172,11 +171,7 @@ func viewWorkflowContent(opts *ViewOptions, client *api.Client, workflow *shared
fmt.Fprintf(out, "ID: %s", cs.Cyanf("%d", workflow.ID))
codeBlock := fmt.Sprintf("```yaml\n%s\n```", yaml)
rendered, err := markdown.RenderWithOpts(codeBlock, markdownStyle,
markdown.RenderOpts{
markdown.WithoutIndentation(),
markdown.WithoutWrap(),
})
rendered, err := markdown.Render(codeBlock, markdown.WithIO(opts.IO), markdown.WithoutIndentation(), markdown.WithWrap(0))
if err != nil {
return err
}

View file

@ -13,7 +13,6 @@ import (
"github.com/cli/cli/v2/pkg/cmdutil"
"github.com/cli/cli/v2/pkg/httpmock"
"github.com/cli/cli/v2/pkg/iostreams"
"github.com/cli/cli/v2/pkg/prompt"
"github.com/google/shlex"
"github.com/stretchr/testify/assert"
)
@ -189,7 +188,6 @@ func TestViewRun(t *testing.T) {
name string
opts *ViewOptions
httpStubs func(*httpmock.Registry)
askStubs func(*prompt.AskStubber)
tty bool
wantOut string
wantErrOut string
@ -417,12 +415,6 @@ func TestViewRun(t *testing.T) {
browser := &cmdutil.TestBrowser{}
tt.opts.Browser = browser
as, teardown := prompt.InitAskStubber()
defer teardown()
if tt.askStubs != nil {
tt.askStubs(as)
}
t.Run(tt.name, func(t *testing.T) {
err := runView(tt.opts)
if tt.wantErr {

View file

@ -69,35 +69,37 @@ func (s *IOStreams) HasTrueColor() bool {
return s.hasTrueColor
}
func (s *IOStreams) DetectTerminalTheme() string {
// DetectTerminalTheme is a utility to call before starting the output pager so that the terminal background
// can be reliably detected.
func (s *IOStreams) DetectTerminalTheme() {
if !s.ColorEnabled() {
s.terminalTheme = "none"
return "none"
return
}
if s.pagerProcess != nil {
s.terminalTheme = "none"
return "none"
return
}
style := os.Getenv("GLAMOUR_STYLE")
if style != "" && style != "auto" {
s.terminalTheme = "none"
return "none"
return
}
if termenv.HasDarkBackground() {
s.terminalTheme = "dark"
return "dark"
return
}
s.terminalTheme = "light"
return "light"
}
// TerminalTheme returns "light", "dark", or "none" depending on the background color of the terminal.
func (s *IOStreams) TerminalTheme() string {
if s.terminalTheme == "" {
return "none"
s.DetectTerminalTheme()
}
return s.terminalTheme

View file

@ -7,8 +7,6 @@ import (
"github.com/charmbracelet/glamour"
)
type RenderOpts []glamour.TermRendererOption
func WithoutIndentation() glamour.TermRendererOption {
overrides := []byte(`
{
@ -23,15 +21,38 @@ func WithoutIndentation() glamour.TermRendererOption {
return glamour.WithStylesFromJSONBytes(overrides)
}
func WithoutWrap() glamour.TermRendererOption {
return glamour.WithWordWrap(0)
func WithWrap(w int) glamour.TermRendererOption {
return glamour.WithWordWrap(w)
}
func render(text string, opts RenderOpts) (string, error) {
type IOStreams interface {
TerminalTheme() string
}
func WithIO(io IOStreams) glamour.TermRendererOption {
style := os.Getenv("GLAMOUR_STYLE")
if style == "" || style == "auto" {
theme := io.TerminalTheme()
switch theme {
case "light", "dark":
style = theme
default:
style = "notty"
}
}
return glamour.WithStylePath(style)
}
func WithBaseURL(u string) glamour.TermRendererOption {
return glamour.WithBaseURL(u)
}
func Render(text string, opts ...glamour.TermRendererOption) (string, error) {
// Glamour rendering preserves carriage return characters in code blocks, but
// we need to ensure that no such characters are present in the output.
text = strings.ReplaceAll(text, "\r\n", "\n")
opts = append(opts, glamour.WithEmoji(), glamour.WithPreservedNewLines())
tr, err := glamour.NewTermRenderer(opts...)
if err != nil {
return "", err
@ -39,59 +60,3 @@ func render(text string, opts RenderOpts) (string, error) {
return tr.Render(text)
}
func Render(text, style string) (string, error) {
opts := RenderOpts{
glamour.WithStylePath(style),
glamour.WithEmoji(),
}
return render(text, opts)
}
func RenderWithOpts(text, style string, opts RenderOpts) (string, error) {
defaultOpts := RenderOpts{
glamour.WithStylePath(style),
glamour.WithEmoji(),
}
opts = append(defaultOpts, opts...)
return render(text, opts)
}
func RenderWithBaseURL(text, style, baseURL string) (string, error) {
opts := RenderOpts{
glamour.WithStylePath(style),
glamour.WithEmoji(),
glamour.WithBaseURL(baseURL),
}
return render(text, opts)
}
func RenderWithWrap(text, style string, wrap int) (string, error) {
opts := RenderOpts{
glamour.WithStylePath(style),
glamour.WithEmoji(),
glamour.WithWordWrap(wrap),
}
return render(text, opts)
}
func GetStyle(defaultStyle string) string {
style := fromEnv()
if style != "" && style != "auto" {
return style
}
if defaultStyle == "light" || defaultStyle == "dark" {
return defaultStyle
}
return "notty"
}
var fromEnv = func() string {
return os.Getenv("GLAMOUR_STYLE")
}

View file

@ -1,50 +1,46 @@
package markdown
import (
"os"
"testing"
"github.com/stretchr/testify/assert"
)
func Test_Render(t *testing.T) {
os.Unsetenv("GLAMOUR_STYLE")
type input struct {
text string
style string
}
type output struct {
wantsErr bool
theme string
}
tests := []struct {
name string
input input
output output
name string
input input
wantsErr bool
}{
{
name: "invalid glamour style",
name: "light theme",
input: input{
text: "some text",
style: "invalid",
},
output: output{
wantsErr: true,
theme: "light",
},
wantsErr: false,
},
{
name: "valid glamour style",
name: "dark theme",
input: input{
text: "some text",
style: "dark",
},
output: output{
wantsErr: false,
theme: "dark",
},
wantsErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
_, err := Render(tt.input.text, tt.input.style)
if tt.output.wantsErr {
_, err := Render(tt.input.text, WithIO(terminalThemer(tt.input.theme)))
if tt.wantsErr {
assert.Error(t, err)
return
}
@ -53,89 +49,8 @@ func Test_Render(t *testing.T) {
}
}
func Test_GetStyle(t *testing.T) {
type input struct {
glamourStyle string
terminalTheme string
}
type output struct {
style string
}
tests := []struct {
name string
input input
output output
}{
{
name: "no glamour style and no terminal theme",
input: input{
glamourStyle: "",
terminalTheme: "none",
},
output: output{
style: "notty",
},
},
{
name: "auto glamour style and no terminal theme",
input: input{
glamourStyle: "auto",
terminalTheme: "none",
},
output: output{
style: "notty",
},
},
{
name: "user glamour style and no terminal theme",
input: input{
glamourStyle: "somestyle",
terminalTheme: "none",
},
output: output{
style: "somestyle",
},
},
{
name: "no glamour style and light terminal theme",
input: input{
glamourStyle: "",
terminalTheme: "light",
},
output: output{
style: "light",
},
},
{
name: "no glamour style and dark terminal theme",
input: input{
glamourStyle: "",
terminalTheme: "dark",
},
output: output{
style: "dark",
},
},
{
name: "no glamour style and unknown terminal theme",
input: input{
glamourStyle: "",
terminalTheme: "unknown",
},
output: output{
style: "notty",
},
},
}
type terminalThemer string
for _, tt := range tests {
fromEnv = func() string {
return tt.input.glamourStyle
}
t.Run(tt.name, func(t *testing.T) {
style := GetStyle(tt.input.terminalTheme)
assert.Equal(t, tt.output.style, style)
})
}
func (tt terminalThemer) TerminalTheme() string {
return string(tt)
}

View file

@ -2,73 +2,143 @@ package prompt
import (
"fmt"
"reflect"
"strings"
"github.com/AlecAivazis/survey/v2"
"github.com/AlecAivazis/survey/v2/core"
"github.com/cli/cli/v2/pkg/surveyext"
)
type AskStubber struct {
Asks [][]*survey.Question
AskOnes []*survey.Prompt
Count int
OneCount int
Stubs [][]*QuestionStub
StubOnes []*PromptStub
stubs []*QuestionStub
}
type testing interface {
Errorf(format string, args ...interface{})
Cleanup(func())
}
func NewAskStubber(t testing) *AskStubber {
as, teardown := InitAskStubber()
t.Cleanup(func() {
teardown()
for _, s := range as.stubs {
if !s.matched {
t.Errorf("unmatched prompt stub: %+v", s)
}
}
})
return as
}
// Deprecated: use NewAskStubber
func InitAskStubber() (*AskStubber, func()) {
origSurveyAsk := SurveyAsk
origSurveyAskOne := SurveyAskOne
as := AskStubber{}
SurveyAskOne = func(p survey.Prompt, response interface{}, opts ...survey.AskOpt) error {
as.AskOnes = append(as.AskOnes, &p)
count := as.OneCount
as.OneCount += 1
if count >= len(as.StubOnes) {
panic(fmt.Sprintf("more asks than stubs. most recent call: %v", p))
}
stubbedPrompt := as.StubOnes[count]
if stubbedPrompt.Default {
// TODO this is failing for basic AskOne invocations with a string result.
defaultValue := reflect.ValueOf(p).Elem().FieldByName("Default")
_ = core.WriteAnswer(response, "", defaultValue)
} else {
_ = core.WriteAnswer(response, "", stubbedPrompt.Value)
answerFromStub := func(p survey.Prompt, fieldName string, response interface{}) error {
var message string
var defaultValue interface{}
var options []string
switch pt := p.(type) {
case *survey.Confirm:
message = pt.Message
defaultValue = pt.Default
case *survey.Input:
message = pt.Message
defaultValue = pt.Default
case *survey.Select:
message = pt.Message
options = pt.Options
case *survey.MultiSelect:
message = pt.Message
options = pt.Options
case *survey.Password:
message = pt.Message
case *surveyext.GhEditor:
message = pt.Message
defaultValue = pt.Default
default:
return fmt.Errorf("prompt type %T is not supported by the stubber", pt)
}
var stub *QuestionStub
for _, s := range as.stubs {
if !s.matched && (s.message == "" && strings.EqualFold(s.Name, fieldName) || s.message == message) {
stub = s
stub.matched = true
break
}
}
if stub == nil {
return fmt.Errorf("no prompt stub for %q", message)
}
if len(stub.options) > 0 {
if err := compareOptions(stub.options, options); err != nil {
return fmt.Errorf("stubbed options mismatch for %q: %v", message, err)
}
}
userValue := stub.Value
if stringValue, ok := stub.Value.(string); ok && len(options) > 0 {
foundIndex := -1
for i, o := range options {
if o == stringValue {
foundIndex = i
break
}
}
if foundIndex < 0 {
return fmt.Errorf("answer %q not found in options for %q: %v", stringValue, message, options)
}
userValue = core.OptionAnswer{
Value: stringValue,
Index: foundIndex,
}
}
if stub.Default {
if defaultIndex, ok := defaultValue.(int); ok && len(options) > 0 {
userValue = core.OptionAnswer{
Value: options[defaultIndex],
Index: defaultIndex,
}
} else if defaultValue == nil && len(options) > 0 {
userValue = core.OptionAnswer{
Value: options[0],
Index: 0,
}
} else {
userValue = defaultValue
}
}
if err := core.WriteAnswer(response, fieldName, userValue); err != nil {
topic := fmt.Sprintf("field %q", fieldName)
if fieldName == "" {
topic = fmt.Sprintf("%q", message)
}
return fmt.Errorf("AskStubber failed writing the answer for %s: %w", topic, err)
}
return nil
}
SurveyAskOne = func(p survey.Prompt, response interface{}, opts ...survey.AskOpt) error {
return answerFromStub(p, "", response)
}
SurveyAsk = func(qs []*survey.Question, response interface{}, opts ...survey.AskOpt) error {
as.Asks = append(as.Asks, qs)
count := as.Count
as.Count += 1
if count >= len(as.Stubs) {
panic(fmt.Sprintf("more asks than stubs. most recent call: %#v", qs))
}
// actually set response
stubbedQuestions := as.Stubs[count]
if len(stubbedQuestions) != len(qs) {
panic(fmt.Sprintf("asked questions: %d; stubbed questions: %d", len(qs), len(stubbedQuestions)))
}
for i, sq := range stubbedQuestions {
q := qs[i]
if q.Name != sq.Name {
panic(fmt.Sprintf("stubbed question mismatch: %s != %s", q.Name, sq.Name))
}
if sq.Default {
defaultValue := reflect.ValueOf(q.Prompt).Elem().FieldByName("Default")
_ = core.WriteAnswer(response, q.Name, defaultValue)
} else {
_ = core.WriteAnswer(response, q.Name, sq.Value)
for _, q := range qs {
if err := answerFromStub(q.Prompt, q.Name, response); err != nil {
return err
}
}
return nil
}
teardown := func() {
SurveyAsk = origSurveyAsk
SurveyAskOne = origSurveyAskOne
@ -76,30 +146,59 @@ func InitAskStubber() (*AskStubber, func()) {
return &as, teardown
}
type PromptStub struct {
Value interface{}
Default bool
}
type QuestionStub struct {
Name string
Value interface{}
Default bool
matched bool
message string
options []string
}
// AssertOptions asserts the options presented to the user in Selects and MultiSelects.
func (s *QuestionStub) AssertOptions(opts []string) *QuestionStub {
s.options = opts
return s
}
// AnswerWith defines an answer for the given stub.
func (s *QuestionStub) AnswerWith(v interface{}) *QuestionStub {
s.Value = v
return s
}
// AnswerDefault marks the current stub to be answered with the default value for the prompt question.
func (s *QuestionStub) AnswerDefault() *QuestionStub {
s.Default = true
return s
}
// Deprecated: use StubPrompt
func (as *AskStubber) StubOne(value interface{}) {
as.StubOnes = append(as.StubOnes, &PromptStub{
Value: value,
})
}
func (as *AskStubber) StubOneDefault() {
as.StubOnes = append(as.StubOnes, &PromptStub{
Default: true,
})
as.Stub([]*QuestionStub{{Value: value}})
}
// Deprecated: use StubPrompt
func (as *AskStubber) Stub(stubbedQuestions []*QuestionStub) {
// A call to .Ask takes a list of questions; a stub is then a list of questions in the same order.
as.Stubs = append(as.Stubs, stubbedQuestions)
as.stubs = append(as.stubs, stubbedQuestions...)
}
// StubPrompt records a stub for an interactive prompt matched by its message.
func (as *AskStubber) StubPrompt(msg string) *QuestionStub {
stub := &QuestionStub{message: msg}
as.stubs = append(as.stubs, stub)
return stub
}
func compareOptions(expected, got []string) error {
if len(expected) != len(got) {
return fmt.Errorf("expected %v, got %v (length mismatch)", expected, got)
}
for i, v := range expected {
if v != got[i] {
return fmt.Errorf("expected %v, got %v", expected, got)
}
}
return nil
}

24
script/nolint-insert Executable file
View file

@ -0,0 +1,24 @@
#!/bin/bash
# Usage: script/nolint-insert
# script/nolint-insert 'nolint:staticcheck // <explanation>'
set -e
insert-line() {
local n=$'\n'
sed -i.bak "${2}i\\${n}${3}${n}" "$1"
rm "$1.bak"
}
reverse() {
awk '{a[i++]=$0} END {for (j=i-1; j>=0;) print a[j--] }'
}
comment="${1}"
golangci-lint run --out-format json | jq -r '.Issues[] | [.Pos.Filename, .Pos.Line, .FromLinter, .Text] | @tsv' | reverse | while IFS=$'\t' read -r filename line linter text; do
directive="nolint:${linter} // $text"
[ -z "$comment" ] || directive="$comment"
insert-line "$filename" "$line" "//${directive}"
done
go fmt ./...