Skip to content

Commit f93d77c

Browse files
committed
feat: add file copying function into new directory
1 parent 966ea89 commit f93d77c

File tree

2 files changed

+122
-17
lines changed

2 files changed

+122
-17
lines changed

cmd/create.go

Lines changed: 38 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,11 @@ package cmd
22

33
import (
44
"fmt"
5+
"os"
6+
"path/filepath"
57

68
"github.com/onkernel/cli/pkg/create"
9+
"github.com/pterm/pterm"
710
"github.com/spf13/cobra"
811
)
912

@@ -40,28 +43,46 @@ func runCreateApp(cmd *cobra.Command, args []string) error {
4043
return fmt.Errorf("failed to get template: %w", err)
4144
}
4245

43-
fmt.Printf("Creating application '%s' with language '%s' and template '%s'...\n", appName, language, template)
46+
// Get absolute path for the app directory
47+
appPath, err := filepath.Abs(appName)
48+
if err != nil {
49+
return fmt.Errorf("failed to resolve app path: %w", err)
50+
}
4451

45-
// TODO: create the project structure
52+
// Check if directory already exists
53+
if _, err := os.Stat(appPath); err == nil {
54+
return fmt.Errorf("directory %s already exists", appName)
55+
}
4656

47-
// print "Creating a new TypeScript Sample App" or similar. Essentially the language and template name combined.
57+
// Create the app directory
58+
if err := os.MkdirAll(appPath, 0755); err != nil {
59+
return fmt.Errorf("failed to create directory: %w", err)
60+
}
4861

49-
/*
50-
Print the following:
51-
✔ TypeScript environment set up successfully
62+
fmt.Printf("\nCreating a new %s %s\n\n", language, template)
63+
64+
spinner, _ := pterm.DefaultSpinner.Start("Copying template files...")
65+
66+
if err := create.CopyTemplateFiles(appPath, language, template); err != nil {
67+
spinner.Fail("Failed to copy template files")
68+
return fmt.Errorf("failed to copy template files: %w", err)
69+
}
70+
spinner.Success("✔ TypeScript environment set up successfully")
5271

53-
🎉 Kernel app created successfully!
72+
nextSteps := fmt.Sprintf(`Next steps:
73+
brew install onkernel/tap/kernel
74+
cd %s
75+
kernel login # or: export KERNEL_API_KEY=<YOUR_API_KEY>
76+
kernel deploy index.ts
77+
kernel invoke ts-basic get-page-title --payload '{"url": "https://www.google.com"}'
78+
# Do this in a separate tab
79+
kernel login # or: export KERNEL_API_KEY=<YOUR_API_KEY>
80+
kernel logs ts-basic --follow
81+
`, appName)
5482

55-
Next steps:
56-
brew install onkernel/tap/kernel
57-
cd my-kernel-app
58-
kernel login # or: export KERNEL_API_KEY=<YOUR_API_KEY>
59-
kernel deploy index.ts
60-
kernel invoke ts-basic get-page-title --payload '{"url": "https://www.google.com"}'
61-
# Do this in a separate tab
62-
kernel login # or: export KERNEL_API_KEY=<YOUR_API_KEY>
63-
kernel logs ts-basic --follow
64-
*/
83+
pterm.Success.Println("🎉 Kernel app created successfully!")
84+
pterm.Println()
85+
pterm.FgYellow.Println(nextSteps)
6586

6687
return nil
6788
}

pkg/create/copy.go

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
package create
2+
3+
import (
4+
"fmt"
5+
"io/fs"
6+
"os"
7+
"path/filepath"
8+
9+
"github.com/onkernel/cli/pkg/templates"
10+
)
11+
12+
const (
13+
DIR_PERM = 0755 // rwxr-xr-x
14+
FILE_PERM = 0644 // rw-r--r--
15+
)
16+
17+
// CopyTemplateFiles copies all files and directories from the specified embedded template
18+
// into the target application path. It uses the given language and template names
19+
// to locate the template inside the embedded filesystem.
20+
//
21+
// - appPath: filesystem path where the files should be written (the project directory)
22+
// - language: language subdirectory (e.g., "typescript")
23+
// - template: template subdirectory (e.g., "sample-app")
24+
//
25+
// The function will recursively walk through the embedded template directory and
26+
// replicate all files and folders in appPath. If a file named "_gitignore" is encountered,
27+
// it is renamed to ".gitignore" in the output, to work around file embedding limitations.
28+
//
29+
// Returns an error if the template path is invalid, empty, or if any file operations fail.
30+
func CopyTemplateFiles(appPath, language, template string) error {
31+
// Build the template path within the embedded FS (e.g., "typescript/sample-app")
32+
templatePath := filepath.Join(language, template)
33+
34+
// Check if the template exists and is non-empty
35+
entries, err := fs.ReadDir(templates.FS, templatePath)
36+
if err != nil {
37+
return fmt.Errorf("template not found: %s/%s", language, template)
38+
}
39+
if len(entries) == 0 {
40+
return fmt.Errorf("template directory is empty: %s/%s", language, template)
41+
}
42+
43+
// Walk through the embedded template directory and copy contents
44+
return fs.WalkDir(templates.FS, templatePath, func(path string, d fs.DirEntry, err error) error {
45+
if err != nil {
46+
return err
47+
}
48+
49+
// Determine the path relative to the root of the template
50+
relPath, err := filepath.Rel(templatePath, path)
51+
if err != nil {
52+
return err
53+
}
54+
55+
// Skip the template root directory itself
56+
if relPath == "." {
57+
return nil
58+
}
59+
60+
destPath := filepath.Join(appPath, relPath)
61+
62+
if d.IsDir() {
63+
return os.MkdirAll(destPath, DIR_PERM)
64+
}
65+
66+
// Read the file content from the embedded filesystem
67+
content, err := fs.ReadFile(templates.FS, path)
68+
if err != nil {
69+
return fmt.Errorf("failed to read template file %s: %w", path, err)
70+
}
71+
72+
// Rename _gitignore to .gitignore in the destination
73+
if filepath.Base(destPath) == "_gitignore" {
74+
destPath = filepath.Join(filepath.Dir(destPath), ".gitignore")
75+
}
76+
77+
// Write the file to disk in the target project directory
78+
if err := os.WriteFile(destPath, content, FILE_PERM); err != nil {
79+
return fmt.Errorf("failed to write file %s: %w", destPath, err)
80+
}
81+
82+
return nil
83+
})
84+
}

0 commit comments

Comments
 (0)