Initial commit

This commit is contained in:
Zhongwei Li
2025-11-30 08:37:58 +08:00
commit f0a4617f0c
38 changed files with 4166 additions and 0 deletions

View File

@@ -0,0 +1,63 @@
.PHONY: setup build run clean lint format test
# Build variables
VERSION ?= $(shell git describe --tags --always --dirty 2>/dev/null || echo "dev")
COMMIT := $(shell git rev-parse --short HEAD 2>/dev/null || echo "unknown")
BUILD_DATE := $(shell date -u +"%Y-%m-%dT%H:%M:%SZ")
LDFLAGS := -X main.version=$(VERSION) -X main.commit=$(COMMIT) -X main.date=$(BUILD_DATE)
# Note: Static linking is not used because SQLite requires CGO, which links dynamically
# to system libraries. Attempting static linking causes getaddrinfo warnings and
# potential runtime compatibility issues.
# Default target
all: build
# Install development tools
setup:
@echo "Installing development tools..."
@go install mvdan.cc/gofumpt@latest
@go install github.com/golangci/golangci-lint/cmd/golangci-lint@latest
@echo "✅ Development tools installed"
# Build the application
build:
@echo "Building {{PROJECT_NAME}}..."
@CGO_ENABLED=1 go build -ldflags "$(LDFLAGS)" -o {{PROJECT_NAME}} .
@echo "✅ Built: {{PROJECT_NAME}}"
# Run the application
run: build
./{{PROJECT_NAME}}
# Clean build artifacts
clean:
@rm -f {{PROJECT_NAME}}
@rm -f *.db
@echo "✅ Cleaned"
# Lint code
lint:
@test -f $(HOME)/go/bin/golangci-lint || { \
echo "❌ golangci-lint not found. Install with: make setup"; \
exit 1; \
}
@echo "Running linters..."
@$(HOME)/go/bin/golangci-lint run --timeout 5m
@echo "✅ Lint complete"
# Format code
format:
@go fmt ./...
@test -f $(HOME)/go/bin/gofumpt || { \
echo "❌ gofumpt not found. Install with: make setup"; \
exit 1; \
}
@$(HOME)/go/bin/gofumpt -l -w .
@echo "✅ Format complete"
# Run tests
test:
@echo "Running tests..."
@go test ./...
@echo "✅ Tests complete"

View File

@@ -0,0 +1,45 @@
name: CI
on:
pull_request:
branches: [ main ]
workflow_call:
jobs:
lint-test:
name: CI (Lint, Test)
runs-on: ubuntu-latest
steps:
- name: Check for [noci] in commit message
id: check_commit
run: |
if [[ "${{ github.event.head_commit.message }}" == "[noci]"* ]]; then
echo "skip=true" >> $GITHUB_OUTPUT
else
echo "skip=false" >> $GITHUB_OUTPUT
fi
- name: Checkout code
if: steps.check_commit.outputs.skip != 'true'
uses: actions/checkout@v4
- name: Set up Go
if: steps.check_commit.outputs.skip != 'true'
uses: actions/setup-go@v5
with:
go-version: '1.23'
- name: Download dependencies
if: steps.check_commit.outputs.skip != 'true'
run: go mod download
- name: Run golangci-lint
if: steps.check_commit.outputs.skip != 'true'
uses: golangci/golangci-lint-action@v6
with:
version: latest
args: --timeout=5m
- name: Run tests
if: steps.check_commit.outputs.skip != 'true'
run: go test -race ./...

View File

