* feat(help): Add CLI help command Fixes #136 * chore: remove binary * feat(mcp): Add TCP transport Fixes #126 * feat(io): Migrate pkg/mcp to use Medium abstraction Fixes #103 * chore(io): Migrate internal/cmd/docs/* to Medium abstraction Fixes #113 * chore(io): Migrate internal/cmd/dev/* to Medium abstraction Fixes #114 * chore(io): Migrate internal/cmd/setup/* to Medium abstraction * chore(io): Complete migration of internal/cmd/dev/* to Medium abstraction * chore(io): Migrate internal/cmd/sdk, pkgcmd, and workspace to Medium abstraction * style: fix formatting in internal/variants Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * refactor(io): simplify local Medium implementation Rewrote to match the simpler TypeScript pattern: - path() sanitizes and returns string directly - Each method calls path() once - No complex symlink validation - Less code, less attack surface Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * test(mcp): update sandboxing tests for simplified Medium The simplified io/local.Medium implementation: - Sanitizes .. to . (no error, path is cleaned) - Allows absolute paths through (caller validates if needed) - Follows symlinks (no traversal blocking) Update tests to match this simplified behavior. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * fix(updater): resolve PkgVersion duplicate declaration Remove var PkgVersion from updater.go since go generate creates const PkgVersion in version.go. Track version.go in git to ensure builds work without running go generate first. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
289 lines
5.8 KiB
Go
289 lines
5.8 KiB
Go
// cmd_repo.go implements repository setup with .core/ configuration.
|
|
//
|
|
// When running setup in an existing git repository, this generates
|
|
// build.yaml, release.yaml, and test.yaml configurations based on
|
|
// detected project type.
|
|
|
|
package setup
|
|
|
|
import (
|
|
"fmt"
|
|
"os/exec"
|
|
"path/filepath"
|
|
"strings"
|
|
|
|
"github.com/host-uk/core/pkg/i18n"
|
|
coreio "github.com/host-uk/core/pkg/io"
|
|
)
|
|
|
|
// runRepoSetup sets up the current repository with .core/ configuration.
|
|
func runRepoSetup(repoPath string, dryRun bool) error {
|
|
fmt.Printf("%s %s: %s\n", dimStyle.Render(">>"), i18n.T("cmd.setup.repo.setting_up"), repoPath)
|
|
|
|
// Detect project type
|
|
projectType := detectProjectType(repoPath)
|
|
fmt.Printf("%s %s: %s\n", dimStyle.Render(">>"), i18n.T("cmd.setup.repo.detected_type"), projectType)
|
|
|
|
// Create .core directory
|
|
coreDir := filepath.Join(repoPath, ".core")
|
|
if !dryRun {
|
|
if err := coreio.Local.EnsureDir(coreDir); err != nil {
|
|
return fmt.Errorf("failed to create .core directory: %w", err)
|
|
}
|
|
}
|
|
|
|
// Generate configs based on project type
|
|
name := filepath.Base(repoPath)
|
|
configs := map[string]string{
|
|
"build.yaml": generateBuildConfig(repoPath, projectType),
|
|
"release.yaml": generateReleaseConfig(name, projectType),
|
|
"test.yaml": generateTestConfig(projectType),
|
|
}
|
|
|
|
if dryRun {
|
|
fmt.Printf("\n%s %s:\n", dimStyle.Render(">>"), i18n.T("cmd.setup.repo.would_create"))
|
|
for filename, content := range configs {
|
|
fmt.Printf("\n %s:\n", filepath.Join(coreDir, filename))
|
|
// Indent content for display
|
|
for _, line := range strings.Split(content, "\n") {
|
|
fmt.Printf(" %s\n", line)
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
for filename, content := range configs {
|
|
configPath := filepath.Join(coreDir, filename)
|
|
if err := coreio.Local.Write(configPath, content); err != nil {
|
|
return fmt.Errorf("failed to write %s: %w", filename, err)
|
|
}
|
|
fmt.Printf("%s %s %s\n", successStyle.Render(">>"), i18n.T("cmd.setup.repo.created"), configPath)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// detectProjectType identifies the project type from files present.
|
|
func detectProjectType(path string) string {
|
|
// Check in priority order
|
|
if coreio.Local.IsFile(filepath.Join(path, "wails.json")) {
|
|
return "wails"
|
|
}
|
|
if coreio.Local.IsFile(filepath.Join(path, "go.mod")) {
|
|
return "go"
|
|
}
|
|
if coreio.Local.IsFile(filepath.Join(path, "composer.json")) {
|
|
return "php"
|
|
}
|
|
if coreio.Local.IsFile(filepath.Join(path, "package.json")) {
|
|
return "node"
|
|
}
|
|
return "unknown"
|
|
}
|
|
|
|
// generateBuildConfig creates a build.yaml configuration based on project type.
|
|
func generateBuildConfig(path, projectType string) string {
|
|
name := filepath.Base(path)
|
|
|
|
switch projectType {
|
|
case "go", "wails":
|
|
return fmt.Sprintf(`version: 1
|
|
project:
|
|
name: %s
|
|
description: Go application
|
|
main: ./cmd/%s
|
|
binary: %s
|
|
build:
|
|
cgo: false
|
|
flags:
|
|
- -trimpath
|
|
ldflags:
|
|
- -s
|
|
- -w
|
|
targets:
|
|
- os: linux
|
|
arch: amd64
|
|
- os: linux
|
|
arch: arm64
|
|
- os: darwin
|
|
arch: amd64
|
|
- os: darwin
|
|
arch: arm64
|
|
- os: windows
|
|
arch: amd64
|
|
`, name, name, name)
|
|
|
|
case "php":
|
|
return fmt.Sprintf(`version: 1
|
|
project:
|
|
name: %s
|
|
description: PHP application
|
|
type: php
|
|
build:
|
|
dockerfile: Dockerfile
|
|
image: %s
|
|
`, name, name)
|
|
|
|
case "node":
|
|
return fmt.Sprintf(`version: 1
|
|
project:
|
|
name: %s
|
|
description: Node.js application
|
|
type: node
|
|
build:
|
|
script: npm run build
|
|
output: dist
|
|
`, name)
|
|
|
|
default:
|
|
return fmt.Sprintf(`version: 1
|
|
project:
|
|
name: %s
|
|
description: Application
|
|
`, name)
|
|
}
|
|
}
|
|
|
|
// generateReleaseConfig creates a release.yaml configuration.
|
|
func generateReleaseConfig(name, projectType string) string {
|
|
// Try to detect GitHub repo from git remote
|
|
repo := detectGitHubRepo()
|
|
if repo == "" {
|
|
repo = "owner/" + name
|
|
}
|
|
|
|
base := fmt.Sprintf(`version: 1
|
|
project:
|
|
name: %s
|
|
repository: %s
|
|
`, name, repo)
|
|
|
|
switch projectType {
|
|
case "go", "wails":
|
|
return base + `
|
|
changelog:
|
|
include:
|
|
- feat
|
|
- fix
|
|
- perf
|
|
- refactor
|
|
exclude:
|
|
- chore
|
|
- docs
|
|
- style
|
|
- test
|
|
|
|
publishers:
|
|
- type: github
|
|
draft: false
|
|
prerelease: false
|
|
`
|
|
case "php":
|
|
return base + `
|
|
changelog:
|
|
include:
|
|
- feat
|
|
- fix
|
|
- perf
|
|
|
|
publishers:
|
|
- type: github
|
|
draft: false
|
|
`
|
|
default:
|
|
return base + `
|
|
changelog:
|
|
include:
|
|
- feat
|
|
- fix
|
|
|
|
publishers:
|
|
- type: github
|
|
`
|
|
}
|
|
}
|
|
|
|
// generateTestConfig creates a test.yaml configuration.
|
|
func generateTestConfig(projectType string) string {
|
|
switch projectType {
|
|
case "go", "wails":
|
|
return `version: 1
|
|
|
|
commands:
|
|
- name: unit
|
|
run: go test ./...
|
|
- name: coverage
|
|
run: go test -coverprofile=coverage.out ./...
|
|
- name: race
|
|
run: go test -race ./...
|
|
|
|
env:
|
|
CGO_ENABLED: "0"
|
|
`
|
|
case "php":
|
|
return `version: 1
|
|
|
|
commands:
|
|
- name: unit
|
|
run: vendor/bin/pest --parallel
|
|
- name: types
|
|
run: vendor/bin/phpstan analyse
|
|
- name: lint
|
|
run: vendor/bin/pint --test
|
|
|
|
env:
|
|
APP_ENV: testing
|
|
DB_CONNECTION: sqlite
|
|
`
|
|
case "node":
|
|
return `version: 1
|
|
|
|
commands:
|
|
- name: unit
|
|
run: npm test
|
|
- name: lint
|
|
run: npm run lint
|
|
- name: typecheck
|
|
run: npm run typecheck
|
|
|
|
env:
|
|
NODE_ENV: test
|
|
`
|
|
default:
|
|
return `version: 1
|
|
|
|
commands:
|
|
- name: test
|
|
run: echo "No tests configured"
|
|
`
|
|
}
|
|
}
|
|
|
|
// detectGitHubRepo tries to extract owner/repo from git remote.
|
|
func detectGitHubRepo() string {
|
|
cmd := exec.Command("git", "remote", "get-url", "origin")
|
|
output, err := cmd.Output()
|
|
if err != nil {
|
|
return ""
|
|
}
|
|
|
|
url := strings.TrimSpace(string(output))
|
|
|
|
// Handle SSH format: git@github.com:owner/repo.git
|
|
if strings.HasPrefix(url, "git@github.com:") {
|
|
repo := strings.TrimPrefix(url, "git@github.com:")
|
|
repo = strings.TrimSuffix(repo, ".git")
|
|
return repo
|
|
}
|
|
|
|
// Handle HTTPS format: https://github.com/owner/repo.git
|
|
if strings.Contains(url, "github.com/") {
|
|
parts := strings.Split(url, "github.com/")
|
|
if len(parts) == 2 {
|
|
repo := strings.TrimSuffix(parts[1], ".git")
|
|
return repo
|
|
}
|
|
}
|
|
|
|
return ""
|
|
}
|