add ability to set up a webapp
This commit is contained in:
6
.idea/vcs.xml
generated
Normal file
6
.idea/vcs.xml
generated
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="VcsDirectoryMappings">
|
||||||
|
<mapping directory="$PROJECT_DIR$" vcs="Git" />
|
||||||
|
</component>
|
||||||
|
</project>
|
@ -7,7 +7,12 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
commands := []*cli.Command{}
|
commands := []*cli.Command{
|
||||||
|
createCmd(),
|
||||||
|
generateCmd(),
|
||||||
|
webappCmd(),
|
||||||
|
tailwindCmd(),
|
||||||
|
}
|
||||||
|
|
||||||
app := &cli.App{
|
app := &cli.App{
|
||||||
Name: "Masonry",
|
Name: "Masonry",
|
||||||
|
@ -1 +1,206 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
_ "embed"
|
||||||
|
"fmt"
|
||||||
|
"github.com/urfave/cli/v2"
|
||||||
|
"golang.org/x/text/cases"
|
||||||
|
"golang.org/x/text/language"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"strings"
|
||||||
|
"text/template"
|
||||||
|
)
|
||||||
|
|
||||||
|
//go:embed templates/proto/application.proto.tmpl
|
||||||
|
var protoTemplate string
|
||||||
|
|
||||||
|
//go:embed templates/backend/main.go.tmpl
|
||||||
|
var mainGoTemplate string
|
||||||
|
|
||||||
|
func createCmd() *cli.Command {
|
||||||
|
return &cli.Command{
|
||||||
|
Name: "create",
|
||||||
|
Aliases: []string{"c"},
|
||||||
|
Usage: "Create a new app in a directory with the given name",
|
||||||
|
Description: "This command will create a new folder with the given name and generate a new app in that folder.",
|
||||||
|
Flags: []cli.Flag{
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: "name",
|
||||||
|
Category: "generator",
|
||||||
|
Usage: "The name of the app to create",
|
||||||
|
Required: true,
|
||||||
|
Aliases: []string{"n"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Action: func(c *cli.Context) error {
|
||||||
|
applicationName := c.String("name")
|
||||||
|
|
||||||
|
fmt.Printf("Creating app: %v\n", applicationName)
|
||||||
|
|
||||||
|
// make a directory with the given name from the working directory
|
||||||
|
err := os.Mkdir(applicationName, 0755)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("error creating app directory | %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// generate the app in the new directory
|
||||||
|
// cd into the new directory
|
||||||
|
err = os.Chdir(applicationName)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("error changing directory | %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// initialize a go module
|
||||||
|
cmd := exec.Command("go", "mod", "init", applicationName)
|
||||||
|
err = cmd.Run()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("error initializing go module | %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// create a directory to proto files
|
||||||
|
err = os.Mkdir("proto", 0755)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("error creating proto directory | %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// create a directory to store the generated code
|
||||||
|
err = os.Mkdir("gen", 0755)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("error creating gen directory | %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// create a directory for generated go code
|
||||||
|
err = os.Mkdir("gen/go", 0755)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("error creating gen/go directory | %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// create a directory for generated typescript code
|
||||||
|
err = os.Mkdir("gen/ts", 0755)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("error creating gen/ts directory | %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// create a main.go file
|
||||||
|
mainFile, err := os.Create("main.go")
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("error creating main.go file | %w", err)
|
||||||
|
}
|
||||||
|
defer mainFile.Close()
|
||||||
|
|
||||||
|
titleMaker := cases.Title(language.English)
|
||||||
|
|
||||||
|
// render the main.go file from the template
|
||||||
|
goTemplate := template.Must(template.New("main").Parse(mainGoTemplate))
|
||||||
|
err = goTemplate.Execute(mainFile, map[string]string{"AppName": strings.ToLower(applicationName), "AppNameCaps": titleMaker.String(applicationName)})
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("error rendering main.go file | %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// create a proto file
|
||||||
|
protoFile, err := os.Create("proto/service.proto")
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("error creating proto file | %w", err)
|
||||||
|
}
|
||||||
|
defer protoFile.Close()
|
||||||
|
|
||||||
|
// render the proto file from the template
|
||||||
|
t := template.Must(template.New("proto").Parse(protoTemplate))
|
||||||
|
err = t.Execute(protoFile, map[string]string{"AppName": strings.ToLower(applicationName), "AppNameCaps": titleMaker.String(applicationName), "ObjName": "Product"})
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("error rendering proto file | %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// set up the webapp
|
||||||
|
err = setupWebapp("webapp") // since the app is already in its own named folder, we name it webapp
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("error setting up webapp | %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func generateCmd() *cli.Command {
|
||||||
|
return &cli.Command{
|
||||||
|
Name: "generate",
|
||||||
|
Aliases: []string{"g"},
|
||||||
|
Usage: "Generate code from proto files",
|
||||||
|
Description: "This command will generate code from the proto files in the proto directory and place them in a language folder in the gen folder.",
|
||||||
|
Action: func(c *cli.Context) error {
|
||||||
|
fmt.Println("Generating code...")
|
||||||
|
|
||||||
|
// generate go code
|
||||||
|
cmd := exec.Command("protoc", "-I", ".", "--go_out", "gen/go", "--go-grpc_out", "gen/go", "--go-grpc_opt=require_unimplemented_servers=false", "--gorm_out", "gen/go", "proto/*.proto")
|
||||||
|
err := cmd.Run()
|
||||||
|
if err != nil {
|
||||||
|
|
||||||
|
var buff bytes.Buffer
|
||||||
|
cmd.Stderr = &buff
|
||||||
|
fmt.Println(buff.String())
|
||||||
|
|
||||||
|
return fmt.Errorf("error generating go code | %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
//// generate typescript code
|
||||||
|
//cmd = exec.Command("protoc", "--ts_out=gen/ts", "proto/*.proto")
|
||||||
|
//err = cmd.Run()
|
||||||
|
//if err != nil {
|
||||||
|
// return fmt.Errorf("error generating typescript code | %w", err)
|
||||||
|
//}
|
||||||
|
|
||||||
|
// TODO: if there is a webapp folder present, generate vue code from the proto files
|
||||||
|
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func webappCmd() *cli.Command {
|
||||||
|
return &cli.Command{
|
||||||
|
Name: "webapp",
|
||||||
|
Aliases: []string{"w"},
|
||||||
|
Flags: []cli.Flag{
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: "name",
|
||||||
|
Aliases: []string{"n"},
|
||||||
|
Category: "generator",
|
||||||
|
Usage: "The name of the webapp to create, this will be the name of the directory",
|
||||||
|
Required: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Usage: "Set up a webapp",
|
||||||
|
Description: "This command will set up a webapp ",
|
||||||
|
Action: func(c *cli.Context) error {
|
||||||
|
fmt.Println("Setting up webapp named: ", c.String("name"))
|
||||||
|
name := c.String("name")
|
||||||
|
|
||||||
|
err := setupWebapp(name)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("error setting up webapp | %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func tailwindCmd() *cli.Command {
|
||||||
|
return &cli.Command{
|
||||||
|
Name: "tailwind",
|
||||||
|
Aliases: []string{"t"},
|
||||||
|
Usage: "Set up tailwindcss in a vite webapp, if you built using masonry then this is already done for you",
|
||||||
|
Action: func(c *cli.Context) error {
|
||||||
|
fmt.Println("Setting up tailwindcss")
|
||||||
|
err := setupTailwind()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("error setting up tailwindcss | %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
84
cmd/cli/templates/backend/main.go.tmpl
Normal file
84
cmd/cli/templates/backend/main.go.tmpl
Normal file
@ -0,0 +1,84 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"github.com/payne8/go-libsql-dual-driver"
|
||||||
|
sqlite "github.com/ytsruh/gorm-libsql"
|
||||||
|
"google.golang.org/grpc"
|
||||||
|
"google.golang.org/grpc/reflection"
|
||||||
|
"gorm.io/gorm"
|
||||||
|
"log"
|
||||||
|
"net"
|
||||||
|
"os"
|
||||||
|
pb "{{ .AppName }}/gen/go"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
logger := log.New(os.Stdout, "{{ .AppName }} ", log.LstdFlags)
|
||||||
|
primaryUrl := os.Getenv("LIBSQL_DATABASE_URL")
|
||||||
|
authToken := os.Getenv("LIBSQL_AUTH_TOKEN")
|
||||||
|
|
||||||
|
tdb, err := libsqldb.NewLibSqlDB(
|
||||||
|
primaryUrl,
|
||||||
|
//libsqldb.WithMigrationFiles(migrationFiles),
|
||||||
|
libsqldb.WithAuthToken(authToken),
|
||||||
|
libsqldb.WithLocalDBName("local.db"), // will not be used for remote-only
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
logger.Printf("failed to open db %s: %s", primaryUrl, err)
|
||||||
|
log.Fatalln(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// instantiate the grom ORM
|
||||||
|
gormDB, err := gorm.Open(sqlite.New(sqlite.Config{Conn: tdb.DB}), &gorm.Config{})
|
||||||
|
if err != nil {
|
||||||
|
logger.Printf("failed to open gorm db %s: %s", primaryUrl, err)
|
||||||
|
log.Fatalln(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// err = gormDB.AutoMigrate(&pb.UserORM{}) // TODO: figure out how to automate this part
|
||||||
|
// if err != nil {
|
||||||
|
// logger.Printf("failed to migrate user: %s", err)
|
||||||
|
// log.Fatalln(err)
|
||||||
|
// return
|
||||||
|
// }
|
||||||
|
// err = gormDB.AutoMigrate(&pb.ProductORM{}) // TODO: figure out how to automate this part
|
||||||
|
// if err != nil {
|
||||||
|
// logger.Printf("failed to migrate product: %s", err)
|
||||||
|
// log.Fatalln(err)
|
||||||
|
// return
|
||||||
|
// }
|
||||||
|
|
||||||
|
handlers := pb.{{ .AppNameCaps }}DefaultServer{
|
||||||
|
DB: gormDB,
|
||||||
|
}
|
||||||
|
|
||||||
|
grpcServer := grpc.NewServer(grpc.ChainUnaryInterceptor(
|
||||||
|
func(ctx context.Context, req any, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (resp any, err error) {
|
||||||
|
// TODO: this is an example of a middleware - we will use this for authentication
|
||||||
|
// we will have a function that checks the token and a switch statement that checks if the method is public or not
|
||||||
|
logger.Printf("request: %s", info.FullMethod)
|
||||||
|
return handler(ctx, req)
|
||||||
|
},
|
||||||
|
))
|
||||||
|
pb.Register{{ .AppNameCaps }}Server(grpcServer, &handlers)
|
||||||
|
reflection.Register(grpcServer)
|
||||||
|
|
||||||
|
// start the server
|
||||||
|
listener, err := net.Listen("tcp", ":9000")
|
||||||
|
if err != nil {
|
||||||
|
logger.Printf("failed to listen: %s", err)
|
||||||
|
log.Fatalln(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
logger.Printf("listening on %s", listener.Addr().String())
|
||||||
|
|
||||||
|
err = grpcServer.Serve(listener)
|
||||||
|
if err != nil {
|
||||||
|
logger.Printf("failed to serve: %s", err)
|
||||||
|
log.Fatalln(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
68
cmd/cli/templates/proto/application.proto.tmpl
Normal file
68
cmd/cli/templates/proto/application.proto.tmpl
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||||
|
syntax = "proto3";
|
||||||
|
|
||||||
|
package {{ .AppName }};
|
||||||
|
|
||||||
|
import "gorm/options/gorm.proto";
|
||||||
|
//import "gorm/types/types.proto";
|
||||||
|
|
||||||
|
option go_package = "./;pb";
|
||||||
|
|
||||||
|
service {{ .AppNameCaps }} {
|
||||||
|
option (gorm.server).autogen = true;
|
||||||
|
// Add your service methods here
|
||||||
|
|
||||||
|
rpc Create{{ .ObjName }} (Create{{ .ObjName }}Request) returns (Create{{ .ObjName }}Response) {}
|
||||||
|
|
||||||
|
rpc Read{{ .ObjName }} (Read{{ .ObjName }}Request) returns (Read{{ .ObjName }}Response) {}
|
||||||
|
|
||||||
|
rpc List{{ .ObjName }}s (List{{ .ObjName }}sRequest) returns (List{{ .ObjName }}sResponse) {}
|
||||||
|
|
||||||
|
rpc Update{{ .ObjName }} (Update{{ .ObjName }}Request) returns (Update{{ .ObjName }}Response) {}
|
||||||
|
|
||||||
|
rpc Delete{{ .ObjName }} (Delete{{ .ObjName }}Request) returns (Delete{{ .ObjName }}Response) {
|
||||||
|
option (gorm.method).object_type = "{{ .ObjName }}";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
message Create{{ .ObjName }}Request {
|
||||||
|
{{ .ObjName }} payload = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
message Create{{ .ObjName }}Response {
|
||||||
|
{{ .ObjName }} result = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
message Read{{ .ObjName }}Request {
|
||||||
|
uint64 id = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
message Read{{ .ObjName }}Response {
|
||||||
|
{{ .ObjName }} result = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
message List{{ .ObjName }}sRequest {}
|
||||||
|
|
||||||
|
message List{{ .ObjName }}sResponse {
|
||||||
|
repeated {{ .ObjName }} results = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
message Update{{ .ObjName }}Request {
|
||||||
|
{{ .ObjName }} payload = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
message Update{{ .ObjName }}Response {
|
||||||
|
{{ .ObjName }} result = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
message Delete{{ .ObjName }}Request {
|
||||||
|
uint64 id = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
message Delete{{ .ObjName }}Response {}
|
||||||
|
|
||||||
|
message {{ .ObjName }} {
|
||||||
|
option (gorm.opts).ormable = true;
|
||||||
|
uint64 id = 1;
|
||||||
|
// add object fields here
|
||||||
|
}
|
15
cmd/cli/templates/vue/AboutView.vue.tmpl
Normal file
15
cmd/cli/templates/vue/AboutView.vue.tmpl
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
<template>
|
||||||
|
<div class="about">
|
||||||
|
<h1>This app was generated using the Masonry CLI tool.</h1>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
@media (min-width: 1024px) {
|
||||||
|
.about {
|
||||||
|
min-height: 100vh;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
8
cmd/cli/templates/vue/HomeView.vue.tmpl
Normal file
8
cmd/cli/templates/vue/HomeView.vue.tmpl
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<main>
|
||||||
|
|
||||||
|
</main>
|
||||||
|
</template>
|
214
cmd/cli/webapp.go
Normal file
214
cmd/cli/webapp.go
Normal file
@ -0,0 +1,214 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
_ "embed"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
//go:embed templates/vue/AboutView.vue.tmpl
|
||||||
|
var aboutViewTemplate string
|
||||||
|
|
||||||
|
//go:embed templates/vue/HomeView.vue.tmpl
|
||||||
|
var homeViewTemplate 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",
|
||||||
|
})
|
||||||
|
|
||||||
|
// 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)
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Println("Exiting the new directory")
|
||||||
|
err = os.Chdir("..")
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("error changing directory | %w", 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
|
||||||
|
}
|
5
go.mod
5
go.mod
@ -2,7 +2,10 @@ module masonry
|
|||||||
|
|
||||||
go 1.23
|
go 1.23
|
||||||
|
|
||||||
require github.com/urfave/cli/v2 v2.27.5
|
require (
|
||||||
|
github.com/urfave/cli/v2 v2.27.5
|
||||||
|
golang.org/x/text v0.22.0
|
||||||
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/cpuguy83/go-md2man/v2 v2.0.5 // indirect
|
github.com/cpuguy83/go-md2man/v2 v2.0.5 // indirect
|
||||||
|
2
go.sum
2
go.sum
@ -6,3 +6,5 @@ github.com/urfave/cli/v2 v2.27.5 h1:WoHEJLdsXr6dDWoJgMq/CboDmyY/8HMMH1fTECbih+w=
|
|||||||
github.com/urfave/cli/v2 v2.27.5/go.mod h1:3Sevf16NykTbInEnD0yKkjDAeZDS0A6bzhBH5hrMvTQ=
|
github.com/urfave/cli/v2 v2.27.5/go.mod h1:3Sevf16NykTbInEnD0yKkjDAeZDS0A6bzhBH5hrMvTQ=
|
||||||
github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 h1:gEOO8jv9F4OT7lGCjxCBTO/36wtF6j2nSip77qHd4x4=
|
github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 h1:gEOO8jv9F4OT7lGCjxCBTO/36wtF6j2nSip77qHd4x4=
|
||||||
github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1/go.mod h1:Ohn+xnUBiLI6FVj/9LpzZWtj1/D6lUovWYBkxHVV3aM=
|
github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1/go.mod h1:Ohn+xnUBiLI6FVj/9LpzZWtj1/D6lUovWYBkxHVV3aM=
|
||||||
|
golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM=
|
||||||
|
golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY=
|
||||||
|
@ -1,26 +0,0 @@
|
|||||||
package daemon
|
|
||||||
|
|
||||||
type DaemonService interface {
|
|
||||||
Start()
|
|
||||||
Stop()
|
|
||||||
}
|
|
||||||
|
|
||||||
type Daemon struct {
|
|
||||||
services []DaemonService
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *Daemon) RegisterDaemonServer(service DaemonService) {
|
|
||||||
d.services = append(d.services, service)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *Daemon) Start() {
|
|
||||||
for _, service := range d.services {
|
|
||||||
go service.Start()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *Daemon) Stop() {
|
|
||||||
for _, service := range d.services {
|
|
||||||
service.Stop()
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,3 +0,0 @@
|
|||||||
# Daemon
|
|
||||||
|
|
||||||
A library that provides a way to register and start multiple long-running services with safe tear-down when they are done.
|
|
@ -1,5 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
|
|
||||||
}
|
|
Reference in New Issue
Block a user