From 20a1b8d01dd4f5e210835fb086d356548daeff72 Mon Sep 17 00:00:00 2001 From: Tobias Fried Date: Mon, 9 Dec 2024 18:08:32 -0700 Subject: [PATCH] feat(docs+cmd): document bootstrap feature, add template list (#456) * chore(lk): show template tags in list * chore(docs): add instructions for bootstrapping an app from template * feat(cmd): add `lk app list-templates` and update README * chore(cmd): show default project with '*' * chore(cmd): bump package version * chore(cmd): use vars and add util tests * chore(cmd): prettify `lk app list-templates` output --- README.md | 25 ++++++++++++++++++++++--- cmd/lk/app.go | 36 +++++++++++++++++++++++++++++++++++- cmd/lk/project.go | 10 ++++++++-- cmd/lk/utils.go | 32 ++++++++++++++++++++++++++++++++ cmd/lk/utils_test.go | 31 +++++++++++++++++++++++++++++++ pkg/bootstrap/bootstrap.go | 17 +++++++++-------- version.go | 2 +- 7 files changed, 138 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index d74151e1..fcd70e4b 100644 --- a/README.md +++ b/README.md @@ -14,10 +14,11 @@ This package includes command line utilities that interacts with LiveKit. It allows you to: +- Bootstrap new applications from templates - Create access tokens -- Access LiveKit APIs, create, delete rooms, etc. +- Access LiveKit APIs, create and delete rooms, etc. - Join a room as a participant, inspecting in-room events -- Start and manage Egress +- Start and manage Egresses - Perform load testing, efficiently simulating real-world load # Installation @@ -55,7 +56,7 @@ make install # Usage -See `lk --help` for a complete list of subcommands. +See `lk --help` for a complete list of subcommands. The `--help` flag can also be used on any subcommand for more information. ## Set up your project (new) @@ -86,6 +87,24 @@ lk project list lk project set-default ``` +## Bootstrapping an application + +The LiveKit CLI can help you bootstrap applications from a number of convenient template repositories, using your project credentials to set up required environment variables and other configuration automatically. To create an application from a template, run the following: + +```shell +lk app create --template my-app +``` + +Then follow the CLI prompts to finish your setup. + +For a list of all available templates, run: + +```shell +lk app list-templates +``` + +See the [LiveKit Templates Index](https://github.com/livekit-examples/index?tab=readme-ov-file) for details about templates, and for instructions on how to contribute your own. + ## Publishing to a room ### Publish demo video track diff --git a/cmd/lk/app.go b/cmd/lk/app.go index 39b817f2..9a30240a 100644 --- a/cmd/lk/app.go +++ b/cmd/lk/app.go @@ -20,6 +20,7 @@ import ( "fmt" "os" "regexp" + "strings" "github.com/charmbracelet/huh" "github.com/charmbracelet/huh/spinner" @@ -78,6 +79,12 @@ var ( }, }, }, + { + Name: "list-templates", + Usage: "List available templates to bootstrap a new application", + Flags: []cli.Flag{jsonFlag}, + Action: listTemplates, + }, { Hidden: true, Name: "install", @@ -175,6 +182,31 @@ func requireProject(ctx context.Context, cmd *cli.Command) error { return err } +func listTemplates(ctx context.Context, cmd *cli.Command) error { + templates, err := bootstrap.FetchTemplates(ctx) + if err != nil { + return err + } + + if cmd.Bool("json") { + PrintJSON(templates) + } else { + const maxDescLength = 64 + table := CreateTable().Headers("Template", "Description").BorderRow(true) + for _, t := range templates { + desc := strings.Join(wrapToLines(t.Desc, maxDescLength), "\n") + url := theme.Focused.Title.Render(t.URL) + tags := theme.Help.ShortDesc.Render("#" + strings.Join(t.Tags, " #")) + table.Row( + t.Name, + desc+"\n\n"+url+"\n"+tags, + ) + } + fmt.Println(table) + } + return nil +} + func setupTemplate(ctx context.Context, cmd *cli.Command) error { verbose := cmd.Bool("verbose") install := cmd.Bool("install") @@ -218,7 +250,9 @@ func setupTemplate(ctx context.Context, cmd *cli.Command) error { WithTheme(theme) var options []huh.Option[string] for _, t := range templateOptions { - options = append(options, huh.NewOption(t.Name, t.URL)) + descStyle := theme.Help.ShortDesc + optionText := t.Name + " " + descStyle.Render("#"+strings.Join(t.Tags, " #")) + options = append(options, huh.NewOption(optionText, t.URL)) } templateSelect.(*huh.Select[string]).Options(options...) preinstallPrompts = append(preinstallPrompts, templateSelect) diff --git a/cmd/lk/project.go b/cmd/lk/project.go index 16c27d3e..fd29123b 100644 --- a/cmd/lk/project.go +++ b/cmd/lk/project.go @@ -262,9 +262,15 @@ func listProjects(ctx context.Context, cmd *cli.Command) error { return baseStyle } }). - Headers("Name", "URL", "API Key", "Default") + Headers("Name", "URL", "API Key") for _, p := range cliConfig.Projects { - table.Row(p.Name, p.URL, p.APIKey, fmt.Sprint(p.Name == cliConfig.DefaultProject)) + var pName string + if p.Name == cliConfig.DefaultProject { + pName = "* " + p.Name + } else { + pName = " " + p.Name + } + table.Row(pName, p.URL, p.APIKey) } fmt.Println(table) } diff --git a/cmd/lk/utils.go b/cmd/lk/utils.go index 8127abf5..19868899 100644 --- a/cmd/lk/utils.go +++ b/cmd/lk/utils.go @@ -160,6 +160,38 @@ func wrapWith(wrap string) func(string) string { } } +func ellipsizeTo(str string, maxLength int) string { + if len(str) <= maxLength { + return str + } + ellipsis := "..." + contentLen := max(0, min(len(str), maxLength-len(ellipsis))) + return str[:contentLen] + ellipsis +} + +func wrapToLines(input string, maxLineLength int) []string { + words := strings.Fields(input) + var lines []string + var currentLine strings.Builder + + for _, word := range words { + if currentLine.Len()+len(word)+1 > maxLineLength { + lines = append(lines, currentLine.String()) + currentLine.Reset() + } + if currentLine.Len() > 0 { + currentLine.WriteString(" ") + } + currentLine.WriteString(word) + } + + if currentLine.Len() > 0 { + lines = append(lines, currentLine.String()) + } + + return lines +} + // Provides a temporary path, a function to relocate it to a permanent path, // and a function to clean up the temporary path that should always be deferred // in the case of a failure to relocate. diff --git a/cmd/lk/utils_test.go b/cmd/lk/utils_test.go index 6ded6360..ff0e4e7f 100644 --- a/cmd/lk/utils_test.go +++ b/cmd/lk/utils_test.go @@ -56,3 +56,34 @@ func TestMapStrings(t *testing.T) { t.Error("mapStrings should apply the function to all elements") } } + +func TestEllipziseTo(t *testing.T) { + str := "This is some long string that should be ellipsized" + ellipsized := ellipsizeTo(str, 12) + if len(ellipsized) != 12 { + t.Error("ellipsizeTo should return a string of the specified length") + } + if ellipsized != "This is s..." { + t.Error("ellipsizeTo should ellipsize the string") + } +} + +func TestWrapToLines(t *testing.T) { + str := "This is a long string that should be wrapped to multiple lines" + wrapped := wrapToLines(str, 10) + if len(wrapped) != 8 { + t.Error("wrapToLines should return a slice of lines") + } + if !slices.Equal([]string{ + "This is a", + "long", + "string", + "that", + "should be", + "wrapped to", + "multiple", + "lines", + }, wrapped) { + t.Error("wrapToLines should wrap the string to the specified width") + } +} diff --git a/pkg/bootstrap/bootstrap.go b/pkg/bootstrap/bootstrap.go index a4421607..8bcf41c8 100644 --- a/pkg/bootstrap/bootstrap.go +++ b/pkg/bootstrap/bootstrap.go @@ -67,14 +67,15 @@ var templateIgnoreFiles = []string{ } type Template struct { - Name string `yaml:"name" json:"name"` - Desc string `yaml:"desc" json:"description,omitempty"` - URL string `yaml:"url" json:"url,omitempty"` - Docs string `yaml:"docs" json:"docs_url,omitempty"` - Image string `yaml:"image" json:"image_ref,omitempty"` - Tags []string `yaml:"tags" json:"tags,omitempty"` - Requires []string `yaml:"requires" json:"requires,omitempty"` - IsSandbox bool `yaml:"is_sandbox" json:"is_sandbox,omitempty"` + Name string `yaml:"name" json:"name"` + Desc string `yaml:"desc" json:"description,omitempty"` + URL string `yaml:"url" json:"url,omitempty"` + Docs string `yaml:"docs" json:"docs_url,omitempty"` + Image string `yaml:"image" json:"image_ref,omitempty"` + Tags []string `yaml:"tags" json:"tags,omitempty"` + Attrs map[string]string `yaml:"attrs" json:"attrs,omitempty"` + Requires []string `yaml:"requires" json:"requires,omitempty"` + IsSandbox bool `yaml:"is_sandbox" json:"is_sandbox,omitempty"` } type SandboxDetails struct { diff --git a/version.go b/version.go index a9015797..f37aabc0 100644 --- a/version.go +++ b/version.go @@ -15,5 +15,5 @@ package livekitcli const ( - Version = "2.2.1" + Version = "2.3.0" )