step 2: init command + SQLite storage + migrations + config
- storage.go: DB wrapper, migration runner (in-code SQL strings) - migrations.go: 001_init (nodes + node_meta + indexes) - vault.go: Init() creates .verstak/ dirs, config.yml, index.db - config.go: YAML config read/write - util/uuid.go: UUIDv7 generator - cmd/verstak/main.go: init --vault PATH command - main_test.go: TestInitCreatesVault, TestInitConfigYAML Acceptance: go build ./... pass, go test ./... pass Init creates test-vault with .verstak/index.db + config.yml Repeat Init is safe.
This commit is contained in:
+43
-9
@@ -3,6 +3,9 @@ package main
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"verstak/internal/core/vault"
|
||||
)
|
||||
|
||||
const version = "0.1.0-dev"
|
||||
@@ -17,18 +20,49 @@ func main() {
|
||||
case "--version", "-v":
|
||||
fmt.Println("Verstak", version)
|
||||
case "--help", "-h":
|
||||
fmt.Println("Verstak — local-first working vault")
|
||||
fmt.Println()
|
||||
fmt.Println("Usage: verstak <command> [flags]")
|
||||
fmt.Println()
|
||||
fmt.Println("Commands:")
|
||||
fmt.Println(" init Initialize a new vault")
|
||||
fmt.Println(" --version Show version")
|
||||
fmt.Println(" --help Show this help")
|
||||
usage()
|
||||
case "init":
|
||||
fmt.Println("TODO: init command (step 2)")
|
||||
runInit()
|
||||
default:
|
||||
fmt.Fprintf(os.Stderr, "Unknown command: %s\n", os.Args[1])
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
func usage() {
|
||||
fmt.Println("Verstak — local-first working vault")
|
||||
fmt.Println()
|
||||
fmt.Println("Usage: verstak <command> [flags]")
|
||||
fmt.Println()
|
||||
fmt.Println("Commands:")
|
||||
fmt.Println(" init --vault PATH Initialize a new vault at PATH")
|
||||
fmt.Println(" --version Show version")
|
||||
fmt.Println(" --help Show this help")
|
||||
}
|
||||
|
||||
func runInit() {
|
||||
vaultPath := "."
|
||||
// Parse --vault flag
|
||||
for i := 2; i < len(os.Args); i++ {
|
||||
if os.Args[i] == "--vault" && i+1 < len(os.Args) {
|
||||
vaultPath = os.Args[i+1]
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
abs, err := filepath.Abs(vaultPath)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
if err := vault.Init(abs); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Init failed: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
fmt.Println("Vault initialized at", abs)
|
||||
fmt.Println(" .verstak/index.db")
|
||||
fmt.Println(" .verstak/config.yml")
|
||||
fmt.Println(" spaces/")
|
||||
}
|
||||
|
||||
@@ -0,0 +1,81 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"verstak/internal/core/storage"
|
||||
"verstak/internal/core/vault"
|
||||
)
|
||||
|
||||
func TestInitCreatesVault(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
vaultPath := filepath.Join(dir, "test-vault")
|
||||
|
||||
if err := vault.Init(vaultPath); err != nil {
|
||||
t.Fatalf("Init failed: %v", err)
|
||||
}
|
||||
|
||||
// Check directories exist.
|
||||
for _, sub := range []string{".verstak", ".verstak/trash", ".verstak/history",
|
||||
".verstak/originals", ".verstak/thumbnails", ".verstak/blobs", "spaces"} {
|
||||
p := filepath.Join(vaultPath, sub)
|
||||
if _, err := os.Stat(p); os.IsNotExist(err) {
|
||||
t.Errorf("missing: %s", p)
|
||||
}
|
||||
}
|
||||
|
||||
// Check index.db exists and is a valid SQLite database.
|
||||
dbPath := filepath.Join(vaultPath, ".verstak", "index.db")
|
||||
db, err := storage.Open(dbPath)
|
||||
if err != nil {
|
||||
t.Fatalf("open db: %v", err)
|
||||
}
|
||||
defer db.Close()
|
||||
|
||||
var count int
|
||||
if err := db.QueryRow("SELECT COUNT(*) FROM nodes").Scan(&count); err != nil {
|
||||
t.Fatalf("query nodes: %v", err)
|
||||
}
|
||||
if count != 0 {
|
||||
t.Errorf("expected 0 nodes, got %d", count)
|
||||
}
|
||||
|
||||
// Idempotent: second Init should not fail.
|
||||
if err := vault.Init(vaultPath); err != nil {
|
||||
t.Fatalf("second Init failed: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestInitConfigYAML(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
vaultPath := filepath.Join(dir, "vault")
|
||||
|
||||
if err := vault.Init(vaultPath); err != nil {
|
||||
t.Fatalf("Init: %v", err)
|
||||
}
|
||||
|
||||
cfgPath := filepath.Join(vaultPath, ".verstak", "config.yml")
|
||||
data, err := os.ReadFile(cfgPath)
|
||||
if err != nil {
|
||||
t.Fatalf("read config: %v", err)
|
||||
}
|
||||
|
||||
if len(data) == 0 {
|
||||
t.Fatal("config.yml empty")
|
||||
}
|
||||
// yaml key should be present.
|
||||
if !containsString(string(data), "engine:") {
|
||||
t.Errorf("config missing engine key:\n%s", string(data))
|
||||
}
|
||||
}
|
||||
|
||||
func containsString(s, sub string) bool {
|
||||
for i := 0; i+len(sub) <= len(s); i++ {
|
||||
if s[i:i+len(sub)] == sub {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
Reference in New Issue
Block a user