Add extension create command

This commit is contained in:
Sam Coe 2021-08-19 12:08:14 -07:00
parent c84bfa9e66
commit e9f7459ce2
No known key found for this signature in database
GPG key ID: 8E322C20F811D086
6 changed files with 169 additions and 0 deletions

View file

@ -141,6 +141,39 @@ func NewCmdExtension(f *cmdutil.Factory) *cobra.Command {
return nil
},
},
&cobra.Command{
Use: "create <name>",
Short: "Create a new extension",
Args: cobra.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
extName := args[0]
if !strings.HasPrefix(extName, "gh-") {
extName = "gh-" + extName
}
if err := m.Create(extName); err != nil {
return err
}
if io.IsStdoutTTY() {
link := "https://docs.github.com/en/github-cli/github-cli/creating-github-cli-extensions"
cs := io.ColorScheme()
out := heredoc.Docf(`
%[1]s Created directory %[2]s
%[1]s Initialized git repository
%[1]s Set up extension scaffolding
%[2]s is ready for development
Install locally with: cd %[2]s && gh extension install .
Publish to GitHub with: gh repo create %[2]s
For more information on writing extensions: %[3]s
`, cs.SuccessIcon(), extName, link)
fmt.Fprint(io.Out, out)
}
return nil
},
},
)
extCmd.Hidden = true

View file

@ -7,6 +7,7 @@ import (
"strings"
"testing"
"github.com/MakeNowJust/heredoc"
"github.com/cli/cli/internal/config"
"github.com/cli/cli/pkg/cmdutil"
"github.com/cli/cli/pkg/extensions"
@ -161,6 +162,50 @@ 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"},
managerStubs: func(em *extensions.ExtensionManagerMock) func(*testing.T) {
em.CreateFunc = func(name string) 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
Install locally with: cd gh-test && gh extension install .
Publish to GitHub with: gh repo create gh-test
For more information on writing extensions: https://docs.github.com/en/github-cli/github-cli/creating-github-cli-extensions
`),
},
{
name: "create extension notty",
args: []string{"create", "gh-test"},
managerStubs: func(em *extensions.ExtensionManagerMock) func(*testing.T) {
em.CreateFunc = func(name string) 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: false,
wantStdout: "",
},
}
for _, tt := range tests {

View file

@ -13,6 +13,7 @@ import (
"runtime"
"strings"
"github.com/MakeNowJust/heredoc"
"github.com/cli/cli/internal/config"
"github.com/cli/cli/pkg/extensions"
"github.com/cli/cli/pkg/findsh"
@ -249,6 +250,32 @@ func (m *Manager) installDir() string {
return filepath.Join(m.dataDir(), "extensions")
}
func (m *Manager) Create(name string) error {
exe, err := m.lookPath("git")
if err != nil {
return err
}
err = os.Mkdir(name, 0755)
if err != nil {
return err
}
initCmd := m.newCommand(exe, "init", "-b", "main", "--quiet", name)
err = initCmd.Run()
if err != nil {
return err
}
fileTmpl := heredoc.Docf(`
#!/bin/bash
echo "Hello %[1]s"
`, name)
filePath := filepath.Join(name, name)
err = ioutil.WriteFile(filePath, []byte(fileTmpl), 0755)
return err
}
func runCmds(cmds []*exec.Cmd, stdout, stderr io.Writer) error {
for _, cmd := range cmds {
cmd.Stdout = stdout

View file

@ -202,6 +202,26 @@ func TestManager_Install(t *testing.T) {
assert.Equal(t, "", stderr.String())
}
func TestManager_Create(t *testing.T) {
tempDir := t.TempDir()
oldWd, _ := os.Getwd()
assert.NoError(t, os.Chdir(tempDir))
t.Cleanup(func() { _ = os.Chdir(oldWd) })
m := newTestManager(tempDir)
err := m.Create("gh-test")
assert.NoError(t, err)
files, err := ioutil.ReadDir(filepath.Join(tempDir, "gh-test"))
assert.NoError(t, err)
assert.Equal(t, 1, len(files))
extFile := files[0]
assert.Equal(t, "gh-test", extFile.Name())
if runtime.GOOS == "windows" {
assert.Equal(t, os.FileMode(0666), extFile.Mode())
} else {
assert.Equal(t, os.FileMode(0755), extFile.Mode())
}
}
func stubExtension(path string) error {
if err := os.MkdirAll(filepath.Dir(path), 0755); err != nil {
return err

View file

@ -21,4 +21,5 @@ type ExtensionManager interface {
Upgrade(name string, force bool, stdout, stderr io.Writer) error
Remove(name string) error
Dispatch(args []string, stdin io.Reader, stdout, stderr io.Writer) (bool, error)
Create(name string) error
}

View file

@ -18,6 +18,9 @@ var _ ExtensionManager = &ExtensionManagerMock{}
//
// // make and configure a mocked ExtensionManager
// mockedExtensionManager := &ExtensionManagerMock{
// CreateFunc: func(name string) error {
// panic("mock out the Create method")
// },
// DispatchFunc: func(args []string, stdin io.Reader, stdout io.Writer, stderr io.Writer) (bool, error) {
// panic("mock out the Dispatch method")
// },
@ -43,6 +46,9 @@ var _ ExtensionManager = &ExtensionManagerMock{}
//
// }
type ExtensionManagerMock struct {
// CreateFunc mocks the Create method.
CreateFunc func(name string) error
// DispatchFunc mocks the Dispatch method.
DispatchFunc func(args []string, stdin io.Reader, stdout io.Writer, stderr io.Writer) (bool, error)
@ -63,6 +69,11 @@ type ExtensionManagerMock struct {
// calls tracks calls to the methods.
calls struct {
// Create holds details about calls to the Create method.
Create []struct {
// Name is the name argument value.
Name string
}
// Dispatch holds details about calls to the Dispatch method.
Dispatch []struct {
// Args is the args argument value.
@ -110,6 +121,7 @@ type ExtensionManagerMock struct {
Stderr io.Writer
}
}
lockCreate sync.RWMutex
lockDispatch sync.RWMutex
lockInstall sync.RWMutex
lockInstallLocal sync.RWMutex
@ -118,6 +130,37 @@ type ExtensionManagerMock struct {
lockUpgrade sync.RWMutex
}
// Create calls CreateFunc.
func (mock *ExtensionManagerMock) Create(name string) error {
if mock.CreateFunc == nil {
panic("ExtensionManagerMock.CreateFunc: method is nil but ExtensionManager.Create was just called")
}
callInfo := struct {
Name string
}{
Name: name,
}
mock.lockCreate.Lock()
mock.calls.Create = append(mock.calls.Create, callInfo)
mock.lockCreate.Unlock()
return mock.CreateFunc(name)
}
// CreateCalls gets all the calls that were made to Create.
// Check the length with:
// len(mockedExtensionManager.CreateCalls())
func (mock *ExtensionManagerMock) CreateCalls() []struct {
Name string
} {
var calls []struct {
Name string
}
mock.lockCreate.RLock()
calls = mock.calls.Create
mock.lockCreate.RUnlock()
return calls
}
// Dispatch calls DispatchFunc.
func (mock *ExtensionManagerMock) Dispatch(args []string, stdin io.Reader, stdout io.Writer, stderr io.Writer) (bool, error) {
if mock.DispatchFunc == nil {