Skip to content

Commit 4337c65

Browse files
committed
refactor: update dependencies
1 parent ea70fee commit 4337c65

File tree

4 files changed

+194
-32
lines changed

4 files changed

+194
-32
lines changed

cmd/create.go

Lines changed: 4 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ func (c CreateCmd) Create(ctx context.Context, ci CreateInput) error {
3737
return fmt.Errorf("failed to create directory: %w", err)
3838
}
3939

40-
pterm.Println(fmt.Sprintf("\nCreating a new %s %s\n", ci.Language, ci.Template))
40+
pterm.Sprintf("\nCreating a new %s %s\n", ci.Language, ci.Template)
4141

4242
spinner, _ := pterm.DefaultSpinner.Start("Copying template files...")
4343

@@ -46,33 +46,12 @@ func (c CreateCmd) Create(ctx context.Context, ci CreateInput) error {
4646
return fmt.Errorf("failed to copy template files: %w", err)
4747
}
4848

49-
ok, _ := create.InstallDependencies(appPath, ci.Language)
50-
if !ok {
51-
pterm.Warning.Println("Failed to install dependencies. Please install them manually:")
52-
switch ci.Language {
53-
case create.LanguageTypeScript:
54-
pterm.Println(fmt.Sprintf(" cd %s", ci.Name))
55-
pterm.Println(" npm install")
56-
case create.LanguagePython:
57-
pterm.Println(fmt.Sprintf(" cd %s", ci.Name))
58-
pterm.Println(" uv venv && source .venv/bin/activate && uv sync")
59-
}
60-
pterm.Println()
61-
} else {
62-
spinner.Success(fmt.Sprintf("✔ %s environment set up successfully", ci.Language))
49+
nextSteps, err := create.InstallDependencies(ci.Name, appPath, ci.Language)
50+
if err != nil {
51+
return fmt.Errorf("failed to install dependencies: %w", err)
6352
}
64-
6553
pterm.Success.Println("🎉 Kernel app created successfully!")
6654
pterm.Println()
67-
68-
nextSteps := fmt.Sprintf(`Next steps:
69-
brew install onkernel/tap/kernel
70-
cd %s
71-
kernel login # or: export KERNEL_API_KEY=<YOUR_API_KEY>
72-
kernel deploy index.ts
73-
kernel invoke ts-basic get-page-title --payload '{"url": "https://www.google.com"}'
74-
`, ci.Name)
75-
7655
pterm.FgYellow.Println(nextSteps)
7756

7857
return nil

cmd/create_test.go

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,15 @@
11
package cmd
22

33
import (
4+
"bytes"
45
"context"
6+
"io"
57
"os"
68
"path/filepath"
79
"testing"
810

911
"github.com/onkernel/cli/pkg/create"
12+
"github.com/pterm/pterm"
1013
"github.com/stretchr/testify/assert"
1114
"github.com/stretchr/testify/require"
1215
)
@@ -234,6 +237,14 @@ func TestCreateCommand_DependencyInstallationFails(t *testing.T) {
234237
os.Chdir(orgDir)
235238
})
236239

