mirror of
https://github.com/boolean-maybe/tiki
synced 2026-04-21 13:37:20 +00:00
208 lines
5.9 KiB
Go
208 lines
5.9 KiB
Go
package config
|
|
|
|
import (
|
|
_ "embed"
|
|
"fmt"
|
|
"os"
|
|
"os/exec"
|
|
"path/filepath"
|
|
"strings"
|
|
|
|
gonanoid "github.com/matoous/go-nanoid/v2"
|
|
)
|
|
|
|
//go:embed board_sample.md
|
|
var initialTaskTemplate string
|
|
|
|
//go:embed backlog_sample_1.md
|
|
var backlogSample1 string
|
|
|
|
//go:embed backlog_sample_2.md
|
|
var backlogSample2 string
|
|
|
|
//go:embed roadmap_now_sample.md
|
|
var roadmapNowSample string
|
|
|
|
//go:embed roadmap_next_sample.md
|
|
var roadmapNextSample string
|
|
|
|
//go:embed roadmap_later_sample.md
|
|
var roadmapLaterSample string
|
|
|
|
//go:embed new.md
|
|
var defaultNewTaskTemplate string
|
|
|
|
//go:embed index.md
|
|
var dokiEntryPoint string
|
|
|
|
//go:embed linked.md
|
|
var dokiLinked string
|
|
|
|
//go:embed default_workflow.yaml
|
|
var defaultWorkflowYAML string
|
|
|
|
//go:embed markdown.png
|
|
var markdownPNG []byte
|
|
|
|
// GenerateRandomID generates a 6-character random alphanumeric ID (lowercase)
|
|
func GenerateRandomID() string {
|
|
const alphabet = "abcdefghijklmnopqrstuvwxyz0123456789"
|
|
const length = 6
|
|
id, err := gonanoid.Generate(alphabet, length)
|
|
if err != nil {
|
|
// Fallback to simple implementation if nanoid fails
|
|
return "error0"
|
|
}
|
|
return id
|
|
}
|
|
|
|
// BootstrapSystem creates the task storage and seeds the initial tiki.
|
|
func BootstrapSystem() error {
|
|
// Create all necessary directories
|
|
if err := EnsureDirs(); err != nil {
|
|
return fmt.Errorf("ensure directories: %w", err)
|
|
}
|
|
|
|
taskDir := GetTaskDir()
|
|
var createdFiles []string
|
|
|
|
// Helper function to create a sample tiki
|
|
createSampleTiki := func(template string) (string, error) {
|
|
randomID := GenerateRandomID()
|
|
taskID := fmt.Sprintf("TIKI-%s", randomID)
|
|
taskFilename := fmt.Sprintf("tiki-%s.md", randomID)
|
|
taskPath := filepath.Join(taskDir, taskFilename)
|
|
|
|
// Replace placeholder in template
|
|
taskContent := strings.Replace(template, "TIKI-XXXXXX", taskID, 1)
|
|
if err := os.WriteFile(taskPath, []byte(taskContent), 0644); err != nil {
|
|
return "", fmt.Errorf("write task: %w", err)
|
|
}
|
|
return taskPath, nil
|
|
}
|
|
|
|
// Create board sample (original welcome tiki)
|
|
boardPath, err := createSampleTiki(initialTaskTemplate)
|
|
if err != nil {
|
|
return fmt.Errorf("create board sample: %w", err)
|
|
}
|
|
createdFiles = append(createdFiles, boardPath)
|
|
|
|
// Create backlog samples
|
|
backlog1Path, err := createSampleTiki(backlogSample1)
|
|
if err != nil {
|
|
return fmt.Errorf("create backlog sample 1: %w", err)
|
|
}
|
|
createdFiles = append(createdFiles, backlog1Path)
|
|
|
|
backlog2Path, err := createSampleTiki(backlogSample2)
|
|
if err != nil {
|
|
return fmt.Errorf("create backlog sample 2: %w", err)
|
|
}
|
|
createdFiles = append(createdFiles, backlog2Path)
|
|
|
|
// Create roadmap samples
|
|
roadmapNowPath, err := createSampleTiki(roadmapNowSample)
|
|
if err != nil {
|
|
return fmt.Errorf("create roadmap now sample: %w", err)
|
|
}
|
|
createdFiles = append(createdFiles, roadmapNowPath)
|
|
|
|
roadmapNextPath, err := createSampleTiki(roadmapNextSample)
|
|
if err != nil {
|
|
return fmt.Errorf("create roadmap next sample: %w", err)
|
|
}
|
|
createdFiles = append(createdFiles, roadmapNextPath)
|
|
|
|
roadmapLaterPath, err := createSampleTiki(roadmapLaterSample)
|
|
if err != nil {
|
|
return fmt.Errorf("create roadmap later sample: %w", err)
|
|
}
|
|
createdFiles = append(createdFiles, roadmapLaterPath)
|
|
|
|
// Write doki documentation files
|
|
dokiDir := GetDokiDir()
|
|
indexPath := filepath.Join(dokiDir, "index.md")
|
|
if err := os.WriteFile(indexPath, []byte(dokiEntryPoint), 0644); err != nil {
|
|
return fmt.Errorf("write doki index: %w", err)
|
|
}
|
|
createdFiles = append(createdFiles, indexPath)
|
|
|
|
linkedPath := filepath.Join(dokiDir, "linked.md")
|
|
if err := os.WriteFile(linkedPath, []byte(dokiLinked), 0644); err != nil {
|
|
return fmt.Errorf("write doki linked: %w", err)
|
|
}
|
|
createdFiles = append(createdFiles, linkedPath)
|
|
|
|
// Copy markdown.png to doki directory
|
|
dokiMarkdownPNGPath := filepath.Join(dokiDir, "markdown.png")
|
|
if err := os.WriteFile(dokiMarkdownPNGPath, markdownPNG, 0644); err != nil {
|
|
return fmt.Errorf("write doki markdown.png: %w", err)
|
|
}
|
|
createdFiles = append(createdFiles, dokiMarkdownPNGPath)
|
|
|
|
// Copy markdown.png to task directory
|
|
tikiMarkdownPNGPath := filepath.Join(taskDir, "markdown.png")
|
|
if err := os.WriteFile(tikiMarkdownPNGPath, markdownPNG, 0644); err != nil {
|
|
return fmt.Errorf("write tiki markdown.png: %w", err)
|
|
}
|
|
createdFiles = append(createdFiles, tikiMarkdownPNGPath)
|
|
|
|
// Write default config.yaml
|
|
defaultConfig := `logging:
|
|
level: error
|
|
header:
|
|
visible: true
|
|
tiki:
|
|
maxPoints: 10
|
|
appearance:
|
|
theme: auto
|
|
gradientThreshold: 256
|
|
`
|
|
configPath := GetProjectConfigFile()
|
|
if err := os.WriteFile(configPath, []byte(defaultConfig), 0644); err != nil {
|
|
return fmt.Errorf("write default config.yaml: %w", err)
|
|
}
|
|
createdFiles = append(createdFiles, configPath)
|
|
|
|
// Git add all created files
|
|
gitArgs := append([]string{"add"}, createdFiles...)
|
|
//nolint:gosec // G204: git command with controlled file paths
|
|
cmd := exec.Command("git", gitArgs...)
|
|
if err := cmd.Run(); err != nil {
|
|
// Non-fatal: log but don't fail bootstrap if git add fails
|
|
fmt.Fprintf(os.Stderr, "warning: failed to git add files: %v\n", err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// GetDefaultNewTaskTemplate returns the embedded new.md template
|
|
func GetDefaultNewTaskTemplate() string {
|
|
return defaultNewTaskTemplate
|
|
}
|
|
|
|
// InstallDefaultWorkflow installs the default workflow.yaml to the user config directory
|
|
// if it does not already exist. This runs on every launch to handle first-run and upgrade cases.
|
|
func InstallDefaultWorkflow() error {
|
|
path := GetUserConfigWorkflowFile()
|
|
|
|
// No-op if the file already exists
|
|
if _, err := os.Stat(path); err == nil {
|
|
return nil
|
|
}
|
|
|
|
// Ensure the config directory exists
|
|
dir := filepath.Dir(path)
|
|
//nolint:gosec // G301: 0755 is appropriate for config directory
|
|
if err := os.MkdirAll(dir, 0755); err != nil {
|
|
return fmt.Errorf("create config directory %s: %w", dir, err)
|
|
}
|
|
|
|
//nolint:gosec // G306: 0644 is appropriate for config file
|
|
if err := os.WriteFile(path, []byte(defaultWorkflowYAML), 0644); err != nil {
|
|
return fmt.Errorf("write default workflow.yaml: %w", err)
|
|
}
|
|
|
|
return nil
|
|
}
|