Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,5 @@ main
main.exe
gothicframework
gothicframework.exe
.claude/
.claude/
coverage.out
84 changes: 39 additions & 45 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,69 +1,63 @@
<img alt="background doc" src="Doc/Assets/gothic-hero.png" width="100%"/>
<img alt="Gothic Framework" src="Doc/Assets/gothic-hero.png" width="100%"/>

[![CI](https://github.com/felipegenef/gothicframework/actions/workflows/ci.yml/badge.svg)](https://github.com/felipegenef/gothicframework/actions/workflows/ci.yml)
[![codecov](https://codecov.io/gh/felipegenef/gothicframework/branch/main/graph/badge.svg)](https://codecov.io/gh/felipegenef/gothicframework)
# Gothic Framework has moved 🚚

<!-- <p align="center">
<img alt="logo" src="Doc/Assets/gothic-g-no-bg.png" width="100"/>
</p>
> [!IMPORTANT]
> ## ⚠️ Active development moved to [github.com/gothicframework](https://github.com/gothicframework)
>
> This repository (`felipegenef/gothicframework`) holds the **v1 / v2** history and is **no longer actively developed**. Gothic **v3** was rewritten and split into focused modules under a new organization, and the CLI was renamed **`gothicframework` → `gothic`**.

<h1 align="center">Gothic Framework</h1> -->

## 🚀 Build Modern Web Apps with Ease

**Gothic Framework** is a developer-first toolset built to help you craft fast, scalable, and modern web applications using the **GOTTH stack**:
**Golang**, **TailwindCSS**, **Templ**, and **HTMX**.

Inspired by frameworks like **Next.js**, Gothic Framework brings powerful full-stack features to Go developers — including edge-ready architecture, SEO enhancements, and a fantastic development experience (DX).

[![See our docs](https://img.shields.io/badge/See_our_docs-ec4899?style=for-the-badge)](https://gothicframework.com)
**Gothic Framework** is a developer-first toolset for building fast, scalable, modern web apps in Go with the **GOTTH stack** — **Go**, **TailwindCSS**, **Templ**, and **HTMX**.

---

## 🧠 Why Gothic?
## Where everything lives now (v3)

Gothic Framework was built to make **Go-based web development** simpler, faster, and more productive without sacrificing performance or modern UX standards.
| Module | Repo | What it is |
|---|---|---|
| **CLI** | [`gothicframework/cli`](https://github.com/gothicframework/cli) | The `gothic` command (scaffold, dev, build, deploy) |
| **Core** | [`gothicframework/core`](https://github.com/gothicframework/core) | Runtime library: file-based routing, caching/ISR, WASM runtime, assets |
| **Components** | [`gothicframework/components`](https://github.com/gothicframework/components) | Reusable Templ components (`RuntimeScripts`, `Styles`, `OptimizedImage`, …) |
| **Middlewares** | [`gothicframework/middlewares`](https://github.com/gothicframework/middlewares) | The single chi middleware that wires the whole runtime |

Whether you're launching a SaaS, building internal tools, or prototyping something new — Gothic Framework has your back.
### Install the v3 CLI

---

## ⚙️ Effortless Deployment, Powerful Infrastructure
```bash
go install github.com/gothicframework/cli/v3/cmd/gothic@latest
gothic init github.com/you/my-app
```

Gothic Framework comes with built-in AWS SAM integration, making deployment to the cloud **as simple as a single command**. Focus on your app’s features and logic — not on cloud setup or DevOps headaches.

<!-- <img alt="infrastructure" src="Doc/Assets/Infrastructure.jpeg" width="100%"/> -->
The libraries version independently of the CLI — you never add them by hand. `gothic init` scaffolds a project that imports them at the right versions.

---

## 🔥 Features Inspired by Next.js

### 🖼️ SEO-Optimized Image Loading
Like the Next.js Image component, Gothic Framework lazy-loads images for better performance and user experience. A low-res placeholder is swapped with the full image after load — smooth and fast.
## Already on v2? Migrate in one command

### 🌐 Static CDN Caching
Static pages can be cached at **CloudFront edge locations** for blazing-fast performance — with cache TTLs of up to **1 year**!
From your existing v2 project root, using this (final) v2 CLI:

### ♻️ Incremental Static Regeneration (ISR)
Rebuild only the parts of your app that change. Gothic Framework lets you specify revalidation intervals — from seconds to **a full year**.
```bash
gothicframework migrate-v3
```

### 🙅‍♀️ Custom 404 Pages
Craft personalized 404 pages to guide users gracefully when something goes missing.
That command installs the new `gothic` CLI, runs the migration for you, and then hands you over to the new command name. Equivalently, if you already installed the v3 CLI:

### ⚡ Link Prefetching
Just like in Next.js, Gothic Framework prefetches pages on **mouseover** so they load instantly on click. That’s snappy navigation!
```bash
go install github.com/gothicframework/cli/v3/cmd/gothic@latest
gothic migrate-v3 # add --dry-run to preview
```

### 🔁 Hot Reloading
Make a change, save, and see it live instantly. No rebuilds. No restarts. Just code.
After migrating, use **`gothic`** instead of **`gothicframework`**:

### 📡 API Routes with JSON
Define backend logic with ease. Gothic Framework supports API routes for JSON payloads out of the box — perfect for building APIs alongside your frontend pages.
| Before (v2) | After (v3) |
|---|---|
| `gothicframework dev` | `gothic dev` |
| `gothicframework build` | `gothic build` |
| `gothicframework deploy` | `gothic deploy` |

### 🧩 File-Based Routing
Drop your components into `/pages`, `/components`, or `/api` — and Gothic automatically maps them to routes. Simple and intuitive.
See the **[v3 CLI repo](https://github.com/gothicframework/cli)** for the full list of breaking changes and new features (OpenTofu-based deploys, Go-based config, the rearchitected WASM runtime, and more).

---

## 🧪 Try Gothic Today
## v1 / v2 reference

Take your Go projects to the next level with a framework that speaks your language and supports modern best practices.
The last v2 release (**v2.18.0**) is this repository at its final state: the full v2 toolchain **plus** the `migrate-v3` bridge above. Older tags remain available for historical reference. For anything new, start with the [v3 CLI](https://github.com/gothicframework/cli).
145 changes: 145 additions & 0 deletions cmd/migrate_v3.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
/*
Copyright © 2025 NAME HERE <EMAIL ADDRESS>
*/
package cmd

import (
"fmt"
"os"
"os/exec"
"path/filepath"
"runtime"
"strings"

"github.com/spf13/cobra"
)

// newCLIInstallPath is the `go install` target for the v3 CLI. Gothic v3 no
// longer lives in this repository (github.com/felipegenef/gothicframework); it
// was split into github.com/gothicframework/* and the binary was renamed
// gothicframework -> gothic. This last v2 release only bridges to it.
const newCLIInstallPath = "github.com/gothicframework/cli/v3/cmd/gothic@latest"

var (
migrateV3DryRun bool
migrateV3Path string
)

// migrateV3Cmd is a BRIDGE, not an in-process migration. The real v2->v3
// migration logic lives in the new `gothic` binary; this command installs that
// binary and delegates to it, then tells the user the command name changed.
var migrateV3Cmd = &cobra.Command{
Use: "migrate-v3",
Short: "Migrate this project to Gothic v3 (new github.com/gothicframework repo + gothic CLI)",
Long: `Gothic v3 moved to a new home: github.com/gothicframework, and the CLI was
renamed from "gothicframework" to "gothic".

This command bridges you there. It:

1. Installs the v3 CLI: go install ` + newCLIInstallPath + `
2. Runs: gothic migrate-v3 [--dry-run] [--path <dir>]
3. From here on, you use "gothic" instead of "gothicframework".

The actual migration is performed by the new gothic binary, so it always
reflects the latest v3 conversion logic.`,
RunE: func(cmd *cobra.Command, args []string) error {
return runMigrateV3Bridge()
},
}

func init() {
migrateV3Cmd.Flags().BoolVar(&migrateV3DryRun, "dry-run", false, "Preview the migration without writing files")
migrateV3Cmd.Flags().StringVar(&migrateV3Path, "path", ".", "Project root to migrate")
rootCmd.AddCommand(migrateV3Cmd)
}

func runMigrateV3Bridge() error {
fmt.Println("▸ Gothic v3 moved to github.com/gothicframework — installing the new 'gothic' CLI…")
fmt.Printf(" go install %s\n\n", newCLIInstallPath)

// 1. Install the new gothic CLI.
install := exec.Command("go", "install", newCLIInstallPath)
install.Stdout = os.Stdout
install.Stderr = os.Stderr
install.Env = os.Environ()
if err := install.Run(); err != nil {
return fmt.Errorf("failed to install the new gothic CLI (%s): %w\n"+
"If the new repositories are private, make sure your git/GOPRIVATE access is configured.", newCLIInstallPath, err)
}

// 2. Locate the freshly installed binary.
gothicBin, err := locateGothic()
if err != nil {
return err
}

// 3. Delegate the real migration to the new binary, forwarding our flags.
delegated := []string{"migrate-v3"}
if migrateV3DryRun {
delegated = append(delegated, "--dry-run")
}
if migrateV3Path != "" && migrateV3Path != "." {
delegated = append(delegated, "--path", migrateV3Path)
}
fmt.Printf("\n▸ Running: gothic %s\n\n", strings.Join(delegated, " "))
run := exec.Command(gothicBin, delegated...)
run.Stdout = os.Stdout
run.Stderr = os.Stderr
run.Stdin = os.Stdin
run.Env = os.Environ()
if err := run.Run(); err != nil {
return fmt.Errorf("gothic migrate-v3 failed: %w", err)
}

// 4. The command name changed — make sure the user knows.
fmt.Println()
fmt.Println("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━")
fmt.Println(" ✓ Migrated to Gothic v3")
fmt.Println()
fmt.Println(" From now on use the new CLI: 'gothic' (not 'gothicframework')")
fmt.Println(" gothic dev # was: gothicframework dev")
fmt.Println(" gothic build # was: gothicframework build")
fmt.Println(" gothic deploy # was: gothicframework deploy")
fmt.Println()
fmt.Println(" If 'gothic' isn't found, add your Go bin dir to PATH:")
fmt.Println(" export PATH=\"$PATH:$(go env GOPATH)/bin\"")
fmt.Println("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━")
return nil
}

// locateGothic finds the gothic binary installed by `go install`, preferring the
// PATH, then GOBIN, then GOPATH/bin.
func locateGothic() (string, error) {
bin := "gothic"
if runtime.GOOS == "windows" {
bin = "gothic.exe"
}
if p, err := exec.LookPath(bin); err == nil {
return p, nil
}
if gobin := goEnv("GOBIN"); gobin != "" {
if p := filepath.Join(gobin, bin); fileExists(p) {
return p, nil
}
}
if gopath := goEnv("GOPATH"); gopath != "" {
if p := filepath.Join(gopath, "bin", bin); fileExists(p) {
return p, nil
}
}
return "", fmt.Errorf("installed the new CLI but couldn't find the '%s' binary on PATH; "+
"add \"$(go env GOPATH)/bin\" to your PATH and run 'gothic migrate-v3'", bin)
}

func goEnv(key string) string {
out, err := exec.Command("go", "env", key).Output()
if err != nil {
return ""
}
return strings.TrimSpace(string(out))
}

func fileExists(p string) bool {
info, err := os.Stat(p)
return err == nil && !info.IsDir()
}
61 changes: 61 additions & 0 deletions cmd/migrate_v3_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
package cmd

import (
"os"
"path/filepath"
"runtime"
"strings"
"testing"
)

func TestMigrateV3FileExists(t *testing.T) {
dir := t.TempDir()
f := filepath.Join(dir, "x")
if err := os.WriteFile(f, []byte("hi"), 0o644); err != nil {
t.Fatal(err)
}
if !fileExists(f) {
t.Errorf("fileExists(%q) = false, want true", f)
}
if fileExists(filepath.Join(dir, "missing")) {
t.Error("fileExists on a missing file = true, want false")
}
if fileExists(dir) {
t.Error("fileExists on a directory = true, want false")
}
}

func TestMigrateV3GoEnv(t *testing.T) {
got := goEnv("GOPATH")
if got == "" {
t.Fatal("goEnv(GOPATH) = empty, want a path")
}
if got != strings.TrimSpace(got) {
t.Errorf("goEnv(GOPATH) not trimmed: %q", got)
}
}

// TestMigrateV3LocateGothicOnPath puts a fake `gothic` first on PATH and asserts
// locateGothic resolves it via exec.LookPath.
func TestMigrateV3LocateGothicOnPath(t *testing.T) {
if runtime.GOOS == "windows" {
t.Skip("exec-bit / .exe resolution differs on Windows")
}
dir := t.TempDir()
bin := filepath.Join(dir, "gothic")
if err := os.WriteFile(bin, []byte("#!/bin/sh\n"), 0o755); err != nil {
t.Fatal(err)
}
t.Setenv("PATH", dir+string(os.PathListSeparator)+os.Getenv("PATH"))

got, err := locateGothic()
if err != nil {
t.Fatalf("locateGothic: %v", err)
}
if filepath.Base(got) != "gothic" {
t.Errorf("locateGothic() = %q, want a path ending in 'gothic'", got)
}
if !fileExists(got) {
t.Errorf("locateGothic() returned a non-existent path %q", got)
}
}
2 changes: 1 addition & 1 deletion cmd/version.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import (
"github.com/spf13/cobra"
)

var CURRENT_VERSION string = "v2.17.4"
var CURRENT_VERSION string = "v2.18.0"

// versionCmd represents the version command
var versionCmd = &cobra.Command{
Expand Down
Loading