240+
var outputBuf bytes.Buffer
241+
multiWriter := io.MultiWriter(&outputBuf, os.Stdout)
242+
pterm.SetDefaultOutput(multiWriter)
243+
244+
t.Cleanup(func() {
245+
pterm.SetDefaultOutput(os.Stdout)
246+
})
247+
237248
// Override the install command to use a command that will fail
238249
originalInstallCommands := create.InstallCommands
239250
create.InstallCommands = map[string]string{
@@ -252,6 +263,93 @@ func TestCreateCommand_DependencyInstallationFails(t *testing.T) {
252263
Language: create.LanguageTypeScript,
253264
Template: "sample-app",
254265
})
266+
267+
output := outputBuf.String()
268+
269+
assert.Contains(t, output, "cd test-app", "should print cd command")
270+
assert.Contains(t, output, "pnpm install", "should print pnpm install command")
271+
}
272+
273+
// TestCreateCommand_RequiredToolMissing tests that the app is created
274+
func TestCreateCommand_RequiredToolMissing(t *testing.T) {
275+
tests := []struct {
276+
name string
277+
language string
278+
template string
279+
}{
280+
{
281+
name: "typescript with missing pnpm",
282+
language: create.LanguageTypeScript,
283+
template: "sample-app",
284+
},
285+
{
286+
name: "python with missing uv",
287+
language: create.LanguagePython,
288+
template: "sample-app",
289+
},
290+
}
291+
292+
for _, tt := range tests {
293+
t.Run(tt.name, func(t *testing.T) {
294+
tmpDir := t.TempDir()
295+
appName := "test-app"
296+
297+
orgDir, err := os.Getwd()
298+
require.NoError(t, err)
299+
300+
err = os.Chdir(tmpDir)
301+
require.NoError(t, err)
302+
303+
t.Cleanup(func() {
304+
os.Chdir(orgDir)
305+
})
306+
307+
// Override the required tool to point to a non-existent command
308+
originalRequiredTools := create.RequiredTools
309+
create.RequiredTools = map[string]string{
310+
create.LanguageTypeScript: "nonexistent-pnpm-tool",
311+
create.LanguagePython: "nonexistent-uv-tool",
312+
}
313+
314+
// Restore original required tools after test
315+
t.Cleanup(func() {
316+
create.RequiredTools = originalRequiredTools
317+
})
318+
319+
// Create the app - should succeed even though required tool is missing
320+
c := CreateCmd{}
321+
err = c.Create(context.Background(), CreateInput{
322+
Name: appName,
323+
Language: tt.language,
324+
Template: tt.template,
325+
})
326+
327+
// Should not return an error - the command should complete successfully
328+
// but skip dependency installation
329+
require.NoError(t, err, "app creation should succeed even when required tool is missing")
330+
331+
// Verify the app directory and files were created
332+
appPath := filepath.Join(tmpDir, appName)
333+
assert.DirExists(t, appPath, "app directory should exist")
334+
335+
// Language-specific file checks
336+
switch tt.language {
337+
case create.LanguageTypeScript:
338+
assert.FileExists(t, filepath.Join(appPath, "package.json"), "package.json should exist")
339+
assert.FileExists(t, filepath.Join(appPath, "index.ts"), "index.ts should exist")
340+
assert.FileExists(t, filepath.Join(appPath, "tsconfig.json"), "tsconfig.json should exist")
341+
342+
// node_modules should NOT exist since pnpm was not available
343+
assert.NoDirExists(t, filepath.Join(appPath, "node_modules"), "node_modules should not exist when pnpm is missing")
344+
case create.LanguagePython:
345+
assert.FileExists(t, filepath.Join(appPath, "pyproject.toml"), "pyproject.toml should exist")
346+
assert.FileExists(t, filepath.Join(appPath, "main.py"), "main.py should exist")
347+
348+
// .venv should NOT exist since uv was not available
349+
assert.NoDirExists(t, filepath.Join(appPath, ".venv"), ".venv should not exist when uv is missing")
350+
}
351+
})
352+
}
255353
}
256354

