Skip to content

Commit 2e06209

Browse files
committed
update tools automatically
1 parent e21aeca commit 2e06209

6 files changed

Lines changed: 301 additions & 54 deletions

File tree

cmd/kitty/main.go

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -173,8 +173,10 @@ func runExtension(name string, args []string) error {
173173
return ee.Wrap(err, "cannot get git root")
174174
}
175175

176-
// TODO verify tools (check exist, check version)
177-
// TODO call tools.GetTool (like below, refactor later)
176+
if err := tools.EnsureInstalled(root, name); err != nil {
177+
return ee.Wrapf(err, "cannot install extension `%s`", name)
178+
}
179+
178180
// run apps
179181
if appBin, err := exec.LookPath(filepath.Join(root, ".kitty", ".bin", name)); err == nil {
180182
// bin extension

internal/extension-registry/install.go

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,15 @@ import (
1616
)
1717

1818
func (a *App) installUnknownVersionTo(version string, dst string) error {
19+
return a.installUnknownVersionToWithProgress(version, dst, true)
20+
}
21+
22+
func (a *App) installUnknownVersionToWithProgress(version string, dst string, showProgress bool) error {
1923
slog.Debug("app installUnknownVersionTo", "version", version, "dst", dst)
2024
o := &installer.InstallOptions{
2125
Version: version,
2226
To: dst,
23-
ShowProgress: true,
27+
ShowProgress: showProgress,
2428
}
2529

2630
err := tryInstall(a.InstallOptions, o)
@@ -36,16 +40,24 @@ func (a *App) installUnknownVersionTo(version string, dst string) error {
3640
}
3741

3842
func (v *Version) InstallUnknownVersionTo(dst string) error {
43+
return v.InstallUnknownVersionToWithProgress(dst, true)
44+
}
45+
46+
func (v *Version) InstallUnknownVersionToWithProgress(dst string, showProgress bool) error {
3947
slog.Debug("version installUnknownVersionTo", "dst", dst)
40-
return v.App.installUnknownVersionTo(v.Version, dst)
48+
return v.App.installUnknownVersionToWithProgress(v.Version, dst, showProgress)
4149
}
4250

4351
func (v *Version) InstallTo(dst string) error {
52+
return v.InstallToWithProgress(dst, true)
53+
}
54+
55+
func (v *Version) InstallToWithProgress(dst string, showProgress bool) error {
4456
slog.Debug("version InstallTo", "version", v, "dst", dst)
4557
o := &installer.InstallOptions{
4658
Version: v.Version,
4759
To: dst,
48-
ShowProgress: true,
60+
ShowProgress: showProgress,
4961
}
5062

5163
err := tryInstall(v.InstallOptions, o)

internal/hooks/invoke.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,9 @@ import (
44
"github.com/ImSingee/go-ex/pp"
55
"github.com/spf13/cobra"
66

7+
"github.com/ImSingee/kitty/internal/lib/git"
78
"github.com/ImSingee/kitty/internal/lib/shells"
9+
"github.com/ImSingee/kitty/internal/tools"
810
)
911

1012
type invokeOptions struct {
@@ -43,6 +45,15 @@ func (o *invokeOptions) invoke() (output string, success bool) {
4345
return "Your kitty version is too low, please upgrade", false
4446
}
4547

48+
root, err := git.GetRoot("")
49+
if err != nil {
50+
return "Cannot find git root: " + err.Error(), false
51+
}
52+
53+
if err := tools.EnsureInstalledQuiet(root); err != nil {
54+
return "Cannot install tools: " + err.Error(), false
55+
}
56+
4657
//pp.Println(`export KITTY_VERSION=` + version) // TODO
4758
return "", true
4859
}

internal/tools/install.go

Lines changed: 134 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,67 @@ import (
1919
)
2020

2121
type installOptions struct {
22+
root string
23+
quiet bool
2224
toInstall []string
2325
}
2426

27+
// EnsureInstalled installs or updates configured tools when their installed
28+
// symlink version does not match the repository configuration.
29+
func EnsureInstalled(root string, apps ...string) error {
30+
return ensureInstalled(root, false, apps...)
31+
}
32+
33+
// EnsureInstalledQuiet is like EnsureInstalled, but suppresses installer
34+
// progress output. This is useful when stdout has a protocol-level meaning.
35+
func EnsureInstalledQuiet(root string, apps ...string) error {
36+
return ensureInstalled(root, true, apps...)
37+
}
38+
39+
func ensureInstalled(root string, quiet bool, apps ...string) error {
40+
o := &installOptions{root: root, quiet: quiet}
41+
42+
currentTools, err := (&listOptions{root: root}).getCurrentToolsMap()
43+
if err != nil {
44+
return ee.Wrap(err, "cannot load current tools")
45+
}
46+
47+
requiredTools := currentTools
48+
if len(apps) > 0 {
49+
requiredTools = make(map[string]string, len(apps))
50+
for _, app := range apps {
51+
if version, ok := currentTools[app]; ok {
52+
requiredTools[app] = version
53+
}
54+
}
55+
}
56+
if len(requiredTools) == 0 {
57+
return nil
58+
}
59+
60+
installedTools, err := (&listOptions{root: root}).getInstalledTools()
61+
if err != nil {
62+
return ee.Wrap(err, "cannot load installed tools")
63+
}
64+
65+
toInstallOrUpdate := getToolsToInstallOrUpdate(requiredTools, installedTools)
66+
if len(toInstallOrUpdate) == 0 {
67+
return nil
68+
}
69+
70+
results, err := o.installTools(currentTools, toInstallOrUpdate)
71+
if err != nil {
72+
return err
73+
}
74+
75+
newToolsInfo := cloneToolsMap(currentTools)
76+
for _, app := range toInstallOrUpdate {
77+
newToolsInfo[app] = results[app].version
78+
}
79+
80+
return o.writeToolsInfo(newToolsInfo)
81+
}
82+
2583
func InstallCommand() *cobra.Command {
2684
o := &installOptions{}
2785
cmd := &cobra.Command{
@@ -44,7 +102,7 @@ func InstallCommand() *cobra.Command {
44102

45103
func (o *installOptions) install() error {
46104
// load tools from config
47-
currentTools, err := (&listOptions{}).getCurrentToolsMap()
105+
currentTools, err := (&listOptions{root: o.root}).getCurrentToolsMap()
48106
if err != nil {
49107
return ee.Wrap(err, "cannot load current tools")
50108
}
@@ -59,15 +117,55 @@ func (o *installOptions) install() error {
59117
}
60118

61119
// load installed tools
62-
installedTools, err := (&listOptions{}).getInstalledTools()
120+
installedTools, err := (&listOptions{root: o.root}).getInstalledTools()
63121
if err != nil {
64122
return ee.Wrap(err, "cannot load installed tools")
65123
}
66124

67125
// diff
126+
var toRemove []string
127+
for app := range installedTools {
128+
if _, ok := currentTools[app]; !ok {
129+
toRemove = append(toRemove, app)
130+
}
131+
}
132+
133+
// install needed (toInstall + toUpdate)
134+
toInstallOrUpdate := getToolsToInstallOrUpdate(currentTools, installedTools)
135+
results, err := o.installTools(currentTools, toInstallOrUpdate)
136+
if err != nil {
137+
return err
138+
}
139+
140+
// remove unneeded (toRemove)
141+
for _, app := range toRemove {
142+
pp.BluePrintln(">>> Remove", app)
143+
144+
// remove from .bin
145+
_ = os.Remove(filepath.Join(o.root, ".kitty", ".bin", app))
146+
}
147+
148+
// generate new tools info
149+
newToolsInfo := make(map[string]string, len(currentTools))
150+
for app, version := range currentTools {
151+
newToolsInfo[app] = version
152+
}
153+
for _, app := range toInstallOrUpdate {
154+
newToolsInfo[app] = results[app].version
155+
}
156+
157+
// save tools info
158+
err = o.writeToolsInfo(newToolsInfo)
159+
if err != nil {
160+
return ee.Wrap(err, "cannot save tools info")
161+
}
162+
163+
return nil
164+
}
165+
166+
func getToolsToInstallOrUpdate(currentTools map[string]string, installedTools map[string]string) []string {
68167
var toInstall []string
69168
var toUpdate []string
70-
var toRemove []string
71169

72170
for app, version := range currentTools {
73171
if version == "-" { // ignore
@@ -82,66 +180,44 @@ func (o *installOptions) install() error {
82180
toInstall = append(toInstall, app)
83181
}
84182
}
85-
for app := range installedTools {
86-
if _, ok := currentTools[app]; !ok {
87-
toRemove = append(toRemove, app)
88-
}
89-
}
90183

91-
// install needed (toInstall + toUpdate)
92184
toInstallOrUpdate := mr.Flats(toInstall, toUpdate)
93185
sort.Strings(toInstallOrUpdate)
186+
return toInstallOrUpdate
187+
}
188+
189+
func (o *installOptions) installTools(currentTools map[string]string, toInstallOrUpdate []string) (map[string]*installResult, error) {
94190
var installFailedApps []string
95191
results := make(map[string]*installResult, len(toInstallOrUpdate))
96192
for _, app := range toInstallOrUpdate {
97193
version := currentTools[app]
98194

99-
pp.BluePrintln(">>> Install", app)
195+
if !o.quiet {
196+
pp.BluePrintln(">>> Install", app)
197+
}
100198

101-
o := &installOneOptions{app: app, version: version}
199+
one := &installOneOptions{root: o.root, quiet: o.quiet, app: app, version: version}
102200

103-
result, err := o.install()
201+
result, err := one.install()
104202
if err != nil {
105-
pp.RedPrintln("ERROR:", err.Error())
203+
if !o.quiet {
204+
pp.RedPrintln("ERROR:", err.Error())
205+
}
106206
installFailedApps = append(installFailedApps, app)
107207
continue
108208
}
109209
results[app] = result
110210
}
111211

112-
// remove unneeded (toRemove)
113-
for _, app := range toRemove {
114-
pp.BluePrintln(">>> Remove", app)
115-
116-
// remove from .bin
117-
_ = os.Remove(filepath.Join(".kitty", ".bin", app))
118-
}
119-
120-
// report error
121212
if len(installFailedApps) > 0 {
122-
return ee.Errorf("failed to install %s", strings.Join(installFailedApps, ", "))
213+
return nil, ee.Errorf("failed to install %s", strings.Join(installFailedApps, ", "))
123214
}
124215

125-
// generate new tools info
126-
newToolsInfo := make(map[string]string, len(currentTools))
127-
for app, version := range currentTools {
128-
newToolsInfo[app] = version
129-
}
130-
for _, app := range toInstallOrUpdate {
131-
newToolsInfo[app] = results[app].version
132-
}
133-
134-
// save tools info
135-
err = o.writeToolsInfo(newToolsInfo)
136-
if err != nil {
137-
return ee.Wrap(err, "cannot save tools info")
138-
}
139-
140-
return nil
216+
return results, nil
141217
}
142218

143219
func (o *installOptions) writeToolsInfo(tools map[string]string) error {
144-
return config.PatchKittyConfig("", func(c map[string]gson.JSON) (save bool, err error) {
220+
return config.PatchKittyConfig(o.root, func(c map[string]gson.JSON) (save bool, err error) {
145221
// if c["tools"] not exist and tools is empty, do nothing
146222
if _, toolsKeyExist := c["tools"]; !toolsKeyExist && len(tools) == 0 {
147223
return false, nil
@@ -153,6 +229,8 @@ func (o *installOptions) writeToolsInfo(tools map[string]string) error {
153229
}
154230

155231
type installOneOptions struct {
232+
root string
233+
quiet bool
156234
app string
157235
version string
158236
}
@@ -168,6 +246,18 @@ func (o *installOneOptions) install() (*installResult, error) {
168246
if o.version == "" {
169247
o.version = "latest"
170248
}
249+
if o.root != "" {
250+
previousWd, err := os.Getwd()
251+
if err != nil {
252+
return nil, ee.Wrap(err, "cannot get working directory")
253+
}
254+
if err := os.Chdir(o.root); err != nil {
255+
return nil, ee.Wrapf(err, "cannot change working directory to %s", o.root)
256+
}
257+
defer func() {
258+
_ = os.Chdir(previousWd)
259+
}()
260+
}
171261

172262
app, version, err := extregistry.GetAppVersion(o.app, o.version)
173263
if err != nil {
@@ -180,23 +270,23 @@ func (o *installOneOptions) install() (*installResult, error) {
180270

181271
// download to .bin/[system-key]/[name]@[version]
182272
rel := filepath.Join("."+string(osKey), app.Name+"@"+version.Version)
183-
dst := filepath.Join(".kitty", ".bin", rel)
273+
dst := filepath.Join(o.root, ".kitty", ".bin", rel)
184274

185275
// TODO 安装到中央工具仓库(而不是 .bin 下)
186276
if version.InstallOptions != nil { // download for version
187-
err = version.InstallTo(dst)
277+
err = version.InstallToWithProgress(dst, !o.quiet)
188278
if err != nil {
189279
return nil, ee.Wrapf(err, "cannot download %s@%s", app.Name, version.Version)
190280
}
191281
} else { // download for unknown version
192-
err := version.InstallUnknownVersionTo(dst)
282+
err := version.InstallUnknownVersionToWithProgress(dst, !o.quiet)
193283
if err != nil {
194284
return nil, ee.Wrapf(err, "cannot download %s@%s", app.Name, o.version)
195285
}
196286
}
197287

198288
// Create soft link .bin/[name] -> .bin/[system-key]/[name]@[version]
199-
err = symlink(rel, filepath.Join(".kitty", ".bin", app.Name))
289+
err = symlink(rel, filepath.Join(o.root, ".kitty", ".bin", app.Name))
200290
if err != nil {
201291
return nil, err
202292
}

0 commit comments

Comments
 (0)