Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: [#576] add package manager functionality #920

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions contracts/packages/manager.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package packages

import (
"github.com/dave/dst"
"github.com/dave/dst/dstutil"
)

type FileModifier interface {
Apply(dir string) error
}

type Manager interface {
Install(dir string) error
Uninstall(dir string) error
}

type GoNodeMatcher interface {
MatchNode(node dst.Node) bool
MatchCursor(cursor *dstutil.Cursor) bool
}

type GoNodeModifier interface {
Apply(node dst.Node) error
}
2 changes: 2 additions & 0 deletions foundation/application.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,8 @@
console.NewEnvDecryptCommand(),
console.NewTestMakeCommand(),
console.NewPackageMakeCommand(),
console.NewPackageInstallCommand(),
console.NewPackageUninstallCommand(),

Check warning on line 65 in foundation/application.go

View check run for this annotation

Codecov / codecov/patch

foundation/application.go#L64-L65

Added lines #L64 - L65 were not covered by tests
console.NewVendorPublishCommand(app.publishes, app.publishGroups),
})
app.setTimezone()
Expand Down
126 changes: 126 additions & 0 deletions foundation/console/package_install_command.go
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we add some test cases for this file? Although It may be a bit complex.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I will add test case in new PR after merged this one.

Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
package console

import (
"fmt"
"os/exec"
"strings"

"github.com/goravel/framework/contracts/console"
"github.com/goravel/framework/contracts/console/command"
"github.com/goravel/framework/errors"
"github.com/goravel/framework/support/color"
)

type PackageInstallCommand struct {
}

func NewPackageInstallCommand() *PackageInstallCommand {
return &PackageInstallCommand{}

Check warning on line 18 in foundation/console/package_install_command.go

View check run for this annotation

Codecov / codecov/patch

foundation/console/package_install_command.go#L17-L18

Added lines #L17 - L18 were not covered by tests
}

// Signature The name and signature of the console command.
func (r *PackageInstallCommand) Signature() string {
return "package:install"

Check warning on line 23 in foundation/console/package_install_command.go

View check run for this annotation

Codecov / codecov/patch

foundation/console/package_install_command.go#L22-L23

Added lines #L22 - L23 were not covered by tests
}

// Description The console command description.
func (r *PackageInstallCommand) Description() string {
return "Install a package"

Check warning on line 28 in foundation/console/package_install_command.go

View check run for this annotation

Codecov / codecov/patch

foundation/console/package_install_command.go#L27-L28

Added lines #L27 - L28 were not covered by tests
}

// Extend The console command extend.
func (r *PackageInstallCommand) Extend() command.Extend {
return command.Extend{
ArgsUsage: " <package@version>",
Category: "package",
}

Check warning on line 36 in foundation/console/package_install_command.go

View check run for this annotation

Codecov / codecov/patch

foundation/console/package_install_command.go#L32-L36

Added lines #L32 - L36 were not covered by tests
}

