Merge pull request #4718 from cli/bin-ext-create
make extension create binary aware
This commit is contained in:
commit
56faf17b06
11 changed files with 539 additions and 84 deletions
|
|
@ -6,11 +6,13 @@ import (
|
|||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/AlecAivazis/survey/v2"
|
||||
"github.com/MakeNowJust/heredoc"
|
||||
"github.com/cli/cli/v2/git"
|
||||
"github.com/cli/cli/v2/internal/ghrepo"
|
||||
"github.com/cli/cli/v2/pkg/cmdutil"
|
||||
"github.com/cli/cli/v2/pkg/extensions"
|
||||
"github.com/cli/cli/v2/pkg/prompt"
|
||||
"github.com/cli/cli/v2/utils"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
|
@ -180,41 +182,127 @@ func NewCmdExtension(f *cmdutil.Factory) *cobra.Command {
|
|||
return nil
|
||||
},
|
||||
},
|
||||
&cobra.Command{
|
||||
Use: "create <name>",
|
||||
Short: "Create a new extension",
|
||||
Args: cmdutil.ExactArgs(1, "must specify a name for the extension"),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
extName := args[0]
|
||||
if !strings.HasPrefix(extName, "gh-") {
|
||||
extName = "gh-" + extName
|
||||
func() *cobra.Command {
|
||||
promptCreate := func() (string, extensions.ExtTemplateType, error) {
|
||||
var extName string
|
||||
var extTmplType int
|
||||
err := prompt.SurveyAskOne(&survey.Input{
|
||||
Message: "Extension name:",
|
||||
}, &extName)
|
||||
if err != nil {
|
||||
return extName, -1, err
|
||||
}
|
||||
if err := m.Create(extName); err != nil {
|
||||
return err
|
||||
}
|
||||
if !io.IsStdoutTTY() {
|
||||
return nil
|
||||
}
|
||||
link := "https://docs.github.com/github-cli/github-cli/creating-github-cli-extensions"
|
||||
cs := io.ColorScheme()
|
||||
out := heredoc.Docf(`
|
||||
err = prompt.SurveyAskOne(&survey.Select{
|
||||
Message: "What kind of extension?",
|
||||
Options: []string{
|
||||
"Script (Bash, Ruby, Python, etc)",
|
||||
"Go",
|
||||
"Other Precompiled (C++, Rust, etc)",
|
||||
},
|
||||
}, &extTmplType)
|
||||
return extName, extensions.ExtTemplateType(extTmplType), err
|
||||
}
|
||||
var flagType string
|
||||
cmd := &cobra.Command{
|
||||
Use: "create [<name>]",
|
||||
Short: "Create a new extension",
|
||||
Example: heredoc.Doc(`
|
||||
# Use interactively
|
||||
gh extension create
|
||||
|
||||
# Create a script-based extension
|
||||
gh extension create foobar
|
||||
|
||||
# Create a Go extension
|
||||
gh extension create --precompiled=go foobar
|
||||
|
||||
# Create a non-Go precompiled extension
|
||||
gh extension create --precompiled=other foobar
|
||||
`),
|
||||
Args: cobra.MaximumNArgs(1),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
if cmd.Flags().Changed("precompiled") {
|
||||
if flagType != "go" && flagType != "other" {
|
||||
return cmdutil.FlagErrorf("value for --precompiled must be 'go' or 'other'. Got '%s'", flagType)
|
||||
}
|
||||
}
|
||||
var extName string
|
||||
var err error
|
||||
tmplType := extensions.GitTemplateType
|
||||
if len(args) == 0 {
|
||||
if io.IsStdoutTTY() {
|
||||
extName, tmplType, err = promptCreate()
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not prompt: %w", err)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
extName = args[0]
|
||||
if flagType == "go" {
|
||||
tmplType = extensions.GoBinTemplateType
|
||||
} else if flagType == "other" {
|
||||
tmplType = extensions.OtherBinTemplateType
|
||||
}
|
||||
}
|
||||
|
||||
var fullName string
|
||||
|
||||
if strings.HasPrefix(extName, "gh-") {
|
||||
fullName = extName
|
||||
extName = extName[3:]
|
||||
} else {
|
||||
fullName = "gh-" + extName
|
||||
}
|
||||
if err := m.Create(fullName, tmplType); err != nil {
|
||||
return err
|
||||
}
|
||||
if !io.IsStdoutTTY() {
|
||||
return nil
|
||||
}
|
||||
|
||||
var goBinChecks string
|
||||
|
||||
steps := fmt.Sprintf(
|
||||
"- run 'cd %[1]s; gh extension install .; gh %[2]s' to see your new extension in action",
|
||||
fullName, extName)
|
||||
|
||||
cs := io.ColorScheme()
|
||||
if tmplType == extensions.GoBinTemplateType {
|
||||
goBinChecks = heredoc.Docf(`
|
||||
%[1]s Downloaded Go dependencies
|
||||
%[1]s Built %[2]s binary
|
||||
`, cs.SuccessIcon(), fullName)
|
||||
steps = heredoc.Docf(`
|
||||
- run 'cd %[1]s; gh extension install .; gh %[2]s' to see your new extension in action
|
||||
- use 'go build && gh %[2]s' to see changes in your code as you develop`, fullName, extName)
|
||||
} else if tmplType == extensions.OtherBinTemplateType {
|
||||
steps = heredoc.Docf(`
|
||||
- run 'cd %[1]s; gh extension install .' to install your extension locally
|
||||
- fill in script/build.sh with your compilation script for automated builds
|
||||
- compile a %[1]s binary locally and run 'gh %[2]s' to see changes`, fullName, extName)
|
||||
}
|
||||
link := "https://docs.github.com/github-cli/github-cli/creating-github-cli-extensions"
|
||||
out := heredoc.Docf(`
|
||||
%[1]s Created directory %[2]s
|
||||
%[1]s Initialized git repository
|
||||
%[1]s Set up extension scaffolding
|
||||
%[6]s
|
||||
%[2]s is ready for development!
|
||||
|
||||
%[2]s is ready for development
|
||||
|
||||
Install locally with: cd %[2]s && gh extension install .
|
||||
|
||||
Publish to GitHub with: gh repo create %[2]s
|
||||
%[4]s
|
||||
%[5]s
|
||||
- commit and use 'gh repo create' to share your extension with others
|
||||
|
||||
For more information on writing extensions:
|
||||
%[3]s
|
||||
`, cs.SuccessIcon(), extName, link)
|
||||
fmt.Fprint(io.Out, out)
|
||||
return nil
|
||||
},
|
||||
},
|
||||
`, cs.SuccessIcon(), fullName, link, cs.Bold("Next Steps"), steps, goBinChecks)
|
||||
fmt.Fprint(io.Out, out)
|
||||
return nil
|
||||
},
|
||||
}
|
||||
cmd.Flags().StringVar(&flagType, "precompiled", "", "Create a precompiled extension. Possible values: go, other")
|
||||
return cmd
|
||||
}(),
|
||||
)
|
||||
|
||||
return &extCmd
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ import (
|
|||
"github.com/cli/cli/v2/pkg/extensions"
|
||||
"github.com/cli/cli/v2/pkg/httpmock"
|
||||
"github.com/cli/cli/v2/pkg/iostreams"
|
||||
"github.com/cli/cli/v2/pkg/prompt"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
|
@ -28,6 +29,7 @@ func TestNewCmdExtension(t *testing.T) {
|
|||
name string
|
||||
args []string
|
||||
managerStubs func(em *extensions.ExtensionManagerMock) func(*testing.T)
|
||||
askStubs func(as *prompt.AskStubber)
|
||||
isTTY bool
|
||||
wantErr bool
|
||||
errMsg string
|
||||
|
|
@ -263,10 +265,75 @@ func TestNewCmdExtension(t *testing.T) {
|
|||
wantStdout: "gh test\tcli/gh-test\t\ngh test2\tcli/gh-test2\tUpgrade available\n",
|
||||
},
|
||||
{
|
||||
name: "create extension tty",
|
||||
args: []string{"create", "test"},
|
||||
name: "create extension interactive",
|
||||
args: []string{"create"},
|
||||
managerStubs: func(em *extensions.ExtensionManagerMock) func(*testing.T) {
|
||||
em.CreateFunc = func(name string) error {
|
||||
em.CreateFunc = func(name string, tmplType extensions.ExtTemplateType) error {
|
||||
return nil
|
||||
}
|
||||
return func(t *testing.T) {
|
||||
calls := em.CreateCalls()
|
||||
assert.Equal(t, 1, len(calls))
|
||||
assert.Equal(t, "gh-test", calls[0].Name)
|
||||
}
|
||||
},
|
||||
isTTY: true,
|
||||
askStubs: func(as *prompt.AskStubber) {
|
||||
as.StubOne("test")
|
||||
as.StubOne(0)
|
||||
},
|
||||
wantStdout: heredoc.Doc(`
|
||||
✓ Created directory gh-test
|
||||
✓ Initialized git repository
|
||||
✓ Set up extension scaffolding
|
||||
|
||||
gh-test is ready for development!
|
||||
|
||||
Next Steps
|
||||
- run 'cd gh-test; gh extension install .; gh test' to see your new extension in action
|
||||
- commit and use 'gh repo create' to share your extension with others
|
||||
|
||||
For more information on writing extensions:
|
||||
https://docs.github.com/github-cli/github-cli/creating-github-cli-extensions
|
||||
`),
|
||||
},
|
||||
{
|
||||
name: "create extension with arg, --precompiled=go",
|
||||
args: []string{"create", "test", "--precompiled", "go"},
|
||||
managerStubs: func(em *extensions.ExtensionManagerMock) func(*testing.T) {
|
||||
em.CreateFunc = func(name string, tmplType extensions.ExtTemplateType) error {
|
||||
return nil
|
||||
}
|
||||
return func(t *testing.T) {
|
||||
calls := em.CreateCalls()
|
||||
assert.Equal(t, 1, len(calls))
|
||||
assert.Equal(t, "gh-test", calls[0].Name)
|
||||
}
|
||||
},
|
||||
isTTY: true,
|
||||
wantStdout: heredoc.Doc(`
|
||||
✓ Created directory gh-test
|
||||
✓ Initialized git repository
|
||||
✓ Set up extension scaffolding
|
||||
✓ Downloaded Go dependencies
|
||||
✓ Built gh-test binary
|
||||
|
||||
gh-test is ready for development!
|
||||
|
||||
Next Steps
|
||||
- run 'cd gh-test; gh extension install .; gh test' to see your new extension in action
|
||||
- use 'go build && gh test' to see changes in your code as you develop
|
||||
- commit and use 'gh repo create' to share your extension with others
|
||||
|
||||
For more information on writing extensions:
|
||||
https://docs.github.com/github-cli/github-cli/creating-github-cli-extensions
|
||||
`),
|
||||
},
|
||||
{
|
||||
name: "create extension with arg, --precompiled=other",
|
||||
args: []string{"create", "test", "--precompiled", "other"},
|
||||
managerStubs: func(em *extensions.ExtensionManagerMock) func(*testing.T) {
|
||||
em.CreateFunc = func(name string, tmplType extensions.ExtTemplateType) error {
|
||||
return nil
|
||||
}
|
||||
return func(t *testing.T) {
|
||||
|
|
@ -281,11 +348,42 @@ func TestNewCmdExtension(t *testing.T) {
|
|||
✓ Initialized git repository
|
||||
✓ Set up extension scaffolding
|
||||
|
||||
gh-test is ready for development
|
||||
gh-test is ready for development!
|
||||
|
||||
Install locally with: cd gh-test && gh extension install .
|
||||
Next Steps
|
||||
- run 'cd gh-test; gh extension install .' to install your extension locally
|
||||
- fill in script/build.sh with your compilation script for automated builds
|
||||
- compile a gh-test binary locally and run 'gh test' to see changes
|
||||
- commit and use 'gh repo create' to share your extension with others
|
||||
|
||||
Publish to GitHub with: gh repo create gh-test
|
||||
For more information on writing extensions:
|
||||
https://docs.github.com/github-cli/github-cli/creating-github-cli-extensions
|
||||
`),
|
||||
},
|
||||
{
|
||||
name: "create extension tty with argument",
|
||||
args: []string{"create", "test"},
|
||||
managerStubs: func(em *extensions.ExtensionManagerMock) func(*testing.T) {
|
||||
em.CreateFunc = func(name string, tmplType extensions.ExtTemplateType) error {
|
||||
return nil
|
||||
}
|
||||
return func(t *testing.T) {
|
||||
calls := em.CreateCalls()
|
||||
assert.Equal(t, 1, len(calls))
|
||||
assert.Equal(t, "gh-test", calls[0].Name)
|
||||
}
|
||||
},
|
||||
isTTY: true,
|
||||
wantStdout: heredoc.Doc(`
|
||||
✓ Created directory gh-test
|
||||
✓ Initialized git repository
|
||||
✓ Set up extension scaffolding
|
||||
|
||||
gh-test is ready for development!
|
||||
|
||||
Next Steps
|
||||
- run 'cd gh-test; gh extension install .; gh test' to see your new extension in action
|
||||
- commit and use 'gh repo create' to share your extension with others
|
||||
|
||||
For more information on writing extensions:
|
||||
https://docs.github.com/github-cli/github-cli/creating-github-cli-extensions
|
||||
|
|
@ -295,7 +393,7 @@ func TestNewCmdExtension(t *testing.T) {
|
|||
name: "create extension notty",
|
||||
args: []string{"create", "gh-test"},
|
||||
managerStubs: func(em *extensions.ExtensionManagerMock) func(*testing.T) {
|
||||
em.CreateFunc = func(name string) error {
|
||||
em.CreateFunc = func(name string, tmplType extensions.ExtTemplateType) error {
|
||||
return nil
|
||||
}
|
||||
return func(t *testing.T) {
|
||||
|
|
@ -321,6 +419,12 @@ func TestNewCmdExtension(t *testing.T) {
|
|||
assertFunc = tt.managerStubs(em)
|
||||
}
|
||||
|
||||
as, teardown := prompt.InitAskStubber()
|
||||
defer teardown()
|
||||
if tt.askStubs != nil {
|
||||
tt.askStubs(as)
|
||||
}
|
||||
|
||||
reg := httpmock.Registry{}
|
||||
defer reg.Verify(t)
|
||||
client := http.Client{Transport: ®}
|
||||
|
|
|
|||
4
pkg/cmd/extension/ext_tmpls/buildScript.sh
Normal file
4
pkg/cmd/extension/ext_tmpls/buildScript.sh
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
#!/usr/bin/env bash
|
||||
echo "TODO implement this script."
|
||||
echo "It should build binaries in dist/<platform>-<arch>[.exe] as needed."
|
||||
exit 1
|
||||
26
pkg/cmd/extension/ext_tmpls/goBinMain.go.txt
Normal file
26
pkg/cmd/extension/ext_tmpls/goBinMain.go.txt
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/cli/go-gh"
|
||||
)
|
||||
|
||||
func main() {
|
||||
fmt.Println("hi world, this is the %s extension!")
|
||||
client, err := gh.RESTClient(nil)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
response := struct {Login string}{}
|
||||
err = client.Get("user", &response)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
fmt.Printf("running as %%s\n", response.Login)
|
||||
}
|
||||
|
||||
// For more examples of using go-gh, see:
|
||||
// https://github.com/cli/go-gh/blob/trunk/example_gh_test.go
|
||||
14
pkg/cmd/extension/ext_tmpls/goBinWorkflow.yml
Normal file
14
pkg/cmd/extension/ext_tmpls/goBinWorkflow.yml
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
name: release
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- "v*"
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
jobs:
|
||||
release:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: cli/gh-extension-precompile@v1
|
||||
16
pkg/cmd/extension/ext_tmpls/otherBinWorkflow.yml
Normal file
16
pkg/cmd/extension/ext_tmpls/otherBinWorkflow.yml
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
name: release
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- "v*"
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
jobs:
|
||||
release:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: cli/gh-extension-precompile@v1
|
||||
with:
|
||||
build_script_override: "script/build.sh"
|
||||
35
pkg/cmd/extension/ext_tmpls/script.sh
Normal file
35
pkg/cmd/extension/ext_tmpls/script.sh
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
#!/usr/bin/env bash
|
||||
set -e
|
||||
|
||||
echo "Hello %[1]s!"
|
||||
|
||||
# Snippets to help get started:
|
||||
|
||||
# Determine if an executable is in the PATH
|
||||
# if ! type -p ruby >/dev/null; then
|
||||
# echo "Ruby not found on the system" >&2
|
||||
# exit 1
|
||||
# fi
|
||||
|
||||
# Pass arguments through to another command
|
||||
# gh issue list "$@" -R cli/cli
|
||||
|
||||
# Using the gh api command to retrieve and format information
|
||||
# QUERY='
|
||||
# query($endCursor: String) {
|
||||
# viewer {
|
||||
# repositories(first: 100, after: $endCursor) {
|
||||
# nodes {
|
||||
# nameWithOwner
|
||||
# stargazerCount
|
||||
# }
|
||||
# }
|
||||
# }
|
||||
# }
|
||||
# '
|
||||
# TEMPLATE='
|
||||
# {{- range $repo := .data.viewer.repositories.nodes -}}
|
||||
# {{- printf "name: %[2]s - stargazers: %[3]s\n" $repo.nameWithOwner $repo.stargazerCount -}}
|
||||
# {{- end -}}
|
||||
# '
|
||||
# exec gh api graphql -f query="${QUERY}" --paginate --template="${TEMPLATE}"
|
||||
|
|
@ -2,6 +2,7 @@ package extension
|
|||
|
||||
import (
|
||||
"bytes"
|
||||
_ "embed"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
|
|
@ -16,7 +17,6 @@ import (
|
|||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/MakeNowJust/heredoc"
|
||||
"github.com/cli/cli/v2/api"
|
||||
"github.com/cli/cli/v2/git"
|
||||
"github.com/cli/cli/v2/internal/config"
|
||||
|
|
@ -543,7 +543,22 @@ func (m *Manager) installDir() string {
|
|||
return filepath.Join(m.dataDir(), "extensions")
|
||||
}
|
||||
|
||||
func (m *Manager) Create(name string) error {
|
||||
//go:embed ext_tmpls/goBinMain.go.txt
|
||||
var mainGoTmpl string
|
||||
|
||||
//go:embed ext_tmpls/goBinWorkflow.yml
|
||||
var goBinWorkflow []byte
|
||||
|
||||
//go:embed ext_tmpls/otherBinWorkflow.yml
|
||||
var otherBinWorkflow []byte
|
||||
|
||||
//go:embed ext_tmpls/script.sh
|
||||
var scriptTmpl string
|
||||
|
||||
//go:embed ext_tmpls/buildScript.sh
|
||||
var buildScript []byte
|
||||
|
||||
func (m *Manager) Create(name string, tmplType extensions.ExtTemplateType) error {
|
||||
exe, err := m.lookPath("git")
|
||||
if err != nil {
|
||||
return err
|
||||
|
|
@ -560,45 +575,15 @@ func (m *Manager) Create(name string) error {
|
|||
return err
|
||||
}
|
||||
|
||||
fileTmpl := heredoc.Docf(`
|
||||
#!/usr/bin/env bash
|
||||
set -e
|
||||
if tmplType == extensions.GoBinTemplateType {
|
||||
return m.goBinScaffolding(exe, name)
|
||||
} else if tmplType == extensions.OtherBinTemplateType {
|
||||
return m.otherBinScaffolding(exe, name)
|
||||
}
|
||||
|
||||
echo "Hello %[1]s!"
|
||||
|
||||
# Snippets to help get started:
|
||||
|
||||
# Determine if an executable is in the PATH
|
||||
# if ! type -p ruby >/dev/null; then
|
||||
# echo "Ruby not found on the system" >&2
|
||||
# exit 1
|
||||
# fi
|
||||
|
||||
# Pass arguments through to another command
|
||||
# gh issue list "$@" -R cli/cli
|
||||
|
||||
# Using the gh api command to retrieve and format information
|
||||
# QUERY='
|
||||
# query($endCursor: String) {
|
||||
# viewer {
|
||||
# repositories(first: 100, after: $endCursor) {
|
||||
# nodes {
|
||||
# nameWithOwner
|
||||
# stargazerCount
|
||||
# }
|
||||
# }
|
||||
# }
|
||||
# }
|
||||
# '
|
||||
# TEMPLATE='
|
||||
# {{- range $repo := .data.viewer.repositories.nodes -}}
|
||||
# {{- printf "name: %[2]s - stargazers: %[3]s\n" $repo.nameWithOwner $repo.stargazerCount -}}
|
||||
# {{- end -}}
|
||||
# '
|
||||
# exec gh api graphql -f query="${QUERY}" --paginate --template="${TEMPLATE}"
|
||||
`, name, "%s", "%v")
|
||||
script := fmt.Sprintf(scriptTmpl, name, "%s", "%v")
|
||||
filePath := filepath.Join(name, name)
|
||||
err = ioutil.WriteFile(filePath, []byte(fileTmpl), 0755)
|
||||
err = ioutil.WriteFile(filePath, []byte(script), 0755)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
@ -609,8 +594,119 @@ func (m *Manager) Create(name string) error {
|
|||
}
|
||||
dir := filepath.Join(wd, name)
|
||||
addCmd := m.newCommand(exe, "-C", dir, "--git-dir="+filepath.Join(dir, ".git"), "add", name, "--chmod=+x")
|
||||
return addCmd.Run()
|
||||
}
|
||||
|
||||
func (m *Manager) otherBinScaffolding(gitExe, name string) error {
|
||||
err := os.MkdirAll(filepath.Join(name, ".github", "workflows"), 0755)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
workflowPath := filepath.Join(".github", "workflows", "release.yml")
|
||||
err = ioutil.WriteFile(filepath.Join(name, workflowPath), otherBinWorkflow, 0644)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = os.Mkdir(filepath.Join(name, "script"), 0755)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
buildScriptPath := filepath.Join("script", "build.sh")
|
||||
err = ioutil.WriteFile(filepath.Join(name, buildScriptPath), buildScript, 0755)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
wd, err := os.Getwd()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
dir := filepath.Join(wd, name)
|
||||
addCmd := m.newCommand(gitExe, "-C", dir, "--git-dir="+filepath.Join(dir, ".git"), "add", buildScriptPath, "--chmod=+x")
|
||||
err = addCmd.Run()
|
||||
return err
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
addCmd = m.newCommand(gitExe, "-C", dir, "--git-dir="+filepath.Join(dir, ".git"), "add", workflowPath)
|
||||
return addCmd.Run()
|
||||
}
|
||||
|
||||
func (m *Manager) goBinScaffolding(gitExe, name string) error {
|
||||
goExe, err := m.lookPath("go")
|
||||
if err != nil {
|
||||
return fmt.Errorf("go is required for creating Go extensions: %w", err)
|
||||
}
|
||||
err = os.MkdirAll(filepath.Join(name, ".github", "workflows"), 0755)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
workflowPath := filepath.Join(".github", "workflows", "release.yml")
|
||||
err = ioutil.WriteFile(filepath.Join(name, workflowPath), goBinWorkflow, 0644)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
mainGo := fmt.Sprintf(mainGoTmpl, name)
|
||||
mainPath := "main.go"
|
||||
|
||||
err = ioutil.WriteFile(filepath.Join(name, mainPath), []byte(mainGo), 0644)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
wd, err := os.Getwd()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
dir := filepath.Join(wd, name)
|
||||
|
||||
host, err := m.config.DefaultHost()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
currentUser, err := api.CurrentLoginName(api.NewClientFromHTTP(m.client), host)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
goCmds := [][]string{
|
||||
{"mod", "init", fmt.Sprintf("%s/%s/%s", host, currentUser, name)},
|
||||
{"mod", "tidy"},
|
||||
{"build"},
|
||||
}
|
||||
|
||||
_, ext := m.platform()
|
||||
ignore := name + ext
|
||||
|
||||
err = ioutil.WriteFile(filepath.Join(name, ".gitignore"), []byte(ignore), 0644)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, args := range goCmds {
|
||||
goCmd := m.newCommand(goExe, args...)
|
||||
goCmd.Dir = dir
|
||||
err = goCmd.Run()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to set up go module: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
addArgs := []string{
|
||||
"-C", dir,
|
||||
"--git-dir=" + filepath.Join(dir, ".git"),
|
||||
"add",
|
||||
workflowPath,
|
||||
mainPath,
|
||||
".gitignore",
|
||||
"go.mod",
|
||||
"go.sum",
|
||||
}
|
||||
|
||||
addCmd := m.newCommand(gitExe, addArgs...)
|
||||
return addCmd.Run()
|
||||
}
|
||||
|
||||
func runCmds(cmds []*exec.Cmd) error {
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ import (
|
|||
"github.com/cli/cli/v2/internal/config"
|
||||
"github.com/cli/cli/v2/internal/ghrepo"
|
||||
"github.com/cli/cli/v2/internal/run"
|
||||
"github.com/cli/cli/v2/pkg/extensions"
|
||||
"github.com/cli/cli/v2/pkg/httpmock"
|
||||
"github.com/cli/cli/v2/pkg/iostreams"
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
|
@ -582,7 +583,7 @@ func TestManager_Create(t *testing.T) {
|
|||
assert.NoError(t, os.Chdir(tempDir))
|
||||
t.Cleanup(func() { _ = os.Chdir(oldWd) })
|
||||
m := newTestManager(tempDir, nil, nil)
|
||||
err := m.Create("gh-test")
|
||||
err := m.Create("gh-test", extensions.GitTemplateType)
|
||||
assert.NoError(t, err)
|
||||
files, err := ioutil.ReadDir(filepath.Join(tempDir, "gh-test"))
|
||||
assert.NoError(t, err)
|
||||
|
|
@ -596,6 +597,63 @@ func TestManager_Create(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestManager_Create_go_binary(t *testing.T) {
|
||||
tempDir := t.TempDir()
|
||||
oldWd, _ := os.Getwd()
|
||||
assert.NoError(t, os.Chdir(tempDir))
|
||||
t.Cleanup(func() { _ = os.Chdir(oldWd) })
|
||||
reg := httpmock.Registry{}
|
||||
defer reg.Verify(t)
|
||||
client := http.Client{Transport: ®}
|
||||
|
||||
reg.Register(
|
||||
httpmock.GraphQL(`query UserCurrent\b`),
|
||||
httpmock.StringResponse(`{"data":{"viewer":{"login":"jillv"}}}`))
|
||||
m := newTestManager(tempDir, &client, nil)
|
||||
|
||||
err := m.Create("gh-test", extensions.GoBinTemplateType)
|
||||
assert.NoError(t, err)
|
||||
|
||||
files, err := ioutil.ReadDir(filepath.Join(tempDir, "gh-test"))
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, 3, len(files))
|
||||
assert.Equal(t, ".gitignore", files[1].Name())
|
||||
assert.Equal(t, "main.go", files[2].Name())
|
||||
|
||||
files, err = ioutil.ReadDir(filepath.Join(tempDir, "gh-test", ".github", "workflows"))
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, 1, len(files))
|
||||
workflowFile := files[0]
|
||||
assert.Equal(t, "release.yml", workflowFile.Name())
|
||||
}
|
||||
|
||||
func TestManager_Create_other_binary(t *testing.T) {
|
||||
tempDir := t.TempDir()
|
||||
oldWd, _ := os.Getwd()
|
||||
assert.NoError(t, os.Chdir(tempDir))
|
||||
t.Cleanup(func() { _ = os.Chdir(oldWd) })
|
||||
m := newTestManager(tempDir, nil, nil)
|
||||
|
||||
err := m.Create("gh-test", extensions.OtherBinTemplateType)
|
||||
assert.NoError(t, err)
|
||||
|
||||
files, err := ioutil.ReadDir(filepath.Join(tempDir, "gh-test"))
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, 2, len(files))
|
||||
|
||||
files, err = ioutil.ReadDir(filepath.Join(tempDir, "gh-test", ".github", "workflows"))
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, 1, len(files))
|
||||
workflowFile := files[0]
|
||||
assert.Equal(t, "release.yml", workflowFile.Name())
|
||||
|
||||
files, err = ioutil.ReadDir(filepath.Join(tempDir, "gh-test", "script"))
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, 1, len(files))
|
||||
buildFile := files[0]
|
||||
assert.Equal(t, "build.sh", buildFile.Name())
|
||||
}
|
||||
|
||||
func stubExtension(path string) error {
|
||||
if err := os.MkdirAll(filepath.Dir(path), 0755); err != nil {
|
||||
return err
|
||||
|
|
|
|||
|
|
@ -6,6 +6,14 @@ import (
|
|||
"github.com/cli/cli/v2/internal/ghrepo"
|
||||
)
|
||||
|
||||
type ExtTemplateType int
|
||||
|
||||
const (
|
||||
GitTemplateType ExtTemplateType = 0
|
||||
GoBinTemplateType ExtTemplateType = 1
|
||||
OtherBinTemplateType ExtTemplateType = 2
|
||||
)
|
||||
|
||||
//go:generate moq -rm -out extension_mock.go . Extension
|
||||
type Extension interface {
|
||||
Name() string // Extension Name without gh-
|
||||
|
|
@ -24,5 +32,5 @@ type ExtensionManager interface {
|
|||
Upgrade(name string, force bool) error
|
||||
Remove(name string) error
|
||||
Dispatch(args []string, stdin io.Reader, stdout, stderr io.Writer) (bool, error)
|
||||
Create(name string) error
|
||||
Create(name string, tmplType ExtTemplateType) error
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ var _ ExtensionManager = &ExtensionManagerMock{}
|
|||
//
|
||||
// // make and configure a mocked ExtensionManager
|
||||
// mockedExtensionManager := &ExtensionManagerMock{
|
||||
// CreateFunc: func(name string) error {
|
||||
// CreateFunc: func(name string, tmplType ExtTemplateType) error {
|
||||
// panic("mock out the Create method")
|
||||
// },
|
||||
// DispatchFunc: func(args []string, stdin io.Reader, stdout io.Writer, stderr io.Writer) (bool, error) {
|
||||
|
|
@ -48,7 +48,7 @@ var _ ExtensionManager = &ExtensionManagerMock{}
|
|||
// }
|
||||
type ExtensionManagerMock struct {
|
||||
// CreateFunc mocks the Create method.
|
||||
CreateFunc func(name string) error
|
||||
CreateFunc func(name string, tmplType ExtTemplateType) error
|
||||
|
||||
// DispatchFunc mocks the Dispatch method.
|
||||
DispatchFunc func(args []string, stdin io.Reader, stdout io.Writer, stderr io.Writer) (bool, error)
|
||||
|
|
@ -74,6 +74,8 @@ type ExtensionManagerMock struct {
|
|||
Create []struct {
|
||||
// Name is the name argument value.
|
||||
Name string
|
||||
// TmplType is the tmplType argument value.
|
||||
TmplType ExtTemplateType
|
||||
}
|
||||
// Dispatch holds details about calls to the Dispatch method.
|
||||
Dispatch []struct {
|
||||
|
|
@ -124,29 +126,33 @@ type ExtensionManagerMock struct {
|
|||
}
|
||||
|
||||
// Create calls CreateFunc.
|
||||
func (mock *ExtensionManagerMock) Create(name string) error {
|
||||
func (mock *ExtensionManagerMock) Create(name string, tmplType ExtTemplateType) error {
|
||||
if mock.CreateFunc == nil {
|
||||
panic("ExtensionManagerMock.CreateFunc: method is nil but ExtensionManager.Create was just called")
|
||||
}
|
||||
callInfo := struct {
|
||||
Name string
|
||||
Name string
|
||||
TmplType ExtTemplateType
|
||||
}{
|
||||
Name: name,
|
||||
Name: name,
|
||||
TmplType: tmplType,
|
||||
}
|
||||
mock.lockCreate.Lock()
|
||||
mock.calls.Create = append(mock.calls.Create, callInfo)
|
||||
mock.lockCreate.Unlock()
|
||||
return mock.CreateFunc(name)
|
||||
return mock.CreateFunc(name, tmplType)
|
||||
}
|
||||
|
||||
// CreateCalls gets all the calls that were made to Create.
|
||||
// Check the length with:
|
||||
// len(mockedExtensionManager.CreateCalls())
|
||||
func (mock *ExtensionManagerMock) CreateCalls() []struct {
|
||||
Name string
|
||||
Name string
|
||||
TmplType ExtTemplateType
|
||||
} {
|
||||
var calls []struct {
|
||||
Name string
|
||||
Name string
|
||||
TmplType ExtTemplateType
|
||||
}
|
||||
mock.lockCreate.RLock()
|
||||
calls = mock.calls.Create
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue