github.com/ActiveState/cli@v0.0.0-20240508170324-6801f60cd051/internal/runners/tutorial/tutorial.go (about) 1 package tutorial 2 3 import ( 4 "fmt" 5 "os" 6 7 "github.com/ActiveState/cli/internal/analytics" 8 "github.com/skratchdot/open-golang/open" 9 10 anaConsts "github.com/ActiveState/cli/internal/analytics/constants" 11 "github.com/ActiveState/cli/internal/constants" 12 "github.com/ActiveState/cli/internal/fileutils" 13 "github.com/ActiveState/cli/internal/language" 14 "github.com/ActiveState/cli/internal/locale" 15 "github.com/ActiveState/cli/internal/osutils/user" 16 "github.com/ActiveState/cli/internal/output" 17 "github.com/ActiveState/cli/internal/primer" 18 "github.com/ActiveState/cli/internal/prompt" 19 "github.com/ActiveState/cli/internal/runbits" 20 "github.com/ActiveState/cli/pkg/platform/api" 21 "github.com/ActiveState/cli/pkg/platform/authentication" 22 ) 23 24 type Tutorial struct { 25 outputer output.Outputer 26 auth *authentication.Auth 27 prompt prompt.Prompter 28 analytics analytics.Dispatcher 29 } 30 31 type primeable interface { 32 primer.Outputer 33 primer.Prompter 34 primer.Auther 35 primer.Configurer 36 primer.Analyticer 37 } 38 39 func New(primer primeable) *Tutorial { 40 return &Tutorial{primer.Output(), primer.Auth(), primer.Prompt(), primer.Analytics()} 41 } 42 43 type NewProjectParams struct { 44 SkipIntro bool 45 Language language.Language 46 } 47 48 func (t *Tutorial) RunNewProject(params NewProjectParams) error { 49 t.analytics.EventWithLabel(anaConsts.CatTutorial, "run", fmt.Sprintf("skipIntro=%v,language=%v", params.SkipIntro, params.Language.String())) 50 51 // Print intro 52 if !params.SkipIntro { 53 t.outputer.Print(locale.Tt("tutorial_newproject_intro")) 54 } 55 56 // Prompt for authentication 57 if !t.auth.Authenticated() { 58 if err := t.authFlow(); err != nil { 59 return err 60 } 61 } 62 63 // Prompt for language 64 lang := params.Language 65 if lang == language.Unset { 66 choice, err := t.prompt.Select( 67 "", 68 locale.Tl("tutorial_language", "What language would you like to use for your new virtual environment?"), 69 []string{language.Perl.Text(), language.Python3.Text(), language.Python2.Text()}, 70 new(string), 71 ) 72 if err != nil { 73 return locale.WrapInputError(err, "err_tutorial_prompt_language", "Invalid response received.") 74 } 75 lang = language.MakeByText(choice) 76 if lang == language.Unknown || lang == language.Unset { 77 return locale.NewError("err_tutorial_language_unknown", "Invalid language selected: {{.V0}}.", choice) 78 } 79 t.analytics.EventWithLabel(anaConsts.CatTutorial, "choose-language", lang.String()) 80 } 81 82 // Prompt for project name 83 defProjectInput := lang.Text() 84 name, err := t.prompt.Input("", locale.Tl("tutorial_prompt_projectname", "What do you want to name your project?"), &defProjectInput) 85 if err != nil { 86 return locale.WrapInputError(err, "err_tutorial_prompt_projectname", "Invalid response received.") 87 } 88 89 // Prompt for project dir 90 homeDir, _ := user.HomeDir() 91 dir, err := t.prompt.Input("", locale.Tl( 92 "tutorial_prompt_projectdir", 93 "Where would you like your project directory to be mapped? This is usually the root of your repository, or the place where you have your project dotfiles."), &homeDir) 94 if err != nil { 95 return locale.WrapInputError(err, "err_tutorial_prompt_projectdir", "Invalid response received.") 96 } 97 98 // Create dir and switch to it 99 if err := fileutils.MkdirUnlessExists(dir); err != nil { 100 return locale.WrapExternalError(err, "err_tutorial_mkdir", "Could not create directory: {{.V0}}.", dir) 101 } 102 if err := os.Chdir(dir); err != nil { 103 return locale.WrapExternalError(err, "err_tutorial_chdir", "Could not change directory to: {{.V0}}", dir) 104 } 105 106 // Run state init 107 if err := runbits.Invoke(t.outputer, "init", t.auth.WhoAmI()+"/"+name, lang.String(), "--path", dir); err != nil { 108 return locale.WrapExternalError(err, "err_tutorial_state_init", "Could not initialize project.") 109 } 110 111 // Run state push 112 if err := runbits.Invoke(t.outputer, "push"); err != nil { 113 return locale.WrapExternalError(err, "err_tutorial_state_push", "Could not push project to ActiveState Platform, try manually running '[ACTIONABLE]state push[/RESET]' from your project directory at {{.V0}}.", dir) 114 } 115 116 // Print outro 117 t.outputer.Print(locale.Tt( 118 "tutorial_newproject_outro", map[string]interface{}{ 119 "Dir": dir, 120 "URL": api.GetPlatformURL(fmt.Sprintf("%s/%s", t.auth.WhoAmI(), name)), 121 "Docs": constants.DocumentationURL, 122 })) 123 124 return nil 125 } 126 127 // authFlow is invoked when the user is not authenticated, it will prompt for sign in or sign up 128 func (t *Tutorial) authFlow() error { 129 t.analytics.Event(anaConsts.CatTutorial, "authentication-flow") 130 131 // Sign in / Sign up choices 132 signIn := locale.Tl("tutorial_signin", "Sign In") 133 signUpCLI := locale.Tl("tutorial_createcli", "Create Account via Command Line") 134 signUpBrowser := locale.Tl("tutorial_createbrowser", "Create Account via Browser") 135 choices := []string{signIn, signUpCLI, signUpBrowser} 136 137 // Prompt for auth 138 choice, err := t.prompt.Select( 139 "", 140 locale.Tl("tutorial_need_account", "In order to create a virtual environment you must have an ActiveState Platform account"), 141 choices, 142 &signIn, 143 ) 144 if err != nil { 145 return locale.WrapInputError(err, "err_tutorial_prompt_account", "Invalid response received.") 146 } 147 148 // Evaluate user selection 149 switch choice { 150 case signIn: 151 t.analytics.EventWithLabel(anaConsts.CatTutorial, "authentication-action", "sign-in") 152 if err := runbits.Invoke(t.outputer, "auth"); err != nil { 153 return locale.WrapExternalError(err, "err_tutorial_signin", "Sign in failed. You could try manually signing in by running '[ACTIONABLE]state auth[/RESET]'.") 154 } 155 case signUpCLI: 156 t.analytics.EventWithLabel(anaConsts.CatTutorial, "authentication-action", "sign-up") 157 if err := runbits.Invoke(t.outputer, "auth", "signup"); err != nil { 158 return locale.WrapExternalError(err, "err_tutorial_signup", "Sign up failed. You could try manually signing up by running '[ACTIONABLE]state auth signup[/RESET]'.") 159 } 160 case signUpBrowser: 161 t.analytics.EventWithLabel(anaConsts.CatTutorial, "authentication-action", "sign-up-browser") 162 signupURL := api.GetPlatformURL(constants.PlatformSignupPath).String() 163 err := open.Run(signupURL) 164 if err != nil { 165 return locale.WrapExternalError(err, "err_tutorial_browser", "Could not open browser, please manually navigate to {{.V0}}.", signupURL) 166 } 167 t.outputer.Notice(locale.Tl("tutorial_signing_ready", "[NOTICE]Please sign in once you have finished signing up via your browser.[/RESET]")) 168 if err := runbits.Invoke(t.outputer, "auth"); err != nil { 169 return locale.WrapExternalError(err, "err_tutorial_signin", "Sign in failed. You could try manually signing in by running '[ACTIONABLE]state auth[/RESET]'.") 170 } 171 } 172 173 if err := t.auth.Authenticate(); err != nil { 174 return locale.WrapError(err, "err_tutorial_auth", "Could not authenticate after invoking `state auth ..`.") 175 } 176 177 t.analytics.Event(anaConsts.CatTutorial, "authentication-flow-complete") 178 179 return nil 180 }