// Handle Execute the console command.
func (r *PackageInstallCommand) Handle(ctx console.Context) error {
var (
err error
pkg = ctx.Argument(0)
)
if pkg == "" {
pkg, err = ctx.Ask("Enter the package name to install", console.AskOption{
Description: "If no version is specified, install the latest",
Placeholder: " E.g example.com/pkg or example.com/pkg@v1.0.0",
Prompt: ">",
Validate: func(s string) error {
if s == "" {
return errors.CommandEmptyPackageName
}

Check warning on line 53 in foundation/console/package_install_command.go

View check run for this annotation

Codecov / codecov/patch

foundation/console/package_install_command.go#L40-L53

Added lines #L40 - L53 were not covered by tests

return nil

Check warning on line 55 in foundation/console/package_install_command.go

View check run for this annotation

Codecov / codecov/patch

foundation/console/package_install_command.go#L55

Added line #L55 was not covered by tests
},
})
if err != nil {
ctx.Error(err.Error())
return nil
}

Check warning on line 61 in foundation/console/package_install_command.go

View check run for this annotation

Codecov / codecov/patch

foundation/console/package_install_command.go#L58-L61

Added lines #L58 - L61 were not covered by tests
}

// handle package version
pkg, version, ok := strings.Cut(pkg, "@")
manager := pkg + "/manager"
if ok {
pkg = pkg + "@" + version
}

Check warning on line 69 in foundation/console/package_install_command.go

View check run for this annotation

Codecov / codecov/patch

foundation/console/package_install_command.go#L65-L69

Added lines #L65 - L69 were not covered by tests

// get package
var output []byte
get := exec.Command("go", "get", pkg)
if err = ctx.Spinner(fmt.Sprintf("> @%s", strings.Join(get.Args, " ")), console.SpinnerOption{
Action: func() error {
output, err = get.CombinedOutput()

return err
},
}); err != nil {
color.Errorf("failed to get package: %s\n", err.Error())
if len(output) > 0 {
color.Red().Println(string(output))
}

Check warning on line 84 in foundation/console/package_install_command.go

View check run for this annotation

Codecov / codecov/patch

foundation/console/package_install_command.go#L72-L84

Added lines #L72 - L84 were not covered by tests

return nil

Check warning on line 86 in foundation/console/package_install_command.go

View check run for this annotation

Codecov / codecov/patch

foundation/console/package_install_command.go#L86

Added line #L86 was not covered by tests
}

// install package
install := exec.Command("go", "run", manager, "install")
if err = ctx.Spinner(fmt.Sprintf("> @%s", strings.Join(install.Args, " ")), console.SpinnerOption{
Action: func() error {
output, err = install.CombinedOutput()

return err
},
}); err != nil {
color.Errorf("failed to install package: %s\n", err.Error())
if len(output) > 0 {
color.Red().Println(string(output))
}

Check warning on line 101 in foundation/console/package_install_command.go

View check run for this annotation

Codecov / codecov/patch

foundation/console/package_install_command.go#L90-L101

Added lines #L90 - L101 were not covered by tests

return nil

Check warning on line 103 in foundation/console/package_install_command.go

View check run for this annotation

Codecov / codecov/patch

foundation/console/package_install_command.go#L103

Added line #L103 was not covered by tests
}

// tidy go.mod file
tidy := exec.Command("go", "mod", "tidy")
if err = ctx.Spinner(fmt.Sprintf("> @%s", strings.Join(tidy.Args, " ")), console.SpinnerOption{
Action: func() error {
output, err = tidy.CombinedOutput()

return err
},
}); err != nil {
color.Errorf("failed to tidy go.mod file: %s\n", err.Error())
if len(output) > 0 {
color.Red().Println(string(output))
}

Check warning on line 118 in foundation/console/package_install_command.go

View check run for this annotation

Codecov / codecov/patch

foundation/console/package_install_command.go#L107-L118

Added lines #L107 - L118 were not covered by tests

return nil

Check warning on line 120 in foundation/console/package_install_command.go

View check run for this annotation

Codecov / codecov/patch

foundation/console/package_install_command.go#L120

Added line #L120 was not covered by tests
}

color.Successf("Package %s installed successfully\n", pkg)

return nil

Check warning on line 125 in foundation/console/package_install_command.go

View check run for this annotation

Codecov / codecov/patch

foundation/console/package_install_command.go#L123-L125

Added lines #L123 - L125 were not covered by tests
}
22 changes: 16 additions & 6 deletions foundation/console/package_make_command.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,12 @@ func (r *PackageMakeCommand) Extend() command.Extend {
return command.Extend{
Category: "make",
Flags: []command.Flag{
&command.BoolFlag{
Name: "manager",
Aliases: []string{"m"},
Usage: "Create a package manager",
DisableDefaultText: true,
},
&command.StringFlag{
Name: "root",
Aliases: []string{"r"},
Expand Down Expand Up @@ -72,12 +78,16 @@ func (r *PackageMakeCommand) Handle(ctx console.Context) error {
packageName := packageName(pkg)
packageMakeCommandStubs := NewPackageMakeCommandStubs(pkg, root)
files := map[string]func() string{
"README.md": packageMakeCommandStubs.Readme,
"service_provider.go": packageMakeCommandStubs.ServiceProvider,
packageName + ".go": packageMakeCommandStubs.Main,
"config/" + packageName + ".go": packageMakeCommandStubs.Config,
"contracts/" + packageName + ".go": packageMakeCommandStubs.Contracts,
"facades/" + packageName + ".go": packageMakeCommandStubs.Facades,
"README.md": packageMakeCommandStubs.Readme,
"service_provider.go": packageMakeCommandStubs.ServiceProvider,
packageName + ".go": packageMakeCommandStubs.Main,
filepath.Join("config", packageName+".go"): packageMakeCommandStubs.Config,
filepath.Join("contracts", packageName+".go"): packageMakeCommandStubs.Contracts,
filepath.Join("facades", packageName+".go"): packageMakeCommandStubs.Facades,
}

if ctx.OptionBool("manager") {
files[filepath.Join("manager", "manager.go")] = packageMakeCommandStubs.Manager
}

for path, content := range files {
Expand Down
97 changes: 97 additions & 0 deletions foundation/console/package_make_command_stubs.go
Original file line number Diff line number Diff line change
Expand Up @@ -122,3 +122,100 @@ func DummyCamelName() contracts.DummyCamelName {

return content
}

func (r PackageMakeCommandStubs) Manager() string {
content := `package main

import (
"os"
"path"
"path/filepath"
"runtime/debug"
"strings"

pkgcontracts "github.com/goravel/framework/contracts/packages"
"github.com/goravel/framework/packages"
"github.com/goravel/framework/support/color"
)

var (
module string
dir string
force bool
)

func init() {
for i, arg := range os.Args {
if arg == "--force" || arg == "-f" {
force = true
}

if (arg == "--dir" || arg == "-d") && len(os.Args) > i+1 {
dir = os.Args[i+1]
}
}

if info, ok := debug.ReadBuildInfo(); ok && strings.HasSuffix(info.Path, "manager") {
module = path.Dir(info.Path)
}

if dir == "" {
dir, _ = os.Getwd()
}
}

func main() {
var pkg = packages.Manager{
ContinueOnError: force,
Module: module,
OnInstall: []pkgcontracts.FileModifier{
packages.ModifyGoFile{
File: filepath.Join("config", "app.go"),
Modifiers: []pkgcontracts.GoNodeModifier{
packages.AddImportSpec(module),
packages.AddProviderSpec(
"&DummyName.ServiceProvider{}",
),
},
},
},
OnUninstall: []pkgcontracts.FileModifier{
packages.ModifyGoFile{
File: filepath.Join("config", "app.go"),
Modifiers: []pkgcontracts.GoNodeModifier{
packages.RemoveImportSpec(module),
packages.RemoveProviderSpec("&DummyName.ServiceProvider{}"),
},
},
},
}

if module == "" {
color.Errorln("Package module name is empty, please run command with module name.")
return
}

if len(os.Args) > 1 && os.Args[1] == "install" {
err := pkg.Install(dir)
if err != nil {
color.Errorln(err)
return
}
color.Successf("Package %s installed successfully\n", module)
}

if len(os.Args) > 1 && os.Args[1] == "uninstall" {
err := pkg.Uninstall(dir)
if err != nil {
color.Errorln(err)
return
}
color.Successf("Package %s uninstalled successfully\n", module)
}
}

`
content = strings.ReplaceAll(content, "DummyName", r.name)

return content
}
44 changes: 38 additions & 6 deletions foundation/console/package_make_command_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,12 @@ func (s *PackageMakeCommandTestSuite) TestExtend() {

if len(got.Flags) > 0 {
s.Run("should have correctly configured StringFlag", func() {
flag, ok := got.Flags[0].(*command.StringFlag)
managerFlag, ok := got.Flags[0].(*command.BoolFlag)
if !ok {
s.Fail("First flag is not BoolFlag (got type: %T)", got.Flags[0])
}

rootFlag, ok := got.Flags[1].(*command.StringFlag)
if !ok {
s.Fail("First flag is not StringFlag (got type: %T)", got.Flags[0])
}
Expand All @@ -52,10 +57,13 @@ func (s *PackageMakeCommandTestSuite) TestExtend() {
got interface{}
expected interface{}
}{
{"Name", flag.Name, "root"},
{"Aliases", flag.Aliases, []string{"r"}},
{"Usage", flag.Usage, "The root path of package, default: packages"},
{"Value", flag.Value, "packages"},
{"Name", rootFlag.Name, "root"},
{"Aliases", rootFlag.Aliases, []string{"r"}},
{"Usage", rootFlag.Usage, "The root path of package, default: packages"},
{"Value", rootFlag.Value, "packages"},
{"Name", managerFlag.Name, "manager"},
{"Aliases", managerFlag.Aliases, []string{"m"}},
{"Usage", managerFlag.Usage, "Create a package manager"},
}

for _, tc := range testCases {
Expand Down Expand Up @@ -93,10 +101,32 @@ func (s *PackageMakeCommandTestSuite) TestHandle() {
},
},
{
name: "name is sms and use default root",
name: "name is sms and use default root(hasn't manager)",
setup: func() {
mockContext.EXPECT().Argument(0).Return("sms").Once()
mockContext.EXPECT().Option("root").Return("packages").Once()
mockContext.EXPECT().OptionBool("manager").Return(false).Once()
mockContext.EXPECT().Success("Package created successfully: packages/sms").Once()
},
assert: func() {
s.NoError(NewPackageMakeCommand().Handle(mockContext))
s.True(file.Exists("packages/sms/README.md"))
s.True(file.Exists("packages/sms/service_provider.go"))
s.True(file.Exists("packages/sms/sms.go"))
s.True(file.Exists("packages/sms/config/sms.go"))
s.True(file.Exists("packages/sms/contracts/sms.go"))
s.True(file.Exists("packages/sms/facades/sms.go"))
s.True(file.Contain("packages/sms/facades/sms.go", "goravel/packages/sms"))
s.True(file.Contain("packages/sms/facades/sms.go", "goravel/packages/sms/contracts"))
s.NoError(file.Remove("packages"))
},
},
{
name: "name is sms and use default root(has manager)",
setup: func() {
mockContext.EXPECT().Argument(0).Return("sms").Once()
mockContext.EXPECT().Option("root").Return("packages").Once()
mockContext.EXPECT().OptionBool("manager").Return(true).Once()
mockContext.EXPECT().Success("Package created successfully: packages/sms").Once()
},
assert: func() {
Expand All @@ -109,6 +139,7 @@ func (s *PackageMakeCommandTestSuite) TestHandle() {
s.True(file.Exists("packages/sms/facades/sms.go"))
s.True(file.Contain("packages/sms/facades/sms.go", "goravel/packages/sms"))
s.True(file.Contain("packages/sms/facades/sms.go", "goravel/packages/sms/contracts"))
s.True(file.Exists("packages/sms/manager/manager.go"))
s.NoError(file.Remove("packages"))
},
},
Expand All @@ -117,6 +148,7 @@ func (s *PackageMakeCommandTestSuite) TestHandle() {
setup: func() {
mockContext.EXPECT().Argument(0).Return("github.com/goravel/sms-aws").Once()
mockContext.EXPECT().Option("root").Return("package").Once()
mockContext.EXPECT().OptionBool("manager").Return(false).Once()
mockContext.EXPECT().Success("Package created successfully: package/github_com_goravel_sms_aws").Once()
},
assert: func() {
Expand Down
Loading