@@ -0,0 +1,36 @@
package cmd
import (
"github.com/spf13/cobra"
"github.com/spf13/viper"
)
// {{COMMAND_NAME}}Cmd represents the {{COMMAND_NAME}} command
var {{COMMAND_NAME}}Cmd = &cobra.Command{
Use: "{{COMMAND_NAME}}",
Short: "A brief description of the {{COMMAND_NAME}} command",
Long: `A longer description of the {{COMMAND_NAME}} command that explains
what it does and how to use it.
Example usage:
{{PROJECT_NAME}} {{COMMAND_NAME}} [flags]`,
RunE: func(cmd *cobra.Command, args []string) error {
log := GetLogger()
cfg := GetConfig()
log.Info("Running {{COMMAND_NAME}} command")
// TODO: Implement command logic here
return nil
},
}
func init() {
rootCmd.AddCommand({{COMMAND_NAME}}Cmd)
// Add command-specific flags here
// Example:
// {{COMMAND_NAME}}Cmd.Flags().StringP("option", "o", "", "An option for this command")
// _ = viper.BindPFlag("{{COMMAND_NAME}}.option", {{COMMAND_NAME}}Cmd.Flags().Lookup("option"))
}

View File

@@ -0,0 +1,17 @@
package config
// Config holds application configuration
type Config struct {
// Core settings
Database string
Verbose bool
Debug bool
LogJSON bool
// Add command-specific configuration fields here as needed
// Example:
// Fetch struct {
// Concurrency int
// Timeout time.Duration
// }
}

View File

@@ -0,0 +1,21 @@
# Configuration file for {{PROJECT_NAME}}
# Copy this to {{PROJECT_NAME}}.yaml and customize as needed
# Database configuration
database: "{{PROJECT_NAME}}.db"
# Logging configuration
verbose: false
debug: false
log_json: false
# Example command-specific configuration
# Uncomment and customize as needed for your commands
#
# fetch:
# concurrency: 10
# timeout: 30s
#
# serve:
# port: 8080
# host: "localhost"

View File

@@ -0,0 +1,12 @@
package cmd
// Application constants and defaults
const (
// DefaultDatabasePath is the default database file path
DefaultDatabasePath = "{{PROJECT_NAME}}.db"
// DefaultConcurrency is the default number of concurrent operations
DefaultConcurrency = 10
// Add other application constants here
)

View File

@@ -0,0 +1,127 @@
package database
import (
"database/sql"
_ "embed"
"fmt"
"os"
"path/filepath"
_ "github.com/mattn/go-sqlite3"
)
//go:embed schema.sql
var schemaSQL string
// DB wraps a SQLite database connection
type DB struct {
conn *sql.DB
}
// New creates and initializes a new database connection
func New(dbPath string) (*DB, error) {
// Ensure directory exists
dir := filepath.Dir(dbPath)
if dir != "." && dir != "/" {
if err := os.MkdirAll(dir, 0755); err != nil {
return nil, fmt.Errorf("failed to create database directory: %w", err)
}
}
// Open database connection
conn, err := sql.Open("sqlite3", fmt.Sprintf("%s?_foreign_keys=ON&_journal_mode=WAL", dbPath))
if err != nil {
return nil, fmt.Errorf("failed to open database: %w", err)
}
// Set connection pool limits (SQLite works best with limited concurrency)
conn.SetMaxOpenConns(1)
conn.SetMaxIdleConns(1)
db := &DB{conn: conn}
// Initialize schema and run migrations
if err := db.InitSchema(); err != nil {
conn.Close()
return nil, fmt.Errorf("failed to initialize schema: %w", err)
}
if err := db.RunMigrations(); err != nil {
conn.Close()
return nil, fmt.Errorf("failed to run migrations: %w", err)
}
return db, nil
}
// Close closes the database connection
func (db *DB) Close() error {
if db.conn != nil {
return db.conn.Close()
}
return nil
}
// InitSchema creates the initial database schema
func (db *DB) InitSchema() error {
_, err := db.conn.Exec(schemaSQL)
if err != nil {
return fmt.Errorf("failed to execute schema: %w", err)
}
return nil
}
// IsInitialized checks if the database has been initialized
func (db *DB) IsInitialized() (bool, error) {
// Check if schema_migrations table exists
var count int
err := db.conn.QueryRow(`
SELECT COUNT(*)
FROM sqlite_master
WHERE type='table' AND name='schema_migrations'
`).Scan(&count)
if err != nil {
return false, fmt.Errorf("failed to check initialization: %w", err)
}
return count > 0, nil
}
// GetMigrationVersion returns the current migration version
func (db *DB) GetMigrationVersion() (int, error) {
var version int
err := db.conn.QueryRow("SELECT COALESCE(MAX(version), 0) FROM schema_migrations").Scan(&version)
if err != nil {
return 0, fmt.Errorf("failed to get migration version: %w", err)
}
return version, nil
}
// ApplyMigration applies a specific migration
func (db *DB) ApplyMigration(version int, sql string) error {
tx, err := db.conn.Begin()
if err != nil {
return fmt.Errorf("failed to begin transaction: %w", err)
}
defer func() { _ = tx.Rollback() }()
// Execute migration SQL
if _, err := tx.Exec(sql); err != nil {
return fmt.Errorf("failed to execute migration %d: %w", version, err)
}
// Record migration
if _, err := tx.Exec(
"INSERT INTO schema_migrations (version) VALUES (?)",
version,
); err != nil {
return fmt.Errorf("failed to record migration %d: %w", version, err)
}
if err := tx.Commit(); err != nil {
return fmt.Errorf("failed to commit migration %d: %w", version, err)
}
return nil
}

View File

@@ -0,0 +1,14 @@
# {{"{{"}} .Title {{"}}"}}
_Generated: {{"{{"}} .Generated {{"}}"}}_
---
{{"{{"}} range .Items -{{"}}"}}
## {{"{{"}} .Name {{"}}"}}
{{"{{"}} .Description {{"}}"}}
---
{{"{{"}} end -{{"}}"}}

View File

@@ -0,0 +1,43 @@
# Binaries for programs and plugins
*.exe
*.exe~
*.dll
*.so
*.dylib
{{PROJECT_NAME}}
# Test binary, built with `go test -c`
*.test
# Output of the go coverage tool
*.out
# Dependency directories
vendor/
# Go workspace file
go.work
# IDEs
.idea/
.vscode/
*.swp
*.swo
*~
# OS files
.DS_Store
Thumbs.db
# Database files
*.db
*.sqlite
*.sqlite3
# Config files (keep examples)
*.yaml
!*.yaml.example
# Build artifacts
build/
dist/

View File

@@ -0,0 +1,10 @@
module {{MODULE_NAME}}
go 1.21
require (
github.com/mattn/go-sqlite3 v1.14.32
github.com/sirupsen/logrus v1.9.3
github.com/spf13/cobra v1.9.1
github.com/spf13/viper v1.20.1
)

View File

@@ -0,0 +1,113 @@
package cmd
import (
"fmt"
"os"
"{{.ModuleName}}/internal/templates"
"github.com/spf13/cobra"
)
const defaultConfigContent = `# Configuration file for {{.ProjectName}}
# Copy this to {{.ProjectName}}.yaml and customize as needed
# Database configuration
database: "{{.ProjectName}}.db"
# Logging configuration
verbose: false
debug: false
log_json: false
# Add your application-specific configuration here
# Example:
# myapp:
# api_url: "https://api.example.com"
# api_token: "your-token-here"
# timeout: 30s
`
// initCmd represents the init command
var initCmd = &cobra.Command{
Use: "init",
Short: "Initialize configuration and template files",
Long: ` + "`" + `Create default configuration file and custom template file for customization.
This command generates:
- {{.ProjectName}}.yaml (configuration file)
- {{.ProjectName}}.md (customizable template, or use --template-file to specify)
Use --force to overwrite existing files.
Example:
{{.ProjectName}} init
{{.ProjectName}} init --template-file my-template.md
{{.ProjectName}} init --force` + "`" + `,
RunE: func(cmd *cobra.Command, args []string) error {
log := GetLogger()
force, _ := cmd.Flags().GetBool("force")
templateFile, _ := cmd.Flags().GetString("template-file")
configFile := "{{.ProjectName}}.yaml"
// Check if config file exists
configExists := fileExists(configFile)
if configExists && !force {
return fmt.Errorf("config file %s already exists (use --force to overwrite)", configFile)
}
// Check if template file exists
templateExists := fileExists(templateFile)
if templateExists && !force {
return fmt.Errorf("template file %s already exists (use --force to overwrite)", templateFile)
}
// Create config file
if err := os.WriteFile(configFile, []byte(defaultConfigContent), 0o644); err != nil {
return fmt.Errorf("failed to create config file: %w", err)
}
if configExists {
log.Infof("Overwrote %s", configFile)
} else {
log.Infof("Created %s", configFile)
}
// Get default template content
templateContent, err := templates.GetDefaultTemplate()
if err != nil {
return fmt.Errorf("failed to get default template: %w", err)
}
// Create template file
if err := os.WriteFile(templateFile, []byte(templateContent), 0o644); err != nil {
return fmt.Errorf("failed to create template file: %w", err)
}
if templateExists {
log.Infof("Overwrote %s", templateFile)
} else {
log.Infof("Created %s", templateFile)
}
fmt.Printf("\n✅ Initialization complete!\n\n")
fmt.Printf("Next steps:\n")
fmt.Printf(" 1. Edit %s and add your configuration\n", configFile)
fmt.Printf(" 2. (Optional) Customize %s for your preferred output format\n", templateFile)
fmt.Printf(" 3. Run: {{.ProjectName}} <command> --help for usage information\n\n")
return nil
},
}
func init() {
rootCmd.AddCommand(initCmd)
initCmd.Flags().Bool("force", false, "Overwrite existing files")
initCmd.Flags().String("template-file", "{{.ProjectName}}.md", "Name of custom template file to create")
}
// fileExists checks if a file exists
func fileExists(path string) bool {
_, err := os.Stat(path)
return err == nil
}

View File

@@ -0,0 +1,7 @@
package main
import "{{MODULE_NAME}}/cmd"
func main() {
cmd.Execute()
}

View File

@@ -0,0 +1,73 @@
package database
import (
"fmt"
)
// getMigrations returns all available migrations
// Add new migrations here with incrementing version numbers
func getMigrations() map[int]string {
return map[int]string{
// Example migration:
// 2: `
// CREATE TABLE IF NOT EXISTS settings (
// key TEXT PRIMARY KEY,
// value TEXT NOT NULL,
// updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
// );
// `,
// Add your migrations here starting from version 2
// (version 1 is the initial schema in schema.sql)
}
}
// RunMigrations executes all pending migrations
func (db *DB) RunMigrations() error {
// Ensure schema_migrations table exists (created by InitSchema)
initialized, err := db.IsInitialized()
if err != nil {
return fmt.Errorf("failed to check initialization: %w", err)
}
if !initialized {
return fmt.Errorf("database not initialized")
}
// Get current version
currentVersion, err := db.GetMigrationVersion()
if err != nil {
return fmt.Errorf("failed to get current version: %w", err)
}
// Get all migrations
migrations := getMigrations()
// Find maximum version
maxVersion := currentVersion
for version := range migrations {
if version > maxVersion {
maxVersion = version
}
}
// Apply pending migrations in order
appliedCount := 0
for version := currentVersion + 1; version <= maxVersion; version++ {
migrationSQL, exists := migrations[version]
if !exists {
return fmt.Errorf("missing migration for version %d", version)
}
if err := db.ApplyMigration(version, migrationSQL); err != nil {
return fmt.Errorf("failed to apply migration %d: %w", version, err)
}
appliedCount++
}
if appliedCount > 0 {
fmt.Printf("Applied %d migration(s), current version: %d\n", appliedCount, maxVersion)
}
return nil
}

View File

