335 lines
11 KiB
Go
335 lines
11 KiB
Go
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
|
|
|
|
//go:embed templates/vue/fetch-rpc.ts.tmpl
|
|
var fetchRPCTemplate 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)
|
|
}
|
|
|
|
// make sure "src/services/" exists
|
|
err = os.Mkdir("src/services", 0755)
|
|
if err != nil {
|
|
return fmt.Errorf("error creating services directory | %w", err)
|
|
}
|
|
|
|
// make sure "src/generated/" exists
|
|
err = os.Mkdir("src/generated", 0755)
|
|
if err != nil {
|
|
return fmt.Errorf("error creating generated 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"},
|
|
{fetchRPCTemplate, "src/services/fetch-rpc.ts"},
|
|
}
|
|
|
|
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)
|
|
}
|
|
}
|
|
|
|
cmd = exec.Command("npm", "install", "openapi-typescript-codegen", "--save-dev")
|
|
err = cmd.Run()
|
|
if err != nil {
|
|
return fmt.Errorf("error installing openapi-typescript-codegen | %w", 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\";\n\n/* https://stackoverflow.com/a/79433685/1486472 */\n@source \"../../node_modules/@masonitestudios/dynamic-vue\";", "./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
|
|
}
|