Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

cli: Move tutorial config flag to example repo #1519

Merged
merged 1 commit into from
Oct 24, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 7 additions & 13 deletions cli/cmd/encore/app/create.go
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,6 @@ func promptRunApp() bool {
// createApp is the implementation of the "encore app create" command.
func createApp(ctx context.Context, name, template string) (err error) {
var lang language
var tutorial bool
defer func() {
// We need to send the telemetry synchronously to ensure it's sent before the command exits.
telemetry.SendSync("app.create", map[string]any{
Expand All @@ -133,7 +132,7 @@ func createApp(ctx context.Context, name, template string) (err error) {
promptAccountCreation()

if name == "" || template == "" {
name, template, lang, tutorial = selectTemplate(name, template, false)
name, template, lang = selectTemplate(name, template, false)
}
// Treat the special name "empty" as the empty app template
// (the rest of the code assumes that's the empty string).
Expand Down Expand Up @@ -194,26 +193,20 @@ func createApp(ctx context.Context, name, template string) (err error) {
_, err = conf.CurrentUser()
loggedIn := err == nil

exCfg, ok := parseExampleConfig(name)
if ok {
_ = os.Remove(exampleJSONPath(name))
}
var app *platform.App
if loggedIn && createAppOnPlatform {
s := spinner.New(spinner.CharSets[14], 100*time.Millisecond)
s.Prefix = "Creating app on encore.dev "
s.Start()

exCfg, ok := parseExampleConfig(name)
app, err = createAppOnServer(name, exCfg)
s.Stop()
if err != nil {
return fmt.Errorf("creating app on encore.dev: %v", err)
}

// Remove the example.json file if the app was successfully created.
if ok {
_ = os.Remove(exampleJSONPath(name))
}
} else {
// Remove the example config file since we're not creating the app on the platform.
_ = os.Remove(exampleJSONPath(name))
}

encoreAppPath := filepath.Join(name, "encore.app")
Expand Down Expand Up @@ -280,7 +273,7 @@ func createApp(ctx context.Context, name, template string) (err error) {
daemon := cmdutil.ConnectDaemon(ctx)
_, err = daemon.CreateApp(ctx, &daemonpb.CreateAppRequest{
AppRoot: appRoot,
Tutorial: tutorial,
Tutorial: exCfg.Tutorial,
Template: template,
})
if err != nil {
Expand Down Expand Up @@ -584,6 +577,7 @@ func rewritePlaceholder(path string, info fs.DirEntry, app *platform.App) error
// exampleConfig is the optional configuration file for example apps.
type exampleConfig struct {
InitialSecrets map[string]string `json:"initial_secrets"`
Tutorial bool `json:"tutorial"`
}

func parseExampleConfig(repoPath string) (cfg exampleConfig, exists bool) {
Expand Down
199 changes: 88 additions & 111 deletions cli/cmd/encore/app/create_form.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"net/http"
"os"
"strings"
"sync"
"time"

"github.com/charmbracelet/bubbles/list"
Expand Down Expand Up @@ -40,7 +41,6 @@ type templateItem struct {
Desc string `json:"desc"`
Template string `json:"template"`
Lang language `json:"lang"`
Tutorial bool `json:"tutorial"`
}

func (i templateItem) Title() string { return i.ItemTitle }
Expand Down Expand Up @@ -412,10 +412,10 @@ func (m templateListModel) SelectedItem() (templateItem, bool) {
return templateItem{}, false
}

func selectTemplate(inputName, inputTemplate string, skipShowingTemplate bool) (appName, template string, selectedLang language, tutorial bool) {
func selectTemplate(inputName, inputTemplate string, skipShowingTemplate bool) (appName, template string, selectedLang language) {
// If we have both name and template already, return them.
if inputName != "" && inputTemplate != "" {
return inputName, inputTemplate, "", false
return inputName, inputTemplate, ""
}

var lang languageSelectModel
Expand Down Expand Up @@ -528,10 +528,9 @@ func selectTemplate(inputName, inputTemplate string, skipShowingTemplate bool) (
cmdutil.Fatal("no template selected")
}
template = sel.Template
tutorial = sel.Tutorial
}

return appName, template, res.lang.Selected(), tutorial
return appName, template, res.lang.Selected()
}

type langItem struct {
Expand Down Expand Up @@ -567,8 +566,75 @@ func (lang language) Display() string {

type loadedTemplates []templateItem

func loadTutorials(ctx context.Context) []templateItem {
url := "https://raw.githubusercontent.com/encoredev/examples/main/cli-tutorials.json"
var defaultTutorials = []templateItem{
{
ItemTitle: "Intro to Encore.ts",
Desc: "An interactive tutorial",
Template: "ts/introduction",
Lang: "ts",
},
}

var defaultTemplates = []templateItem{
{
ItemTitle: "Hello World",
Desc: "A simple REST API",
Template: "hello-world",
Lang: "go",
},
{
ItemTitle: "Hello World",
Desc: "A simple REST API",
Template: "ts/hello-world",
Lang: "ts",
},
{
ItemTitle: "Uptime Monitor",
Desc: "Microservices, SQL Databases, Pub/Sub, Cron Jobs",
Template: "uptime",
Lang: "go",
},
{
ItemTitle: "Uptime Monitor",
Desc: "Microservices, SQL Databases, Pub/Sub, Cron Jobs",
Template: "ts/uptime",
Lang: "ts",
},
{
ItemTitle: "GraphQL",
Desc: "GraphQL API, Microservices, SQL Database",
Template: "graphql",
Lang: "go",
},
{
ItemTitle: "URL Shortener",
Desc: "REST API, SQL Database",
Template: "url-shortener",
Lang: "go",
},
{
ItemTitle: "URL Shortener",
Desc: "REST API, SQL Database",
Template: "ts/url-shortener",
Lang: "ts",
},
{
ItemTitle: "Empty app",
Desc: "Start from scratch (experienced users only)",
Template: "",
Lang: "go",
},
{
ItemTitle: "Empty app",
Desc: "Start from scratch (experienced users only)",
Template: "ts/empty",
Lang: "ts",
},
}

func fetchTemplates(url string, defaults []templateItem) []templateItem {
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
if req, err := http.NewRequestWithContext(ctx, "GET", url, nil); err == nil {
if resp, err := http.DefaultClient.Do(req); err == nil {
if data, err := io.ReadAll(resp.Body); err == nil {
Expand All @@ -582,113 +648,24 @@ func loadTutorials(ctx context.Context) []templateItem {
}
}
}
return []templateItem{
{
ItemTitle: "Intro to Encore.ts",
Desc: "An interactive tutorial",
Template: "ts/introduction",
Lang: "ts",
Tutorial: true,
},
}

return defaults
}

func loadTemplates() tea.Msg {
// Load the templates.
templates := (func() []templateItem {
// Get the list of templates from GitHub
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
url := "https://raw.githubusercontent.com/encoredev/examples/main/cli-templates.json"
if req, err := http.NewRequestWithContext(ctx, "GET", url, nil); err == nil {
if resp, err := http.DefaultClient.Do(req); err == nil {
if data, err := io.ReadAll(resp.Body); err == nil {
if data, err = hujson.Standardize(data); err == nil {
var items []templateItem
if err := json.Unmarshal(data, &items); err == nil && len(items) > 0 {
for i, it := range items {
if it.Lang == "" {
items[i].Lang = languageGo
if strings.Contains(it.Template, "ts/") || strings.Contains(strings.ToLower(it.ItemTitle), "typescript") {
items[i].Lang = languageTS
}
}
}
return append(loadTutorials(ctx), items...)
}
}
}
}
}

// Return a precompiled list of default items in case we can't read them from GitHub.
return []templateItem{
{
ItemTitle: "Intro to Encore.ts",
Desc: "An interactive tutorial",
Template: "ts/introduction",
Lang: "ts",
Tutorial: true,
},
{
ItemTitle: "Hello World",
Desc: "A simple REST API",
Template: "hello-world",
Lang: "go",
},
{
ItemTitle: "Hello World",
Desc: "A simple REST API",
Template: "ts/hello-world",
Lang: "ts",
},
{
ItemTitle: "Uptime Monitor",
Desc: "Microservices, SQL Databases, Pub/Sub, Cron Jobs",
Template: "uptime",
Lang: "go",
},
{
ItemTitle: "Uptime Monitor",
Desc: "Microservices, SQL Databases, Pub/Sub, Cron Jobs",
Template: "ts/uptime",
Lang: "ts",
},
{
ItemTitle: "GraphQL",
Desc: "GraphQL API, Microservices, SQL Database",
Template: "graphql",
Lang: "go",
},
{
ItemTitle: "URL Shortener",
Desc: "REST API, SQL Database",
Template: "url-shortener",
Lang: "go",
},
{
ItemTitle: "URL Shortener",
Desc: "REST API, SQL Database",
Template: "ts/url-shortener",
Lang: "ts",
},
{
ItemTitle: "Empty app",
Desc: "Start from scratch (experienced users only)",
Template: "",
Lang: "go",
},
{
ItemTitle: "Empty app",
Desc: "Start from scratch (experienced users only)",
Template: "ts/empty",
Lang: "ts",
},
}
})()

return loadedTemplates(templates)
var wg sync.WaitGroup
var templates, tutorials []templateItem
wg.Add(1)
go func() {
defer wg.Done()
templates = fetchTemplates("https://raw.githubusercontent.com/encoredev/examples/main/cli-templates.json", defaultTemplates)
}()
wg.Add(1)
go func() {
defer wg.Done()
tutorials = fetchTemplates("https://raw.githubusercontent.com/encoredev/examples/main/cli-tutorials.json", defaultTutorials)
}()
wg.Wait()
return loadedTemplates(append(tutorials, templates...))
}

// incrementalValidateName is like validateName but only
Expand Down
2 changes: 1 addition & 1 deletion cli/cmd/encore/app/initialize.go
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ func initializeApp(name string) error {
cyan := color.New(color.FgCyan)
promptAccountCreation()

name, _, lang, _ := selectTemplate(name, "", true)
name, _, lang := selectTemplate(name, "", true)

if err := validateName(name); err != nil {
return err
Expand Down
Loading