@@ -0,0 +1,126 @@
name: Release
on:
push:
tags:
- 'v*'
jobs:
ci:
uses: ./.github/workflows/ci.yml
build:
needs: ci
strategy:
matrix:
include:
- os: ubuntu-latest
goos: linux
goarch: amd64
- os: ubuntu-latest
goos: linux
goarch: arm64
- os: macos-latest
goos: darwin
goarch: amd64
- os: macos-latest
goos: darwin
goarch: arm64
- os: windows-latest
goos: windows
goarch: amd64
runs-on: ${{ matrix.os }}
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Set up Go
uses: actions/setup-go@v5
with:
go-version: '1.23'
- name: Build
shell: bash
env:
GOOS: ${{ matrix.goos }}
GOARCH: ${{ matrix.goarch }}
run: |
VERSION="${GITHUB_REF#refs/tags/}"
COMMIT="${GITHUB_SHA:0:7}"
BUILD_DATE="$(date -u +%Y-%m-%dT%H:%M:%SZ)"
LDFLAGS="-X main.version=$VERSION -X main.commit=$COMMIT -X main.date=$BUILD_DATE"
go build -ldflags "$LDFLAGS" -o {{PROJECT_NAME}}${{ matrix.goos == 'windows' && '.exe' || '' }}
- name: Package (Unix)
if: matrix.goos != 'windows'
run: |
tar czf {{PROJECT_NAME}}-${{ matrix.goos }}-${{ matrix.goarch }}.tar.gz {{PROJECT_NAME}}
- name: Package (Windows)
if: matrix.goos == 'windows'
run: |
7z a {{PROJECT_NAME}}-${{ matrix.goos }}-${{ matrix.goarch }}.zip {{PROJECT_NAME}}.exe
- name: Upload artifacts
uses: actions/upload-artifact@v4
with:
name: {{PROJECT_NAME}}-${{ matrix.goos }}-${{ matrix.goarch }}
path: |
{{PROJECT_NAME}}-*.tar.gz
{{PROJECT_NAME}}-*.zip
release:
needs: build
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Download all artifacts
uses: actions/download-artifact@v4
- name: Generate checksums
run: |
find . -name "{{PROJECT_NAME}}-*" -type f \( -name "*.tar.gz" -o -name "*.zip" \) -exec sha256sum {} \; > checksums.txt
- name: Create Release
uses: softprops/action-gh-release@v2
with:
files: |
{{PROJECT_NAME}}-*/*.tar.gz
{{PROJECT_NAME}}-*/*.zip
checksums.txt
generate_release_notes: true
# Uncomment to build and push Docker image
# docker:
# needs: build
# runs-on: ubuntu-latest
# steps:
# - name: Checkout code
# uses: actions/checkout@v4
#
# - name: Set up Docker Buildx
# uses: docker/setup-buildx-action@v3
#
# - name: Login to Docker Hub
# uses: docker/login-action@v3
# with:
# username: ${{ secrets.DOCKER_USERNAME }}
# password: ${{ secrets.DOCKER_PASSWORD }}
#
# - name: Extract version
# id: version
# run: echo "VERSION=${GITHUB_REF#refs/tags/v}" >> $GITHUB_OUTPUT
#
# - name: Build and push
# uses: docker/build-push-action@v5
# with:
# context: .
# push: true
# tags: |
# yourusername/{{PROJECT_NAME}}:${{ steps.version.outputs.VERSION }}
# yourusername/{{PROJECT_NAME}}:latest

View File

