Public Access
Compare commits
11
Commits
dev
..
8a11de5b0f
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8a11de5b0f | ||
|
|
b7fe8f3f4e | ||
|
|
0bd1550779 | ||
|
|
1dcc135094 | ||
|
|
39678f3ea6 | ||
|
|
230037c5f5 | ||
|
|
1195fdacb5 | ||
|
|
2f1ad93a74 | ||
|
|
b3ea1013bc | ||
|
|
4806186ae6 | ||
|
|
dff28b1a4c |
No files matched your search
@@ -1,5 +1,5 @@
|
|||||||
PKG_NAME := git.fernandovideira.com/FernandoJVideira/draft
|
PKG_NAME := git.fernandovideira.com/FernandoJVideira/draft
|
||||||
VERSION := v0.1.0
|
VERSION := v0.2.0
|
||||||
GO_SOURCES := $(shell find . -name '*.go' -not -path './assets/testdata/*')
|
GO_SOURCES := $(shell find . -name '*.go' -not -path './assets/testdata/*')
|
||||||
|
|
||||||
.PHONY: version build windows win-arm linux mac prepare-embed clean
|
.PHONY: version build windows win-arm linux mac prepare-embed clean
|
||||||
|
|||||||
@@ -85,7 +85,7 @@ draft new client
|
|||||||
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` 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: [`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 clients ship with a Dockerfile that mounts that `~/.npmrc` as a build secret (wired up in the monorepo's `docker-compose.yml`), so container builds resolve the package without baking the token into the image - Nuxt runs its Nitro server, Vue is served by nginx with an SPA fallback and `/api/` proxied to the api container.
|
||||||
|
|
||||||
### Create a New Monorepo Project
|
### Create a New Monorepo Project
|
||||||
|
|
||||||
|
|||||||
Vendored
+1
-1
Submodule assets/testdata/template updated: 41df89929f...2a37943d70.
+76
-9
@@ -68,8 +68,15 @@ func scaffoldClientNuxt(projectDir, serviceName string) error {
|
|||||||
return fmt.Errorf("error running nuxi init: %w", err)
|
return fmt.Errorf("error running nuxi init: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
version, err := pnpmVersion()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("error detecting pnpm version: %w", err)
|
||||||
|
}
|
||||||
replacements := map[string]string{
|
replacements := map[string]string{
|
||||||
"App Template": serviceName,
|
"App Template": serviceName,
|
||||||
|
// The overlay's Dockerfile pins the pnpm that produced the template's
|
||||||
|
// lockfile; the generated project's lockfile comes from the local pnpm.
|
||||||
|
"[email protected]": "pnpm@" + version,
|
||||||
}
|
}
|
||||||
if err := copyEmbeddedDir("embedded/client-nuxt", projectDir, replacements); err != nil {
|
if err := copyEmbeddedDir("embedded/client-nuxt", projectDir, replacements); err != nil {
|
||||||
return fmt.Errorf("error copying client overlay: %w", err)
|
return fmt.Errorf("error copying client overlay: %w", err)
|
||||||
@@ -81,7 +88,7 @@ func scaffoldClientNuxt(projectDir, serviceName string) error {
|
|||||||
|
|
||||||
fmt.Println("📦 Installing dependencies...")
|
fmt.Println("📦 Installing dependencies...")
|
||||||
if err := runCommand(projectDir, "pnpm", "add", "pinia", "@pinia/nuxt",
|
if err := runCommand(projectDir, "pnpm", "add", "pinia", "@pinia/nuxt",
|
||||||
"@fernandojvideira/vue-ui-kit", "lucide-vue-next",
|
"@fernandojvideira/vue-ui-kit", "@lucide/vue",
|
||||||
); err != nil {
|
); err != nil {
|
||||||
return fmt.Errorf("error installing dependencies: %w", err)
|
return fmt.Errorf("error installing dependencies: %w", err)
|
||||||
}
|
}
|
||||||
@@ -121,6 +128,25 @@ func patchNuxtConfig(projectDir, serviceName string) error {
|
|||||||
css: ['~/assets/css/main.css'],
|
css: ['~/assets/css/main.css'],
|
||||||
vite: {
|
vite: {
|
||||||
plugins: [tailwindcss()],
|
plugins: [tailwindcss()],
|
||||||
|
// vue-ui-kit ships raw .vue/.ts source. Left to Vite's dependency
|
||||||
|
// pre-bundler, its index.ts gets bundled with reka-ui inlined while its
|
||||||
|
// .vue files import reka-ui from source - two module instances, so
|
||||||
|
// reka-ui's provide/inject contexts (Dialog, Tabs, Select...) can't find
|
||||||
|
// each other. Excluding it keeps every import on the one reka-ui.
|
||||||
|
optimizeDeps: {
|
||||||
|
exclude: ['@fernandojvideira/vue-ui-kit'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
hooks: {
|
||||||
|
// /login and /signup are the two tabs of one auth card, served by the
|
||||||
|
// same page file. A second route record (not an alias - the router
|
||||||
|
// would treat switching between a route and its alias as a duplicate
|
||||||
|
// navigation and drop it) lets the tab change the URL while NuxtPage
|
||||||
|
// keeps the same component instance thanks to the page's fixed key.
|
||||||
|
'pages:extend'(pages) {
|
||||||
|
const login = pages.find((page) => page.path === '/login')
|
||||||
|
if (login) pages.push({ ...login, name: 'signup', path: '/signup' })
|
||||||
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
app: {
|
app: {
|
||||||
@@ -146,6 +172,19 @@ func patchNuxtConfig(projectDir, serviceName string) error {
|
|||||||
return os.WriteFile(configPath, []byte(finalContent), 0644)
|
return os.WriteFile(configPath, []byte(finalContent), 0644)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func insidePnpmWorkspace(dir string) bool {
|
||||||
|
for {
|
||||||
|
if _, err := os.Stat(filepath.Join(dir, "pnpm-workspace.yaml")); err == nil {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
parent := filepath.Dir(dir)
|
||||||
|
if parent == dir {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
dir = parent
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func scaffoldClientVue(projectDir, serviceName string) error {
|
func scaffoldClientVue(projectDir, serviceName string) error {
|
||||||
parentDir := filepath.Dir(projectDir)
|
parentDir := filepath.Dir(projectDir)
|
||||||
leafName := filepath.Base(projectDir)
|
leafName := filepath.Base(projectDir)
|
||||||
@@ -166,12 +205,19 @@ func scaffoldClientVue(projectDir, serviceName string) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := os.WriteFile(
|
// A standalone client is its own workspace root and needs vue-demi's build
|
||||||
filepath.Join(projectDir, "pnpm-workspace.yaml"),
|
// script approved. Inside a monorepo the root pnpm-workspace.yaml already
|
||||||
[]byte("allowBuilds:\n vue-demi: true\n"),
|
// does that - and writing one here would make apps/client a separate
|
||||||
0644,
|
// workspace, so the installs below would update a nested lockfile and
|
||||||
); err != nil {
|
// leave the root one describing the template's client instead of this one.
|
||||||
return fmt.Errorf("error writing pnpm-workspace.yaml: %w", err)
|
if !insidePnpmWorkspace(parentDir) {
|
||||||
|
if err := os.WriteFile(
|
||||||
|
filepath.Join(projectDir, "pnpm-workspace.yaml"),
|
||||||
|
[]byte("allowBuilds:\n vue-demi: true\n"),
|
||||||
|
0644,
|
||||||
|
); err != nil {
|
||||||
|
return fmt.Errorf("error writing pnpm-workspace.yaml: %w", err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Println("📦 Installing base dependencies...")
|
fmt.Println("📦 Installing base dependencies...")
|
||||||
@@ -203,13 +249,16 @@ func scaffoldClientVue(projectDir, serviceName string) error {
|
|||||||
|
|
||||||
replacements := map[string]string{
|
replacements := map[string]string{
|
||||||
"App Template": serviceName,
|
"App Template": serviceName,
|
||||||
|
// The overlay's Dockerfile pins the pnpm that produced the template's
|
||||||
|
// lockfile; the generated project's lockfile comes from the local pnpm.
|
||||||
|
"[email protected]": "pnpm@" + version,
|
||||||
}
|
}
|
||||||
if err := copyEmbeddedDir("embedded/client-vue", projectDir, replacements); err != nil {
|
if err := copyEmbeddedDir("embedded/client-vue", projectDir, replacements); err != nil {
|
||||||
return fmt.Errorf("error copying client overlay: %w", err)
|
return fmt.Errorf("error copying client overlay: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
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", "@fernandojvideira/vue-ui-kit", "@lucide/vue")
|
||||||
}
|
}
|
||||||
|
|
||||||
// The library's theme brings its own tokens; --success/--warning are added on
|
// The library's theme brings its own tokens; --success/--warning are added on
|
||||||
@@ -269,13 +318,31 @@ func patchViteConfigForTailwind(projectDir string) error {
|
|||||||
)
|
)
|
||||||
newContent = strings.Replace(newContent, "vueDevTools(),", "vueDevTools(),\n tailwindcss(),", 1)
|
newContent = strings.Replace(newContent, "vueDevTools(),", "vueDevTools(),\n tailwindcss(),", 1)
|
||||||
|
|
||||||
if newContent == string(content) {
|
// create-vue's config ends with the resolve.alias block; hang the
|
||||||
|
// optimizeDeps exclusion off its closing brace.
|
||||||
|
newContent = strings.Replace(newContent,
|
||||||
|
" '@': fileURLToPath(new URL('./src', import.meta.url)),\n },\n },\n})",
|
||||||
|
" '@': fileURLToPath(new URL('./src', import.meta.url)),\n },\n },\n"+viteOptimizeDepsExclude+"})",
|
||||||
|
1,
|
||||||
|
)
|
||||||
|
|
||||||
|
if newContent == string(content) || !strings.Contains(newContent, "optimizeDeps") {
|
||||||
return fmt.Errorf("could not find expected anchors in vite.config.ts")
|
return fmt.Errorf("could not find expected anchors in vite.config.ts")
|
||||||
}
|
}
|
||||||
|
|
||||||
return os.WriteFile(configPath, []byte(newContent), 0644)
|
return os.WriteFile(configPath, []byte(newContent), 0644)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// vue-ui-kit ships raw .vue/.ts source. Left to Vite's dependency pre-bundler,
|
||||||
|
// its index.ts gets bundled with reka-ui inlined while its .vue files import
|
||||||
|
// reka-ui from source - two module instances, so reka-ui's provide/inject
|
||||||
|
// contexts (Dialog, Tabs, Select...) can't find each other. Excluding it keeps
|
||||||
|
// every import on the one reka-ui.
|
||||||
|
const viteOptimizeDepsExclude = ` optimizeDeps: {
|
||||||
|
exclude: ['@fernandojvideira/vue-ui-kit'],
|
||||||
|
},
|
||||||
|
`
|
||||||
|
|
||||||
func patchTsconfigAlias(projectDir string) error {
|
func patchTsconfigAlias(projectDir string) error {
|
||||||
configPath := filepath.Join(projectDir, "tsconfig.json")
|
configPath := filepath.Join(projectDir, "tsconfig.json")
|
||||||
content, err := os.ReadFile(configPath)
|
content, err := os.ReadFile(configPath)
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ var jobs = []syncJob{
|
|||||||
templateName: "client-nuxt",
|
templateName: "client-nuxt",
|
||||||
sourceDir: "assets/testdata/template/apps/client",
|
sourceDir: "assets/testdata/template/apps/client",
|
||||||
targetDir: "assets/embedded/client-nuxt",
|
targetDir: "assets/embedded/client-nuxt",
|
||||||
includePrefixes: []string{"app/", "public/"},
|
includePrefixes: []string{"app/", "public/", "Dockerfile"},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
templateName: "client-vue",
|
templateName: "client-vue",
|
||||||
@@ -52,11 +52,12 @@ var jobs = []syncJob{
|
|||||||
"src/stores/",
|
"src/stores/",
|
||||||
"src/composables/",
|
"src/composables/",
|
||||||
"src/lib/api-client.ts",
|
"src/lib/api-client.ts",
|
||||||
"src/components/AppBrand.vue",
|
"src/components/",
|
||||||
"src/components/AppLayout.vue",
|
|
||||||
"src/views/",
|
"src/views/",
|
||||||
"src/shared/",
|
"src/shared/",
|
||||||
".env.example",
|
".env.example",
|
||||||
|
"Dockerfile",
|
||||||
|
"nginx.conf.template",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in new issue
Block a user