Initial commit
This commit is contained in:
63
skills/go-cli-builder/assets/templates/Makefile.template
Normal file
63
skills/go-cli-builder/assets/templates/Makefile.template
Normal 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"
|
||||
45
skills/go-cli-builder/assets/templates/ci.yml.template
Normal file
45
skills/go-cli-builder/assets/templates/ci.yml.template
Normal 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 ./...
|
||||
36
skills/go-cli-builder/assets/templates/command.go.template
Normal file
36
skills/go-cli-builder/assets/templates/command.go.template
Normal 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"))
|
||||
}
|
||||
17
skills/go-cli-builder/assets/templates/config.go.template
Normal file
17
skills/go-cli-builder/assets/templates/config.go.template
Normal 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
|
||||
// }
|
||||
}
|
||||
21
skills/go-cli-builder/assets/templates/config.yaml.example
Normal file
21
skills/go-cli-builder/assets/templates/config.yaml.example
Normal 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"
|
||||
12
skills/go-cli-builder/assets/templates/constants.go.template
Normal file
12
skills/go-cli-builder/assets/templates/constants.go.template
Normal 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
|
||||
)
|
||||
127
skills/go-cli-builder/assets/templates/database.go.template
Normal file
127
skills/go-cli-builder/assets/templates/database.go.template
Normal 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
|
||||
}
|
||||
14
skills/go-cli-builder/assets/templates/default.md.template
Normal file
14
skills/go-cli-builder/assets/templates/default.md.template
Normal file
@@ -0,0 +1,14 @@
|
||||
# {{"{{"}} .Title {{"}}"}}
|
||||
|
||||
_Generated: {{"{{"}} .Generated {{"}}"}}_
|
||||
|
||||
---
|
||||
|
||||
{{"{{"}} range .Items -{{"}}"}}
|
||||
## {{"{{"}} .Name {{"}}"}}
|
||||
|
||||
{{"{{"}} .Description {{"}}"}}
|
||||
|
||||
---
|
||||
|
||||
{{"{{"}} end -{{"}}"}}
|
||||
43
skills/go-cli-builder/assets/templates/gitignore.template
Normal file
43
skills/go-cli-builder/assets/templates/gitignore.template
Normal 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/
|
||||
10
skills/go-cli-builder/assets/templates/go.mod.template
Normal file
10
skills/go-cli-builder/assets/templates/go.mod.template
Normal 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
|
||||
)
|
||||
113
skills/go-cli-builder/assets/templates/init.go.template
Normal file
113
skills/go-cli-builder/assets/templates/init.go.template
Normal 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
|
||||
}
|
||||
7
skills/go-cli-builder/assets/templates/main.go
Normal file
7
skills/go-cli-builder/assets/templates/main.go
Normal file
@@ -0,0 +1,7 @@
|
||||
package main
|
||||
|
||||
import "{{MODULE_NAME}}/cmd"
|
||||
|
||||
func main() {
|
||||
cmd.Execute()
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
126
skills/go-cli-builder/assets/templates/release.yml.template
Normal file
126
skills/go-cli-builder/assets/templates/release.yml.template
Normal 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
|
||||
@@ -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
|
||||
126
skills/go-cli-builder/assets/templates/root.go.template
Normal file
126
skills/go-cli-builder/assets/templates/root.go.template
Normal 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
|
||||
}
|
||||
24
skills/go-cli-builder/assets/templates/schema.sql.template
Normal file
24
skills/go-cli-builder/assets/templates/schema.sql.template
Normal 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
|
||||
13
skills/go-cli-builder/assets/templates/templates.go.template
Normal file
13
skills/go-cli-builder/assets/templates/templates.go.template
Normal 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
|
||||
}
|
||||
27
skills/go-cli-builder/assets/templates/version.go.template
Normal file
27
skills/go-cli-builder/assets/templates/version.go.template
Normal 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)
|
||||
}
|
||||
Reference in New Issue
Block a user