1 Commits
Author SHA1 Message Date
FernandoJVideira dff28b1a4c Merge pull request 'Version 0.1.0' (#3) from dev into main
Reviewed-on: #3
2026-08-25 20:42:49 +00:00
6 changed files with 85 additions and 99 deletions

No files matched your search

-1
View File
@@ -31,4 +31,3 @@ go.work.sum
# Binary # Binary
/draft* /draft*
prepare-embed-stamp
+17 -22
View File
@@ -1,30 +1,25 @@
PKG_NAME := git.fernandovideira.com/FernandoJVideira/draft PKG_NAME="git.fernandovideira.com/FernandoJVideira/draft"
VERSION := v0.1.0
GO_SOURCES := $(shell find . -name '*.go' -not -path './assets/testdata/*')
.PHONY: version build windows win-arm linux mac prepare-embed clean
version: version:
@go run -ldflags "-X ${PKG_NAME}/cmd.version=${VERSION}" ./main.go version @go run -ldflags "-X ${PKG_NAME}/cmd.version=v0.1.0" ./main.go version
windows: ; @$(MAKE) build GOOS=windows GOARCH=amd64 OUTPUT=draft_windows_amd64.exe build: prepare-embed
win-arm: ; @$(MAKE) build GOOS=windows GOARCH=arm64 OUTPUT=draft_windows_arm64.exe
linux: ; @$(MAKE) build GOOS=linux GOARCH=amd64 OUTPUT=draft_linux_amd64
mac: ; @$(MAKE) build GOOS=darwin GOARCH=arm64 OUTPUT=draft_darwin_arm64
build: $(OUTPUT)
$(OUTPUT): prepare-embed-stamp $(GO_SOURCES)
@echo "Building for $(GOOS)/$(GOARCH)..." @echo "Building for $(GOOS)/$(GOARCH)..."
@GOOS=$(GOOS) GOARCH=$(GOARCH) go build -ldflags "-X ${PKG_NAME}/cmd.version=${VERSION}" -o $(OUTPUT) ./main.go @GOOS=$(GOOS) GOARCH=$(GOARCH) go build -ldflags "-X ${PKG_NAME}/cmd.version=v0.1.0" -o $(OUTPUT) ./main.go
prepare-embed-stamp: windows:
@$(MAKE) build GOOS=windows GOARCH=amd64 OUTPUT=draft_windows_amd64.exe
win-arm:
@$(MAKE) build GOOS=windows GOARCH=arm64 OUTPUT=draft_windows_arm64.exe
linux:
@$(MAKE) build GOOS=linux GOARCH=amd64 OUTPUT=draft_linux_amd64
mac:
@$(MAKE) build GOOS=darwin GOARCH=arm64 OUTPUT=draft_darwin_arm64
prepare-embed:
@git submodule update --init --recursive @git submodule update --init --recursive
@mkdir -p assets/embedded @mkdir -p assets/embedded
@GOOS="" GOARCH="" go run tools/sync_templates.go @GOOS="" GOARCH="" go run tools/sync_templates.go
@touch $@
prepare-embed: prepare-embed-stamp
clean:
@rm -f draft_windows_amd64.exe draft_windows_arm64.exe draft_linux_amd64 draft_darwin_arm64 prepare-embed-stamp
+2 -2
View File
@@ -82,10 +82,10 @@ draft new client
1. Prompts for a project name 1. Prompts for a project name
2. Prompts you to pick a framework — **Nuxt** or **Vue** 2. Prompts you to pick a framework — **Nuxt** or **Vue**
3. Scaffolds the base app via the official tooling (`nuxi init` for Nuxt, `create-vue` for Vue), so the generated boilerplate, dependency versions, and lockfile are always current rather than pinned by Draft 3. Scaffolds the base app via the official tooling (`nuxi init` for Nuxt, `create-vue` + `shadcn-vue` for Vue), so the generated boilerplate, dependency versions, and lockfile are always current rather than pinned by Draft
4. Layers a Draft-maintained overlay on top: an auth store (login/logout/register/token refresh), route guards, API composable, and pages for login/signup/profile/organizations/email verification/invites — all wired up to talk to a Draft-generated API out of the box 4. Layers a Draft-maintained overlay on top: an auth store (login/logout/register/token refresh), route guards, API composable, and pages for login/signup/profile/organizations/email verification/invites — all wired up to talk to a Draft-generated API out of the box
Both frameworks produce a comparable app: [`vue-ui-kit`](https://git.fernandovideira.com/FernandoJVideira/vue-ui-kit) (installed from its own Gitea npm registry - see its README for the one-time `.npmrc` token setup) + Tailwind, the same component library and theme tokens on either framework. Both frameworks produce a comparable app: Nuxt UI + Tailwind for Nuxt, shadcn-vue + Tailwind for Vue.
### Create a New Monorepo Project ### Create a New Monorepo Project
+63 -71
View File
@@ -54,14 +54,10 @@ func genClient(cmd *cobra.Command, args []string) {
} }
func scaffoldClientNuxt(projectDir, serviceName string) error { func scaffoldClientNuxt(projectDir, serviceName string) error {
// "minimal", not "ui" - the Nuxt UI starter is what we'd otherwise get
// for free, but this project uses vue-ui-kit as its component library
// instead, wired up by hand below (Tailwind v4 + the overlay's own
// app/assets/css/main.css already import it).
if err := runCommand("", "npx", "nuxi@latest", "init", projectDir, if err := runCommand("", "npx", "nuxi@latest", "init", projectDir,
"--no-install", "--no-install",
"--packageManager=pnpm", "--packageManager=pnpm",
"-t", "minimal", "-t", "ui",
"--gitInit=false", "--gitInit=false",
"-M", "@pinia/nuxt", "-M", "@pinia/nuxt",
); err != nil { ); err != nil {
@@ -80,21 +76,7 @@ func scaffoldClientNuxt(projectDir, serviceName string) error {
} }
fmt.Println("📦 Installing dependencies...") fmt.Println("📦 Installing dependencies...")
if err := runCommand(projectDir, "pnpm", "add", "pinia", "@pinia/nuxt", return runCommand(projectDir, "pnpm", "add", "pinia", "@pinia/nuxt")
"@fernandojvideira/vue-ui-kit", "lucide-vue-next",
); err != nil {
return fmt.Errorf("error installing dependencies: %w", err)
}
// typescript itself: the "minimal" starter (unlike "ui", and unlike
// create-vue's own output) doesn't include it, but @vue/compiler-sfc
// needs a real TypeScript install to resolve cross-file type extends -
// exactly what several vue-ui-kit components' Props interfaces do.
// Pinned below 7.x (matching create-vue's own "~6.0.0" default) since
// @vue/compiler-sfc's cross-file resolution isn't compatible with
// TypeScript 7 yet - it fails with "No fs option provided to
// compileScript in non-Node environment" on every extended interface.
return runCommand(projectDir, "pnpm", "add", "-D", "tailwindcss", "@tailwindcss/vite", "typescript@~6.0.0", "vue-tsc")
} }
func patchNuxtConfig(projectDir, serviceName string) error { func patchNuxtConfig(projectDir, serviceName string) error {
@@ -104,25 +86,7 @@ func patchNuxtConfig(projectDir, serviceName string) error {
return fmt.Errorf("error reading nuxt.config.ts: %w", err) return fmt.Errorf("error reading nuxt.config.ts: %w", err)
} }
// The "minimal" starter registers no Tailwind plugin and no CSS entry
// point on its own (the "ui" starter did both, bundled inside @nuxt/ui) -
// both are added explicitly here since the overlay's own main.css needs
// to actually run.
newContent := strings.Replace(string(content),
"// https://nuxt.com/docs/api/configuration/nuxt-config\n",
"// https://nuxt.com/docs/api/configuration/nuxt-config\nimport tailwindcss from '@tailwindcss/vite'\n\n",
1,
)
if newContent == string(content) {
return fmt.Errorf("could not find the file-header anchor in nuxt.config.ts")
}
insertion := fmt.Sprintf(` insertion := fmt.Sprintf(`
css: ['~/assets/css/main.css'],
vite: {
plugins: [tailwindcss()],
},
app: { app: {
head: { head: {
title: %q, title: %q,
@@ -138,12 +102,12 @@ func patchNuxtConfig(projectDir, serviceName string) error {
}, },
`, serviceName, serviceName) `, serviceName, serviceName)
finalContent := strings.Replace(newContent, "defineNuxtConfig({", "defineNuxtConfig({"+insertion, 1) newContent := strings.Replace(string(content), "defineNuxtConfig({", "defineNuxtConfig({"+insertion, 1)
if finalContent == newContent { if newContent == string(content) {
return fmt.Errorf("could not find defineNuxtConfig({ anchor in nuxt.config.ts") return fmt.Errorf("could not find defineNuxtConfig({ anchor in nuxt.config.ts")
} }
return os.WriteFile(configPath, []byte(finalContent), 0644) return os.WriteFile(configPath, []byte(newContent), 0644)
} }
func scaffoldClientVue(projectDir, serviceName string) error { func scaffoldClientVue(projectDir, serviceName string) error {
@@ -187,8 +151,8 @@ func scaffoldClientVue(projectDir, serviceName string) error {
if err := os.MkdirAll(assetsDir, 0755); err != nil { if err := os.MkdirAll(assetsDir, 0755); err != nil {
return fmt.Errorf("error creating src/assets: %w", err) return fmt.Errorf("error creating src/assets: %w", err)
} }
if err := os.WriteFile(filepath.Join(assetsDir, "main.css"), []byte(vueUiKitMainCSS), 0644); err != nil { if err := os.WriteFile(filepath.Join(assetsDir, "main.css"), []byte("@import \"tailwindcss\";\n"), 0644); err != nil {
return fmt.Errorf("error writing main.css: %w", err) return fmt.Errorf("error writing main.css stub: %w", err)
} }
if err := patchViteConfigForTailwind(projectDir); err != nil { if err := patchViteConfigForTailwind(projectDir); err != nil {
@@ -197,6 +161,23 @@ func scaffoldClientVue(projectDir, serviceName string) error {
if err := patchTsconfigAlias(projectDir); err != nil { if err := patchTsconfigAlias(projectDir); err != nil {
return err return err
} }
fmt.Println("🎨 Setting up shadcn-vue...")
if err := runCommand(projectDir, "pnpm", "dlx", "shadcn-vue@latest", "init",
"-t", "vite", "-b", "neutral", "--icon-library", "lucide", "-y", "-d",
); err != nil {
return fmt.Errorf("error running shadcn-vue init: %w", err)
}
if err := runCommand(projectDir, "pnpm", "dlx", "shadcn-vue@latest", "add",
"button", "input", "label", "select", "-y",
); err != nil {
return fmt.Errorf("error adding shadcn-vue components: %w", err)
}
if err := patchMainCssTokens(projectDir); err != nil {
return err
}
if err := patchEslintConfig(projectDir); err != nil { if err := patchEslintConfig(projectDir); err != nil {
return err return err
} }
@@ -209,36 +190,9 @@ func scaffoldClientVue(projectDir, serviceName string) error {
} }
fmt.Println("📦 Installing remaining dependencies...") fmt.Println("📦 Installing remaining dependencies...")
return runCommand(projectDir, "pnpm", "add", "ofetch", "@fernandojvideira/vue-ui-kit", "lucide-vue-next") return runCommand(projectDir, "pnpm", "add", "ofetch", "vue-sonner")
} }
// The library's theme brings its own tokens; --success/--warning are added on
// top the same way a consumer would (see vue-ui-kit's THEMING.md) since the
// base theme deliberately doesn't define them.
const vueUiKitMainCSS = `@import "tailwindcss";
@source "../../node_modules/@fernandojvideira/vue-ui-kit/src";
@import "@fernandojvideira/vue-ui-kit/theme.css";
:root {
--success: 142 71% 35%;
--success-foreground: 0 0% 98%;
--warning: 38 92% 40%;
--warning-foreground: 0 0% 98%;
}
.dark {
--success: 142 71% 45%;
--success-foreground: 0 0% 9%;
--warning: 38 92% 55%;
--warning-foreground: 0 0% 9%;
}
@theme inline {
--color-success: hsl(var(--success));
--color-success-foreground: hsl(var(--success-foreground));
--color-warning: hsl(var(--warning));
--color-warning-foreground: hsl(var(--warning-foreground));
}
`
func patchPackageManagerField(projectDir, version string) error { func patchPackageManagerField(projectDir, version string) error {
pkgPath := filepath.Join(projectDir, "package.json") pkgPath := filepath.Join(projectDir, "package.json")
content, err := os.ReadFile(pkgPath) content, err := os.ReadFile(pkgPath)
@@ -299,6 +253,44 @@ func patchTsconfigAlias(projectDir string) error {
return os.WriteFile(configPath, []byte(newContent), 0644) return os.WriteFile(configPath, []byte(newContent), 0644)
} }
func patchMainCssTokens(projectDir string) error {
cssPath := filepath.Join(projectDir, "src", "assets", "main.css")
content, err := os.ReadFile(cssPath)
if err != nil {
return fmt.Errorf("error reading main.css: %w", err)
}
s := string(content)
s2 := strings.Replace(s,
"--color-background: var(--background);",
"--color-background: var(--background);\n --color-success: var(--success);\n --color-success-foreground: var(--success-foreground);\n --color-warning: var(--warning);\n --color-warning-foreground: var(--warning-foreground);",
1,
)
if s2 == s {
return fmt.Errorf("could not find @theme anchor in main.css")
}
s3 := strings.Replace(s2,
"}\n\n.dark {",
" --success: oklch(0.6 0.15 145);\n --success-foreground: oklch(0.985 0 0);\n --warning: oklch(0.75 0.15 80);\n --warning-foreground: oklch(0.145 0 0);\n}\n\n.dark {",
1,
)
if s3 == s2 {
return fmt.Errorf("could not find :root close anchor in main.css")
}
s4 := strings.Replace(s3,
"}\n\n@layer base {",
" --success: oklch(0.65 0.15 145);\n --success-foreground: oklch(0.145 0 0);\n --warning: oklch(0.8 0.15 80);\n --warning-foreground: oklch(0.145 0 0);\n}\n\n@layer base {",
1,
)
if s4 == s3 {
return fmt.Errorf("could not find .dark close anchor in main.css")
}
return os.WriteFile(cssPath, []byte(s4), 0644)
}
func patchEslintConfig(projectDir string) error { func patchEslintConfig(projectDir string) error {
configPath := filepath.Join(projectDir, "eslint.config.ts") configPath := filepath.Join(projectDir, "eslint.config.ts")
content, err := os.ReadFile(configPath) content, err := os.ReadFile(configPath)
+2 -2
View File
@@ -9,8 +9,8 @@ import (
var rootCmd = &cobra.Command{ var rootCmd = &cobra.Command{
Use: "draft", Use: "draft",
Short: "Draft is a CLI tool for bootstrapping Go APIs, clients, and monorepos", Short: "Draft is a CLI tool for bootstrapping Go APIs",
Long: `Draft is a CLI tool built by LKResuts for internal usage to bootstrap Go APIs, Nuxt/Vue clients, and full-stack monorepos combining both`, Long: `Draft is a CLI tool built by LKResuts for internal usage to bootstrap Go APIs`,
} }
func Execute() { func Execute() {