Skip to content

Commit 20a1b8d

Browse files
authored
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
1 parent 77f1841 commit 20a1b8d

File tree

7 files changed

+138
-15
lines changed

7 files changed

+138
-15
lines changed

README.md

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,11 @@
1414

1515
This package includes command line utilities that interacts with LiveKit. It allows you to:
1616

17+
- Bootstrap new applications from templates
1718
- Create access tokens
18-
- Access LiveKit APIs, create, delete rooms, etc.
19+
- Access LiveKit APIs, create and delete rooms, etc.
1920
- Join a room as a participant, inspecting in-room events
20-
- Start and manage Egress
21+
- Start and manage Egresses
2122
- Perform load testing, efficiently simulating real-world load
2223

2324
# Installation
@@ -55,7 +56,7 @@ make install
5556

5657
# Usage
5758

58-
See `lk --help` for a complete list of subcommands.
59+
See `lk --help` for a complete list of subcommands. The `--help` flag can also be used on any subcommand for more information.
5960

6061
## Set up your project (new)
6162

@@ -86,6 +87,24 @@ lk project list
8687
lk project set-default <project_name>
8788
```
8889

90+
## Bootstrapping an application
91+
92+
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:
93+
94+
```shell
95+
lk app create --template <template_name> my-app
96+
```
97+
98+
Then follow the CLI prompts to finish your setup.
99+
100+
For a list of all available templates, run:
101+
102+
```shell
103+
lk app list-templates
104+
```
105+
106+
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.
107+
89108
## Publishing to a room
90109

91110
### Publish demo video track

cmd/lk/app.go

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import (
2020
"fmt"
2121
"os"
2222
"regexp"
23+
"strings"
2324

2425
"github.com/charmbracelet/huh"
2526
"github.com/charmbracelet/huh/spinner"
@@ -78,6 +79,12 @@ var (
7879
},
7980
},
8081
},
82+
{
83+
Name: "list-templates",
84+
Usage: "List available templates to bootstrap a new application",
85+
Flags: []cli.Flag{jsonFlag},
86+
Action: listTemplates,
87+
},
8188
{
8289
Hidden: true,
8390
Name: "install",
@@ -175,6 +182,31 @@ func requireProject(ctx context.Context, cmd *cli.Command) error {
175182
return err
176183
}
177184

185+
func listTemplates(ctx context.Context, cmd *cli.Command) error {
186+
templates, err := bootstrap.FetchTemplates(ctx)
187+
if err != nil {
188+
return err
189+
}
190+
191+
if cmd.Bool("json") {
192+
PrintJSON(templates)
193+
} else {
194+
const maxDescLength = 64
195+
table := CreateTable().Headers("Template", "Description").BorderRow(true)
196+
for _, t := range templates {
197+
desc := strings.Join(wrapToLines(t.Desc, maxDescLength), "\n")
198+
url := theme.Focused.Title.Render(t.URL)
199+
tags := theme.Help.ShortDesc.Render("#" + strings.Join(t.Tags, " #"))
200+
table.Row(
201+
t.Name,
202+
desc+"\n\n"+url+"\n"+tags,
203+
)
204+
}
205+
fmt.Println(table)
206+
}
207+
return nil
208+
}
209+
178210
func setupTemplate(ctx context.Context, cmd *cli.Command) error {
179211
verbose := cmd.Bool("verbose")
180212
install := cmd.Bool("install")
@@ -218,7 +250,9 @@ func setupTemplate(ctx context.Context, cmd *cli.Command) error {
218250
WithTheme(theme)
219251
var options []huh.Option[string]
220252
for _, t := range templateOptions {
221-
options = append(options, huh.NewOption(t.Name, t.URL))
253+
descStyle := theme.Help.ShortDesc
254+
optionText := t.Name + " " + descStyle.Render("#"+strings.Join(t.Tags, " #"))
255+
options = append(options, huh.NewOption(optionText, t.URL))
222256
}
223257
templateSelect.(*huh.Select[string]).Options(options...)
224258
preinstallPrompts = append(preinstallPrompts, templateSelect)

cmd/lk/project.go

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -262,9 +262,15 @@ func listProjects(ctx context.Context, cmd *cli.Command) error {
262262
return baseStyle
263263
}
264264
}).
265-
Headers("Name", "URL", "API Key", "Default")
265+
Headers("Name", "URL", "API Key")
266266
for _, p := range cliConfig.Projects {
267-
table.Row(p.Name, p.URL, p.APIKey, fmt.Sprint(p.Name == cliConfig.DefaultProject))
267+
var pName string
268+
if p.Name == cliConfig.DefaultProject {
269+
pName = "* " + p.Name
270+
} else {
271+
pName = " " + p.Name
272+
}
273+
table.Row(pName, p.URL, p.APIKey)
268274
}
269275
fmt.Println(table)
270276
}

cmd/lk/utils.go

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,38 @@ func wrapWith(wrap string) func(string) string {
160160
}
161161
}
162162

163+
func ellipsizeTo(str string, maxLength int) string {
164+
if len(str) <= maxLength {
165+
return str
166+
}
167+
ellipsis := "..."
168+
contentLen := max(0, min(len(str), maxLength-len(ellipsis)))
169+
return str[:contentLen] + ellipsis
170+
}
171+
172+
func wrapToLines(input string, maxLineLength int) []string {
173+
words := strings.Fields(input)
174+
var lines []string
175+
var currentLine strings.Builder
176+
177+
for _, word := range words {
178+
if currentLine.Len()+len(word)+1 > maxLineLength {
179+
lines = append(lines, currentLine.String())
180+
currentLine.Reset()
181+
}
182+
if currentLine.Len() > 0 {
183+
currentLine.WriteString(" ")
184+
}
185+
currentLine.WriteString(word)
186+
}
187+
188+
if currentLine.Len() > 0 {
189+
lines = append(lines, currentLine.String())
190+
}
191+
192+
return lines
193+
}
194+
163195
// Provides a temporary path, a function to relocate it to a permanent path,
164196
// and a function to clean up the temporary path that should always be deferred
165197
// in the case of a failure to relocate.

cmd/lk/utils_test.go

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,3 +56,34 @@ func TestMapStrings(t *testing.T) {
5656
t.Error("mapStrings should apply the function to all elements")
5757
}
5858
}
59+
60+
func TestEllipziseTo(t *testing.T) {
61+
str := "This is some long string that should be ellipsized"
62+
ellipsized := ellipsizeTo(str, 12)
63+
if len(ellipsized) != 12 {
64+
t.Error("ellipsizeTo should return a string of the specified length")
65+
}
66+
if ellipsized != "This is s..." {
67+
t.Error("ellipsizeTo should ellipsize the string")
68+
}
69+
}
70+
71+
func TestWrapToLines(t *testing.T) {
72+
str := "This is a long string that should be wrapped to multiple lines"
73+
wrapped := wrapToLines(str, 10)
74+
if len(wrapped) != 8 {
75+
t.Error("wrapToLines should return a slice of lines")
76+
}
77+
if !slices.Equal([]string{
78+
"This is a",
79+
"long",
80+
"string",
81+
"that",
82+
"should be",
83+
"wrapped to",
84+
"multiple",
85+
"lines",
86+
}, wrapped) {
87+
t.Error("wrapToLines should wrap the string to the specified width")
88+
}
89+
}

pkg/bootstrap/bootstrap.go

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -67,14 +67,15 @@ var templateIgnoreFiles = []string{
6767
}
6868

6969
type Template struct {
70-
Name string `yaml:"name" json:"name"`
71-
Desc string `yaml:"desc" json:"description,omitempty"`
72-
URL string `yaml:"url" json:"url,omitempty"`
73-
Docs string `yaml:"docs" json:"docs_url,omitempty"`
74-
Image string `yaml:"image" json:"image_ref,omitempty"`
75-
Tags []string `yaml:"tags" json:"tags,omitempty"`
76-
Requires []string `yaml:"requires" json:"requires,omitempty"`
77-
IsSandbox bool `yaml:"is_sandbox" json:"is_sandbox,omitempty"`
70+
Name string `yaml:"name" json:"name"`
71+
Desc string `yaml:"desc" json:"description,omitempty"`
72+
URL string `yaml:"url" json:"url,omitempty"`
73+
Docs string `yaml:"docs" json:"docs_url,omitempty"`
74+
Image string `yaml:"image" json:"image_ref,omitempty"`
75+
Tags []string `yaml:"tags" json:"tags,omitempty"`
76+
Attrs map[string]string `yaml:"attrs" json:"attrs,omitempty"`
77+
Requires []string `yaml:"requires" json:"requires,omitempty"`
78+
IsSandbox bool `yaml:"is_sandbox" json:"is_sandbox,omitempty"`
7879
}
7980

8081
type SandboxDetails struct {

version.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,5 +15,5 @@
1515
package livekitcli
1616

1717
const (
18-
Version = "2.2.1"
18+
Version = "2.3.0"
1919
)

0 commit comments

Comments
 (0)