@@ -0,0 +1,135 @@
name: Rolling Release
on:
push:
branches: [ main ]
jobs:
ci:
uses: ./.github/workflows/ci.yml
build:
needs: ci
strategy:
matrix:
include:
- os: ubuntu-latest
goos: linux
goarch: amd64
- os: ubuntu-latest
goos: linux
goarch: arm64
- os: macos-latest
goos: darwin
goarch: amd64
- os: macos-latest
goos: darwin
goarch: arm64
- os: windows-latest
goos: windows
goarch: amd64
runs-on: ${{ matrix.os }}
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Set up Go
uses: actions/setup-go@v5
with:
go-version: '1.23'
- name: Build
shell: bash
env:
GOOS: ${{ matrix.goos }}
GOARCH: ${{ matrix.goarch }}
run: |
VERSION="rolling-${GITHUB_SHA:0:7}"
COMMIT="${GITHUB_SHA:0:7}"
BUILD_DATE="$(date -u +%Y-%m-%dT%H:%M:%SZ)"
LDFLAGS="-X main.version=$VERSION -X main.commit=$COMMIT -X main.date=$BUILD_DATE"
go build -ldflags "$LDFLAGS" -o {{PROJECT_NAME}}${{ matrix.goos == 'windows' && '.exe' || '' }}
- name: Package (Unix)
if: matrix.goos != 'windows'
run: |
tar czf {{PROJECT_NAME}}-${{ matrix.goos }}-${{ matrix.goarch }}.tar.gz {{PROJECT_NAME}}
- name: Package (Windows)
if: matrix.goos == 'windows'
run: |
7z a {{PROJECT_NAME}}-${{ matrix.goos }}-${{ matrix.goarch }}.zip {{PROJECT_NAME}}.exe
- name: Upload artifacts
uses: actions/upload-artifact@v4
with:
name: {{PROJECT_NAME}}-${{ matrix.goos }}-${{ matrix.goarch }}
path: |
{{PROJECT_NAME}}-*.tar.gz
{{PROJECT_NAME}}-*.zip
release:
needs: build
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Download all artifacts
uses: actions/download-artifact@v4
- name: Generate checksums
run: |
find . -name "{{PROJECT_NAME}}-*" -type f \( -name "*.tar.gz" -o -name "*.zip" \) -exec sha256sum {} \; > checksums.txt
- name: Delete existing rolling release
env:
GH_TOKEN: ${{ github.token }}
run: |
gh release delete latest --yes || true
git push origin :refs/tags/latest || true
- name: Create Rolling Release
uses: softprops/action-gh-release@v2
with:
tag_name: latest
name: Rolling Release
body: |
**Automated rolling release built from the latest commit on the main branch.**
⚠️ This release may be unstable and is intended for testing purposes only.
**Commit:** ${{ github.sha }}
**Built:** ${{ github.event.head_commit.timestamp }}
prerelease: true
files: |
{{PROJECT_NAME}}-*/*.tar.gz
{{PROJECT_NAME}}-*/*.zip
checksums.txt
# Uncomment to build and push Docker image
# docker:
# needs: build
# runs-on: ubuntu-latest
# steps:
# - name: Checkout code
# uses: actions/checkout@v4
#
# - name: Set up Docker Buildx
# uses: docker/setup-buildx-action@v3
#
# - name: Login to Docker Hub
# uses: docker/login-action@v3
# with:
# username: ${{ secrets.DOCKER_USERNAME }}
# password: ${{ secrets.DOCKER_PASSWORD }}
#
# - name: Build and push
# uses: docker/build-push-action@v5
# with:
# context: .
# push: true
# tags: yourusername/{{PROJECT_NAME}}:latest

View File

