package main import ( _ "embed" "fmt" "golang.org/x/text/cases" "golang.org/x/text/language" "os" "os/exec" "strings" "text/template" ) //go:embed templates/vue/AboutView.vue.tmpl var aboutViewTemplate string //go:embed templates/vue/HomeView.vue.tmpl var homeViewTemplate string //go:embed templates/vue/SideNavBar.vue.tmpl var sideNavBarTemplate string //go:embed templates/vue/Header.vue.tmpl var headerTemplateSource string //go:embed templates/vue/LoginLayout.vue.tmpl var loginLayoutTemplate string //go:embed templates/vue/MainLayout.vue.tmpl var mainLayoutTemplate string //go:embed templates/vue/Password.vue.tmpl var passwordTemplate string //go:embed templates/vue/authentication.ts.tmpl var authenticationTemplate string //go:embed templates/vue/App.vue.tmpl var vueAppTemplate string //go:embed templates/vue/router-index.ts.tmpl var routerIndexTemplate string //go:embed templates/vue/IconArrowFromShapeRight.vue.tmpl var iconArrowFromShapeRightTemplate string //go:embed templates/vue/IconHome.vue.tmpl var iconHomeTemplate string //go:embed templates/vue/IconGear.vue.tmpl var iconGearTemplate string func setupWebapp(name string) error { fmt.Println("Setting up webapp with Vue 3, TypeScript, Vue Router, Pinia, and ESLint with Prettier") cmd := exec.Command("npm", "create", "vue@latest", "--", "--ts", "--router", "--pinia", "--eslint-with-prettier", name) err := cmd.Run() if err != nil { return fmt.Errorf("error setting up webapp | %w", err) } fmt.Println("Switching to the new directory") // cd into the new directory err = os.Chdir(name) if err != nil { return fmt.Errorf("error changing directory | %w", err) } fmt.Println("Installing base dependencies") // install all dependencies and format the code cmd = exec.Command("npm", "install", "&&", "npm", "run", "format") err = cmd.Run() if err != nil { return fmt.Errorf("error changing directory | %w", err) } // make src/assets/base.css and src/assets/main.css mostly empty files err = os.WriteFile("src/assets/base.css", []byte(""), 0644) if err != nil { return fmt.Errorf("error writing base.css | %w", err) } err = os.WriteFile("src/assets/main.css", []byte("@import './base.css';"), 0644) if err != nil { return fmt.Errorf("error writing main.css | %w", err) } fmt.Println("Setting up Tailwind CSS") err = setupTailwind() if err != nil { return fmt.Errorf("error setting up tailwind | %w", err) } // clean up the default example files err = removeListOfFiles([]string{ "src/components/HelloWorld.vue", "src/components/TheWelcome.vue", "src/components/WelcomeItem.vue", "src/views/AboutView.vue", // replace with new AboutView.vue "src/components/icons/IconCommunity.vue", "src/components/icons/IconDocumentation.vue", "src/components/icons/IconEcosystem.vue", "src/components/icons/IconSupport.vue", "src/components/icons/IconTooling.vue", "src/assets/logo.svg", }) // make sure "src/layouts/" exists err = os.Mkdir("src/layouts", 0755) if err != nil { return fmt.Errorf("error creating layouts directory | %w", err) } // create a new about view from the aboutViewTemplate err = os.WriteFile("src/views/AboutView.vue", []byte(aboutViewTemplate), 0644) if err != nil { return fmt.Errorf("error writing new AboutView.vue | %w", err) } // create a new home view from the homeViewTemplate err = os.WriteFile("src/views/HomeView.vue", []byte(homeViewTemplate), 0644) if err != nil { return fmt.Errorf("error writing new HomeView.vue | %w", err) } // create src/components/SideNavBar.vue from a template err = os.WriteFile("src/components/SideNavBar.vue", []byte(sideNavBarTemplate), 0644) if err != nil { return fmt.Errorf("error writing new SideNavBar.vue | %w", err) } titleMaker := cases.Title(language.English) // create src/components/Header.vue from a template err = renderTemplate("src/components/Header.vue", headerTemplateSource, map[string]string{"AppName": strings.ToLower(name), "AppNameCaps": titleMaker.String(name)}) if err != nil { return fmt.Errorf("error writing new Header.vue | %w", err) } err = renderTemplate("src/layouts/MainLayout.vue", mainLayoutTemplate, map[string]string{"AppName": strings.ToLower(name), "AppNameCaps": titleMaker.String(name)}) if err != nil { return fmt.Errorf("error writing new MainLayout.vue | %w", err) } // create src/layouts/Login.vue from a template err = os.WriteFile("src/layouts/Login.vue", []byte(loginLayoutTemplate), 0644) if err != nil { return fmt.Errorf("error writing new Login.vue | %w", err) } straightCopies := []struct{ src, dest string }{ {vueAppTemplate, "src/App.vue"}, {passwordTemplate, "src/views/Password.vue"}, {authenticationTemplate, "src/stores/authentication.ts"}, {routerIndexTemplate, "src/router/index.ts"}, {iconArrowFromShapeRightTemplate, "src/components/icons/IconArrowFromShapeRight.vue"}, {iconHomeTemplate, "src/components/icons/IconHome.vue"}, {iconGearTemplate, "src/components/icons/IconGear.vue"}, } for _, srcDest := range straightCopies { err = os.WriteFile(srcDest.dest, []byte(srcDest.src), 0644) if err != nil { return fmt.Errorf("error writing new file %s | %w", srcDest.dest, err) } } fmt.Println("Exiting the new directory") err = os.Chdir("..") if err != nil { return fmt.Errorf("error changing directory | %w", err) } return nil } func renderTemplate(fileLocation string, templateSource string, templateData map[string]string) error { headerFile, err := os.Create(fileLocation) if err != nil { return fmt.Errorf("error creating new file %s | %w", fileLocation, err) } defer headerFile.Close() headerTemplate := template.Must(template.New("header").Parse(templateSource)) err = headerTemplate.Execute(headerFile, templateData) if err != nil { return fmt.Errorf("error rendering file %s | %w", fileLocation, err) } return nil } func setupTailwind() error { // check that there is a vite.config.ts file and a package.json file _, err := os.Stat("vite.config.ts") if err != nil { return fmt.Errorf("error checking for vite.config.ts | %w", err) } _, err = os.Stat("package.json") if err != nil { return fmt.Errorf("error checking for package.json | %w", err) } cmd := exec.Command("npm", "install", "tailwindcss", "@tailwindcss/vite") err = cmd.Run() if err != nil { return fmt.Errorf("error installing tailwindcss | %w", err) } // In the vite.config.ts insert the following code after the last import statement `import tailwindcss from '@tailwindcss/vite'` err = insertImportStatement("import tailwindcss from '@tailwindcss/vite'", "vite.config.ts") if err != nil { return fmt.Errorf("error inserting import statement | %w", err) } // In the vite.config.ts insert the following code after the last plugin usage `tailwindcss(),` err = insertPluginUsage(" tailwindcss(),", "vite.config.ts") if err != nil { return fmt.Errorf("error inserting plugin usage | %w", err) } // in the base.css file add an @import statement for tailwind // check that './src/assets/base.css' exists _, err = os.Stat("./src/assets/base.css") if err != nil { return fmt.Errorf("error checking for base.css | %w", err) } err = insertAtTopLineOfFile("@import \"tailwindcss\";", "./src/assets/base.css") if err != nil { return fmt.Errorf("error inserting import statement | %w", err) } return nil } func insertImportStatement(importStatement string, filename string) error { fileContent, err := os.ReadFile(fmt.Sprintf(".%c%s", os.PathSeparator, filename)) if err != nil { return fmt.Errorf("error reading %s | %w", filename, err) } fileLines := strings.Split(strings.ReplaceAll(string(fileContent), "\r\n", "\n"), "\n") lastImportIndex := -1 // finds the last import statement in the file and appends after that one for i, line := range fileLines { if strings.HasPrefix(line, "import") { lastImportIndex = i } } if lastImportIndex != -1 { fileLines = append(fileLines[:lastImportIndex+1], append([]string{importStatement}, fileLines[lastImportIndex+1:]...)...) } else { // add the import to the top of the file fileLines = append([]string{importStatement}, fileLines...) } err = os.WriteFile(filename, []byte(strings.Join(fileLines, "\n")), 0644) if err != nil { return fmt.Errorf("error writing vite.config.ts | %w", err) } return nil } func insertPluginUsage(pluginUsage string, filename string) error { fileContent, err := os.ReadFile(fmt.Sprintf(".%c%s", os.PathSeparator, filename)) if err != nil { return fmt.Errorf("error reading %s | %w", filename, err) } fileLines := strings.Split(strings.ReplaceAll(string(fileContent), "\r\n", "\n"), "\n") startOfPluginsIndex := -1 // finds the `plugins: [` line in the file and appends after that one for i, line := range fileLines { if strings.Contains(line, "plugins: [") { startOfPluginsIndex = i } } if startOfPluginsIndex != -1 { fileLines = append(fileLines[:startOfPluginsIndex+1], append([]string{pluginUsage}, fileLines[startOfPluginsIndex+1:]...)...) } else { // add the import to the top of the file fileLines = append([]string{pluginUsage}, fileLines...) } err = os.WriteFile(filename, []byte(strings.Join(fileLines, "\n")), 0644) if err != nil { return fmt.Errorf("error writing vite.config.ts | %w", err) } return nil } func insertAtTopLineOfFile(content string, filename string) error { fileContent, err := os.ReadFile(fmt.Sprintf(".%c%s", os.PathSeparator, filename)) if err != nil { return fmt.Errorf("error reading %s | %w", filename, err) } fileLines := strings.Split(strings.ReplaceAll(string(fileContent), "\r\n", "\n"), "\n") fileLines = append([]string{content}, fileLines...) err = os.WriteFile(filename, []byte(strings.Join(fileLines, "\n")), 0644) if err != nil { return fmt.Errorf("error writing vite.config.ts | %w", err) } return nil } func removeListOfFiles(files []string) error { for _, file := range files { err := os.Remove(file) if err != nil { return fmt.Errorf("error removing %s | %w", file, err) } } return nil }