257355
func getTemplateInfo() []struct {

pkg/create/dependencies.go

Lines changed: 77 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,22 +8,93 @@ import (
88
)
99

1010
// InstallDependencies sets up project dependencies based on language
11-
func InstallDependencies(appPath string, language string) (bool, error) {
11+
func InstallDependencies(appName string, appPath string, language string) (string, error) {
1212
installCommand, ok := InstallCommands[language]
1313
if !ok {
14-
return false, fmt.Errorf("unsupported language: %s", language)
14+
return "", fmt.Errorf("unsupported language: %s", language)
1515
}
1616

17-
spinner, _ := pterm.DefaultSpinner.Start(fmt.Sprintf("Setting up %s environment...", language))
17+
requiredTool := RequiredTools[language]
18+
if requiredTool != "" && !RequiredTools.CheckToolAvailable(language) {
19+
return getNextStepsWithToolInstall(appName, language, requiredTool), nil
20+
}
21+
22+
spinner, _ := pterm.DefaultSpinner.Start(pterm.Sprintf("Setting up %s environment...", language))
1823

1924
cmd := exec.Command("sh", "-c", installCommand)
2025
cmd.Dir = appPath
2126

2227
if err := cmd.Run(); err != nil {
2328
spinner.Stop()
24-
return false, nil
29+
pterm.Warning.Println("Failed to install dependencies. Please install them manually:")
30+
switch language {
31+
case LanguageTypeScript:
32+
pterm.Printfln(" cd %s", appName)
33+
pterm.Printfln(" pnpm install")
34+
case LanguagePython:
35+
pterm.Printfln(" cd %s", appName)
36+
pterm.Println(" uv venv && source .venv/bin/activate && uv sync")
37+
}
38+
pterm.Println()
39+
return getNextStepsStandard(appName), nil
2540
}
2641

27-
spinner.Success(fmt.Sprintf("%s environment set up successfully", language))
28-
return true, nil
42+
spinner.Success(pterm.Sprintf("✔ %s environment set up successfully", language))
43+
44+
return getNextStepsStandard(appName), nil
45+
}
46+
47+
// getNextStepsWithToolInstall returns next steps message including tool installation
48+
func getNextStepsWithToolInstall(appName string, language string, requiredTool string) string {
49+
pterm.Warning.Printfln(" %s is not installed or not in PATH", requiredTool)
50+
51+
switch language {
52+
case LanguageTypeScript:
53+
return pterm.FgYellow.Sprintf(`Next steps:
54+
# Install pnpm (choose one):
55+
npm install -g pnpm
56+
# or: brew install pnpm
57+
# or: curl -fsSL https://get.pnpm.io/install.sh | sh -
58+
59+
# Then install dependencies:
60+
cd %s
61+
pnpm install
62+
63+
# Deploy your app:
64+
brew install onkernel/tap/kernel
65+
kernel login # or: export KERNEL_API_KEY=<YOUR_API_KEY>
66+
kernel deploy index.ts
67+
kernel invoke ts-basic get-page-title --payload '{"url": "https://www.google.com"}'
68+
`, appName)
69+
case LanguagePython:
70+
return pterm.FgYellow.Sprintf(`Next steps:
71+
# Install uv (choose one):
72+
curl -LsSf https://astral.sh/uv/install.sh | sh
73+
# or: brew install uv
74+
# or: pipx install uv
75+
76+
# Then set up your environment:
77+
cd %s
78+
uv venv && source .venv/bin/activate && uv sync
79+
80+
# Deploy your app:
81+
brew install onkernel/tap/kernel
82+
kernel login # or: export KERNEL_API_KEY=<YOUR_API_KEY>
83+
kernel deploy index.py
84+
kernel invoke py-basic get-page-title --payload '{"url": "https://www.google.com"}'
85+
`, appName)
86+
default:
87+
return ""
88+
}
89+
}
90+
91+
// getNextStepsStandard returns standard next steps message
92+
func getNextStepsStandard(appName string) string {
93+
return pterm.FgYellow.Sprintf(`Next steps:
94+
brew install onkernel/tap/kernel
95+
cd %s
96+
kernel login # or: export KERNEL_API_KEY=<YOUR_API_KEY>
97+
kernel deploy index.ts
98+
kernel invoke ts-basic get-page-title --payload '{"url": "https://www.google.com"}'
99+
`, appName)
29100
}

pkg/create/types.go

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
package create
22

3+
import "os/exec"
4+
35
const (
46
DefaultAppName = "my-kernel-app"
57
AppNamePrompt = "What is the name of your project?"
@@ -14,8 +16,20 @@ const (
1416
LanguageShorthandPython = "py"
1517
)
1618

19+
type Tools map[string]string
20+
21+
var RequiredTools = Tools{
22+
LanguageTypeScript: "pnpm",
23+
LanguagePython: "uv",
24+
}
25+
26+
func (t Tools) CheckToolAvailable(tool string) bool {
27+
_, err := exec.LookPath(t[tool])
28+
return err == nil
29+
}
30+
1731
var InstallCommands = map[string]string{
18-
LanguageTypeScript: "npm install",
32+
LanguageTypeScript: "pnpm install",
1933
LanguagePython: "uv venv",
2034
}
2135

0 commit comments

Comments
 (0)