@@ -0,0 +1,126 @@
package cmd
import (
"fmt"
"os"
"{{MODULE_NAME}}/internal/config"
"github.com/sirupsen/logrus"
"github.com/spf13/cobra"
"github.com/spf13/viper"
)
var (
cfgFile string
log = logrus.New()
cfg *config.Config
)
// rootCmd represents the base command when called without any subcommands
var rootCmd = &cobra.Command{
Use: "{{PROJECT_NAME}}",
Short: "A brief description of your application",
Long: `A longer description of what your application does and how it works.
This can be multiple lines and should provide helpful context about the
purpose and usage of your CLI tool.`,
PersistentPreRun: func(cmd *cobra.Command, args []string) {
initConfig()
setupLogging()
},
}
// Execute adds all child commands to the root command and sets appropriate flags.
func Execute() {
if err := rootCmd.Execute(); err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
}
func init() {
// Configuration file flag
rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is ./{{PROJECT_NAME}}.yaml)")
// Logging flags
rootCmd.PersistentFlags().BoolP("verbose", "v", false, "verbose output")
rootCmd.PersistentFlags().Bool("debug", false, "debug output")
rootCmd.PersistentFlags().Bool("log-json", false, "output logs in JSON format")
// Database flag
rootCmd.PersistentFlags().String("database", "{{PROJECT_NAME}}.db", "database file path")
// Bind flags to viper
_ = viper.BindPFlag("verbose", rootCmd.PersistentFlags().Lookup("verbose"))
_ = viper.BindPFlag("debug", rootCmd.PersistentFlags().Lookup("debug"))
_ = viper.BindPFlag("log_json", rootCmd.PersistentFlags().Lookup("log-json"))
_ = viper.BindPFlag("database", rootCmd.PersistentFlags().Lookup("database"))
}
// initConfig reads in config file and ENV variables if set.
func initConfig() {
if cfgFile != "" {
// Use config file from the flag
viper.SetConfigFile(cfgFile)
} else {
// Search for config in current directory
viper.AddConfigPath(".")
viper.SetConfigType("yaml")
viper.SetConfigName("{{PROJECT_NAME}}")
}
// Set defaults
viper.SetDefault("database", "{{PROJECT_NAME}}.db")
viper.SetDefault("verbose", false)
viper.SetDefault("debug", false)
viper.SetDefault("log_json", false)
// Read in environment variables that match
viper.AutomaticEnv()
// If a config file is found, read it in
if err := viper.ReadInConfig(); err != nil {
if cfgFile != "" {
// Only error if config was explicitly specified
fmt.Fprintf(os.Stderr, "Error reading config file: %v\n", err)
os.Exit(1)
}
}
}
// setupLogging configures the logger based on configuration
func setupLogging() {
if viper.GetBool("log_json") {
log.SetFormatter(&logrus.JSONFormatter{})
} else {
log.SetFormatter(&logrus.TextFormatter{
FullTimestamp: true,
})
}
if viper.GetBool("debug") {
log.SetLevel(logrus.DebugLevel)
} else if viper.GetBool("verbose") {
log.SetLevel(logrus.InfoLevel)
} else {
log.SetLevel(logrus.WarnLevel)
}
}
// GetConfig returns the application configuration, loading it if necessary
func GetConfig() *config.Config {
if cfg == nil {
cfg = &config.Config{
Database: viper.GetString("database"),
Verbose: viper.GetBool("verbose"),
Debug: viper.GetBool("debug"),
LogJSON: viper.GetBool("log_json"),
}
}
return cfg
}
// GetLogger returns the configured logger
func GetLogger() *logrus.Logger {
return log
}

View File

@@ -0,0 +1,24 @@
-- Initial database schema for {{PROJECT_NAME}}
-- This is version 1 of the schema
-- Migration tracking table
CREATE TABLE IF NOT EXISTS schema_migrations (
version INTEGER PRIMARY KEY,
applied_at DATETIME DEFAULT CURRENT_TIMESTAMP
);
-- Insert initial version
INSERT OR IGNORE INTO schema_migrations (version) VALUES (1);
-- Example table - customize for your application
-- CREATE TABLE IF NOT EXISTS items (
-- id INTEGER PRIMARY KEY AUTOINCREMENT,
-- name TEXT NOT NULL,
-- description TEXT,
-- created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
-- updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
-- );
--
-- CREATE INDEX IF NOT EXISTS idx_items_name ON items(name);
-- Add your initial schema tables here

View File

@@ -0,0 +1,13 @@
package templates
import (
_ "embed"
)
//go:embed default.md
var defaultTemplate string
// GetDefaultTemplate returns the embedded default template content
func GetDefaultTemplate() (string, error) {
return defaultTemplate, nil
}

View File

@@ -0,0 +1,27 @@
package cmd
import (
"fmt"
"github.com/spf13/cobra"
)
var (
version = "dev"
commit = "unknown"
date = "unknown"
)
// versionCmd represents the version command
var versionCmd = &cobra.Command{
Use: "version",
Short: "Print version information",
Long: `Print the version, commit hash, and build date of this application.`,
Run: func(cmd *cobra.Command, args []string) {
fmt.Printf("{{PROJECT_NAME}} %s (commit: %s, built: %s)\n", version, commit, date)
},
}
func init() {
rootCmd.AddCommand(versionCmd)
}